feat: Category store integration (#5218)

* Add more actions

* Complete sidebar store integration

* Complete portal list store integration

* Fixed the specs

* Added missing specs

* Add comment

* Code cleanup

* Fixed all the spec issues

* Add portal and article API specs

* Add category name in article list

* Add more locales

* Code beautification

* Exclude locale from codeclimate ci

* feat: Category store integration

* chore: Minor fixes

* chore: API call fixes

* chore: Minor fixes

* chore: Minor fixes

* chore: Adds the ability for get articles based on categories

* chore: minor fixes

* chore: Minor fixes

* chore: fixes specs and minor improvements

* chore: Review fixes

* chore: Minor fixes

* chore: Review fixes

* chore: Review fixes

* chore: Spacing fixes

* Code cleanup

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sivin Varghese
2022-08-10 10:48:41 +05:30
committed by GitHub
parent 16ad263a3a
commit 9bc75225fe
14 changed files with 196 additions and 80 deletions

View File

@@ -0,0 +1,185 @@
<template>
<woot-modal :show.sync="show" :on-close="onClose">
<woot-modal-header
:header-title="$t('HELP_CENTER.CATEGORY.ADD.TITLE')"
:header-content="$t('HELP_CENTER.CATEGORY.ADD.SUB_TITLE')"
/>
<form class="row" @submit.prevent="onCreate">
<div class="medium-12 columns">
<div class="row article-info">
<div class="columns medium-6">
<label>
<span>{{ $t('HELP_CENTER.CATEGORY.ADD.PORTAL') }}</span>
<p class="value">{{ portalName }}</p>
</label>
</div>
<div class="columns medium-6">
<label>
<span>{{ $t('HELP_CENTER.CATEGORY.ADD.LOCALE') }}</span>
<p class="value">{{ locale }}</p>
</label>
</div>
</div>
<woot-input
v-model.trim="name"
:class="{ error: $v.name.$error }"
class="medium-12 columns"
:error="nameError"
:label="$t('HELP_CENTER.CATEGORY.ADD.NAME.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.ADD.NAME.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.ADD.NAME.HELP_TEXT')"
@input="onNameChange"
/>
<woot-input
v-model.trim="slug"
:class="{ error: $v.slug.$error }"
class="medium-12 columns"
:error="slugError"
:label="$t('HELP_CENTER.CATEGORY.ADD.SLUG.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.ADD.SLUG.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.ADD.SLUG.HELP_TEXT')"
@input="$v.slug.$touch"
/>
<label>
{{ $t('HELP_CENTER.CATEGORY.ADD.DESCRIPTION.LABEL') }}
<textarea
v-model="description"
rows="3"
type="text"
:placeholder="
$t('HELP_CENTER.CATEGORY.ADD.DESCRIPTION.PLACEHOLDER')
"
/>
</label>
<div class="medium-12 columns">
<div class="modal-footer justify-content-end w-full">
<woot-button class="button clear" @click.prevent="onClose">
{{ $t('HELP_CENTER.CATEGORY.ADD.BUTTONS.CANCEL') }}
</woot-button>
<woot-button @click="addCategory">
{{ $t('HELP_CENTER.CATEGORY.ADD.BUTTONS.CREATE') }}
</woot-button>
</div>
</div>
</div>
</form>
</woot-modal>
</template>
<script>
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { required, minLength } from 'vuelidate/lib/validators';
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
export default {
mixins: [alertMixin],
props: {
show: {
type: Boolean,
default: true,
},
portalName: {
type: String,
default: '',
},
locale: {
type: String,
default: '',
},
},
data() {
return {
name: '',
slug: '',
description: '',
};
},
validations: {
name: {
required,
minLength: minLength(2),
},
slug: {
required,
},
},
computed: {
...mapGetters({
selectedPortal: 'portals/getSelectedPortal',
}),
selectedPortalSlug() {
return this.selectedPortal?.slug;
},
nameError() {
if (this.$v.name.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
}
return '';
},
slugError() {
if (this.$v.slug.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
}
return '';
},
},
methods: {
onNameChange() {
this.slug = convertToCategorySlug(this.name);
},
onCreate() {
this.$emit('create');
},
onClose() {
this.$emit('cancel');
},
async addCategory() {
const { name, slug, description } = this;
const data = {
name,
slug,
description,
};
this.$v.$touch();
if (this.$v.$invalid) {
return;
}
try {
await this.$store.dispatch('categories/create', {
portalSlug: this.selectedPortalSlug,
categoryObj: data,
});
this.alertMessage = this.$t(
'HELP_CENTER.CATEGORY.ADD.API.SUCCESS_MESSAGE'
);
this.onClose();
} catch (error) {
const errorMessage = error?.message;
this.alertMessage =
errorMessage || this.$t('HELP_CENTER.CATEGORY.ADD.API.ERROR_MESSAGE');
} finally {
this.showAlert(this.alertMessage);
}
},
},
};
</script>
<style scoped lang="scss">
.article-info {
width: 100%;
margin: 0 0 var(--space-normal);
.value {
color: var(--s-600);
}
}
.input-container::v-deep {
margin: 0 0 var(--space-normal);
input {
margin-bottom: 0;
}
}
</style>

View File

@@ -13,6 +13,7 @@
:accessible-menu-items="accessibleMenuItems"
:additional-secondary-menu-items="additionalSecondaryMenuItems"
@open-popover="openPortalPopover"
@open-modal="onClickOpenAddCatogoryModal"
/>
</div>
<section class="app-content columns">
@@ -33,6 +34,12 @@
:active-portal="selectedPortal"
@close-popover="closePortalPopover"
/>
<add-category
v-if="showAddCategoryModal"
:portal-name="selectedPortalName"
:locale="selectedPortalLocale"
@cancel="onClickCloseAddCategoryModal"
/>
</section>
</div>
</template>
@@ -45,8 +52,10 @@ import PortalPopover from '../components/PortalPopover.vue';
import HelpCenterSidebar from '../components/Sidebar/Sidebar.vue';
import CommandBar from 'dashboard/routes/dashboard/commands/commandbar.vue';
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal';
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel';
import portalMixin from '../mixins/portalMixin';
import AddCategory from '../components/AddCategory.vue';
export default {
components: {
Sidebar,
@@ -55,6 +64,7 @@ export default {
WootKeyShortcutModal,
NotificationPanel,
PortalPopover,
AddCategory,
},
mixins: [portalMixin],
data() {
@@ -62,6 +72,7 @@ export default {
showShortcutModal: false,
showNotificationPanel: false,
showPortalPopover: false,
showAddCategoryModal: false,
};
},
@@ -70,9 +81,13 @@ export default {
accountId: 'getCurrentAccountId',
selectedPortal: 'portals/getSelectedPortal',
portals: 'portals/allPortals',
categories: 'categories/allCategories',
meta: 'portals/getMeta',
isFetching: 'portals/isFetchingPortals',
}),
selectedPortalName() {
return this.selectedPortal ? this.selectedPortal.name : '';
},
selectedPortalSlug() {
return this.portalSlug || this.selectedPortal?.slug;
},
@@ -98,18 +113,18 @@ export default {
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles`
),
toolTip: 'All Articles',
toStateName: 'list_all_selectedPortalLocale_articles',
toStateName: 'list_all_locale_articles',
},
{
icon: 'pen',
label: 'HELP_CENTER.MY_ARTICLES',
key: 'mine_articles',
key: 'list_mine_articles',
count: mineArticlesCount,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles/mine`
),
toolTip: 'My articles',
toStateName: 'mine_articles',
toStateName: 'list_mine_articles',
},
{
icon: 'draft',
@@ -142,26 +157,15 @@ export default {
label: 'HELP_CENTER.CATEGORY',
hasSubMenu: true,
key: 'category',
children: [
{
id: 1,
label: 'Getting started',
count: 12,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/portals/:portalSlug/:locale/categories/getting-started`
),
},
{
id: 2,
label: 'Channel',
count: 19,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/portals/:portalSlug/:locale/categories/channel`
),
},
],
children: this.categories.map(category => ({
id: category.id,
label: category.name,
count: category.meta.articles_count,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${category.locale}/categories/${category.slug}`
),
})),
},
];
},
@@ -173,11 +177,15 @@ export default {
},
},
mounted() {
this.fetchPortals();
this.fetchPortalsAndItsCategories();
},
methods: {
fetchPortals() {
this.$store.dispatch('portals/index');
fetchPortalsAndItsCategories() {
this.$store.dispatch('portals/index').then(() => {
this.$store.dispatch('categories/index', {
portalSlug: this.selectedPortalSlug,
});
});
},
toggleKeyShortcutModal() {
this.showShortcutModal = true;
@@ -197,6 +205,18 @@ export default {
closePortalPopover() {
this.showPortalPopover = false;
},
openPortalPage() {
this.$router.push({
name: 'list_all_portals',
});
this.showPortalPopover = false;
},
onClickOpenAddCatogoryModal() {
this.showAddCategoryModal = true;
},
onClickCloseAddCategoryModal() {
this.showAddCategoryModal = false;
},
},
};
</script>

View File

@@ -19,6 +19,8 @@
:key="menuItem.key"
:menu-item="menuItem"
:is-help-center-sidebar="true"
:is-category-empty="!hasCategory"
@open="onClickOpenAddCatogoryModal"
/>
</transition-group>
</div>
@@ -60,6 +62,14 @@ export default {
data() {
return {};
},
computed: {
hasCategory() {
return (
this.additionalSecondaryMenuItems[0] &&
this.additionalSecondaryMenuItems[0].children.length > 0
);
},
},
methods: {
onSearch(value) {
this.$emit('input', value);
@@ -67,6 +77,9 @@ export default {
openPortalPopover() {
this.$emit('open-popover');
},
onClickOpenAddCatogoryModal() {
this.$emit('open-modal');
},
},
};
</script>

View File

@@ -0,0 +1,30 @@
import AddCategoryComponent from '../AddCategory.vue';
import { action } from '@storybook/addon-actions';
export default {
title: 'Components/Help Center',
component: AddCategoryComponent,
argTypes: {
show: {
defaultValue: true,
control: {
type: 'boolean',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AddCategoryComponent },
template:
'<add-category-component v-bind="$props" @create="onCreate" @cancel="onClose" />',
});
export const AddCategory = Template.bind({});
AddCategory.args = {
portalName: 'Chatwoot help center',
locale: 'En-US',
onCreate: action('create'),
onClose: action('cancel'),
};

View File

@@ -9,7 +9,7 @@ const ShowPortal = () => import('./pages/portals/ShowPortal');
const ListAllCategories = () => import('./pages/categories/ListAllCategories');
const NewCategory = () => import('./pages/categories/NewCategory');
const EditCategory = () => import('./pages/categories/EditCategory');
const ShowCategory = () => import('./pages/categories/ShowCategory');
// const ShowCategory = () => import('./pages/categories/ShowCategory');
const ListCategoryArticles = () =>
import('./pages/articles/ListCategoryArticles');
@@ -103,7 +103,7 @@ const categoryRoutes = [
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'show_category',
roles: ['administrator', 'agent'],
component: ShowCategory,
component: ListAllArticles,
},
{
path: getPortalRoute(

View File

@@ -2,7 +2,7 @@
<div class="container">
<article-header
:header-title="headerTitle"
:count="meta.count"
:count="articleCount"
selected-value="Published"
@newArticlePage="newArticlePage"
/>
@@ -10,7 +10,7 @@
:articles="articles"
:article-count="articles.length"
:current-page="Number(meta.currentPage)"
:total-count="meta.count"
:total-count="articleCount"
@on-page-change="onPageChange"
/>
<div v-if="shouldShowLoader" class="articles--loader">
@@ -45,17 +45,31 @@ export default {
computed: {
...mapGetters({
articles: 'articles/allArticles',
categories: 'categories/allCategories',
selectedPortal: 'portals/getSelectedPortal',
uiFlags: 'articles/uiFlags',
meta: 'articles/getMeta',
isFetching: 'articles/isFetching',
currentUserId: 'getCurrentUserID',
}),
selectedCategory() {
return this.categories.find(
category => category.slug === this.selectedCategorySlug
);
},
shouldShowEmptyState() {
return !this.isFetching && !this.articles.length;
},
shouldShowLoader() {
return this.isFetching && !this.articles.length;
},
selectedPortalSlug() {
return this.selectedPortal?.slug;
},
selectedCategorySlug() {
const { categorySlug } = this.$route.params;
return categorySlug;
},
articleType() {
return this.$route.path.split('/').pop();
},
@@ -68,6 +82,9 @@ export default {
case 'archived':
return this.$t('HELP_CENTER.HEADER.TITLES.ARCHIVED');
default:
if (this.$route.name === 'show_category') {
return this.headerTitleInCategoryView;
}
return this.$t('HELP_CENTER.HEADER.TITLES.ALL_ARTICLES');
}
},
@@ -89,6 +106,14 @@ export default {
}
return null;
},
articleCount() {
return this.articles ? this.articles.length : 0;
},
headerTitleInCategoryView() {
return this.categories && this.categories.length
? this.selectedCategory.name
: '';
},
},
watch: {
$route() {
@@ -111,6 +136,7 @@ export default {
locale: this.$route.params.locale,
status: this.status,
author_id: this.author,
category_slug: this.selectedCategorySlug,
});
},
onPageChange(page) {