feat: Updates sidebar to accomodate sub menu (#3416)

* Enhancement: Updates sidebar to a new design (#2733)

* feat: Changes primary navbar to new design (#2598)

* feat: updates design for secondary navbar (#2612)

* Changes primary nvbar to new design

* Updates design for contexual sidebar

* Fixes issues with JSON

* Remove duplication of notificatons in Navigation

* Fixes broken tests

* Fixes broken tests

* Update app/javascript/dashboard/components/layout/AvailabilityStatus.vue

* Update app/javascript/dashboard/components/layout/AvailabilityStatus.vue

* Update app/javascript/dashboard/components/layout/SidebarItem.vue

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

* Update app/javascript/dashboard/components/layout/SidebarItem.vue

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

* Update app/javascript/dashboard/modules/sidebar/components/Secondary.vue

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

* Chore: Update design changes to features

* Fixes menu transitions and refactors code

* Refactors sidebar routeing logic

* lint error fixes

* Fixes dropdown menu styles

* Fixes secondary new item links

* Fixes lint scss issues

* fixes linter issues

* Fixes broken test cases

* Update AvailabilityStatus.spec.js

* Review feedbacks

* Fixes add modal for label

* Add tooltip for primary menu item

* Tooltip for notifications

* Adds tooltip for primary menu items

* Review fixes

* Review fixes

* Fix merge issues

* fixes logo size for login pages

* fixes Merge breaks with styles

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Nithin David Thomas 2021-12-01 12:45:39 +05:30 committed by GitHub
parent c792cfc0be
commit b01d032d0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1119 additions and 562 deletions

View file

@ -1,18 +1,18 @@
<template>
<div class="current-user--row">
<woot-button
v-tooltip.right="$t(`SIDEBAR.PROFILE_SETTINGS`)"
variant="link"
class="current-user"
@click="handleClick"
>
<thumbnail
:src="currentUser.avatar_url"
:username="currentUserAvailableName"
:username="currentUser.name"
:status="statusOfAgent"
should-show-status-always
size="32px"
/>
<div class="current-user--data">
<h3 class="current-user--name text-truncate">
{{ currentUserAvailableName }}
</h3>
<h5 v-if="currentRole" class="current-user--role">
{{ $t(`AGENT_MGMT.AGENT_TYPES.${currentRole.toUpperCase()}`) }}
</h5>
</div>
</div>
</woot-button>
</template>
<script>
import { mapGetters } from 'vuex';
@ -25,39 +25,25 @@ export default {
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
currentRole: 'getCurrentRole',
currentUserAvailability: 'getCurrentUserAvailability',
}),
currentUserAvailableName() {
return this.currentUser.name;
statusOfAgent() {
return this.currentUserAvailability || 'offline';
},
},
methods: {
handleClick() {
this.$emit('toggle-menu');
},
},
};
</script>
<style scoped lang="scss">
.current-user--row {
.current-user {
align-items: center;
display: flex;
}
.current-user--data {
display: flex;
flex-direction: column;
.current-user--name {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-bottom: var(--space-micro);
margin-left: var(--space-one);
max-width: 12rem;
}
.current-user--role {
color: var(--color-gray);
font-size: var(--font-size-mini);
margin-bottom: var(--zero);
margin-left: var(--space-one);
text-transform: capitalize;
}
border-radius: 50%;
border: 2px solid var(--white);
}
</style>

View file

@ -1,13 +1,19 @@
<template>
<span class="notifications" @click.stop="showNotification">
<fluent-icon icon="alert" />
<span v-if="unreadCount" class="unread-badge">{{ unreadCount }}</span>
</span>
<div class="notifications-link">
<primary-nav-item
name="NOTIFICATIONS"
icon="alert"
:to="`/app/accounts/${accountId}/notifications`"
:count="unreadCount"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import PrimaryNavItem from 'dashboard/modules/sidebar/components/PrimaryNavItem';
export default {
components: { PrimaryNavItem },
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
@ -15,40 +21,20 @@ export default {
}),
unreadCount() {
if (!this.notificationMetadata.unreadCount) {
return 0;
return '0';
}
return this.notificationMetadata.unreadCount < 100
? this.notificationMetadata.unreadCount
? `${this.notificationMetadata.unreadCount}`
: '99+';
},
},
methods: {
showNotification() {
this.$router.push(`/app/accounts/${this.accountId}/notifications`);
},
},
methods: {},
};
</script>
<style scoped lang="scss">
.notifications {
font-size: var(--font-size-big);
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
position: relative;
.unread-badge {
background: var(--r-300);
border-radius: var(--space-small);
color: var(--white);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
left: var(--space-slab);
padding: 0 var(--space-smaller);
position: absolute;
top: var(--space-smaller);
}
.notifications-link {
margin-bottom: var(--space-small);
}
</style>

View file

@ -2,15 +2,19 @@
<transition name="menu-slide">
<div
v-if="show"
v-on-clickaway="() => $emit('close')"
class="dropdown-pane dropdowm--top"
v-on-clickaway="onClickAway"
class="dropdown-pane"
:class="{ 'dropdown-pane--open': show }"
>
<availability-status />
<li class="divider" />
<woot-dropdown-menu>
<woot-dropdown-item v-if="showChangeAccountOption">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
class=" change-accounts--button"
icon="arrow-swap"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
@ -19,36 +23,50 @@
<woot-dropdown-item v-if="globalConfig.chatwootInboxToken">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
class=" change-accounts--button"
icon="ion-help-buoy"
@click="$emit('show-support-chat-window')"
>
Contact Support
{{ $t('SIDEBAR_ITEMS.CONTACT_SUPPORT') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
class=" change-accounts--button"
@click="$emit('key-shortcut-modal')"
icon="keyboard"
@click="handleKeyboardHelpClick"
>
{{ $t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item>
<router-link
v-slot="{ href, isActive, navigate }"
:to="`/app/accounts/${accountId}/profile/settings`"
class="button clear small change-accounts--button"
custom
>
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
<a
:href="href"
class="button small clear secondary"
:class="{ 'is-active': isActive }"
@click="e => handleProfileSettingClick(e, navigate)"
>
<fluent-icon icon="person" class="icon icon--font" />
<span class="button__content">
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
</span>
</a>
</router-link>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
class=" change-accounts--button"
icon="power"
@click="logout"
>
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
@ -63,13 +81,15 @@
import { mixin as clickaway } from 'vue-clickaway';
import { mapGetters } from 'vuex';
import Auth from '../../../api/auth';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus';
export default {
components: {
WootDropdownMenu,
WootDropdownItem,
AvailabilityStatus,
},
mixins: [clickaway],
props: {
@ -88,18 +108,34 @@ export default {
if (this.globalConfig.createNewAccountFromDashboard) {
return true;
}
return this.currentUser.accounts.length > 1;
const { accounts = [] } = this.currentUser;
return accounts.length > 1;
},
},
methods: {
handleProfileSettingClick(e, navigate) {
this.$emit('close');
navigate(e);
},
handleKeyboardHelpClick() {
this.$emit('key-shortcut-modal');
this.$emit('close');
},
logout() {
Auth.logout();
},
onClickAway() {
if (this.show) this.$emit('close');
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-pane {
right: 0;
left: var(--space-slab);
bottom: var(--space-larger);
min-width: 16.8rem;
z-index: var(--z-index-much-higher);
}
</style>

View file

@ -2,13 +2,20 @@ import AgentDetails from '../AgentDetails';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import VTooltip from 'v-tooltip';
import i18n from 'dashboard/i18n';
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
import WootButton from 'dashboard/components/ui/WootButton';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueI18n);
localVue.component('thumbnail', Thumbnail);
localVue.component('woot-button', WootButton);
localVue.component('woot-button', WootButton);
localVue.use(VTooltip, {
defaultHtml: false,
});
const i18nConfig = new VueI18n({
locale: 'en',
@ -16,7 +23,11 @@ const i18nConfig = new VueI18n({
});
describe('agentDetails', () => {
const currentUser = { name: 'Neymar Junior', avatar_url: '' };
const currentUser = {
name: 'Neymar Junior',
avatar_url: '',
availability_status: 'online',
};
const currentRole = 'agent';
let store = null;
let actions = null;
@ -31,6 +42,7 @@ describe('agentDetails', () => {
getters: {
getCurrentUser: () => currentUser,
getCurrentRole: () => currentRole,
getCurrentUserAvailability: () => currentUser.availability_status,
},
},
};
@ -47,14 +59,8 @@ describe('agentDetails', () => {
});
});
it('shows the agent name', () => {
const agentTitle = agentDetails.find('.current-user--name');
expect(agentTitle.text()).toBe('Neymar Junior');
});
it('shows the agent role', () => {
const agentTitle = agentDetails.find('.current-user--role');
expect(agentTitle.text()).toBe('Agent');
it(' the agent status', () => {
expect(agentDetails.find('thumbnail-stub').vm.status).toBe('online');
});
it('agent thumbnail exists', () => {

View file

@ -50,8 +50,9 @@ describe('notificationBell', () => {
localVue,
i18n: i18nConfig,
});
const statusViewTitle = notificationBell.find('.unread-badge');
expect(statusViewTitle.text()).toBe('19');
const statusViewTitle = notificationBell.find('primary-nav-item-stub');
expect(statusViewTitle.vm.count).toBe('19');
});
it('it should return unread count 99+ ', async () => {
@ -61,7 +62,7 @@ describe('notificationBell', () => {
localVue,
i18n: i18nConfig,
});
const statusViewTitle = notificationBell.find('.unread-badge');
expect(statusViewTitle.text()).toBe('99+');
const statusViewTitle = notificationBell.find('primary-nav-item-stub');
expect(statusViewTitle.vm.count).toBe('99+');
});
});