Files
chatwoot/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue
Shivam Mishra 6d3ecfe3c1 feat: Add new sidebar for Chatwoot V4 (#10291)
This PR has the initial version of the new sidebar targeted for the next major redesign of the app. This PR includes the following changes

- Components in the `layouts-next` and `base-next` directories in `dashboard/components`
- Two generic components `Avatar` and `Icon`
- `SidebarGroup` component to manage expandable sidebar groups with nested navigation items. This includes handling active states, transitions, and permissions.
- `SidebarGroupHeader` component to display the header of each navigation group with optional icons and active state indication.
- `SidebarGroupLeaf` component for individual navigation items within a group, supporting icons and active state.
- `SidebarGroupSeparator` component to visually separate nested navigation items. (They look a lot like header)
- `SidebarGroupEmptyLeaf` component to render empty state of any navigation groups.

----

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
2024-10-23 18:32:37 -07:00

365 lines
11 KiB
Vue

<script>
import { defineAsyncComponent } from 'vue';
import { mapGetters } from 'vuex';
import UpgradePage from './UpgradePage.vue';
import NextSidebar from 'next/sidebar/Sidebar.vue';
import { frontendURL } from '../../../../helper/URLHelper';
import Sidebar from 'dashboard/components/layout/Sidebar.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import PortalPopover from '../components/PortalPopover.vue';
import HelpCenterSidebar from '../components/Sidebar/Sidebar.vue';
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
import { useUISettings } from 'dashboard/composables/useUISettings';
import portalMixin from '../mixins/portalMixin';
import AddCategory from '../pages/categories/AddCategory.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { emitter } from 'shared/helpers/mitt';
const CommandBar = defineAsyncComponent(
() => import('dashboard/routes/dashboard/commands/commandbar.vue')
);
export default {
components: {
NextSidebar,
AccountSelector,
AddCategory,
CommandBar,
HelpCenterSidebar,
NotificationPanel,
PortalPopover,
Sidebar,
UpgradePage,
WootKeyShortcutModal,
},
mixins: [portalMixin],
setup() {
const { uiSettings, updateUISettings } = useUISettings();
return {
uiSettings,
updateUISettings,
};
},
data() {
return {
isOnDesktop: true,
showShortcutModal: false,
showNotificationPanel: false,
showPortalPopover: false,
showAddCategoryModal: false,
lastActivePortalSlug: '',
showAccountModal: false,
};
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
portals: 'portals/allPortals',
categories: 'categories/allCategories',
meta: 'portals/getMeta',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
isHelpCenterEnabled() {
return this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.HELP_CENTER
);
},
showNextSidebar() {
return this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.CHATWOOT_V4
);
},
isSidebarOpen() {
const { show_help_center_secondary_sidebar: showSecondarySidebar } =
this.uiSettings;
return showSecondarySidebar;
},
showHelpCenterSidebar() {
if (!this.isHelpCenterEnabled) {
return false;
}
return this.portals.length === 0 ? false : this.isSidebarOpen;
},
selectedPortal() {
const slug = this.$route.params.portalSlug || this.lastActivePortalSlug;
if (slug) return this.$store.getters['portals/portalBySlug'](slug);
return this.$store.getters['portals/allPortals'][0];
},
selectedLocaleInPortal() {
return this.$route.params.locale || this.defaultPortalLocale;
},
selectedPortalName() {
return this.selectedPortal ? this.selectedPortal.name : '';
},
selectedPortalSlug() {
return this.selectedPortal ? this.selectedPortal?.slug : '';
},
defaultPortalLocale() {
return this.selectedPortal
? this.selectedPortal?.meta?.default_locale
: '';
},
accessibleMenuItems() {
if (!this.selectedPortal) return [];
const {
allArticlesCount,
mineArticlesCount,
draftArticlesCount,
archivedArticlesCount,
} = this.meta;
return [
{
icon: 'book',
label: 'HELP_CENTER.ALL_ARTICLES',
key: 'list_all_locale_articles',
count: allArticlesCount,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles`
),
toolTip: 'All Articles',
toStateName: 'list_all_locale_articles',
},
{
icon: 'pen',
label: 'HELP_CENTER.MY_ARTICLES',
key: 'list_mine_articles',
count: mineArticlesCount,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/mine`
),
toolTip: 'My articles',
toStateName: 'list_mine_articles',
},
{
icon: 'draft',
label: 'HELP_CENTER.DRAFT',
key: 'list_draft_articles',
count: draftArticlesCount,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/draft`
),
toolTip: 'Draft',
toStateName: 'list_draft_articles',
},
{
icon: 'archive',
label: 'HELP_CENTER.ARCHIVED',
key: 'list_archived_articles',
count: archivedArticlesCount,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/archived`
),
toolTip: 'Archived',
toStateName: 'list_archived_articles',
},
{
icon: 'settings',
label: 'HELP_CENTER.SETTINGS',
key: 'edit_portal_information',
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/edit`
),
toStateName: 'edit_portal_information',
},
];
},
additionalSecondaryMenuItems() {
if (!this.selectedPortal) return [];
return [
{
icon: 'folder',
label: 'HELP_CENTER.CATEGORY',
hasSubMenu: true,
showNewButton: true,
key: 'category',
children: this.categories.map(category => ({
id: category.id,
label: category.icon
? `${category.icon} ${category.name}`
: category.name,
count: category.meta.articles_count,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${category.locale}/categories/${category.slug}`
),
})),
},
];
},
currentRoute() {
return ' ';
},
headerTitle() {
return this.selectedPortal ? this.selectedPortal.name : '';
},
},
watch: {
'$route.name'() {
const routeName = this.$route?.name;
const routeParams = this.$route?.params;
const updateMetaInAllPortals = routeName === 'list_all_portals';
const updateMetaInEditArticle =
routeName === 'edit_article' && routeParams?.recentlyCreated;
const updateMetaInLocaleArticles =
routeName === 'list_all_locale_articles' &&
routeParams?.recentlyDeleted;
if (
updateMetaInAllPortals ||
updateMetaInEditArticle ||
updateMetaInLocaleArticles
) {
this.fetchPortalAndItsCategories();
}
},
},
mounted() {
emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
const slug = this.$route.params.portalSlug;
if (slug) this.lastActivePortalSlug = slug;
this.fetchPortalAndItsCategories();
},
unmounted() {
emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
},
updated() {
const slug = this.$route.params.portalSlug;
if (slug !== this.lastActivePortalSlug) {
this.lastActivePortalSlug = slug;
this.updateUISettings({
last_active_portal_slug: slug,
last_active_locale_code: this.selectedLocaleInPortal,
});
}
},
methods: {
toggleSidebar() {
if (this.portals.length > 0) {
this.updateUISettings({
show_help_center_secondary_sidebar: !this.isSidebarOpen,
});
}
},
async fetchPortalAndItsCategories() {
await this.$store.dispatch('portals/index');
const selectedPortalParam = {
portalSlug: this.selectedPortalSlug,
locale: this.selectedLocaleInPortal,
};
this.$store.dispatch('portals/show', selectedPortalParam);
this.$store.dispatch('categories/index', selectedPortalParam);
this.$store.dispatch('agents/get');
},
toggleKeyShortcutModal() {
this.showShortcutModal = true;
},
closeKeyShortcutModal() {
this.showShortcutModal = false;
},
openNotificationPanel() {
this.showNotificationPanel = true;
},
closeNotificationPanel() {
this.showNotificationPanel = false;
},
openPortalPopover() {
this.showPortalPopover = !this.showPortalPopover;
},
closePortalPopover() {
this.showPortalPopover = false;
},
onClickOpenAddCategoryModal() {
this.showAddCategoryModal = true;
},
onClickCloseAddCategoryModal() {
this.showAddCategoryModal = false;
},
toggleAccountModal() {
this.showAccountModal = !this.showAccountModal;
},
},
};
</script>
<template>
<div class="flex flex-grow-0 w-full h-full min-h-0 app-wrapper">
<NextSidebar
v-if="showNextSidebar"
@toggle-account-modal="toggleAccountModal"
@open-notification-panel="openNotificationPanel"
@open-key-shortcut-modal="toggleKeyShortcutModal"
@close-key-shortcut-modal="closeKeyShortcutModal"
/>
<Sidebar
v-else
:route="currentRoute"
@toggle-account-modal="toggleAccountModal"
@open-notification-panel="openNotificationPanel"
@open-key-shortcut-modal="toggleKeyShortcutModal"
@close-key-shortcut-modal="closeKeyShortcutModal"
/>
<HelpCenterSidebar
v-if="showHelpCenterSidebar"
:header-title="headerTitle"
:portal-slug="selectedPortalSlug"
:locale-slug="selectedLocaleInPortal"
:sub-title="localeName(selectedLocaleInPortal)"
:accessible-menu-items="accessibleMenuItems"
:additional-secondary-menu-items="additionalSecondaryMenuItems"
@open-popover="openPortalPopover"
@open-modal="onClickOpenAddCategoryModal"
/>
<section
v-if="isHelpCenterEnabled"
class="flex flex-1 h-full px-0 overflow-hidden bg-white dark:bg-slate-900"
>
<router-view @reload-locale="fetchPortalAndItsCategories" />
<CommandBar />
<AccountSelector
:show-account-modal="showAccountModal"
@close-account-modal="toggleAccountModal"
/>
<WootKeyShortcutModal
v-if="showShortcutModal"
@close="closeKeyShortcutModal"
@clickaway="closeKeyShortcutModal"
/>
<NotificationPanel
v-if="showNotificationPanel"
@close="closeNotificationPanel"
/>
<PortalPopover
v-if="showPortalPopover"
:portals="portals"
:active-portal-slug="selectedPortalSlug"
:active-locale="selectedLocaleInPortal"
@fetch-portal="fetchPortalAndItsCategories"
@close-popover="closePortalPopover"
/>
<AddCategory
v-if="showAddCategoryModal"
v-model:show="showAddCategoryModal"
:portal-name="selectedPortalName"
:locale="selectedLocaleInPortal"
:portal-slug="selectedPortalSlug"
@cancel="onClickCloseAddCategoryModal"
/>
</section>
<UpgradePage v-else />
</div>
</template>