Merge branch 'develop' into feat/add-more-ai-reply-options

This commit is contained in:
Muhsin Keloth
2023-07-06 12:08:15 +05:30
committed by GitHub
87 changed files with 687 additions and 1463 deletions
+18 -18
View File
@@ -73,7 +73,7 @@ jobs:
- run:
name: yarn
command: yarn install --cache-folder ~/.cache/yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache
- save_cache:
@@ -104,9 +104,8 @@ jobs:
fi
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
# Database setup
- run: yarn install --check-files
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
@@ -117,7 +116,7 @@ jobs:
- run:
name: Rubocop
command: bundle exec rubocop
# - run:
# name: Brakeman
# command: bundle exec brakeman
@@ -126,6 +125,21 @@ jobs:
name: eslint
command: yarn run eslint
# Run frontend tests
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
# Run rails tests
- run:
name: Run backend tests
@@ -145,20 +159,6 @@ jobs:
command: |
~/tmp/cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
- persist_to_workspace:
root: coverage
paths:
+30 -1
View File
@@ -3,7 +3,8 @@
v-if="!authUIFlags.isFetching"
id="app"
class="app-wrapper app-root"
:class="{ 'app-rtl--wrapper': isRTLView }"
:class="{ 'app-rtl--wrapper': isRTLView, dark: theme === 'dark' }"
:dir="isRTLView ? 'rtl' : 'ltr'"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
@@ -34,10 +35,12 @@ import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer';
import rtlMixin from 'shared/mixins/rtlMixin';
import { LocalStorage } from 'shared/helpers/localStorage';
import {
registerSubscription,
verifyServiceWorkerExistence,
} from './helper/pushHelper';
import { LOCAL_STORAGE_KEYS } from './constants/localStorage';
export default {
name: 'App',
@@ -58,6 +61,7 @@ export default {
return {
showAddAccountModal: false,
latestChatwootVersion: null,
theme: 'light',
};
},
@@ -89,9 +93,34 @@ export default {
},
},
mounted() {
this.initializeColorTheme();
this.listenToThemeChanges();
this.setLocale(window.chatwootConfig.selectedLocale);
},
methods: {
initializeColorTheme() {
this.setColorTheme(
window.matchMedia('(prefers-color-scheme: dark)').matches
);
},
setColorTheme(isOSOnDarkMode) {
const selectedColorScheme =
LocalStorage.get(LOCAL_STORAGE_KEYS.COLOR_SCHEME) || 'light';
if (
(selectedColorScheme === 'auto' && isOSOnDarkMode) ||
selectedColorScheme === 'dark'
) {
this.theme = 'dark';
document.body.classList.add('dark');
} else {
this.theme = 'light ';
document.body.classList.remove('dark');
}
},
listenToThemeChanges() {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
mql.onchange = e => this.setColorTheme(e.matches);
},
setLocale(locale) {
this.$root.$i18n.locale = locale;
},
@@ -20,15 +20,6 @@ select {
margin-left: var(--space-normal);
}
.tooltip {
background-color: var(--black-transparent);
border-radius: $space-smaller;
font-size: $font-size-mini;
max-width: var(--space-giga);
padding: $space-smaller $space-small;
z-index: 999;
}
code {
border: 0;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
@@ -1,5 +1,5 @@
.bg-light {
@include background-light;
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {
@@ -22,43 +22,43 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin border-normal() {
border: 1px solid $color-border;
@apply border border-slate-50 dark:border-slate-700;
}
@mixin border-normal-left() {
border-left: 1px solid $color-border;
@apply border-l border-slate-50 dark:border-slate-700;
}
@mixin border-normal-top() {
border-top: 1px solid $color-border;
@apply border-t border-slate-50 dark:border-slate-700;
}
@mixin border-normal-right() {
border-right: 1px solid $color-border;
@apply border-r border-slate-50 dark:border-slate-700;
}
@mixin border-normal-bottom() {
border-bottom: 1px solid $color-border;
@apply border-b border-slate-50 dark:border-slate-700;
}
@mixin border-light() {
border: 1px solid $color-border-light;
@apply border border-slate-25 dark:border-slate-700;
}
@mixin border-light-left() {
border-left: 1px solid $color-border-light;
@apply border-l border-slate-25 dark:border-slate-700;
}
@mixin border-light-top() {
border-top: 1px solid $color-border-light;
@apply border-t border-slate-25 dark:border-slate-700;
}
@mixin border-light-right() {
border-right: 1px solid $color-border-light;
@apply border-r border-slate-25 dark:border-slate-700;
}
@mixin border-light-bottom() {
border-bottom: 1px solid $color-border-light;
@apply border-b border-slate-25 dark:border-slate-700;
}
// background
@@ -67,11 +67,11 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin background-light() {
background: $color-background-light;
@apply bg-slate-50 dark:bg-slate-800;
}
@mixin background-white() {
background: $color-white;
@apply bg-white dark:bg-slate-900;
}
// input form
@@ -3,9 +3,6 @@
// Primary sidebar
.primary--sidebar {
border-left: 1px solid var(--s-50);
border-right: 0;
.options-menu.dropdown-pane {
right: var(--space-smaller);
@@ -20,63 +17,6 @@
}
}
// Secondary sidebar
.secondary-sidebar {
.secondary-menu {
border-left: 1px solid var(--s-50);
border-right: 0;
.nested.vertical.menu {
.badge--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.menu-label {
text-align: right;
}
}
.secondary-menu--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.account-context--group .account-context--switch-group {
--overlay-shadow: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
background-image: var(--overlay-shadow);
}
// Help center sidebar
.sidebar-header--wrap .header-title--wrap {
margin-left: unset;
margin-right: var(--space-small);
}
}
}
// Woot button
.button {
.icon--emoji + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon--font + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
}
// Settings header
.settings-header {
.header--icon {
@@ -43,17 +43,13 @@
}
.border-right {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
.border-left {
border-left: 1px solid var(--color-border);
}
.bg-white {
background-color: var(--white);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
@@ -1,3 +1,7 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@@ -67,3 +71,8 @@
@import 'plugins/dropdown';
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
@apply bg-slate-900 text-white py-1 px-2 z-40 text-xs rounded-md dark:bg-slate-200 dark:text-slate-900;
}
@@ -4,10 +4,8 @@
// Conversation header - Light BG
.settings-header {
@include background-white;
@include flex;
@include flex-align($x: justify, $y: middle);
border-bottom: 1px solid var(--s-50);
height: $header-height;
min-height: $header-height;
padding: $space-small $space-normal;
@@ -27,7 +25,7 @@
.wizard-box {
.item {
@include background-light;
// @include background-light;
cursor: pointer;
padding: $space-normal $space-normal $space-normal $space-medium;
@@ -128,8 +126,8 @@
}
.wizard-body {
@include background-white;
@include border-light;
@include background-white;
@include full-height();
padding: $space-medium;
@@ -1,207 +1,201 @@
$default-button-height: 2.5rem;
.button {
align-items: center;
display: inline-flex;
height: $default-button-height;
margin-bottom: 0;
@apply items-center inline-flex h-10 mb-0;
.button__content {
width: 100%;
@apply w-full;
img,
svg {
@apply inline-block;
}
}
.spinner {
padding: 0 var(--space-small);
@apply px-2 py-0;
}
.icon--emoji+.button__content {
padding-left: var(--space-small);
.icon--emoji + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
.icon--font+.button__content {
padding-left: var(--space-small);
.icon--font + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
// @TODDO - Remove after moving all buttons to woot-button
.icon+.button__content {
padding-left: var(--space-small);
width: auto;
.icon + .button__content {
@apply pl-2 w-auto rtl:pr-2 rtl:pl-0;
}
&.expanded {
display: flex;
justify-content: center;
@apply flex justify-center text-center;
}
&.round {
border-radius: $space-larger;
@apply rounded-full;
}
// @TODO Use with link
&.compact {
padding-bottom: 0;
padding-top: 0;
@apply pb-0 pt-0;
}
&.hollow {
border-color: var(--s-200);
color: var(--w-700);
@apply border border-slate-200 text-woot-700 dark:text-woot-100 hover:bg-woot-50 dark:hover:bg-woot-900;
&.secondary {
border-color: var(--s-200);
color: var(--s-700);
@apply text-slate-700 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
}
&.success {
border-color: var(--s-200);
color: var(--g-700);
@apply text-green-700 dark:text-green-100 hover:bg-green-50 dark:hover:bg-green-800;
}
&.alert {
border-color: var(--s-200);
color: var(--r-700);
@apply text-red-700 dark:text-red-100 hover:bg-red-50 dark:hover:bg-red-800;
}
&.warning {
border-color: var(--s-200);
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-100 hover:bg-yellow-50 dark:hover:bg-yellow-800;
}
&:hover {
background: var(--s-75);
border-color: var(--s-100);
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
&.secondary {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700;
}
&.success {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700;
}
&.alert {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700;
}
&.warning {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700;
}
}
}
// Smooth style
&.smooth {
@include button-style(var(--w-50), var(--w-100), var(--w-700));
@apply bg-woot-50 dark:bg-woot-800 text-woot-700 dark:text-woot-100 hover:text-woot-700 dark:hover:text-woot-700 hover:bg-woot-100 dark:hover:bg-woot-900;
&.secondary {
@include button-style(var(--s-50), var(--s-100), var(--s-700));
@apply bg-slate-50 dark:bg-slate-700 text-slate-700 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800;
}
&.success {
@include button-style(var(--g-50), var(--g-100), var(--g-700));
@apply bg-green-50 dark:bg-green-700 text-green-700 dark:text-green-100 hover:bg-green-100 dark:hover:bg-green-800;
}
&.alert {
@include button-style(var(--r-50), var(--r-100), var(--r-700));
@apply bg-red-50 dark:bg-red-700 text-red-700 dark:text-red-100 hover:bg-red-100 dark:hover:bg-red-800;
}
&.warning {
@include button-style(var(--y-100), var(--y-200), var(--y-700));
@apply bg-yellow-50 dark:bg-yellow-700 text-yellow-700 dark:text-yellow-100 hover:bg-yellow-100 dark:hover:bg-yellow-800;
}
}
&.clear {
color: var(--w-700);
@apply text-woot-500 dark:text-woot-500;
&.secondary {
color: var(--s-700);
@apply text-slate-700 dark:text-slate-100;
}
&.success {
color: var(--g-700);
@apply text-green-700 dark:text-green-100;
}
&.alert {
color: var(--r-700);
@apply text-red-700 dark:text-red-100;
}
&.warning {
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-100;
}
&:hover {
background: var(--w-50);
@apply hover:bg-woot-50 dark:hover:bg-woot-300;
&.secondary {
background: var(--s-50);
@apply hover:bg-slate-50 dark:hover:bg-slate-700;
}
&.success {
background: var(--g-50);
@apply hover:bg-green-50 dark:hover:bg-green-800;
}
&.alert {
background: var(--r-50);
@apply hover:bg-red-50 dark:hover:bg-red-800;
}
&.warning {
background: var(--y-50);
@apply hover:bg-yellow-50 dark:hover:bg-yellow-800;
}
}
&:active {
&.secondary {
@apply active:bg-slate-100 dark:active:bg-slate-900;
}
}
&:focus {
&.secondary {
@apply focus:bg-slate-50 dark:focus:bg-slate-700;
}
}
}
// Sizes
&.tiny {
height: var(--space-medium);
@apply h-6;
.icon+.button__content {
padding-left: var(--space-micro);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.small {
height: var(--space-large);
padding-bottom: var(--space-smaller);
padding-top: var(--space-smaller);
@apply h-8 pb-1 pt-1;
.icon+.button__content {
padding-left: var(--space-smaller);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.large {
height: var(--space-larger);
@apply h-12;
}
&.button--only-icon {
justify-content: center;
padding-left: 0;
padding-right: 0;
width: $default-button-height;
@apply justify-center pl-0 pr-0 w-10;
&.tiny {
width: var(--space-medium);
@apply w-6;
}
&.small {
width: var(--space-large);
@apply w-8;
}
&.large {
width: var(--space-larger);
@apply w-12;
}
}
&.link {
height: auto;
margin: 0;
padding: 0;
@apply h-auto m-0 p-0;
&:hover {
text-decoration: underline;
@apply underline;
}
}
}
@@ -59,11 +59,6 @@ $resolve-button-width: 8.25rem;
flex-direction: row;
flex-grow: 1;
justify-content: flex-end;
margin-top: var(--space-small);
@include breakpoint(medium up) {
margin-top: 0;
}
&.has-open-sidebar {
justify-content: flex-end;
@@ -12,9 +12,7 @@
.conversation {
@include flex;
@include flex-shrink;
border-bottom: 1px solid transparent;
border-left: var(--space-micro) solid transparent;
border-top: 1px solid transparent;
cursor: pointer;
padding: 0 var(--space-normal);
position: relative;
@@ -22,35 +20,11 @@
&.active {
animation: left-shift-animation 0.25s $swift-ease-out-function;
background: var(--color-background);
border-bottom-color: var(--color-border-light);
border-left-color: var(--color-woot);
border-top-color: var(--color-border-light);
.conversation--details {
border-top-color: transparent;
}
+ .conversation .conversation--details {
border-top-color: transparent;
}
}
&:first-child {
.conversation--details {
border-top-color: transparent;
}
}
&:last-child {
.conversation--details {
border-bottom-color: var(--color-border-light);
}
}
.conversation--details {
@include border-light-bottom;
@include border-light-top;
border-bottom-color: transparent;
padding: var(--space-slab) 0;
}
@@ -82,6 +56,11 @@
text-overflow: ellipsis;
white-space: nowrap;
width: 16.875rem;
img,
svg {
display: inline-block;
}
}
.conversation--meta {
@@ -390,3 +390,42 @@
margin-bottom: 0;
}
}
.conversation-sidebar-wrap {
flex: 0 0;
flex-basis: 100%;
height: auto;
overflow: auto;
z-index: var(--z-index-low);
@include breakpoint(medium up) {
flex-basis: 17.5rem;
}
@include breakpoint(large up) {
flex-basis: 18.75rem;
}
@include breakpoint(xlarge up) {
flex-basis: 19.375rem;
}
@include breakpoint(xxlarge up) {
flex-basis: 20.625rem;
}
@include breakpoint(xxxlarge up) {
flex-basis: 25rem;
}
}
.activity-wrap {
.message-text--metadata {
.time {
@include breakpoint(xlarge up) {
margin-left: var(--space-small);
}
}
}
}
@@ -61,3 +61,7 @@ input {
border-color: var(--r-400);
}
}
label {
@apply dark:text-slate-300;
}
@@ -34,7 +34,6 @@
.modal-container {
@include normal-shadow;
background-color: $color-white;
border-radius: $space-smaller;
max-height: 100%;
overflow: auto;
@@ -1,6 +1,6 @@
.reply-box {
transition: box-shadow 0.35s $swift-ease-out-function,
height 2s $swift-ease-out-function;
height 2s $swift-ease-out-function;
&.is-focused {
box-shadow: var(--shadow);
@@ -1,18 +1,3 @@
.loading-state {
padding: $space-jumbo $space-smaller;
.message {
color: $color-gray;
display: block;
text-align: center;
width: 100%;
}
.spinner {
float: none;
top: -$space-smaller;
}
}
// EMPTY STATES
.empty-state {
@@ -4,15 +4,13 @@ table {
thead {
th {
font-weight: var(--font-weight-bold);
text-align: left;
text-transform: uppercase;
@apply font-medium text-left uppercase text-slate-900 dark:text-slate-200;
}
}
tbody {
tr {
border-bottom: 1px solid var(--color-border-light);
@apply border-b border-slate-50 dark:border-slate-700;
}
td {
@@ -126,27 +126,28 @@
class="conversations-list"
:class="{ 'is-context-menu-open': isContextMenuOpen }"
>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
<div>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
</div>
<div v-if="chatListLoading" class="text-center">
<span class="spinner" />
</div>
@@ -155,6 +156,7 @@
v-if="!hasCurrentPageEndReached && !chatListLoading"
variant="clear"
size="expanded"
class="text-center"
@click="loadMoreConversations"
>
{{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }}
@@ -956,8 +958,6 @@ export default {
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/woot';
.spinner {
margin-top: var(--space-normal);
margin-bottom: var(--space-normal);
@@ -52,7 +52,8 @@ export default {
},
computed: {
modalContainerClassName() {
let className = 'modal-container skip-context-menu';
let className =
'modal-container bg-white dark:bg-slate-800 skip-context-menu';
if (this.fullWidth) {
return `${className} modal-container--full-width`;
}
@@ -1,10 +1,17 @@
<template>
<div class="column page-top-bar">
<img v-if="headerImage" :src="headerImage" alt="No image" />
<h2 class="page-sub-title">
<h2
ref="modalHeaderTitle"
class="text-slate-800 text-lg dark:text-slate-100"
>
{{ headerTitle }}
</h2>
<p v-if="headerContent" class="small-12 column wrap-content">
<p
v-if="headerContent"
ref="modalHeaderContent"
class="small-12 column wrap-content"
>
{{ headerContent }}
<span v-if="headerContentValue" class="content-value">
{{ headerContentValue }}
@@ -1,5 +1,5 @@
<template>
<div class="row settings--section" :class="{ border: showBorder }">
<div class="row settings--section" :class="{ 'border-bottom': showBorder }">
<div class="medium-4 small-12 title--section">
<p v-if="title" class="sub-block-title">
{{ title }}
@@ -50,7 +50,7 @@ export default {
display: flex;
padding: 0 $space-normal $space-normal 0;
&.border {
&.border-bottom {
padding-top: $space-normal;
border-bottom: 1px solid $color-border;
}
@@ -3,8 +3,8 @@
size="small"
variant="clear"
color-scheme="secondary"
class="-ml-3 text-black-900 dark:text-slate-300"
icon="list"
class="toggle-sidebar"
@click="onMenuItemClick"
/>
</template>
@@ -20,8 +20,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.toggle-sidebar {
margin-left: var(--space-minus-small);
}
</style>
@@ -4,7 +4,7 @@
<woot-dropdown-item
v-for="status in availabilityStatuses"
:key="status.value"
class="status-items"
class="flex items-baseline"
>
<woot-button
size="small"
@@ -18,23 +18,25 @@
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider />
<woot-dropdown-item class="auto-offline--toggle">
<div class="info-wrap">
<woot-dropdown-item class="m-0 flex items-center justify-between p-2">
<div class="flex items-center">
<fluent-icon
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
icon="info"
size="14"
class="info-icon"
class="mt-px"
/>
<span class="auto-offline--text">
<span
class="my-0 mx-1 text-xs font-medium text-slate-600 dark:text-slate-100"
>
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
</span>
</div>
<woot-switch
size="small"
class="auto-offline--switch"
class="mt-px mx-1 mb-0"
:value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
@@ -138,71 +140,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.status {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: var(--space-micro) var(--space-smaller);
}
.status-view {
display: flex;
align-items: baseline;
& &--title {
color: var(--b-600);
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-left: var(--space-small);
&:first-letter {
text-transform: capitalize;
}
}
}
.status-change {
.dropdown-pane {
top: -132px;
right: var(--space-normal);
}
.status-items {
display: flex;
align-items: baseline;
}
}
.auto-offline--toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding: var(--space-smaller);
margin: 0;
.info-wrap {
display: flex;
align-items: center;
}
.info-icon {
margin-top: -1px;
}
.auto-offline--switch {
margin: -1px var(--space-micro) 0;
}
.auto-offline--text {
margin: 0 var(--space-smaller);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
color: var(--s-700);
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<aside class="woot-sidebar">
<aside class="h-full flex">
<primary-sidebar
:logo-source="globalConfig.logoThumbnail"
:installation-name="globalConfig.installationName"
@@ -11,22 +11,21 @@
@key-shortcut-modal="toggleKeyShortcutModal"
@open-notification-panel="openNotificationPanel"
/>
<div class="secondary-sidebar">
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</div>
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</aside>
</template>
@@ -214,87 +213,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.woot-sidebar {
background: var(--white);
display: flex;
min-height: 0;
height: 100%;
width: fit-content;
}
</style>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.account-selector--modal {
.modal-container {
width: 25rem;
}
}
.secondary-sidebar {
overflow-y: auto;
height: 100%;
}
.account-selector {
cursor: pointer;
padding: $space-small $space-large;
.selected--account {
margin-top: -$space-smaller;
& + .account--details {
padding-left: $space-normal - $space-micro;
}
}
.account--details {
padding-left: $space-large + $space-smaller;
}
&:last-child {
margin-bottom: $space-large;
}
a {
align-items: center;
cursor: pointer;
display: flex;
.account--name {
cursor: pointer;
font-size: $font-size-medium;
font-weight: $font-weight-medium;
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: $font-size-mini;
text-transform: capitalize;
}
}
}
.app-context-menu {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
height: 3.75rem;
}
.current-user--options {
font-size: $font-size-big;
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
}
.secondary-menu .nested.vertical.menu {
margin-left: var(--space-small);
}
</style>
@@ -1,25 +1,29 @@
<template>
<div
v-if="showShowCurrentAccountContext"
class="account-context--group"
class="rounded-md text-xs py-2 px-2 mt-2 relative border border-slate-50 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700 cursor-pointer"
@mouseover="setShowSwitch"
@mouseleave="resetShowSwitch"
>
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="account-context--name text-ellipsis">
<p class="text-ellipsis font-medium mb-0">
{{ account.name }}
</p>
<transition name="fade">
<div v-if="showSwitchButton" class="account-context--switch-group">
<woot-button
variant="clear"
size="tiny"
icon="arrow-swap"
class="switch-button"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
<div
v-if="showSwitchButton"
class="bg-gradient-to-r rtl:bg-gradient-to-l from-transparent via-white dark:via-slate-700 to-white dark:to-slate-700 flex items-center h-full justify-end absolute top-0 right-0 w-full"
>
<div class="my-0 mx-2">
<woot-button
variant="clear"
size="tiny"
icon="arrow-swap"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
</div>
</div>
</transition>
</div>
@@ -51,51 +55,6 @@ export default {
};
</script>
<style scoped lang="scss">
.account-context--group {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
font-size: var(--font-size-mini);
padding: var(--space-small);
margin: var(--space-small) var(--space-small) 0 var(--space-small);
position: relative;
&:hover {
background: var(--b-100);
}
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
}
}
.switch-button {
margin: 0 var(--space-small);
}
.account-context--switch-group {
--overlay-shadow: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
align-items: center;
background-image: var(--overlay-shadow);
border-top-left-radius: 0;
border-top-right-radius: var(--border-radius-normal);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--border-radius-normal);
display: flex;
height: 100%;
justify-content: flex-end;
opacity: 1;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
@@ -2,31 +2,39 @@
<woot-modal
:show="showAccountModal"
:on-close="() => $emit('close-account-modal')"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="account-selector--wrap">
<div class="px-8 pt-4 pb-8">
<div
v-for="account in currentUser.accounts"
:id="`account-${account.id}`"
:key="account.id"
class="account-selector"
class="pt-0 pb-0"
>
<button
class="button expanded clear link"
class="flex justify-between items-center expanded clear link cursor-pointer px-4 py-3 w-full rounded-lg hover:underline hover:bg-slate-25 dark:hover:bg-slate-900"
@click="onChangeAccount(account.id)"
>
<span class="button__content">
<label :for="account.name" class="account-details--wrap">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
<span class="w-full">
<label :for="account.name" class="text-left rtl:text-right">
<div
class="text-slate-700 text-lg dark:text-slate-100 font-medium hover:underline-offset-4 leading-5"
>
{{ account.name }}
</div>
<div
class="text-slate-500 text-xs dark:text-slate-500 font-medium hover:underline-offset-4"
>
{{ account.role }}
</div>
</label>
</span>
<fluent-icon
v-show="account.id === accountId"
class="selected--account"
class="text-slate-800 dark:text-slate-100"
icon="checkmark-circle"
type="solid"
size="24"
@@ -74,32 +82,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.account-selector--wrap {
margin-top: var(--space-normal);
}
.account-selector {
padding-top: 0;
padding-bottom: 0;
.button {
display: flex;
justify-content: space-between;
padding: var(--space-one) var(--space-normal);
.account-details--wrap {
text-align: left;
.account--name {
cursor: pointer;
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: var(--font-size-mini);
text-transform: capitalize;
}
}
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<div class="logo">
<div class="w-8 h-8">
<router-link :to="dashboardPath" replace>
<img :src="source" :alt="name" />
</router-link>
@@ -30,17 +30,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
$logo-size: 32px;
.logo {
padding: var(--space-normal);
img {
width: $logo-size;
height: $logo-size;
object-fit: cover;
object-position: left center;
}
}
</style>
@@ -1,15 +1,25 @@
<template>
<div class="notifications-link">
<woot-button
class-names="notifications-link--button"
variant="clear"
color-scheme="secondary"
:class="{ 'is-active': isNotificationPanelActive }"
<div class="mb-4">
<button
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-800 dark:hover:text-slate-100 hover:text-slate-600 relative"
:class="{
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50': isNotificationPanelActive,
}"
@click="openNotificationPanel"
>
<fluent-icon icon="alert" />
<span v-if="unreadCount" class="badge warning">{{ unreadCount }}</span>
</woot-button>
<fluent-icon
icon="alert"
:class="{
'text-woot-500': isNotificationPanelActive,
}"
/>
<span
v-if="unreadCount"
class="text-black-900 bg-yellow-300 absolute -top-0.5 -right-1 p-1 text-xxs min-w-[1rem] rounded-full"
>
{{ unreadCount }}
</span>
</button>
</div>
</template>
<script>
@@ -43,37 +53,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.notifications-link {
margin-bottom: var(--space-small);
}
.badge {
position: absolute;
right: var(--space-minus-smaller);
top: var(--space-minus-smaller);
}
.notifications-link--button {
display: flex;
position: relative;
border-radius: var(--border-radius-large);
border: 1px solid transparent;
color: var(--s-600);
margin: var(--space-small) 0;
&:hover {
background: var(--w-50);
color: var(--s-600);
}
&:focus {
border-color: var(--w-500);
}
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
</style>
@@ -3,11 +3,10 @@
<div
v-if="show"
v-on-clickaway="onClickAway"
class="options-menu dropdown-pane"
:class="{ 'dropdown-pane--open': show }"
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-10 rounded-md drop-shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
:class="{ 'block visible': show }"
>
<availability-status />
<li class="divider" />
<woot-dropdown-menu>
<woot-dropdown-item v-if="showChangeAccountOption">
<woot-button
@@ -50,7 +49,7 @@
>
<a
:href="href"
class="button small clear secondary custom-sidebar--button"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
:class="{ 'is-active': isActive }"
@click="e => handleProfileSettingClick(e, navigate)"
>
@@ -64,7 +63,7 @@
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
<a
href="/super_admin"
class="button small clear secondary custom-sidebar--button"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
target="_blank"
rel="noopener nofollow noreferrer"
@click="$emit('close')"
@@ -149,17 +148,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.options-menu.dropdown-pane {
left: var(--space-slab);
bottom: var(--space-larger);
min-width: var(--space-giga);
top: unset;
z-index: var(--z-index-low);
}
.custom-sidebar--button {
height: var(--space-large) !important;
padding: var(--space-smaller) 0.675rem !important;
}
</style>
@@ -1,11 +1,14 @@
<template>
<div class="primary--sidebar">
<logo
:source="logoSource"
:name="installationName"
:account-id="accountId"
/>
<nav class="menu vertical">
<div
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-700 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
>
<div class="flex flex-col items-center">
<logo
:source="logoSource"
:name="installationName"
:account-id="accountId"
class="m-4 mb-10"
/>
<primary-nav-item
v-for="menuItem in menuItems"
:key="menuItem.toState"
@@ -14,8 +17,8 @@
:to="menuItem.toState"
:is-child-menu-active="menuItem.key === activeMenuItem"
/>
</nav>
<div class="menu vertical user-menu">
</div>
<div class="flex flex-col items-center justify-end pb-6">
<primary-nav-item
v-if="!isACustomBrandedInstance"
icon="book-open-globe"
@@ -101,27 +104,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.primary--sidebar {
display: flex;
flex-direction: column;
width: var(--space-jumbo);
border-right: 1px solid var(--s-50);
box-sizing: content-box;
height: 100%;
flex-shrink: 0;
}
.menu {
align-items: center;
margin-top: var(--space-medium);
}
.user-menu {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: flex-end;
margin-bottom: var(--space-normal);
}
</style>
@@ -3,15 +3,28 @@
<a
v-tooltip.right="$t(`SIDEBAR.${name}`)"
:href="href"
class="button clear button--only-icon menu-item"
:class="{ 'is-active': isActive || isChildMenuActive }"
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-800 dark:hover:text-slate-100 hover:text-slate-600 relative"
:class="{
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
isActive || isChildMenuActive,
}"
:rel="openInNewPage ? 'noopener noreferrer nofollow' : undefined"
:target="openInNewPage ? '_blank' : undefined"
@click="navigate"
>
<fluent-icon :icon="icon" />
<span class="show-for-sr">{{ name }}</span>
<span v-if="count" class="badge warning">{{ count }}</span>
<fluent-icon
:icon="icon"
:class="{
'text-woot-500': isActive || isChildMenuActive,
}"
/>
<span class="sr-only">{{ name }}</span>
<span
v-if="count"
class="text-black-900 bg-yellow-500 absolute -top-1 -right-1"
>
{{ count }}
</span>
</a>
</router-link>
</template>
@@ -45,40 +58,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.button {
margin: var(--space-small) 0;
}
.menu-item {
display: inline-flex;
position: relative;
border-radius: var(--border-radius-large);
border: 1px solid transparent;
color: var(--s-600);
&:hover {
background: var(--w-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-500);
}
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
.icon {
font-size: var(--font-size-default);
}
.badge {
position: absolute;
right: var(--space-minus-smaller);
top: var(--space-minus-smaller);
}
</style>
@@ -1,7 +1,14 @@
<template>
<div v-if="hasSecondaryMenu" class="main-nav secondary-menu">
<div
v-if="hasSecondaryMenu"
class="h-full overflow-auto w-48 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-700 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
>
<account-context @toggle-accounts="toggleAccountModal" />
<transition-group name="menu-list" tag="ul" class="menu vertical">
<transition-group
name="menu-list"
tag="ul"
class="pt-2 list-none ml-0 mb-0"
>
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
:key="menuItem.toState"
@@ -249,27 +256,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.secondary-menu {
display: flex;
flex-direction: column;
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100%;
width: 12.5rem;
flex-shrink: 0;
overflow-y: hidden;
position: unset;
&:hover {
overflow-y: hidden;
}
.menu {
padding: var(--space-small);
overflow-y: auto;
}
}
</style>
@@ -5,38 +5,69 @@
custom
active-class="active"
>
<li :class="{ active: isActive }">
<li
class="font-medium h-7 my-1 hover:bg-slate-25 hover:text-bg-50 flex items-center px-2 rounded-md dark:hover:bg-slate-800"
:class="{
'bg-woot-25 dark:bg-slate-800': isActive,
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
}"
@click="navigate"
>
<a
:href="href"
class="button clear menu-item text-truncate"
:class="{ 'is-active': isActive, 'text-truncate': shouldTruncate }"
@click="navigate"
class="inline-flex text-left max-w-full w-full items-center"
>
<span v-if="icon" class="badge--icon">
<fluent-icon class="inbox-icon" :icon="icon" size="12" />
<span
v-if="icon"
class="inline-flex items-center justify-center w-4 rounded-sm bg-slate-100 dark:bg-slate-700 p-0.5 mr-1.5 rtl:mr-0 rtl:ml-1.5"
>
<fluent-icon
class="text-xxs text-slate-600 dark:text-slate-200"
:class="{
'text-woot-500 dark:text-woot-500': isActive,
}"
:icon="icon"
size="12"
/>
</span>
<span
v-if="labelColor"
class="badge--label"
class="inline-flex rounded-sm bg-slate-100 h-3 w-3.5 mr-1.5 rtl:mr-0 rtl:ml-1.5 border border-slate-50 dark:border-slate-900"
:style="{ backgroundColor: labelColor }"
/>
<span
:title="menuTitle"
class="menu-label button__content"
:class="{ 'text-truncate': shouldTruncate }"
<div
class="items-center flex overflow-hidden whitespace-nowrap text-ellipsis w-full justify-between"
>
{{ label }}
<span v-if="showChildCount" class="count-view">
<span
:title="menuTitle"
class="text-sm text-slate-600 dark:text-slate-100"
:class="{
'text-woot-500 dark:text-woot-500': isActive,
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
}"
>
{{ label }}
</span>
<span
v-if="showChildCount"
class="bg-slate-50 dark:bg-slate-700 rounded text-xxs font-medium mx-1 py-0 px-1"
:class="
isCountZero
? `text-slate-300 dark:text-slate-600`
: `text-slate-600 dark:text-slate-50`
"
>
{{ childItemCount }}
</span>
</span>
<span v-if="count" class="badge" :class="{ secondary: !isActive }">
{{ count }}
</span>
<span v-if="warningIcon" class="badge--icon">
</div>
<span
v-if="warningIcon"
class="inline-flex rounded-sm mr-1 bg-slate-100"
>
<fluent-icon
v-tooltip.top-end="$t('SIDEBAR.FACEBOOK_REAUTHORIZE')"
class="inbox-icon"
class="text-xxs"
:icon="warningIcon"
size="12"
/>
@@ -72,10 +103,6 @@ export default {
type: String,
default: '',
},
count: {
type: String,
default: '',
},
showChildCount: {
type: Boolean,
default: false,
@@ -89,96 +116,12 @@ export default {
showIcon() {
return { 'text-truncate': this.shouldTruncate };
},
isCountZero() {
return this.childItemCount === 0;
},
menuTitle() {
return this.shouldTruncate ? this.label : '';
},
},
};
</script>
<style lang="scss" scoped>
$badge-size: var(--space-normal);
$label-badge-size: var(--space-slab);
.button {
margin: var(--space-small) 0;
}
.menu-item {
display: inline-flex;
color: var(--s-600);
font-weight: var(--font-weight-medium);
width: 100%;
height: var(--space-medium);
padding: var(--space-smaller) var(--space-smaller);
margin: var(--space-smaller) 0;
text-align: left;
line-height: 1.2;
&:hover {
background: var(--s-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-300);
}
&.is-active {
background: var(--w-25);
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-500);
}
}
.menu-label {
flex-grow: 1;
}
.inbox-icon {
font-size: var(--font-size-nano);
}
.badge--label,
.badge--icon {
display: inline-flex;
border-radius: var(--border-radius-small);
margin-right: var(--space-smaller);
background: var(--s-100);
}
.badge--icon {
align-items: center;
height: $badge-size;
justify-content: center;
min-width: $badge-size;
}
.badge--label {
height: $label-badge-size;
min-width: $label-badge-size;
margin-left: var(--space-smaller);
border: 1px solid var(--color-border-light);
}
.badge.secondary {
min-width: unset;
background: var(--s-75);
color: var(--s-600);
font-weight: var(--font-weight-bold);
}
.count-view {
background: var(--s-50);
border-radius: var(--border-radius-normal);
color: var(--s-600);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-bold);
margin-left: var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
line-height: var(--font-size-small);
}
</style>
@@ -1,46 +1,57 @@
<template>
<li v-show="isMenuItemVisible" class="sidebar-item">
<div v-if="hasSubMenu" class="secondary-menu--wrap">
<span class="secondary-menu--header fs-small">
<li v-show="isMenuItemVisible" class="mt-1">
<div v-if="hasSubMenu" class="flex justify-between">
<span
class="text-sm text-slate-700 dark:text-slate-200 font-semibold my-2 px-2 pt-1"
>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
</span>
<div v-if="menuItem.showNewButton" class="submenu-icons">
<div v-if="menuItem.showNewButton" class="flex items-center">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
class="submenu-icon"
class="p-0 ml-2"
@click="onClickOpen"
/>
</div>
</div>
<router-link
v-else
class="secondary-menu--title secondary-menu--link fs-small"
class="rounded-lg leading-4 font-medium flex items-center p-2 m-0 text-sm text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
:class="computedClass"
:to="menuItem && menuItem.toState"
>
<fluent-icon
:icon="menuItem.icon"
class="secondary-menu--icon"
class="min-w-[1rem] mr-1.5 rtl:mr-0 rtl:ml-1.5"
size="14"
/>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
<span v-if="showChildCount(menuItem.count)" class="count-view">
<span
v-if="showChildCount(menuItem.count)"
class="rounded-md text-xxs font-medium mx-1 py-0 px-1"
:class="{
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600': isActiveView,
'bg-slate-50 dark:bg-slate-700': !isActiveView,
}"
>
{{ `${menuItem.count}` }}
</span>
<span
v-if="menuItem.beta"
data-view-component="true"
label="Beta"
class="beta"
class="px-1 mx-1 inline-block font-medium leading-4 border border-green-400 text-green-500 rounded-lg text-xxs"
>
{{ $t('SIDEBAR.BETA') }}
</span>
</router-link>
<ul v-if="hasSubMenu" class="nested vertical menu">
<ul v-if="hasSubMenu" class="list-none ml-0 mb-0">
<secondary-child-nav-item
v-for="child in menuItem.children"
:key="child.id"
@@ -55,21 +66,21 @@
/>
<router-link
v-if="showItem(menuItem)"
v-slot="{ href, isActive, navigate }"
v-slot="{ href, navigate }"
:to="menuItem.toState"
custom
>
<li class="menu-item--new">
<a
:href="href"
class="button small link clear secondary"
:class="{ 'is-active': isActive }"
@click="e => newLinkClick(e, navigate)"
>
<fluent-icon icon="add" size="16" />
<span class="button__content">
<li class="pl-1">
<a :href="href">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
@click="e => newLinkClick(e, navigate)"
>
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
</span>
</woot-button>
</a>
</li>
</router-link>
@@ -108,6 +119,12 @@ export default {
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
globalConfig: 'globalConfig/get',
}),
isCountZero() {
return this.menuItem.count === 0;
},
isActiveView() {
return this.computedClass.includes('active-view');
},
hasSubMenu() {
return !!this.menuItem.children;
},
@@ -178,7 +195,7 @@ export default {
this.isUnattended ||
this.isCurrentRoute
) {
return 'is-active';
return 'bg-woot-25 dark:bg-slate-800 text-woot-500 dark:text-woot-500 hover:text-woot-500 dark:hover:text-woot-500 active-view';
}
if (this.hasSubMenu) {
if (
@@ -187,12 +204,12 @@ export default {
this.isIntegrationsSettings ||
this.isApplicationsSettings
) {
return 'is-active';
return 'bg-woot-25 dark:bg-slate-800 text-woot-500 dark:text-woot-500 hover:text-woot-500 dark:hover:text-woot-500 active-view';
}
return ' ';
return 'hover:text-slate-700 dark:hover:text-slate-100';
}
return '';
return 'hover:text-slate-700 dark:hover:text-slate-100';
},
},
methods: {
@@ -233,132 +250,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.sidebar-item {
margin: var(--space-smaller) 0 0;
}
.secondary-menu--wrap {
display: flex;
justify-content: space-between;
margin-top: var(--space-small);
}
.secondary-menu--header {
color: var(--s-700);
display: flex;
font-weight: var(--font-weight-bold);
line-height: var(--space-normal);
margin: var(--space-small) 0;
padding: 0 var(--space-small);
}
.secondary-menu--title {
color: var(--s-600);
display: flex;
font-weight: var(--font-weight-medium);
line-height: var(--space-normal);
margin: var(--space-small) 0;
padding: 0 var(--space-small);
}
.secondary-menu--link {
display: flex;
align-items: center;
margin: 0;
padding: var(--space-small);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-normal);
color: var(--s-700);
&:hover {
background: var(--s-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-300);
}
&.router-link-exact-active,
&.is-active {
background: var(--w-25);
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-600);
}
}
.secondary-menu--icon {
margin-right: var(--space-smaller);
min-width: var(--space-normal);
}
.sub-menu-link {
color: var(--s-600);
}
.wrap {
display: flex;
align-items: center;
}
.label-color--display {
border-radius: var(--space-smaller);
height: var(--space-normal);
margin-right: var(--space-small);
min-width: var(--space-normal);
width: var(--space-normal);
}
.inbox-icon {
position: relative;
top: -1px;
}
.sidebar-item .menu-item--new {
padding: var(--space-small) 0;
.button {
display: inline-flex;
color: var(--s-500);
}
}
.beta {
padding-right: var(--space-smaller) !important;
padding-left: var(--space-smaller) !important;
margin: 0 var(--space-smaller) !important;
display: inline-block;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
line-height: 14px;
border: 1px solid transparent;
border-radius: 2em;
color: var(--g-800);
border-color: var(--g-700);
}
.count-view {
background: var(--s-50);
border-radius: var(--border-radius-normal);
color: var(--s-600);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-bold);
margin: 0 var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
}
.submenu-icons {
display: flex;
align-items: center;
.submenu-icon {
padding: 0;
margin-left: var(--space-small);
}
}
</style>
@@ -4,7 +4,6 @@ import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n';
import WootModal from 'dashboard/components/Modal';
import WootModalHeader from 'dashboard/components/ModalHeader';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
@@ -38,9 +37,7 @@ describe('accountSelctor', () => {
},
],
};
const accountId = 1;
const globalConfig = { createNewAccountFromDashboard: false };
let store = null;
let actions = null;
let modules = null;
@@ -49,44 +46,46 @@ describe('accountSelctor', () => {
modules = {
auth: {
getters: {
getCurrentAccountId: () => accountId,
getCurrentAccountId: () => 1,
getCurrentUser: () => currentUser,
},
},
globalConfig: {
getters: {
'globalConfig/get': () => globalConfig,
'globalConfig/get': () => ({ createNewAccountFromDashboard: false }),
},
},
};
store = new Vuex.Store({
actions,
modules,
});
let store = new Vuex.Store({ actions, modules });
accountSelector = mount(AccountSelector, {
store,
localVue,
i18n: i18nConfig,
propsData: {
showAccountModal: true,
},
propsData: { showAccountModal: true },
stubs: { WootButton: { template: '<button />' } },
});
});
it('title and sub title exist', () => {
const headerComponent = accountSelector.findComponent(WootModalHeader);
const topBar = headerComponent.find('.page-top-bar');
const titleComponent = topBar.find('.page-sub-title');
expect(titleComponent.text()).toBe('Switch Account');
const subTitleComponent = topBar.find('p');
expect(subTitleComponent.text()).toBe(
'Select an account from the following list'
);
const title = headerComponent.findComponent({ ref: 'modalHeaderTitle' });
expect(title.text()).toBe('Switch Account');
const content = headerComponent.findComponent({
ref: 'modalHeaderContent',
});
expect(content.text()).toBe('Select an account from the following list');
});
it('first account item is checked', () => {
const accountFirstItem = accountSelector.find('.account-selector svg');
expect(accountFirstItem.exists()).toBe(true);
const selectedAccountCheckmark = accountSelector.find(
'#account-1 > button > svg'
);
expect(selectedAccountCheckmark.exists()).toBe(true);
const otherAccountCheckmark = accountSelector.find(
'#account-2 > button > svg'
);
expect(otherAccountCheckmark.exists()).toBe(true);
});
});
@@ -19,10 +19,7 @@ localVue.component('woot-dropdown-menu', WootDropdownMenu);
localVue.component('woot-dropdown-divider', WootDropdownDivider);
localVue.component('woot-dropdown-item', WootDropdownItem);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
describe('AvailabilityStatus', () => {
const currentAvailability = 'online';
@@ -48,15 +45,13 @@ describe('AvailabilityStatus', () => {
},
};
store = new Vuex.Store({
actions,
modules,
});
store = new Vuex.Store({ actions, modules });
availabilityStatus = mount(AvailabilityStatus, {
store,
localVue,
i18n: i18nConfig,
stubs: { WootSwitch: { template: '<button />' } },
});
});
@@ -3,7 +3,9 @@ import SidemenuIcon from '../SidemenuIcon';
describe('SidemenuIcon', () => {
test('matches snapshot', () => {
const wrapper = shallowMount(SidemenuIcon);
const wrapper = shallowMount(SidemenuIcon, {
stubs: { WootButton: { template: '<button><slot /></button>' } },
});
expect(wrapper.vm).toBeTruthy();
expect(wrapper.element).toMatchSnapshot();
});
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SidemenuIcon matches snapshot 1`] = `
<woot-button
class="toggle-sidebar"
<button
class="-ml-3 text-black-900 dark:text-slate-300"
color-scheme="secondary"
icon="list"
size="small"
@@ -5,7 +5,7 @@
delay: { show: 1500, hide: 0 },
hideOnClick: true,
}"
class="time-ago"
class="text-xxs text-black-600 leading-4 ml-auto hover:text-black-900"
>
<span>{{ `${createdAtTime}${lastActivityTime}` }}</span>
</div>
@@ -109,16 +109,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.time-ago {
color: var(--b-600);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-normal);
line-height: var(--space-normal);
margin-left: auto;
&:hover {
color: var(--b-900);
}
}
</style>
@@ -18,7 +18,11 @@
:icon="icon"
:icon-size="iconSize"
/>
<span v-if="$slots.default" class="button__content">
<span
v-if="$slots.default"
class="button__content"
:class="{ 'text-left rtl:text-right': size !== 'expanded' }"
>
<slot />
</span>
</button>
@@ -1,6 +1,11 @@
<template>
<div class="row loading-state">
<h6 class="message">{{ message }}<span class="spinner" /></h6>
<div class="flex items-center justify-center p-8">
<h6
class="block text-base text-center w-100 text-slate-800 dark:text-slate-300"
>
<span class="mr-3">{{ message }}</span>
<span class="spinner" />
</h6>
</div>
</template>
<script>
@@ -121,8 +121,7 @@ export default {
.top-box {
display: flex;
justify-content: space-between;
background: var(--b-50);
@apply bg-black-50 dark:bg-slate-800;
}
.button-group {
@@ -145,11 +144,11 @@ export default {
.button--reply {
border-radius: 0;
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
&:hover,
&:focus {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
}
@@ -157,7 +156,7 @@ export default {
border-radius: 0;
&.is-active {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
background: var(--y-50);
}
@@ -1,5 +1,9 @@
<template>
<div :class="`status-badge status-badge__${status}`" />
<div
:class="
`status-badge status-badge__${status} rounded-full w-2.5 h-2.5 mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-flex`
"
/>
</template>
<script>
export default {
@@ -9,21 +13,15 @@ export default {
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/variables';
.status-badge {
width: var(--space-one);
height: var(--space-one);
margin-right: var(--space-micro);
display: inline-block;
border-radius: 50%;
&__online {
background: var(--g-400);
@apply bg-green-400;
}
&__offline {
background: var(--s-500);
@apply bg-slate-500;
}
&__busy {
background: var(--y-500);
@apply bg-yellow-500;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div
class="conversation-details-wrap"
class="conversation-details-wrap bg-slate-25 dark:bg-slate-800"
:class="{ 'with-border-right': !isOnExpandedLayout }"
>
<conversation-header
@@ -23,7 +23,10 @@
:show-badge="false"
/>
</woot-tabs>
<div v-show="!activeIndex" class="messages-and-sidebar">
<div
v-show="!activeIndex"
class="flex bg-slate-25 dark:bg-slate-800 m-0 h-full min-h-0"
>
<messages-view
v-if="currentChat.id"
:inbox-id="inboxId"
@@ -139,16 +142,14 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.conversation-details-wrap {
display: flex;
flex-direction: column;
min-width: 0;
width: 100%;
background: var(--color-background-light);
&.with-border-right {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
}
@@ -158,20 +159,8 @@ export default {
min-height: var(--dashboard-app-tabs-height);
}
.messages-and-sidebar {
display: flex;
background: var(--color-background-light);
margin: 0;
height: 100%;
min-height: 0;
}
.conversation-sidebar-wrap {
border-right: 1px solid var(--color-border);
height: auto;
flex: 0 0;
z-index: var(--z-index-low);
overflow: auto;
@apply border-r border-slate-50 dark:border-slate-700;
background: white;
flex-basis: 100%;
@@ -1,6 +1,6 @@
<template>
<div
class="conversation"
class="conversation group border-t border-transparent"
:class="{
active: isActiveChat,
'unread-chat': hasUnread,
@@ -30,7 +30,9 @@
:status="currentContact.availability_status"
size="40px"
/>
<div class="conversation--details columns">
<div
class="conversation--details border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800 columns"
>
<div class="conversation--metadata">
<inbox-name v-if="showInboxName" :inbox="inbox" />
<div class="conversation-metadata-attributes">
@@ -1,6 +1,8 @@
<template>
<div class="conv-header">
<div class="conversation-header--details">
<div class="conv-header flex-col md:flex-row">
<div
class="flex-1 w-100 flex flex-col md:flex-row items-center justify-center"
>
<div class="user">
<back-button v-if="showBackButton" :back-url="backButtonUrl" />
<Thumbnail
@@ -44,7 +46,7 @@
</div>
</div>
<div
class="header-actions-wrap"
class="header-actions-wrap mt-3 lg:mt-0"
:class="{ 'has-open-sidebar': isContactPanelOpen }"
>
<more-actions :conversation-id="currentChat.id" />
@@ -159,27 +161,9 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.conv-header {
flex: 0 0 var(--space-jumbo);
flex-direction: row;
@include breakpoint(medium up) {
flex-direction: column;
}
}
.conversation-header--details {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: 100%;
@include breakpoint(medium up) {
flex-direction: row;
}
}
.option__desc {
@@ -30,7 +30,7 @@
</p>
</div>
<div class="features-item">
<h2 class="block-title">
<h2 class="block-title text-black-900 dark:text-slate-200">
<span class="emoji">💬</span>
<span class="conversation--title">{{
$t('ONBOARDING.ALL_CONVERSATION.TITLE')
@@ -41,7 +41,7 @@
</p>
</div>
<div class="features-item">
<h2 class="block-title">
<h2 class="block-title text-black-900 dark:text-slate-200">
<span class="emoji">👥</span>
{{ $t('ONBOARDING.TEAM_MEMBERS.TITLE') }}
</h2>
@@ -53,7 +53,7 @@
</router-link>
</div>
<div class="features-item">
<h2 class="block-title">
<h2 class="block-title text-black-900 dark:text-slate-200">
<span class="emoji">📥</span>{{ $t('ONBOARDING.INBOXES.TITLE') }}
</h2>
<p class="intro-body">
@@ -64,7 +64,7 @@
</router-link>
</div>
<div class="features-item">
<h2 class="block-title">
<h2 class="block-title text-black-900 dark:text-slate-200">
<span class="emoji">🔖</span>{{ $t('ONBOARDING.LABELS.TITLE') }}
</h2>
<p class="intro-body">
@@ -6,8 +6,11 @@
delay: { show: 1500, hide: 0 },
hideOnClick: true,
}"
class="conversation-priority-mark"
:class="{ urgent: priority === CONVERSATION_PRIORITY.URGENT }"
class="shrink-0 rounded-xs inline-flex w-3 h-3 "
:class="{
'bg-red-50 text-red-500': isUrgent,
'bg-slate-50 text-slate-600': !isUrgent,
}"
>
<fluent-icon
:icon="`priority-${priority.toLowerCase()}`"
@@ -41,24 +44,9 @@ export default {
`CONVERSATION.PRIORITY.OPTIONS.${this.priority.toUpperCase()}`
);
},
isUrgent() {
return this.priority === CONVERSATION_PRIORITY.URGENT;
},
},
};
</script>
<style scoped lang="scss">
.conversation-priority-mark {
align-items: center;
flex-shrink: 0;
background: var(--s-50);
border-radius: var(--border-radius-small);
color: var(--s-600);
display: inline-flex;
width: var(--space-snug);
height: var(--space-snug);
&.urgent {
background: var(--r-50);
color: var(--r-500);
}
}
</style>
@@ -1144,8 +1144,7 @@ export default {
}
.reply-box {
border-top: 1px solid var(--color-border);
background: white;
@apply border-r border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
&.is-private {
background: var(--y-50);
@@ -254,8 +254,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.right {
.message-text--metadata {
align-items: center;
@@ -319,10 +317,6 @@ export default {
text-align: center;
font-size: var(--font-size-micro);
margin-left: 0;
@include breakpoint(xlarge up) {
margin-left: var(--space-small);
}
}
}
}
@@ -16,10 +16,7 @@ localVue.use(VTooltip);
localVue.component('fluent-icon', FluentIcon);
localVue.component('woot-button', Button);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
describe('MoveActions', () => {
let currentChat = { id: 8, muted: false };
@@ -47,25 +44,22 @@ describe('MoveActions', () => {
unmuteConversation = jest.fn(() => Promise.resolve());
modules = {
conversations: {
actions: {
muteConversation,
unmuteConversation,
},
conversations: { actions: { muteConversation, unmuteConversation } },
};
getters = { getSelectedChat: () => currentChat };
store = new Vuex.Store({ state, modules, getters });
moreActions = mount(MoreActions, {
store,
localVue,
i18n: i18nConfig,
stubs: {
WootModal: { template: '<div><slot/> </div>' },
WootModalHeader: { template: '<div><slot/> </div>' },
},
};
getters = {
getSelectedChat: () => currentChat,
};
store = new Vuex.Store({
state,
modules,
getters,
});
moreActions = mount(MoreActions, { store, localVue, i18n: i18nConfig });
});
describe('muting discussion', () => {
@@ -2,4 +2,5 @@ export const LOCAL_STORAGE_KEYS = {
DISMISSED_UPDATES: 'dismissedUpdates',
WIDGET_BUILDER: 'widgetBubble_',
DRAFT_MESSAGES: 'draftMessages',
COLOR_SCHEME: 'color_scheme',
};
@@ -1,5 +1,8 @@
<template>
<blockquote ref="messageContainer" class="message">
<blockquote
ref="messageContainer"
class="message border-l-2 border-slate-100"
>
<p class="header">
<strong class="author">
{{ author }}
@@ -80,8 +83,6 @@ export default {
<style scoped lang="scss">
.message {
border-color: var(--s-100);
border-width: var(--space-micro);
padding: 0 var(--space-small);
margin-top: var(--space-small);
}
@@ -1,5 +1,5 @@
<template>
<div class="row app-wrapper">
<div class="row app-wrapper dark:text-slate-300">
<sidebar
:route="currentRoute"
:show-secondary-sidebar="isSidebarOpen"
@@ -154,12 +154,11 @@ export default {
}
.contact--panel {
height: 100%;
background: white;
font-size: var(--font-size-small);
overflow-y: auto;
overflow: auto;
position: relative;
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
.list-group {
@@ -1,7 +1,7 @@
<template>
<div class="wrap">
<div class="header">
<h5 class="block-title">
<h5 class="block-title text-black-900 dark:text-slate-200">
{{ $t('EVENTS.HEADER.TITLE') }}
</h5>
</div>
@@ -8,11 +8,9 @@ describe('AddReminder', () => {
wrapper = shallowMount(AddReminder, {
mocks: {
$t: x => x,
$store: {
getters: {},
state: {},
},
$store: { getters: {}, state: {} },
},
stubs: { WootButton: { template: '<button />' } },
});
});
@@ -124,7 +124,7 @@ export default {
@import '~dashboard/assets/scss/mixins';
.left {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
overflow: auto;
}
@@ -1,5 +1,7 @@
<template>
<div class="medium-3 bg-white contact--panel">
<div
class="medium-3 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-300 contact--panel"
>
<contact-info
:contact="contact"
:channel-type="channelType"
@@ -258,7 +260,6 @@ export default {
@import '~dashboard/assets/scss/variables';
.contact--panel {
background: white;
border-left: 1px solid var(--color-border);
font-size: $font-size-small;
overflow-y: auto;
@@ -1,5 +1,5 @@
<template>
<section class="conversation-page">
<section class="conversation-page bg-white dark:bg-slate-900">
<chat-list
:show-conversation-list="showConversationList"
:conversation-inbox="inboxId"
@@ -22,7 +22,7 @@
</div>
<draggable
tag="div"
class="article-container--border"
class="border-t-0"
:disabled="!dragEnabled"
:list="localArticles"
ghost-class="article-ghost-class"
@@ -142,11 +142,6 @@ export default {
.article-container {
width: 100%;
& > :not([hidden]) ~ :not([hidden]) {
border-top-width: 1px;
border-bottom-width: 0px;
}
.article-container--header {
margin: 0 var(--space-minus-normal);
padding: 0 var(--space-normal);
@@ -1,5 +1,5 @@
<template>
<div class="row app-wrapper">
<div class="h-full flex">
<sidebar
:route="currentRoute"
@toggle-account-modal="toggleAccountModal"
@@ -7,19 +7,17 @@
@open-key-shortcut-modal="toggleKeyShortcutModal"
@close-key-shortcut-modal="closeKeyShortcutModal"
/>
<div class="secondary-sidebar">
<help-center-sidebar
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"
/>
</div>
<help-center-sidebar
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 class="app-content columns">
<router-view />
<command-bar />
@@ -2,7 +2,7 @@
<div v-on-clickaway="closePortalPopover" class="portal-popover__container">
<header>
<div class="actions">
<h2 class="block-title">
<h2 class="block-title text-black-900 dark:text-slate-200">
{{ $t('HELP_CENTER.PORTAL.POPOVER.TITLE') }}
</h2>
<div>
@@ -1,7 +1,7 @@
<template>
<div class="wizard-body columns content-box small-9">
<div class="medium-12 columns">
<h3 class="block-title">
<h3 class="block-title text-black-900 dark:text-slate-200">
{{
$t(
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.TITLE'
@@ -1,7 +1,7 @@
<template>
<div class="wizard-body height-auto small-9 columns">
<div class="medium-12 columns">
<h3 class="block-title">
<h3 class="block-title text-black-900 dark:text-slate-200">
{{
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.TITLE')
}}
@@ -1,5 +1,7 @@
<template>
<div class="main-nav secondary-menu">
<div
class="h-full overflow-auto w-60 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-700 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
>
<sidebar-header
:thumbnail-src="thumbnailSrc"
:header-title="headerTitle"
@@ -7,7 +9,11 @@
:portal-link="portalLink"
@open-popover="openPortalPopover"
/>
<transition-group name="menu-list" tag="ul" class="menu vertical">
<transition-group
name="menu-list"
tag="ul"
class="pt-2 list-none ml-0 mb-0"
>
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
:key="menuItem.toState"
@@ -22,7 +28,7 @@
<p
v-if="!hasCategory"
key="empty-category-nessage"
class="empty-text text-muted"
class="p-1.5 px-4 text-slate-300"
>
{{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }}
</p>
@@ -96,32 +102,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/woot';
.secondary-menu {
display: flex;
flex-direction: column;
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100%;
width: var(--space-giga);
flex-shrink: 0;
overflow: hidden;
padding: var(--space-small);
position: unset;
&:hover {
overflow: auto;
}
.menu {
padding: var(--space-small);
overflow-y: auto;
}
}
.empty-text {
padding: var(--space-smaller) var(--space-normal);
}
</style>
@@ -1,20 +1,26 @@
<template>
<div class="sidebar-header--wrap">
<div class="header-left--side">
<div
class="flex h-16 items-center justify-between py-4 px-0 mb-1/4 border-b border-slate-50 dark:border-slate-700"
>
<div class="flex items-center">
<thumbnail
size="32px"
:src="thumbnailSrc"
:username="headerTitle"
variant="square"
/>
<div class="header-title--wrap">
<h4 class="text-block-title title-view text-truncate">
<div class="flex items-start flex-col ml-2 rtl:ml-0 rtl:mr-2">
<h4
class="text-sm w-28 h-4 mb-0 leading-4 overflow-hidden whitespace-nowrap text-ellipsis text-slate-800 dark:text-slate-100"
>
{{ headerTitle }}
</h4>
<span class="sub-title--view">{{ subTitle }}</span>
<span class="h-4 leading-4 text-slate-600 dark:text-slate-200 text-xs">
{{ subTitle }}
</span>
</div>
</div>
<div class="header-right--side">
<div class="flex items-end">
<woot-button
variant="clear"
color-scheme="secondary"
@@ -67,47 +73,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.sidebar-header--wrap {
display: flex;
height: var(--space-jumbo);
align-items: center;
justify-content: space-between;
padding: var(--space-normal);
margin: var(--space-minus-small);
margin-bottom: var(--space-smaller);
border-bottom: 1px solid var(--color-border-light);
}
.header-title--wrap {
display: flex;
align-items: flex-start;
flex-direction: column;
margin-left: var(--space-small);
}
.title-view {
width: var(--space-mega);
margin-bottom: var(--space-zero);
height: var(--space-normal);
line-height: var(--space-normal);
}
.sub-title--view {
font-size: var(--font-size-mini);
color: var(--s-600);
height: var(--space-normal);
line-height: var(--space-normal);
}
.header-left--side {
display: flex;
align-items: center;
}
.header-right--side {
display: flex;
align-items: flex-end;
}
</style>
@@ -1,7 +1,7 @@
<template>
<transition name="popover-animation">
<div class="article-settings--container">
<h3 class="block-title">
<h3 class="block-title text-black-900 dark:text-slate-200">
{{ $t('HELP_CENTER.ARTICLE_SETTINGS.TITLE') }}
</h3>
<div class="form-wrap">
@@ -2,10 +2,10 @@
<div class="modal-mask">
<div
v-on-clickaway="closeNotificationPanel"
class="notification-wrap flex-space-between"
class="notification-wrap flex justify-between z-10 rounded-md shadow-md absolute bg-white dark:bg-slate-800 left-14 rtl:left-auto rtl:right-14 m-4"
>
<div class="header-wrap w-full flex-space-between">
<div class="header-title--wrap flex-view">
<div class="header-wrap w-full flex justify-between">
<div class="header-title--wrap flex">
<span class="header-title">
{{ $t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.TITLE') }}
</span>
@@ -13,7 +13,7 @@
{{ totalUnreadNotifications }}
</span>
</div>
<div class="flex-view">
<div class="flex">
<woot-button
v-if="!noUnreadNotificationAvailable"
color-scheme="primary"
@@ -48,8 +48,8 @@
:on-click-notification="openConversation"
:in-last-page="inLastPage"
/>
<div v-if="records.length !== 0" class="footer-wrap flex-space-between">
<div class="flex-view">
<div v-if="records.length !== 0" class="footer-wrap flex justify-between">
<div class="flex">
<woot-button
size="medium"
variant="clear"
@@ -75,7 +75,7 @@
/>
</div>
<span class="page-count"> {{ currentPage }} - {{ lastPage }} </span>
<div class="flex-view">
<div class="flex">
<woot-button
color-scheme="secondary"
variant="clear"
@@ -236,24 +236,10 @@ export default {
};
</script>
<style lang="scss" scoped>
.flex-view {
display: flex;
}
.flex-space-between {
display: flex;
justify-content: space-between;
}
.notification-wrap {
flex-direction: column;
height: 90vh;
width: 32.5rem;
background-color: var(--white);
border-radius: var(--border-radius-medium);
position: absolute;
left: var(--space-jumbo);
margin: var(--space-small);
}
.header-wrap {
@@ -1,6 +1,8 @@
<template>
<div class="settings-header">
<h1 class="page-title">
<div
class="settings-header bg-white dark:bg-slate-900 border-b border-slate-50 dark:border-slate-800"
>
<h1 class="page-title text-black-900 dark:text-slate-300">
<woot-sidemenu-icon v-if="showSidemenuIcon" />
<back-button
v-if="showBackButton"
@@ -1,9 +1,11 @@
<template>
<div class="columns profile--settings">
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
<div class="small-12 row profile--settings--row">
<div
class="small-12 row profile--settings--row border-b border-slate-25 dark:border-slate-700"
>
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.TITLE') }}
</h4>
<p>{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE') }}</p>
@@ -82,9 +84,11 @@
</div>
</div>
<div class="profile--settings--row row">
<div
class="profile--settings--row border-slate-25 dark:border-slate-700 text-black-900 dark:text-slate-300 row"
>
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }}
</h4>
<p>
@@ -288,7 +292,6 @@ export default {
}
.profile--settings--row {
@include border-normal-bottom;
padding: $space-normal;
.small-3 {
padding: $space-normal $space-medium $space-normal 0;
@@ -1,7 +1,7 @@
<template>
<div class="settings--content">
<div class="widget-builder-container">
<div class="settings-container">
<div class="settings-container w-100 lg:w-[40%]">
<div class="settings-content">
<form @submit.prevent="updateWidget">
<woot-avatar-uploader
@@ -123,7 +123,7 @@
</form>
</div>
</div>
<div class="widget-container">
<div class="widget-container w-100 lg:w-[60%]">
<input-radio-group
name="widget-view-options"
:items="getWidgetViewOptions"
@@ -428,11 +428,6 @@ export default {
}
.settings-container {
width: 40%;
@include breakpoint(900px down) {
width: 100%;
}
.settings-content {
padding: var(--space-normal) var(--space-zero);
overflow-y: scroll;
@@ -445,12 +440,6 @@ export default {
}
.widget-container {
width: 60%;
@include breakpoint(900px down) {
width: 100%;
}
.widget-preview {
display: flex;
flex-direction: column;
@@ -1,8 +1,8 @@
<template>
<form @submit.prevent="changePassword()">
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.TITLE') }}
</h4>
<p>{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.NOTE') }}</p>
@@ -3,7 +3,7 @@
<form @submit.prevent="updateUser('profile')">
<div class="small-12 row profile--settings--row">
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.PROFILE_SECTION.TITLE') }}
</h4>
<p>{{ $t('PROFILE_SETTINGS.FORM.PROFILE_SECTION.NOTE') }}</p>
@@ -70,9 +70,9 @@
</div>
</form>
<message-signature />
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.TITLE') }}
</h4>
<p>
@@ -97,9 +97,9 @@
</div>
<change-password v-if="!globalConfig.disableUserProfileUpdate" />
<notification-settings />
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.TITLE') }}
</h4>
<p>
@@ -1,8 +1,8 @@
<template>
<form @submit.prevent="updateSignature()">
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.TITLE') }}
</h4>
<p>{{ $t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.NOTE') }}</p>
@@ -1,8 +1,8 @@
<template>
<div id="profile-settings-notifications">
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3 ">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.TITLE') }}
</h4>
<p>
@@ -139,9 +139,9 @@
</div>
</div>
</div>
<div class="profile--settings--row row">
<div class="profile--settings--row text-black-900 dark:text-slate-300 row">
<div class="columns small-3 ">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.TITLE') }}
</h4>
<p>
@@ -236,10 +236,10 @@
</div>
<div
v-if="vapidPublicKey && hasPushAPISupport"
class="profile--settings--row row push-row"
class="profile--settings--row text-black-900 dark:text-slate-300 row push-row"
>
<div class="columns small-3 ">
<h4 class="block-title">
<h4 class="block-title text-black-900 dark:text-slate-200">
{{ $t('PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.TITLE') }}
</h4>
<p>{{ $t('PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.NOTE') }}</p>
@@ -37,9 +37,8 @@ describe('dateSeparator', () => {
dateSeparator = shallowMount(DateSeparator, {
store,
localVue,
propsData: {
date: 'Nov 18, 2019',
},
propsData: { date: 'Nov 18, 2019' },
mocks: { $t: msg => msg },
i18n: i18nConfig,
mixins: [darkModeMixin],
});
@@ -1,13 +1,10 @@
<template>
<li class="dropdown-menu--divider" :tabindex="null" :aria-disabled="true" />
<li
class="list-none my-1 mx-0 border-b border-slate-50 dark:border-slate-700"
:tabindex="null"
:aria-disabled="true"
/>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.dropdown-menu--divider {
list-style: none;
margin: var(--space-small) 0;
border-bottom: 1px solid var(--s-50);
}
</style>
@@ -1,6 +1,10 @@
<template>
<li class="dropdown-menu--header" :tabindex="null" :aria-disabled="true">
<span class="title">{{ title }}</span>
<li class="inline-flex list-none" :tabindex="null" :aria-disabled="true">
<span
class="text-xs text-slate-600 dark:text-slate-100 mt-1 font-medium w-full block text-left rtl:text-right whitespace-nowrap p-2"
>
{{ title }}
</span>
<slot />
</li>
</template>
@@ -15,22 +19,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.dropdown-menu--header {
display: inline-flex;
list-style: none;
.title {
width: 100%;
display: block;
text-align: left;
white-space: nowrap;
padding: var(--space-small) var(--space-small);
margin-top: var(--space-smaller);
font-size: var(--font-size-mini);
color: var(--s-600);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-normal);
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<li
class="dropdown-menu__item"
class="dropdown-menu__item list-none mb-1"
:class="{
'is-disabled': disabled,
}"
@@ -28,26 +28,21 @@ export default {
</script>
<style lang="scss" scoped>
.dropdown-menu__item {
list-style: none;
margin-bottom: var(--space-micro);
::v-deep {
a,
.button {
display: inline-flex;
white-space: nowrap;
width: 100%;
text-align: left;
color: var(--s-700);
&:hover {
background: var(--color-background);
}
&:focus {
background: var(--color-background);
}
@apply inline-flex whitespace-nowrap w-full text-left rtl:text-right;
}
}
}
// A hacky fix to remove the background that came from the foundation styles node module file
// Can be removed once we remove the foundation styles node module
.dropdown.menu {
// Top-level item
> li > a {
background: transparent;
padding: 4px 10.8px;
}
}
</style>
@@ -1,52 +1,45 @@
import TemplateParser from '../../../../dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue';
import { mount, createLocalVue } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { templates } from './fixtures';
const localVue = createLocalVue();
import VueI18n from 'vue-i18n';
import Vue from 'vue';
import Vuelidate from 'vuelidate';
Vue.use(Vuelidate);
import i18n from 'dashboard/i18n';
localVue.use(VueI18n);
localVue.use(Vuelidate);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
const config = {
localVue,
i18n: i18nConfig,
stubs: {
WootButton: { template: '<button />' },
WootInput: { template: '<input />' },
},
};
describe('#WhatsAppTemplates', () => {
it('returns all variables from a template string', () => {
const wrapper = mount(TemplateParser, {
localVue,
propsData: {
template: templates[0],
},
i18n: i18nConfig,
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[0] },
});
expect(wrapper.vm.variables).toEqual(['{{1}}', '{{2}}', '{{3}}']);
});
it('returns no variables from a template string if it does not contain variables', () => {
const wrapper = mount(TemplateParser, {
localVue,
propsData: {
template: templates[12],
},
i18n: i18nConfig,
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[12] },
});
expect(wrapper.vm.variables).toBeNull();
});
it('returns the body of a template', () => {
const wrapper = mount(TemplateParser, {
localVue,
propsData: {
template: templates[1],
},
i18n: i18nConfig,
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[1] },
});
const expectedOutput = templates[1].components.find(i => i.type === 'BODY')
.text;
@@ -54,17 +47,12 @@ describe('#WhatsAppTemplates', () => {
});
it('generates the templates from variable input', async () => {
const wrapper = mount(TemplateParser, {
localVue,
propsData: {
template: templates[0],
},
const wrapper = shallowMount(TemplateParser, {
...config,
propsData: { template: templates[0] },
data: () => {
return {
processedParams: {},
};
return { processedParams: {} };
},
i18n: i18nConfig,
});
await wrapper.setData({
processedParams: { '1': 'abc', '2': 'xyz', '3': 'qwerty' },
@@ -4,11 +4,7 @@ import GoogleOAuthButton from './Button.vue';
function getWrapper(showSeparator) {
return shallowMount(GoogleOAuthButton, {
propsData: { showSeparator: showSeparator },
methods: {
$t(text) {
return text;
},
},
mocks: { $t: text => text },
});
}
@@ -1,55 +0,0 @@
<template>
<woot-button
size="expanded"
color-scheme="primary"
class-names="submit--button"
:is-disabled="isDisabled"
:is-loading="isLoading"
@click="onClick"
>
{{ label }}
<fluent-icon :icon="icon" size="18" />
</woot-button>
</template>
<script>
export default {
props: {
label: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
isDisabled: {
type: Boolean,
default: false,
},
isLoading: {
type: Boolean,
default: false,
},
},
methods: {
onClick() {
this.$emit('click');
},
},
};
</script>
<style lang="scss" scoped>
.submit--button {
align-items: center;
display: flex;
margin: 0 0 var(--space-normal) 0;
&::v-deep .button__content {
align-items: center;
display: flex;
justify-content: center;
}
}
</style>
+1 -1
View File
@@ -62,7 +62,7 @@ development:
host: 0.0.0.0
port: 3035
public: 0.0.0.0:3035
hmr: false
hmr: true
# Inline should be set to true if using HMR
inline: true
overlay: true
+6
View File
@@ -1,14 +1,20 @@
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
darkMode: 'class',
content: [
'./app/javascript/widget/**/*.vue',
'./app/javascript/v3/**/*.vue',
'./app/javascript/dashboard/**/*.vue',
'./app/javascript/portal/**/*.vue',
'./app/javascript/shared/**/*.vue',
'./app/javascript/survey/**/*.vue',
'./app/views/**/*.html.erb',
],
theme: {
fontSize: {
xxs: '0.625rem',
...defaultTheme.fontSize,
},
colors: {
transparent: 'transparent',
white: '#fff',