feat: Vite + vue 3 💚 (#10047)
Fixes https://github.com/chatwoot/chatwoot/issues/8436 Fixes https://github.com/chatwoot/chatwoot/issues/9767 Fixes https://github.com/chatwoot/chatwoot/issues/10156 Fixes https://github.com/chatwoot/chatwoot/issues/6031 Fixes https://github.com/chatwoot/chatwoot/issues/5696 Fixes https://github.com/chatwoot/chatwoot/issues/9250 Fixes https://github.com/chatwoot/chatwoot/issues/9762 --------- Co-authored-by: Pranav <pranavrajs@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
co-authored by
Pranav
Sivin Varghese
parent
e0bf2bd9d4
commit
42f6621afb
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import Sidebar from '../../components/layout/Sidebar.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
|
||||
@@ -8,7 +9,10 @@ import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.v
|
||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
const CommandBar = () => import('./commands/commandbar.vue');
|
||||
const CommandBar = defineAsyncComponent(
|
||||
() => import('./commands/commandbar.vue')
|
||||
);
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -36,6 +40,7 @@ export default {
|
||||
showShortcutModal: false,
|
||||
isNotificationPanel: false,
|
||||
displayLayoutType: '',
|
||||
hasBanner: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -75,15 +80,22 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.handleResize();
|
||||
this.$nextTick(this.checkBanner);
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.$emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
window.addEventListener('resize', this.checkBanner);
|
||||
emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
},
|
||||
beforeDestroy() {
|
||||
unmounted() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.$emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
window.removeEventListener('resize', this.checkBanner);
|
||||
emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
},
|
||||
|
||||
methods: {
|
||||
checkBanner() {
|
||||
this.hasBanner =
|
||||
document.getElementsByClassName('woot-banner').length > 0;
|
||||
},
|
||||
handleResize() {
|
||||
const { SMALL_SCREEN_BREAKPOINT, LAYOUT_TYPES } = wootConstants;
|
||||
let throttled = false;
|
||||
@@ -142,11 +154,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-wrap flex-grow-0 w-full h-full max-w-full min-h-0 ml-auto mr-auto app-wrapper dark:text-slate-300"
|
||||
>
|
||||
<div class="flex flex-wrap app-wrapper dark:text-slate-300">
|
||||
<Sidebar
|
||||
:route="currentRoute"
|
||||
:has-banner="hasBanner"
|
||||
:show-secondary-sidebar="isSidebarOpen"
|
||||
@openNotificationPanel="openNotificationPanel"
|
||||
@toggleAccountModal="toggleAccountModal"
|
||||
@@ -154,7 +165,7 @@ export default {
|
||||
@closeKeyShortcutModal="closeKeyShortcutModal"
|
||||
@showAddLabelPopup="showAddLabelPopup"
|
||||
/>
|
||||
<section class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
||||
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
||||
<router-view />
|
||||
<CommandBar />
|
||||
<AccountSelector
|
||||
@@ -178,6 +189,6 @@ export default {
|
||||
<woot-modal :show.sync="showAddLabelModal" :on-close="hideAddLabelPopup">
|
||||
<AddLabelModal @close="hideAddLabelPopup" />
|
||||
</woot-modal>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStore, useStoreGetters } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useEmitter } from 'dashboard/composables/emitter';
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||
|
||||
@@ -3,7 +3,7 @@ import '@chatwoot/ninja-keys';
|
||||
import { ref, computed, watchEffect, onMounted } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAppearanceHotKeys } from 'dashboard/composables/commands/useAppearanceHotKeys';
|
||||
import { useInboxHotKeys } from 'dashboard/composables/commands/useInboxHotKeys';
|
||||
import { useGoToCommandHotKeys } from 'dashboard/composables/commands/useGoToCommandHotKeys';
|
||||
@@ -13,7 +13,6 @@ import wootConstants from 'dashboard/constants/globals';
|
||||
import { GENERAL_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
const store = useStore();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
|
||||
const ninjakeys = ref(null);
|
||||
@@ -55,7 +54,7 @@ const onSelected = item => {
|
||||
selectedSnoozeType.value = null;
|
||||
}
|
||||
|
||||
track(GENERAL_EVENTS.COMMAND_BAR, {
|
||||
useTrack(GENERAL_EVENTS.COMMAND_BAR, {
|
||||
section,
|
||||
action: title,
|
||||
});
|
||||
@@ -97,7 +96,7 @@ onMounted(setCommandBarData);
|
||||
<style lang="scss">
|
||||
ninja-keys {
|
||||
--ninja-accent-color: var(--w-500);
|
||||
--ninja-font-family: 'PlusJakarta';
|
||||
--ninja-font-family: 'Inter';
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import AddReminder from './AddReminder';
|
||||
|
||||
export default {
|
||||
title: 'Components/Reminder/Add',
|
||||
component: AddReminder,
|
||||
argTypes: {
|
||||
options: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { AddReminder },
|
||||
template:
|
||||
'<add-reminder v-bind="$props" @add="onAdd" @click="onClick"></add-reminder>',
|
||||
});
|
||||
|
||||
export const Add = Template.bind({});
|
||||
Add.args = {
|
||||
onAdd: action('Added'),
|
||||
onClick: action('Label'),
|
||||
options: [
|
||||
{
|
||||
id: '12345',
|
||||
name: 'calls',
|
||||
},
|
||||
{
|
||||
id: '12346',
|
||||
name: 'meeting',
|
||||
},
|
||||
{
|
||||
id: '12347',
|
||||
name: 'review',
|
||||
},
|
||||
],
|
||||
};
|
||||
+65
-64
@@ -4,7 +4,7 @@ import ContactConversations from 'dashboard/routes/dashboard/conversation/Contac
|
||||
import ContactInfo from 'dashboard/routes/dashboard/conversation/contact/ContactInfo.vue';
|
||||
import ContactLabel from 'dashboard/routes/dashboard/contacts/components/ContactLabels.vue';
|
||||
import CustomAttributes from 'dashboard/routes/dashboard/conversation/customAttributes/CustomAttributes.vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
|
||||
export default {
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
ContactInfo,
|
||||
ContactLabel,
|
||||
CustomAttributes,
|
||||
Draggable: draggable,
|
||||
Draggable,
|
||||
},
|
||||
props: {
|
||||
contact: {
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative w-1/4 h-full overflow-y-auto text-sm bg-white dark:bg-slate-900 border-slate-50 dark:border-slate-800/50"
|
||||
class="relative w-1/4 h-full min-h-screen overflow-y-auto text-sm bg-white dark:bg-slate-900 border-slate-50 dark:border-slate-800/50"
|
||||
:class="showAvatar ? 'border-l border-solid ' : 'border-r border-solid'"
|
||||
>
|
||||
<ContactInfo
|
||||
@@ -89,69 +89,70 @@ export default {
|
||||
@panelClose="onClose"
|
||||
@togglePanel="onClose"
|
||||
/>
|
||||
<Draggable
|
||||
:list="contactSidebarItems"
|
||||
:disabled="!dragEnabled"
|
||||
class="list-group"
|
||||
ghost-class="ghost"
|
||||
@start="dragging = true"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<transition-group>
|
||||
<div
|
||||
v-for="element in contactSidebarItems"
|
||||
:key="element.name"
|
||||
class="list-group-item"
|
||||
>
|
||||
<div v-if="element.name === 'contact_attributes'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
|
||||
compact
|
||||
@click="
|
||||
value => toggleSidebarUIState('is_ct_custom_attr_open', value)
|
||||
"
|
||||
>
|
||||
<CustomAttributes
|
||||
:contact-id="contact.id"
|
||||
attribute-type="contact_attribute"
|
||||
attribute-from="contact_panel"
|
||||
:custom-attributes="contact.custom_attributes"
|
||||
:empty-state-message="
|
||||
$t('CONTACT_PANEL.SIDEBAR_SECTIONS.NO_RECORDS_FOUND')
|
||||
<div class="list-group">
|
||||
<Draggable
|
||||
:list="contactSidebarItems"
|
||||
:disabled="!dragEnabled"
|
||||
ghost-class="ghost"
|
||||
item-key="name"
|
||||
@start="dragging = true"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div :key="element.name" class="list-group-item">
|
||||
<div v-if="element.name === 'contact_attributes'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
|
||||
compact
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_ct_custom_attr_open', value)
|
||||
"
|
||||
/>
|
||||
</AccordionItem>
|
||||
>
|
||||
<CustomAttributes
|
||||
:contact-id="contact.id"
|
||||
attribute-type="contact_attribute"
|
||||
attribute-from="contact_panel"
|
||||
:custom-attributes="contact.custom_attributes"
|
||||
:empty-state-message="
|
||||
$t('CONTACT_PANEL.SIDEBAR_SECTIONS.NO_RECORDS_FOUND')
|
||||
"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-if="element.name === 'contact_labels'">
|
||||
<AccordionItem
|
||||
:title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CONTACT_LABELS')"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_labels_open')"
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_ct_labels_open', value)
|
||||
"
|
||||
>
|
||||
<ContactLabel :contact-id="contact.id" class="contact-labels" />
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-if="element.name === 'previous_conversation'">
|
||||
<AccordionItem
|
||||
:title="
|
||||
$t('CONTACT_PANEL.SIDEBAR_SECTIONS.PREVIOUS_CONVERSATIONS')
|
||||
"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_prev_conv_open')"
|
||||
compact
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_ct_prev_conv_open', value)
|
||||
"
|
||||
>
|
||||
<ContactConversations
|
||||
v-if="contact.id"
|
||||
:contact-id="contact.id"
|
||||
conversation-id=""
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="element.name === 'contact_labels'">
|
||||
<AccordionItem
|
||||
:title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CONTACT_LABELS')"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_labels_open')"
|
||||
@click="value => toggleSidebarUIState('is_ct_labels_open', value)"
|
||||
>
|
||||
<ContactLabel :contact-id="contact.id" class="contact-labels" />
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-if="element.name === 'previous_conversation'">
|
||||
<AccordionItem
|
||||
:title="
|
||||
$t('CONTACT_PANEL.SIDEBAR_SECTIONS.PREVIOUS_CONVERSATIONS')
|
||||
"
|
||||
:is-open="isContactSidebarItemOpen('is_ct_prev_conv_open')"
|
||||
@click="
|
||||
value => toggleSidebarUIState('is_ct_prev_conv_open', value)
|
||||
"
|
||||
>
|
||||
<ContactConversations
|
||||
v-if="contact.id"
|
||||
:contact-id="contact.id"
|
||||
conversation-id=""
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</Draggable>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import { useFilter } from 'shared/composables/useFilter';
|
||||
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { validateConversationOrContactFilters } from 'dashboard/helper/validations.js';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -230,7 +231,7 @@ export default {
|
||||
JSON.parse(JSON.stringify(this.appliedFilters))
|
||||
);
|
||||
this.$emit('applyFilter', this.appliedFilters);
|
||||
this.$track(CONTACTS_EVENTS.APPLY_FILTER, {
|
||||
useTrack(CONTACTS_EVENTS.APPLY_FILTER, {
|
||||
applied_filters: this.appliedFilters.map(filter => ({
|
||||
key: filter.attribute_key,
|
||||
operator: filter.filter_operator,
|
||||
|
||||
@@ -1,264 +1,178 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { VeTable } from 'vue-easytable';
|
||||
import { getCountryFlag } from 'dashboard/helper/flag';
|
||||
<script setup>
|
||||
import { h, ref, computed, defineEmits } from 'vue';
|
||||
import {
|
||||
useVueTable,
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
} from '@tanstack/vue-table';
|
||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmptyState,
|
||||
Spinner,
|
||||
VeTable,
|
||||
// Table components
|
||||
import Table from 'dashboard/components/table/Table.vue';
|
||||
import NameCell from './ContactsTable/NameCell.vue';
|
||||
import EmailCell from './ContactsTable/EmailCell.vue';
|
||||
import TelCell from './ContactsTable/TelCell.vue';
|
||||
import CountryCell from './ContactsTable/CountryCell.vue';
|
||||
import ProfilesCell from './ContactsTable/ProfilesCell.vue';
|
||||
|
||||
const props = defineProps({
|
||||
contacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
props: {
|
||||
contacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showSearchEmptyState: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onClickContact: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sortParam: {
|
||||
type: String,
|
||||
default: 'last_activity_at',
|
||||
},
|
||||
sortOrder: {
|
||||
type: String,
|
||||
default: 'desc',
|
||||
},
|
||||
showSearchEmptyState: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
data() {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onSortChange']);
|
||||
const { t } = useI18n();
|
||||
|
||||
const tableData = computed(() => {
|
||||
if (props.isLoading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.contacts.map(item => {
|
||||
// Note: The attributes used here is in snake case
|
||||
// as it simplier the sort attribute calculation
|
||||
const additional = item.additional_attributes || {};
|
||||
const { last_activity_at: lastActivityAt } = item;
|
||||
const { created_at: createdAt } = item;
|
||||
return {
|
||||
sortConfig: {},
|
||||
sortOption: {
|
||||
sortAlways: true,
|
||||
sortChange: params => this.$emit('onSortChange', params),
|
||||
},
|
||||
...item,
|
||||
profiles: additional.social_profiles || {},
|
||||
last_activity_at: lastActivityAt ? dynamicTime(lastActivityAt) : null,
|
||||
created_at: createdAt ? dynamicTime(createdAt) : null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isRTL: 'accounts/isRTL',
|
||||
}),
|
||||
tableData() {
|
||||
if (this.isLoading) {
|
||||
return [];
|
||||
}
|
||||
return this.contacts.map(item => {
|
||||
// Note: The attributes used here is in snake case
|
||||
// as it simplier the sort attribute calculation
|
||||
const additional = item.additional_attributes || {};
|
||||
const { last_activity_at: lastActivityAt } = item;
|
||||
const { created_at: createdAt } = item;
|
||||
return {
|
||||
...item,
|
||||
phone_number: item.phone_number || '---',
|
||||
company: additional.company_name || '---',
|
||||
profiles: additional.social_profiles || {},
|
||||
city: additional.city || '---',
|
||||
country: additional.country,
|
||||
countryCode: additional.country_code,
|
||||
conversationsCount: item.conversations_count || '---',
|
||||
last_activity_at: lastActivityAt
|
||||
? dynamicTime(lastActivityAt)
|
||||
: '---',
|
||||
created_at: createdAt ? dynamicTime(createdAt) : '---',
|
||||
};
|
||||
});
|
||||
},
|
||||
columns() {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
key: 'name',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.NAME'),
|
||||
fixed: 'left',
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
sortBy: this.sortConfig.name || '',
|
||||
width: 300,
|
||||
renderBodyCell: ({ row }) => (
|
||||
<woot-button
|
||||
variant="clear"
|
||||
onClick={() => this.onClickContact(row.id)}
|
||||
>
|
||||
<div class="row--user-block">
|
||||
<Thumbnail
|
||||
src={row.thumbnail}
|
||||
size="32px"
|
||||
username={row.name}
|
||||
status={row.availability_status}
|
||||
/>
|
||||
<div class="user-block">
|
||||
<h6 class="overflow-hidden text-base whitespace-nowrap text-ellipsis">
|
||||
<router-link
|
||||
to={`/app/accounts/${this.$route.params.accountId}/contacts/${row.id}`}
|
||||
class="user-name"
|
||||
>
|
||||
{row.name}
|
||||
</router-link>
|
||||
</h6>
|
||||
<button class="button clear small link view-details--button">
|
||||
{this.$t('CONTACTS_PAGE.LIST.VIEW_DETAILS')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</woot-button>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
key: 'email',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.EMAIL_ADDRESS'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
sortBy: this.sortConfig.email || '',
|
||||
width: 240,
|
||||
renderBodyCell: ({ row }) => {
|
||||
if (row.email)
|
||||
return (
|
||||
<div class="overflow-hidden whitespace-nowrap text-ellipsis text-woot-500 dark:text-woot-500">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
href={`mailto:${row.email}`}
|
||||
>
|
||||
{row.email}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
return '---';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'phone_number',
|
||||
key: 'phone_number',
|
||||
sortBy: this.sortConfig.phone_number || '',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.PHONE_NUMBER'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
},
|
||||
{
|
||||
field: 'company',
|
||||
key: 'company',
|
||||
sortBy: this.sortConfig.company_name || '',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COMPANY'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
},
|
||||
{
|
||||
field: 'city',
|
||||
key: 'city',
|
||||
sortBy: this.sortConfig.city || '',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CITY'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
},
|
||||
{
|
||||
field: 'country',
|
||||
key: 'country',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COUNTRY'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
sortBy: this.sortConfig.country || '',
|
||||
renderBodyCell: ({ row }) => {
|
||||
if (row.country) {
|
||||
return (
|
||||
<div class="overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{`${getCountryFlag(row.countryCode)} ${row.country}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '---';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'profiles',
|
||||
key: 'profiles',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.SOCIAL_PROFILES'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
renderBodyCell: ({ row }) => {
|
||||
const { profiles } = row;
|
||||
});
|
||||
});
|
||||
|
||||
const items = Object.keys(profiles);
|
||||
const defaulSpanRender = cellProps =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
class: cellProps.getValue() ? '' : 'text-slate-300 dark:text-slate-700',
|
||||
},
|
||||
cellProps.getValue() ? cellProps.getValue() : '---'
|
||||
);
|
||||
|
||||
if (!items.length) return '---';
|
||||
const columnHelper = createColumnHelper();
|
||||
const columns = [
|
||||
columnHelper.accessor('name', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.NAME'),
|
||||
cell: cellProps => h(NameCell, cellProps),
|
||||
size: 250,
|
||||
}),
|
||||
columnHelper.accessor('email', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.EMAIL_ADDRESS'),
|
||||
cell: cellProps => h(EmailCell, { email: cellProps.getValue() }),
|
||||
size: 250,
|
||||
}),
|
||||
columnHelper.accessor('phone_number', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.PHONE_NUMBER'),
|
||||
size: 200,
|
||||
cell: cellProps =>
|
||||
h(TelCell, {
|
||||
phoneNumber: cellProps.getValue(),
|
||||
defaultCountry: cellProps.row.original.country_code,
|
||||
}),
|
||||
}),
|
||||
columnHelper.accessor('company_name', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.COMPANY'),
|
||||
size: 200,
|
||||
cell: defaulSpanRender,
|
||||
}),
|
||||
columnHelper.accessor('city', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.CITY'),
|
||||
cell: defaulSpanRender,
|
||||
size: 200,
|
||||
}),
|
||||
columnHelper.accessor('country', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.COUNTRY'),
|
||||
size: 200,
|
||||
cell: cellProps =>
|
||||
h(CountryCell, {
|
||||
countryCode: cellProps.row.original.country_code,
|
||||
country: cellProps.getValue(),
|
||||
}),
|
||||
}),
|
||||
columnHelper.accessor('profiles', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.SOCIAL_PROFILES'),
|
||||
size: 200,
|
||||
enableSorting: false,
|
||||
cell: cellProps =>
|
||||
h(ProfilesCell, {
|
||||
profiles: cellProps.getValue(),
|
||||
}),
|
||||
}),
|
||||
columnHelper.accessor('last_activity_at', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.LAST_ACTIVITY'),
|
||||
size: 200,
|
||||
cell: defaulSpanRender,
|
||||
}),
|
||||
columnHelper.accessor('created_at', {
|
||||
header: t('CONTACTS_PAGE.LIST.TABLE_HEADER.CREATED_AT'),
|
||||
size: 200,
|
||||
cell: defaulSpanRender,
|
||||
}),
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="cell--social-profiles flex gap-0.5 items-center">
|
||||
{items.map(
|
||||
profile =>
|
||||
profiles[profile] && (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
href={`https://${profile}.com/${profiles[profile]}`}
|
||||
>
|
||||
<FluentIcon icon={`brand-${profile}`} />
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'last_activity_at',
|
||||
key: 'last_activity_at',
|
||||
sortBy: this.sortConfig.last_activity_at || '',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.LAST_ACTIVITY'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
key: 'created_at',
|
||||
sortBy: this.sortConfig.created_at || '',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CREATED_AT'),
|
||||
align: this.isRTL ? 'right' : 'left',
|
||||
},
|
||||
];
|
||||
// type ColumnSort = {
|
||||
// id: string
|
||||
// desc: boolean
|
||||
// }
|
||||
// type SortingState = ColumnSort[]
|
||||
const sortingState = ref([{ last_activity_at: 'desc' }]);
|
||||
|
||||
const table = useVueTable({
|
||||
get data() {
|
||||
return tableData.value;
|
||||
},
|
||||
columns,
|
||||
enableMultiSort: false,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
state: {
|
||||
get sorting() {
|
||||
return sortingState.value;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
sortOrder() {
|
||||
this.setSortConfig();
|
||||
},
|
||||
sortParam() {
|
||||
this.setSortConfig();
|
||||
},
|
||||
onSortingChange: updater => {
|
||||
// onSortingChange returns a callback that allows us to trigger the sort when we need
|
||||
// See more docs here: https://tanstack.com/table/latest/docs/api/features/sorting#onsortingchange
|
||||
// IMO, I don't like this API, but it's what we have for now. Would be great if we could just listen the the changes
|
||||
// to the sorting state and emit the event when it changes
|
||||
// But we can easily wrap this later as a separate composable
|
||||
const updatedSortState = updater(sortingState.value);
|
||||
// we pick the first item from the array, as we are not using multi-sorting
|
||||
const [sort] = updatedSortState;
|
||||
|
||||
if (sort) {
|
||||
sortingState.value = updatedSortState;
|
||||
emit('onSortChange', { [sort.id]: sort.desc ? 'desc' : 'asc' });
|
||||
} else {
|
||||
// If the sorting is empty, we reset to the default sorting
|
||||
sortingState.value = [{ last_activity_at: 'desc' }];
|
||||
emit('onSortChange', {});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setSortConfig();
|
||||
},
|
||||
methods: {
|
||||
setSortConfig() {
|
||||
this.sortConfig = { [this.sortParam]: this.sortOrder };
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="flex-1 h-full -mt-1 overflow-hidden bg-white contacts-table-wrap dark:bg-slate-900"
|
||||
>
|
||||
<VeTable
|
||||
fixed-header
|
||||
max-height="calc(100vh - 7.125rem)"
|
||||
scroll-width="187rem"
|
||||
:columns="columns"
|
||||
:table-data="tableData"
|
||||
:border-around="false"
|
||||
:sort-option="sortOption"
|
||||
/>
|
||||
<section class="flex-1 h-full overflow-auto bg-white dark:bg-slate-900">
|
||||
<section class="overflow-x-auto">
|
||||
<Table fixed :table="table" />
|
||||
</section>
|
||||
|
||||
<EmptyState
|
||||
v-if="showSearchEmptyState"
|
||||
@@ -274,51 +188,3 @@ export default {
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contacts-table-wrap::v-deep {
|
||||
.ve-table {
|
||||
@apply pb-8;
|
||||
}
|
||||
.row--user-block {
|
||||
@apply items-center flex text-left;
|
||||
|
||||
.user-block {
|
||||
@apply items-start flex flex-col my-0 mx-2;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
@apply text-sm font-medium m-0 capitalize;
|
||||
}
|
||||
|
||||
.view-details--button {
|
||||
@apply text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
|
||||
.user-email {
|
||||
@apply m-0;
|
||||
}
|
||||
}
|
||||
|
||||
.ve-table-header-th {
|
||||
padding: var(--space-small) var(--space-two) !important;
|
||||
}
|
||||
|
||||
.ve-table-body-td {
|
||||
padding: var(--space-small) var(--space-two) !important;
|
||||
}
|
||||
|
||||
.ve-table-header-th {
|
||||
font-size: var(--font-size-mini) !important;
|
||||
}
|
||||
.ve-table-sort {
|
||||
@apply -top-1;
|
||||
}
|
||||
}
|
||||
|
||||
.cell--social-profiles {
|
||||
a {
|
||||
@apply text-slate-300 dark:text-slate-400 text-lg min-w-[2rem] text-center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import BaseCell from 'dashboard/components/table/BaseCell.vue';
|
||||
import { computed } from 'vue';
|
||||
import { getCountryFlag } from 'dashboard/helper/flag';
|
||||
|
||||
const props = defineProps({
|
||||
countryCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const countryFlag = computed(() => {
|
||||
if (!props.countryCode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getCountryFlag(props.countryCode);
|
||||
});
|
||||
|
||||
const formattedCountryName = computed(() => {
|
||||
if (!props.country) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${countryFlag.value} ${props.country}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCell :content="formattedCountryName" />
|
||||
</template>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
import BaseCell from 'dashboard/components/table/BaseCell.vue';
|
||||
defineProps({
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCell>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="text-woot-500 dark:text-woot-500"
|
||||
:href="`mailto:${email}`"
|
||||
>
|
||||
{{ email }}
|
||||
</a>
|
||||
</BaseCell>
|
||||
</template>
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import BaseCell from 'dashboard/components/table/BaseCell.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { inject } from 'vue';
|
||||
|
||||
defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const openContactInfoPanel = inject('openContactInfoPanel');
|
||||
const isRTL = useMapGetter('accounts/isRTL');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCell>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
class="!px-0"
|
||||
@click="() => openContactInfoPanel(row.original.id)"
|
||||
>
|
||||
<div class="items-center flex" :class="{ 'flex-row-reverse': isRTL }">
|
||||
<Thumbnail
|
||||
size="32px"
|
||||
:src="row.original.thumbnail"
|
||||
:username="row.original.name"
|
||||
:status="row.original.availability_status"
|
||||
/>
|
||||
<div class="items-start flex flex-col my-0 mx-2">
|
||||
<h6 class="overflow-hidden text-base whitespace-nowrap text-ellipsis">
|
||||
<router-link
|
||||
:to="`/app/accounts/${route.params.accountId}/contacts/${row.original.id}`"
|
||||
class="text-sm font-medium m-0 capitalize"
|
||||
>
|
||||
{{ row.original.name }}
|
||||
</router-link>
|
||||
</h6>
|
||||
<button
|
||||
class="button clear small link text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.LIST.VIEW_DETAILS') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</woot-button>
|
||||
</BaseCell>
|
||||
</template>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import BaseCell from 'dashboard/components/table/BaseCell.vue';
|
||||
import { computed } from 'vue';
|
||||
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
profiles: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const filteredProfiles = computed(() =>
|
||||
Object.keys(props.profiles).filter(profile => props.profiles[profile])
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCell class="flex gap-2 items-center text-slate-300 dark:text-slate-400">
|
||||
<template v-if="filteredProfiles.length">
|
||||
<a
|
||||
v-for="profile in filteredProfiles"
|
||||
:key="profile"
|
||||
:href="`https://${profile}.com/${profiles[profile]}`"
|
||||
target="_blank"
|
||||
class="hover:text-slate-500"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<FluentIcon class="size-4" :icon="`brand-${profile}`" />
|
||||
</a>
|
||||
</template>
|
||||
</BaseCell>
|
||||
</template>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import BaseCell from 'dashboard/components/table/BaseCell.vue';
|
||||
import { computed } from 'vue';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js';
|
||||
|
||||
const props = defineProps({
|
||||
phoneNumber: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultCountry: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const formattedNumber = computed(() => {
|
||||
if (!props.phoneNumber) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedNumber = parsePhoneNumber(
|
||||
props.phoneNumber,
|
||||
props.defaultCountry
|
||||
);
|
||||
|
||||
if (parsedNumber) {
|
||||
return parsedNumber.formatInternational();
|
||||
}
|
||||
return props.phoneNumber;
|
||||
} catch (error) {
|
||||
return props.phoneNumber;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCell :content="formattedNumber" />
|
||||
</template>
|
||||
@@ -16,6 +16,7 @@ import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCust
|
||||
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const FILTER_TYPE_CONTACT = 1;
|
||||
@@ -32,6 +33,11 @@ export default {
|
||||
AddCustomViews,
|
||||
DeleteCustomViews,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
openContactInfoPanel: this.openContactInfoPanel,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
label: { type: String, default: '' },
|
||||
segmentsId: {
|
||||
@@ -272,7 +278,7 @@ export default {
|
||||
const sortBy =
|
||||
Object.entries(params).find(pair => Boolean(pair[1])) || [];
|
||||
|
||||
this.$track(CONTACTS_EVENTS.APPLY_SORT, {
|
||||
useTrack(CONTACTS_EVENTS.APPLY_SORT, {
|
||||
appliedOn: sortBy[0],
|
||||
order: sortBy[1],
|
||||
});
|
||||
@@ -410,7 +416,6 @@ export default {
|
||||
:contacts="records"
|
||||
:show-search-empty-state="showEmptySearchResult"
|
||||
:is-loading="uiFlags.isFetching"
|
||||
:on-click-contact="openContactInfoPanel"
|
||||
:active-contact-id="selectedContactId"
|
||||
@onSortChange="onSortChange"
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import Modal from '../../../../components/Modal.vue';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -29,7 +30,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$track(CONTACTS_EVENTS.IMPORT_MODAL_OPEN);
|
||||
useTrack(CONTACTS_EVENTS.IMPORT_MODAL_OPEN);
|
||||
},
|
||||
methods: {
|
||||
async uploadFile() {
|
||||
@@ -38,10 +39,10 @@ export default {
|
||||
await this.$store.dispatch('contacts/import', this.file);
|
||||
this.onClose();
|
||||
useAlert(this.$t('IMPORT_CONTACTS.SUCCESS_MESSAGE'));
|
||||
this.$track(CONTACTS_EVENTS.IMPORT_SUCCESS);
|
||||
useTrack(CONTACTS_EVENTS.IMPORT_SUCCESS);
|
||||
} catch (error) {
|
||||
useAlert(error.message || this.$t('IMPORT_CONTACTS.ERROR_MESSAGE'));
|
||||
this.$track(CONTACTS_EVENTS.IMPORT_FAILURE);
|
||||
useTrack(CONTACTS_EVENTS.IMPORT_FAILURE);
|
||||
}
|
||||
},
|
||||
handleFileUpload() {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import ReminderItem from './ReminderItem';
|
||||
|
||||
export default {
|
||||
title: 'Components/Reminder/Item',
|
||||
component: ReminderItem,
|
||||
argTypes: {
|
||||
id: {
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
text: {
|
||||
defaultValue:
|
||||
'A copy and paste musical notes symbols & music symbols collection for easy access.',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
isCompleted: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
defaultValue: '03/06/2020',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
defaultValue: 'Call',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ReminderItem },
|
||||
template:
|
||||
'<reminder-item v-bind="$props" @completed="onClick" @edit="onEdit" @delete="onDelete"></reminder-item>',
|
||||
});
|
||||
|
||||
export const Item = Template.bind({});
|
||||
Item.args = {
|
||||
onClick: action('Marked'),
|
||||
onEdit: action('Edit'),
|
||||
onDelete: action('Delete'),
|
||||
};
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import SectionHeader from './SectionHeader';
|
||||
|
||||
export default {
|
||||
title: 'Components/Events/Section',
|
||||
component: SectionHeader,
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { SectionHeader },
|
||||
template:
|
||||
'<section-header v-bind="$props" @notes="onClickNotes" @events="onClickEvents" @conversation="onClickConversation"></section-header>',
|
||||
});
|
||||
|
||||
export const Section = Template.bind({});
|
||||
Section.args = {
|
||||
onClickNotes: action('notes'),
|
||||
onClickEvents: action('events'),
|
||||
onClickConversation: action('conversation'),
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import TimelineCard from './TimelineCard';
|
||||
|
||||
export default {
|
||||
title: 'Components/Events/Timeline',
|
||||
component: TimelineCard,
|
||||
argTypes: {
|
||||
eventType: {
|
||||
defaultValue: 'Commented',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
eventPath: {
|
||||
defaultValue: 'chatwoot/chatwoot',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
eventBody: {
|
||||
defaultValue:
|
||||
'Commentedmany variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
timeStamp: {
|
||||
defaultValue: '1618046084',
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { TimelineCard },
|
||||
template: '<timeline-card v-bind="$props" @more="onClick"></timeline-card>',
|
||||
});
|
||||
|
||||
export const Timeline = Template.bind({});
|
||||
Timeline.args = {
|
||||
onClick: action('more'),
|
||||
};
|
||||
@@ -98,8 +98,9 @@ export default {
|
||||
<div class="w-3/4 h-full">
|
||||
<woot-tabs :index="selectedTabIndex" @change="onClickTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.key"
|
||||
:index="index"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
|
||||
@@ -49,7 +49,10 @@ export default {
|
||||
<template>
|
||||
<div class="contact-conversation--panel">
|
||||
<div v-if="!uiFlags.isFetching" class="contact-conversation__wrap">
|
||||
<div v-if="!previousConversations.length" class="no-label-message">
|
||||
<div
|
||||
v-if="!previousConversations.length"
|
||||
class="no-label-message px-4 p-3"
|
||||
>
|
||||
<span>
|
||||
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
<script setup>
|
||||
import { computed, watch, onMounted, ref } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
|
||||
import AccordionItem from 'dashboard/components/Accordion/AccordionItem.vue';
|
||||
import ContactConversations from './ContactConversations.vue';
|
||||
import ConversationAction from './ConversationAction.vue';
|
||||
@@ -9,126 +11,84 @@ import ConversationParticipant from './ConversationParticipant.vue';
|
||||
import ContactInfo from './contact/ContactInfo.vue';
|
||||
import ConversationInfo from './ConversationInfo.vue';
|
||||
import CustomAttributes from './customAttributes/CustomAttributes.vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import Draggable from 'vuedraggable';
|
||||
import MacrosList from './Macros/List.vue';
|
||||
export default {
|
||||
components: {
|
||||
AccordionItem,
|
||||
ContactConversations,
|
||||
ContactInfo,
|
||||
ConversationInfo,
|
||||
CustomAttributes,
|
||||
ConversationAction,
|
||||
ConversationParticipant,
|
||||
Draggable: draggable,
|
||||
MacrosList,
|
||||
},
|
||||
props: {
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
inboxId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
onToggle: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
updateUISettings,
|
||||
isContactSidebarItemOpen,
|
||||
conversationSidebarItemsOrder,
|
||||
toggleSidebarUIState,
|
||||
} = useUISettings();
|
||||
|
||||
return {
|
||||
updateUISettings,
|
||||
isContactSidebarItemOpen,
|
||||
conversationSidebarItemsOrder,
|
||||
toggleSidebarUIState,
|
||||
};
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragEnabled: true,
|
||||
conversationSidebarItems: [],
|
||||
dragging: false,
|
||||
};
|
||||
inboxId: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
conversationAdditionalAttributes() {
|
||||
return this.currentConversationMetaData.additional_attributes || {};
|
||||
},
|
||||
channelType() {
|
||||
return this.currentChat.meta?.channel;
|
||||
},
|
||||
contact() {
|
||||
return this.$store.getters['contacts/getContact'](this.contactId);
|
||||
},
|
||||
contactAdditionalAttributes() {
|
||||
return this.contact.additional_attributes || {};
|
||||
},
|
||||
contactId() {
|
||||
return this.currentChat.meta?.sender?.id;
|
||||
},
|
||||
currentConversationMetaData() {
|
||||
return this.$store.getters[
|
||||
'conversationMetadata/getConversationMetadata'
|
||||
](this.conversationId);
|
||||
},
|
||||
hasContactAttributes() {
|
||||
const { custom_attributes: customAttributes } = this.contact;
|
||||
return customAttributes && Object.keys(customAttributes).length;
|
||||
},
|
||||
onToggle: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
watch: {
|
||||
conversationId(newConversationId, prevConversationId) {
|
||||
if (newConversationId && newConversationId !== prevConversationId) {
|
||||
this.getContactDetails();
|
||||
}
|
||||
},
|
||||
contactId() {
|
||||
this.getContactDetails();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.conversationSidebarItems = this.conversationSidebarItemsOrder;
|
||||
this.getContactDetails();
|
||||
this.$store.dispatch('attributes/get', 0);
|
||||
},
|
||||
methods: {
|
||||
onPanelToggle() {
|
||||
this.onToggle();
|
||||
},
|
||||
getContactDetails() {
|
||||
if (this.contactId) {
|
||||
this.$store.dispatch('contacts/show', { id: this.contactId });
|
||||
}
|
||||
},
|
||||
getAttributesByModel() {
|
||||
if (this.contactId) {
|
||||
this.$store.dispatch('contacts/show', { id: this.contactId });
|
||||
}
|
||||
},
|
||||
openTranscriptModal() {
|
||||
this.showTranscriptModal = true;
|
||||
},
|
||||
});
|
||||
|
||||
onDragEnd() {
|
||||
this.dragging = false;
|
||||
this.updateUISettings({
|
||||
conversation_sidebar_items_order: this.conversationSidebarItems,
|
||||
});
|
||||
},
|
||||
},
|
||||
const {
|
||||
updateUISettings,
|
||||
isContactSidebarItemOpen,
|
||||
conversationSidebarItemsOrder,
|
||||
toggleSidebarUIState,
|
||||
} = useUISettings();
|
||||
|
||||
const dragging = ref(false);
|
||||
const conversationSidebarItems = ref([]);
|
||||
|
||||
const store = useStore();
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
const conversationId = computed(() => props.conversationId);
|
||||
const conversationMetadataGetter = useMapGetter(
|
||||
'conversationMetadata/getConversationMetadata'
|
||||
);
|
||||
const currentConversationMetaData = computed(() =>
|
||||
conversationMetadataGetter.value(conversationId.value)
|
||||
);
|
||||
const conversationAdditionalAttributes = computed(
|
||||
() => currentConversationMetaData.value.additional_attributes || {}
|
||||
);
|
||||
|
||||
const channelType = computed(() => currentChat.value.meta?.channel);
|
||||
|
||||
const contactGetter = useMapGetter('contacts/getContact');
|
||||
const contactId = computed(() => currentChat.value.meta?.sender?.id);
|
||||
const contact = computed(() => contactGetter.value(contactId.value));
|
||||
const contactAdditionalAttributes = computed(
|
||||
() => contact.value.additional_attributes || {}
|
||||
);
|
||||
|
||||
const getContactDetails = () => {
|
||||
if (contactId.value) {
|
||||
store.dispatch('contacts/show', { id: contactId.value });
|
||||
}
|
||||
};
|
||||
|
||||
watch(conversationId, (newConversationId, prevConversationId) => {
|
||||
if (newConversationId && newConversationId !== prevConversationId) {
|
||||
getContactDetails();
|
||||
}
|
||||
});
|
||||
|
||||
watch(contactId, getContactDetails);
|
||||
|
||||
const onPanelToggle = props.onToggle;
|
||||
|
||||
const onDragEnd = () => {
|
||||
dragging.value = false;
|
||||
updateUISettings({
|
||||
conversation_sidebar_items_order: conversationSidebarItems.value,
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
conversationSidebarItems.value = conversationSidebarItemsOrder.value;
|
||||
getContactDetails();
|
||||
store.dispatch('attributes/get', 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -140,125 +100,127 @@ export default {
|
||||
:channel-type="channelType"
|
||||
@toggle-panel="onPanelToggle"
|
||||
/>
|
||||
<Draggable
|
||||
:list="conversationSidebarItems"
|
||||
:disabled="!dragEnabled"
|
||||
animation="200"
|
||||
class="list-group"
|
||||
ghost-class="ghost"
|
||||
handle=".drag-handle"
|
||||
@start="dragging = true"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<transition-group>
|
||||
<div
|
||||
v-for="element in conversationSidebarItems"
|
||||
:key="element.name"
|
||||
class="bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div
|
||||
v-if="element.name === 'conversation_actions'"
|
||||
class="conversation--actions"
|
||||
>
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_ACTIONS')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_actions_open')"
|
||||
@click="
|
||||
value => toggleSidebarUIState('is_conv_actions_open', value)
|
||||
"
|
||||
<div class="list-group">
|
||||
<Draggable
|
||||
:list="conversationSidebarItems"
|
||||
animation="200"
|
||||
ghost-class="ghost"
|
||||
handle=".drag-handle"
|
||||
item-key="name"
|
||||
@start="dragging = true"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div :key="element.name" class="bg-white dark:bg-gray-800">
|
||||
<div
|
||||
v-if="element.name === 'conversation_actions'"
|
||||
class="conversation--actions"
|
||||
>
|
||||
<ConversationAction
|
||||
:conversation-id="conversationId"
|
||||
:inbox-id="inboxId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="element.name === 'conversation_participants'"
|
||||
class="conversation--actions"
|
||||
>
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_PARTICIPANTS.SIDEBAR_TITLE')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_participants_open')"
|
||||
@click="
|
||||
value =>
|
||||
toggleSidebarUIState('is_conv_participants_open', value)
|
||||
"
|
||||
>
|
||||
<ConversationParticipant
|
||||
:conversation-id="conversationId"
|
||||
:inbox-id="inboxId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'conversation_info'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_details_open')"
|
||||
compact
|
||||
@click="
|
||||
value => toggleSidebarUIState('is_conv_details_open', value)
|
||||
"
|
||||
>
|
||||
<ConversationInfo
|
||||
:conversation-attributes="conversationAdditionalAttributes"
|
||||
:contact-attributes="contactAdditionalAttributes"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'contact_attributes'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
|
||||
:is-open="isContactSidebarItemOpen('is_contact_attributes_open')"
|
||||
compact
|
||||
@click="
|
||||
value =>
|
||||
toggleSidebarUIState('is_contact_attributes_open', value)
|
||||
"
|
||||
>
|
||||
<CustomAttributes
|
||||
attribute-type="contact_attribute"
|
||||
attribute-from="conversation_contact_panel"
|
||||
:contact-id="contact.id"
|
||||
:empty-state-message="
|
||||
$t('CONVERSATION_CUSTOM_ATTRIBUTES.NO_RECORDS_FOUND')
|
||||
<AccordionItem
|
||||
:title="
|
||||
$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_ACTIONS')
|
||||
"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'previous_conversation'">
|
||||
<AccordionItem
|
||||
v-if="contact.id"
|
||||
:title="
|
||||
$t('CONVERSATION_SIDEBAR.ACCORDION.PREVIOUS_CONVERSATION')
|
||||
"
|
||||
:is-open="isContactSidebarItemOpen('is_previous_conv_open')"
|
||||
@click="
|
||||
value => toggleSidebarUIState('is_previous_conv_open', value)
|
||||
"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_actions_open')"
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_conv_actions_open', value)
|
||||
"
|
||||
>
|
||||
<ConversationAction
|
||||
:conversation-id="conversationId"
|
||||
:inbox-id="inboxId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="element.name === 'conversation_participants'"
|
||||
class="conversation--actions"
|
||||
>
|
||||
<ContactConversations
|
||||
:contact-id="contact.id"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<woot-feature-toggle
|
||||
v-else-if="element.name === 'macros'"
|
||||
feature-key="macros"
|
||||
>
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.MACROS')"
|
||||
:is-open="isContactSidebarItemOpen('is_macro_open')"
|
||||
compact
|
||||
@click="value => toggleSidebarUIState('is_macro_open', value)"
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_PARTICIPANTS.SIDEBAR_TITLE')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_participants_open')"
|
||||
@toggle="
|
||||
value =>
|
||||
toggleSidebarUIState('is_conv_participants_open', value)
|
||||
"
|
||||
>
|
||||
<ConversationParticipant
|
||||
:conversation-id="conversationId"
|
||||
:inbox-id="inboxId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'conversation_info'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
|
||||
:is-open="isContactSidebarItemOpen('is_conv_details_open')"
|
||||
compact
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_conv_details_open', value)
|
||||
"
|
||||
>
|
||||
<ConversationInfo
|
||||
:conversation-attributes="conversationAdditionalAttributes"
|
||||
:contact-attributes="contactAdditionalAttributes"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'contact_attributes'">
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONTACT_ATTRIBUTES')"
|
||||
:is-open="
|
||||
isContactSidebarItemOpen('is_contact_attributes_open')
|
||||
"
|
||||
compact
|
||||
@toggle="
|
||||
value =>
|
||||
toggleSidebarUIState('is_contact_attributes_open', value)
|
||||
"
|
||||
>
|
||||
<CustomAttributes
|
||||
attribute-type="contact_attribute"
|
||||
attribute-from="conversation_contact_panel"
|
||||
:contact-id="contact.id"
|
||||
:empty-state-message="
|
||||
$t('CONVERSATION_CUSTOM_ATTRIBUTES.NO_RECORDS_FOUND')
|
||||
"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<div v-else-if="element.name === 'previous_conversation'">
|
||||
<AccordionItem
|
||||
v-if="contact.id"
|
||||
:title="
|
||||
$t('CONVERSATION_SIDEBAR.ACCORDION.PREVIOUS_CONVERSATION')
|
||||
"
|
||||
:is-open="isContactSidebarItemOpen('is_previous_conv_open')"
|
||||
compact
|
||||
@toggle="
|
||||
value => toggleSidebarUIState('is_previous_conv_open', value)
|
||||
"
|
||||
>
|
||||
<ContactConversations
|
||||
:contact-id="contact.id"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<woot-feature-toggle
|
||||
v-else-if="element.name === 'macros'"
|
||||
feature-key="macros"
|
||||
>
|
||||
<MacrosList :conversation-id="conversationId" />
|
||||
</AccordionItem>
|
||||
</woot-feature-toggle>
|
||||
</div>
|
||||
</transition-group>
|
||||
</Draggable>
|
||||
<AccordionItem
|
||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.MACROS')"
|
||||
:is-open="isContactSidebarItemOpen('is_macro_open')"
|
||||
compact
|
||||
@toggle="value => toggleSidebarUIState('is_macro_open', value)"
|
||||
>
|
||||
<MacrosList :conversation-id="conversationId" />
|
||||
</AccordionItem>
|
||||
</woot-feature-toggle>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
||||
import ConversationLabels from './labels/LabelBox.vue';
|
||||
import { CONVERSATION_PRIORITY } from '../../../../shared/constants/messages';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -128,7 +129,7 @@ export default {
|
||||
this.$store
|
||||
.dispatch('assignPriority', { conversationId, priority })
|
||||
.then(() => {
|
||||
this.$track(CONVERSATION_EVENTS.CHANGE_PRIORITY, {
|
||||
useTrack(CONVERSATION_EVENTS.CHANGE_PRIORITY, {
|
||||
oldValue,
|
||||
newValue: priority,
|
||||
from: 'Conversation Sidebar',
|
||||
@@ -233,7 +234,7 @@ export default {
|
||||
:input-placeholder="
|
||||
$t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.PLACEHOLDER.AGENT')
|
||||
"
|
||||
@click="onClickAssignAgent"
|
||||
@select="onClickAssignAgent"
|
||||
/>
|
||||
</div>
|
||||
<div class="multiselect-wrap--small">
|
||||
@@ -252,7 +253,7 @@ export default {
|
||||
:input-placeholder="
|
||||
$t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.PLACEHOLDER.INPUT')
|
||||
"
|
||||
@click="onClickAssignTeam"
|
||||
@select="onClickAssignTeam"
|
||||
/>
|
||||
</div>
|
||||
<div class="multiselect-wrap--small">
|
||||
@@ -270,7 +271,7 @@ export default {
|
||||
:input-placeholder="
|
||||
$t('CONVERSATION.PRIORITY.CHANGE_PRIORITY.INPUT_PLACEHOLDER')
|
||||
"
|
||||
@click="onClickAssignPriority"
|
||||
@select="onClickAssignPriority"
|
||||
/>
|
||||
</div>
|
||||
<ContactDetailsItem
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue';
|
||||
import { getLanguageName } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||
import ContactDetailsItem from './ContactDetailsItem.vue';
|
||||
import CustomAttributes from './customAttributes/CustomAttributes.vue';
|
||||
|
||||
const props = defineProps({
|
||||
conversationAttributes: {
|
||||
type: Object,
|
||||
|
||||
@@ -225,7 +225,7 @@ export default {
|
||||
:options="agentsList"
|
||||
:selected-items="selectedWatchers"
|
||||
has-thumbnail
|
||||
@click="onClickItem"
|
||||
@select="onClickItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import PopOverSearch from './search/PopOverSearch.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import CmdBarConversationSnooze from 'dashboard/routes/dashboard/commands/CmdBarConversationSnooze.vue';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -170,7 +171,7 @@ export default {
|
||||
after: messageId,
|
||||
})
|
||||
.then(() => {
|
||||
this.$emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE, { messageId });
|
||||
emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE, { messageId });
|
||||
});
|
||||
} else {
|
||||
this.$store.dispatch('clearSelectedState');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import MacroPreview from './MacroPreview.vue';
|
||||
import { CONVERSATION_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -31,7 +32,7 @@ export default {
|
||||
macroId: macro.id,
|
||||
conversationIds: [this.conversationId],
|
||||
});
|
||||
this.$track(CONVERSATION_EVENTS.EXECUTED_A_MACRO);
|
||||
useTrack(CONVERSATION_EVENTS.EXECUTED_A_MACRO);
|
||||
useAlert(this.$t('MACROS.EXECUTE.EXECUTED_SUCCESSFULLY'));
|
||||
} catch (error) {
|
||||
useAlert(this.$t('MACROS.ERROR'));
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
DuplicateContactException,
|
||||
ExceptionWithMessage,
|
||||
} from 'shared/helpers/CustomErrors';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, email } from '@vuelidate/validators';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import { isPhoneNumberValid } from 'shared/helpers/Validators';
|
||||
import parsePhoneNumber from 'libphonenumber-js';
|
||||
@@ -280,7 +280,7 @@ export default {
|
||||
:username-avatar="name"
|
||||
:delete-avatar="!!avatarUrl"
|
||||
class="settings-item"
|
||||
@change="handleImageUpload"
|
||||
@onAvatarSelect="handleImageUpload"
|
||||
@onAvatarDelete="handleAvatarDelete"
|
||||
/>
|
||||
</div>
|
||||
@@ -290,7 +290,7 @@ export default {
|
||||
<label :class="{ error: v$.name.$error }">
|
||||
{{ $t('CONTACT_FORM.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
:placeholder="$t('CONTACT_FORM.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.name.$touch"
|
||||
@@ -300,7 +300,7 @@ export default {
|
||||
<label :class="{ error: v$.email.$error }">
|
||||
{{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.LABEL') }}
|
||||
<input
|
||||
v-model.trim="email"
|
||||
v-model="email"
|
||||
type="text"
|
||||
:placeholder="$t('CONTACT_FORM.FORM.EMAIL_ADDRESS.PLACEHOLDER')"
|
||||
@input="v$.email.$touch"
|
||||
@@ -315,7 +315,7 @@ export default {
|
||||
<label :class="{ error: v$.description.$error }">
|
||||
{{ $t('CONTACT_FORM.FORM.BIO.LABEL') }}
|
||||
<textarea
|
||||
v-model.trim="description"
|
||||
v-model="description"
|
||||
type="text"
|
||||
:placeholder="$t('CONTACT_FORM.FORM.BIO.PLACEHOLDER')"
|
||||
@input="v$.description.$touch"
|
||||
@@ -352,7 +352,7 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<woot-input
|
||||
v-model.trim="companyName"
|
||||
v-model="companyName"
|
||||
class="w-full"
|
||||
:label="$t('CONTACT_FORM.FORM.COMPANY_NAME.LABEL')"
|
||||
:placeholder="$t('CONTACT_FORM.FORM.COMPANY_NAME.PLACEHOLDER')"
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isAInboxViewRoute,
|
||||
getConversationDashboardRoute,
|
||||
} from '../../../../helper/routeHelpers';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -94,9 +95,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
dynamicTime,
|
||||
toggleMergeModal() {
|
||||
this.showMergeModal = !this.showMergeModal;
|
||||
},
|
||||
toggleEditModal() {
|
||||
this.showEditModal = !this.showEditModal;
|
||||
},
|
||||
@@ -105,7 +103,7 @@ export default {
|
||||
},
|
||||
toggleConversationModal() {
|
||||
this.showConversationModal = !this.showConversationModal;
|
||||
this.$emitter.emit(
|
||||
emitter.emit(
|
||||
BUS_EVENTS.NEW_CONVERSATION_MODAL,
|
||||
this.showConversationModal
|
||||
);
|
||||
@@ -157,8 +155,11 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
closeMergeModal() {
|
||||
this.showMergeModal = false;
|
||||
},
|
||||
openMergeModal() {
|
||||
this.toggleMergeModal();
|
||||
this.showMergeModal = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -315,7 +316,7 @@ export default {
|
||||
v-if="showMergeModal"
|
||||
:primary-contact="contact"
|
||||
:show="showMergeModal"
|
||||
@close="toggleMergeModal"
|
||||
@close="closeMergeModal"
|
||||
/>
|
||||
</div>
|
||||
<woot-delete-modal
|
||||
|
||||
+32
-29
@@ -1,24 +1,34 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead.vue';
|
||||
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
|
||||
import MessageSignatureMissingAlert from 'dashboard/components/widgets/conversation/MessageSignatureMissingAlert';
|
||||
import InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem.vue';
|
||||
import WhatsappTemplates from './WhatsappTemplates.vue';
|
||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||
import { getInboxSource } from 'dashboard/helper/inbox';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, requiredIf } from '@vuelidate/validators';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import FileUpload from 'vue-upload-component';
|
||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
||||
// constants & helpers
|
||||
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||
import { getInboxSource } from 'dashboard/helper/inbox';
|
||||
|
||||
// store
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
// composables
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { required, requiredIf } from '@vuelidate/validators';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
|
||||
// mixins
|
||||
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
|
||||
// components
|
||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
|
||||
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
|
||||
import InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem.vue';
|
||||
import MessageSignatureMissingAlert from 'dashboard/components/widgets/conversation/MessageSignatureMissingAlert.vue';
|
||||
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import FileUpload from 'vue-upload-component';
|
||||
import WhatsappTemplates from './WhatsappTemplates.vue';
|
||||
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
@@ -346,12 +356,13 @@ export default {
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||
selected-label=""
|
||||
select-label=""
|
||||
class="reset-base"
|
||||
deselect-label=""
|
||||
:max-height="160"
|
||||
close-on-select
|
||||
:options="[...inboxes]"
|
||||
>
|
||||
<template slot="singleLabel" slot-scope="{ option }">
|
||||
<template #singleLabel="{ option }">
|
||||
<InboxDropdownItem
|
||||
v-if="option.name"
|
||||
:name="option.name"
|
||||
@@ -362,7 +373,7 @@ export default {
|
||||
{{ $t('NEW_CONVERSATION.FORM.INBOX.PLACEHOLDER') }}
|
||||
</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="{ option }">
|
||||
<template #option="{ option }">
|
||||
<InboxDropdownItem
|
||||
:name="option.name"
|
||||
:inbox-identifier="computedInboxSource(option)"
|
||||
@@ -420,7 +431,7 @@ export default {
|
||||
<CannedResponse
|
||||
v-if="showCannedResponseMenu && hasSlashCommand"
|
||||
:search-key="cannedResponseSearchKey"
|
||||
@click="replaceTextWithCannedResponse"
|
||||
@replace="replaceTextWithCannedResponse"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="isEmailOrWebWidgetInbox">
|
||||
@@ -589,13 +600,5 @@ export default {
|
||||
.mention--box {
|
||||
@apply left-0 m-auto right-0 top-auto h-fit;
|
||||
}
|
||||
|
||||
.multiselect .multiselect__content .multiselect__option span {
|
||||
@apply inline-flex w-6 text-slate-600 dark:text-slate-400;
|
||||
}
|
||||
|
||||
.multiselect .multiselect__content .multiselect__option {
|
||||
@apply py-0.5 px-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore, useStoreGetters } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
import CustomAttribute from 'dashboard/components/CustomAttribute.vue';
|
||||
|
||||
@@ -99,7 +99,7 @@ export default {
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
class="max-w-[calc(100%-0.5rem)]"
|
||||
@click="removeLabelFromConversation"
|
||||
@remove="removeLabelFromConversation"
|
||||
/>
|
||||
|
||||
<div class="dropdown-wrap">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -63,7 +64,7 @@ export default {
|
||||
: this.$t('FILTER.CUSTOM_VIEWS.ADD.API_SEGMENTS.SUCCESS_MESSAGE');
|
||||
this.onClose();
|
||||
|
||||
this.$track(CONTACTS_EVENTS.SAVE_FILTER, {
|
||||
useTrack(CONTACTS_EVENTS.SAVE_FILTER, {
|
||||
type: this.filterType === 0 ? 'folder' : 'segment',
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
@blur="v$.name.$touch"
|
||||
/>
|
||||
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||
<woot-button :disabled="isButtonDisabled">
|
||||
{{ $t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON') }}
|
||||
</woot-button>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
showDeletePopup: {
|
||||
@@ -66,7 +68,7 @@ export default {
|
||||
? this.$t('FILTER.CUSTOM_VIEWS.DELETE.API_FOLDERS.SUCCESS_MESSAGE')
|
||||
: this.$t('FILTER.CUSTOM_VIEWS.DELETE.API_SEGMENTS.SUCCESS_MESSAGE')
|
||||
);
|
||||
this.$track(CONTACTS_EVENTS.DELETE_FILTER, {
|
||||
useTrack(CONTACTS_EVENTS.DELETE_FILTER, {
|
||||
type: this.filterType === 0 ? 'folder' : 'segment',
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
routes: [
|
||||
...helpcenterRoutes.routes,
|
||||
{
|
||||
path: frontendURL('accounts/:account_id'),
|
||||
path: frontendURL('accounts/:accountId'),
|
||||
component: AppContainer,
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script>
|
||||
import Modal from 'dashboard/components/Modal.vue';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import allLocales from 'shared/constants/locales.js';
|
||||
import { PORTALS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -74,7 +75,7 @@ export default {
|
||||
'HELP_CENTER.PORTAL.ADD_LOCALE.API.SUCCESS_MESSAGE'
|
||||
);
|
||||
this.onClose();
|
||||
this.$track(PORTALS_EVENTS.CREATE_LOCALE, {
|
||||
useTrack(PORTALS_EVENTS.CREATE_LOCALE, {
|
||||
localeAdded: this.selectedLocale,
|
||||
totalLocales: updatedLocales.length,
|
||||
from: this.$route.name,
|
||||
@@ -142,6 +143,7 @@ export default {
|
||||
input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -1,54 +1,33 @@
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, defineEmits } from 'vue';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||
import WootArticleEditor from 'dashboard/components/widgets/WootWriter/FullEditor.vue';
|
||||
import FullEditor from 'dashboard/components/widgets/WootWriter/FullEditor.vue';
|
||||
import { ARTICLE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootArticleEditor,
|
||||
ResizableTextArea,
|
||||
const { article } = defineProps({
|
||||
article: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
props: {
|
||||
article: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['saveArticle']);
|
||||
|
||||
const saveArticle = debounce(value => emit('saveArticle', value), 400, false);
|
||||
|
||||
const articleTitle = computed({
|
||||
get: () => article.title,
|
||||
set: title => {
|
||||
saveArticle({ title });
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
articleTitle: '',
|
||||
articleContent: '',
|
||||
saveArticle: () => {},
|
||||
customEditorMenuOptions: ARTICLE_EDITOR_MENU_OPTIONS,
|
||||
};
|
||||
});
|
||||
|
||||
const articleContent = computed({
|
||||
get: () => article.content,
|
||||
set: content => {
|
||||
saveArticle({ content });
|
||||
},
|
||||
mounted() {
|
||||
this.articleTitle = this.article.title;
|
||||
this.articleContent = this.article.content;
|
||||
this.saveArticle = debounce(
|
||||
values => {
|
||||
this.$emit('saveArticle', values);
|
||||
},
|
||||
300,
|
||||
false
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
onBlur() {
|
||||
this.$emit('blur');
|
||||
},
|
||||
onTitleInput() {
|
||||
this.saveArticle({ title: this.articleTitle });
|
||||
},
|
||||
onContentInput() {
|
||||
this.saveArticle({ content: this.articleContent });
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -59,18 +38,12 @@ export default {
|
||||
:rows="1"
|
||||
class="article-heading"
|
||||
:placeholder="$t('HELP_CENTER.EDIT_ARTICLE.TITLE_PLACEHOLDER')"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="onTitleInput"
|
||||
/>
|
||||
<WootArticleEditor
|
||||
<FullEditor
|
||||
v-model="articleContent"
|
||||
class="article-content"
|
||||
:placeholder="$t('HELP_CENTER.EDIT_ARTICLE.CONTENT_PLACEHOLDER')"
|
||||
:enabled-menu-options="customEditorMenuOptions"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="onContentInput"
|
||||
:enabled-menu-options="ARTICLE_EDITOR_MENU_OPTIONS"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import ArticleItem from './ArticleItem.vue';
|
||||
import TableFooter from 'dashboard/components/widgets/TableFooter.vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ArticleItem,
|
||||
TableFooter,
|
||||
Draggable: draggable,
|
||||
Draggable,
|
||||
},
|
||||
props: {
|
||||
articles: {
|
||||
@@ -35,9 +35,7 @@ export default {
|
||||
computed: {
|
||||
dragEnabled() {
|
||||
// dragging allowed only on category page
|
||||
return (
|
||||
this.articles.length > 1 && !this.isFetching && this.onCategoryPage
|
||||
);
|
||||
return this.articles.length > 1 && this.onCategoryPage;
|
||||
},
|
||||
onCategoryPage() {
|
||||
return this.$route.name === 'show_category';
|
||||
@@ -127,22 +125,24 @@ export default {
|
||||
:disabled="!dragEnabled"
|
||||
:list="localArticles"
|
||||
ghost-class="article-ghost-class"
|
||||
item-key="id"
|
||||
@start="dragging = true"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<ArticleItem
|
||||
v-for="article in localArticles"
|
||||
:id="article.id"
|
||||
:key="article.id"
|
||||
:class="{ draggable: onCategoryPage }"
|
||||
:title="article.title"
|
||||
:author="article.author"
|
||||
:show-drag-icon="dragEnabled"
|
||||
:category="article.category"
|
||||
:views="article.views"
|
||||
:status="article.status"
|
||||
:updated-at="article.updated_at"
|
||||
/>
|
||||
<template #item="{ element }">
|
||||
<ArticleItem
|
||||
:id="element.id"
|
||||
:key="element.id"
|
||||
:class="{ draggable: onCategoryPage }"
|
||||
:title="element.title"
|
||||
:author="element.author"
|
||||
:show-drag-icon="dragEnabled"
|
||||
:category="element.category"
|
||||
:views="element.views"
|
||||
:status="element.status"
|
||||
:updated-at="element.updated_at"
|
||||
/>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
||||
<TableFooter
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ export default {
|
||||
$t('HELP_CENTER.HEADER.LOCALE_SELECT.SEARCH_PLACEHOLDER')
|
||||
"
|
||||
:no-search-result="$t('HELP_CENTER.HEADER.LOCALE_SELECT.NO_RESULT')"
|
||||
@click="onClickSelectItem"
|
||||
@select="onClickSelectItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
@@ -78,9 +79,9 @@ export default {
|
||||
this.statusUpdateSuccessMessage(status);
|
||||
this.closeActionsDropdown();
|
||||
if (status === this.ARTICLE_STATUS_TYPES.ARCHIVE) {
|
||||
this.$track(PORTALS_EVENTS.ARCHIVE_ARTICLE, { uiFrom: 'header' });
|
||||
useTrack(PORTALS_EVENTS.ARCHIVE_ARTICLE, { uiFrom: 'header' });
|
||||
} else if (status === this.ARTICLE_STATUS_TYPES.PUBLISH) {
|
||||
this.$track(PORTALS_EVENTS.PUBLISH_ARTICLE);
|
||||
useTrack(PORTALS_EVENTS.PUBLISH_ARTICLE);
|
||||
}
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
|
||||
+10
-6
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import UpgradePage from './UpgradePage.vue';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
@@ -13,8 +14,11 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import portalMixin from '../mixins/portalMixin';
|
||||
import AddCategory from '../pages/categories/AddCategory.vue';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
const CommandBar = () =>
|
||||
import('dashboard/routes/dashboard/commands/commandbar.vue');
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
const CommandBar = defineAsyncComponent(
|
||||
() => import('dashboard/routes/dashboard/commands/commandbar.vue')
|
||||
);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -214,15 +218,15 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
emitter.on(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
|
||||
const slug = this.$route.params.portalSlug;
|
||||
if (slug) this.lastActivePortalSlug = slug;
|
||||
|
||||
this.fetchPortalAndItsCategories();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
unmounted() {
|
||||
emitter.off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
|
||||
},
|
||||
updated() {
|
||||
const slug = this.$route.params.portalSlug;
|
||||
@@ -305,7 +309,7 @@ export default {
|
||||
/>
|
||||
<section
|
||||
v-if="isHelpCenterEnabled"
|
||||
class="flex flex-1 h-full min-h-0 px-0 overflow-hidden bg-white dark:bg-slate-900"
|
||||
class="flex flex-1 h-full px-0 overflow-hidden bg-white dark:bg-slate-900"
|
||||
>
|
||||
<router-view @reloadLocale="fetchPortalAndItsCategories" />
|
||||
<CommandBar />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import LocaleItemTable from './PortalListItemTable.vue';
|
||||
import { PORTALS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -129,7 +130,7 @@ export default {
|
||||
'HELP_CENTER.PORTAL.CHANGE_DEFAULT_LOCALE.API.ERROR_MESSAGE'
|
||||
),
|
||||
});
|
||||
this.$track(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
useTrack(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
newLocale: localeCode,
|
||||
from: this.$route.name,
|
||||
});
|
||||
@@ -149,7 +150,7 @@ export default {
|
||||
'HELP_CENTER.PORTAL.DELETE_LOCALE.API.ERROR_MESSAGE'
|
||||
),
|
||||
});
|
||||
this.$track(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
useTrack(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
deletedLocale: localeCode,
|
||||
from: this.$route.name,
|
||||
});
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
|
||||
import { defineComponent, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineOptions, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||
@@ -31,7 +31,7 @@ const props = defineProps({
|
||||
});
|
||||
const emit = defineEmits(['submit', 'deleteLogo']);
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'PortalSettingsBasicForm',
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ function onFileChange({ file }) {
|
||||
<woot-avatar-uploader
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.LOGO.LABEL')"
|
||||
:src="state.logoUrl"
|
||||
@change="onFileChange"
|
||||
@onAvatarSelect="onFileChange"
|
||||
/>
|
||||
<div v-if="showDeleteButton" class="avatar-delete-btn">
|
||||
<woot-button
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@ import wootConstants from 'dashboard/constants/globals';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { url } from '@vuelidate/validators';
|
||||
|
||||
import { defineComponent, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineOptions, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const props = defineProps({
|
||||
portal: {
|
||||
type: Object,
|
||||
@@ -20,7 +20,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'PortalSettingsCustomizationForm',
|
||||
});
|
||||
|
||||
|
||||
@@ -74,11 +74,7 @@ export default {
|
||||
class="px-4"
|
||||
@openPopover="openPortalPopover"
|
||||
/>
|
||||
<transition-group
|
||||
name="menu-list"
|
||||
tag="ul"
|
||||
class="px-4 py-2 mb-0 ml-0 list-none"
|
||||
>
|
||||
<transition-group name="menu-list" tag="ul" class="p-2 mb-0 ml-0 list-none">
|
||||
<SecondaryNavItem
|
||||
v-for="menuItem in accessibleMenuItems"
|
||||
:key="menuItem.toState"
|
||||
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-12 sm:gap-16 items-center justify-center py-0 px-4 md:px-0 w-full h-full max-w-full overflow-auto bg-white dark:bg-slate-900"
|
||||
class="flex flex-col gap-12 sm:gap-16 items-center justify-center py-0 px-4 md:px-0 w-full min-h-screen max-w-full overflow-auto bg-white dark:bg-slate-900"
|
||||
>
|
||||
<div class="flex flex-col justify-start sm:justify-center gap-6">
|
||||
<div class="flex flex-col gap-1.5 items-start sm:items-center">
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import AddCategoryComponent from '../../pages/categories/AddCategory.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: AddCategoryComponent,
|
||||
argTypes: {
|
||||
show: {
|
||||
defaultValue: true,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { AddCategoryComponent },
|
||||
template:
|
||||
'<add-category-component v-bind="$props" @create="onCreate" @cancel="onClose" />',
|
||||
});
|
||||
|
||||
export const AddCategory = Template.bind({});
|
||||
AddCategory.args = {
|
||||
portalName: 'Chatwoot help center',
|
||||
locale: 'En-US',
|
||||
onCreate: action('create'),
|
||||
onClose: action('cancel'),
|
||||
};
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: ArticleEditor,
|
||||
argTypes: {
|
||||
article: {
|
||||
defaultValue: {},
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleEditor },
|
||||
template:
|
||||
'<article-editor v-bind="$props" @focus="onFocus" @blur="onBlur"></article-editor>',
|
||||
});
|
||||
|
||||
export const EditArticleView = Template.bind({});
|
||||
EditArticleView.args = {
|
||||
article: {
|
||||
id: '1',
|
||||
title: 'Lorem ipsum',
|
||||
content:
|
||||
'L**orem ipsum** dolor sit amet, consectetur adipiscing elit. Congue diam orci tellus *varius per cras turpis aliquet commodo dolor justo* rutrum lorem venenatis aliquet orci curae hac. Sagittis ultrices felis **`ante placerat condimentum parturient erat consequat`** sollicitudin *sagittis potenti sollicitudin* quis velit at placerat mi torquent. Dignissim luctus nulla suspendisse purus cras commodo ipsum orci tempus morbi metus conubia et hac potenti quam suspendisse feugiat. Turpis eros dictum tellus natoque laoreet lacus dolor cras interdum **vitae gravida tincidunt ultricies tempor convallis tortor rhoncus suspendisse.** Nisi lacinia etiam vivamus tellus sed taciti potenti quam praesent congue euismod mauris est eu risus convallis taciti etiam. Inceptos iaculis turpis leo porta pellentesque dictum `bibendum blandit parturient nulla leo pretium` rhoncus litora dapibus fringilla hac litora.',
|
||||
},
|
||||
onFocus: action('focus'),
|
||||
onBlur: action('blur'),
|
||||
};
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import ArticleHeader from '../Header/ArticleHeader';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Header',
|
||||
component: ArticleHeader,
|
||||
argTypes: {
|
||||
headerTitle: {
|
||||
defaultValue: 'All articles',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
count: {
|
||||
defaultValue: 112,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
selectedValue: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleHeader },
|
||||
template:
|
||||
'<article-header v-bind="$props" @openModal="openFilterModal" @open="openDropdown" @close="closeDropdown" ></article-header>',
|
||||
});
|
||||
|
||||
export const ArticleHeaderView = Template.bind({});
|
||||
ArticleHeaderView.args = {
|
||||
headerTitle: 'All articles',
|
||||
count: 112,
|
||||
selectedValue: 'Status',
|
||||
openFilterModal: action('openedFilterModal'),
|
||||
openDropdown: action('opened'),
|
||||
closeDropdown: action('closed'),
|
||||
};
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
import ArticleItemComponent from '../ArticleItem.vue';
|
||||
const STATUS_LIST = {
|
||||
published: 'published',
|
||||
draft: 'draft',
|
||||
archived: 'archived',
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: ArticleItemComponent,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Setup your account',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
defaultValue: 13,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
category: {
|
||||
defaultValue: 'Getting started',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'select',
|
||||
options: STATUS_LIST,
|
||||
},
|
||||
},
|
||||
updatedAt: {
|
||||
defaultValue: '1657255863',
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleItemComponent },
|
||||
template:
|
||||
'<article-item-component v-bind="$props" ></article-item-component>',
|
||||
});
|
||||
|
||||
export const ArticleItem = Template.bind({});
|
||||
ArticleItem.args = {
|
||||
title: 'Setup your account',
|
||||
author: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
category: 'Getting started',
|
||||
views: 12,
|
||||
status: 'published',
|
||||
updatedAt: 1657255863,
|
||||
};
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import ArticleSearchArticleView from '../ArticleSearch/ArticleView';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearch/ArticleView',
|
||||
component: ArticleSearchArticleView,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Insert article',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
selectedValue: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleSearchArticleView },
|
||||
template:
|
||||
'<article-search-article-view v-bind="$props"></article-search-article-view>',
|
||||
});
|
||||
|
||||
export const ArticleHeaderView = Template.bind({});
|
||||
ArticleHeaderView.args = {
|
||||
title: 'Insert article',
|
||||
url: 'https://staging.chatwoot.com/hc/home-rental-app/articles/filter-your-housing-search-results-0',
|
||||
};
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import ArticleSearchHeader from '../ArticleSearch/Header';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearch/Header',
|
||||
component: ArticleSearchHeader,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Insert article',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
selectedValue: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleSearchHeader },
|
||||
template: '<article-search-header v-bind="$props"></article-search-header>',
|
||||
});
|
||||
|
||||
export const ArticleHeaderView = Template.bind({});
|
||||
ArticleHeaderView.args = {
|
||||
title: 'Insert article',
|
||||
};
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import ArticleSearchResultItem from '../ArticleSearch/ArticleSearchResultItem.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearchResultItem',
|
||||
component: ArticleSearchResultItem,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Setup your account',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
body: {
|
||||
defaultValue:
|
||||
'You can integrate your Chatwoot account with multiple conversation channels like website live-chat, email, Facebook page, Twitter handle, WhatsApp, etc. You can view all of your conversations from different channels on one dashboard. This helps in reducing the time and friction involved with switching between multiple tools.',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
category: {
|
||||
defaultValue: 'Getting started',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
locale: {
|
||||
defaultValue: 'en-US',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
url: {
|
||||
defaultValue: '/app/accounts/1/conversations/23842',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleSearchResultItem },
|
||||
template:
|
||||
'<ArticleSearchResultItem v-bind="$props" ></ArticleSearchResultItem>',
|
||||
});
|
||||
|
||||
export const ArticleSearchResultItemStory = Template.bind({});
|
||||
ArticleSearchResultItemStory.args = {
|
||||
title: 'Setup your account',
|
||||
body: `You can integrate your Chatwoot account with multiple conversation channels like website live-chat, email, Facebook page, Twitter handle, WhatsApp, etc. You can view all of your conversations from different channels on one dashboard. This helps in reducing the time and friction involved with switching between multiple tools.
|
||||
|
||||
You can manage your conversations and collaborate with your team on the go with Chatwoot mobile apps (available for Android and iOS).
|
||||
|
||||
In this user guide, we’ve explained the features, capabilities, modes of operation, and step-by-step procedures for easily using the Chatwoot platform.`,
|
||||
};
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
import ArticleTableComponent from '../ArticleTable.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: ArticleTableComponent,
|
||||
argTypes: {
|
||||
articles: {
|
||||
defaultValue: [],
|
||||
control: {
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
articleCount: {
|
||||
defaultValue: 10,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
currentPage: {
|
||||
defaultValue: 1,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleTableComponent },
|
||||
template:
|
||||
'<article-table-component @onPageChange="onPageChange" v-bind="$props" ></article-table-component>',
|
||||
});
|
||||
|
||||
export const ArticleTable = Template.bind({});
|
||||
ArticleTable.args = {
|
||||
articles: [
|
||||
{
|
||||
title: 'Setup your account',
|
||||
author: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Getting started',
|
||||
status: 'published',
|
||||
updatedAt: 1657255863,
|
||||
},
|
||||
{
|
||||
title: 'Docker Configuration',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Engineering',
|
||||
status: 'draft',
|
||||
updatedAt: 1656658046,
|
||||
},
|
||||
{
|
||||
title: 'Campaigns',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 28,
|
||||
category: 'Engineering',
|
||||
status: 'archived',
|
||||
updatedAt: 1657590446,
|
||||
},
|
||||
],
|
||||
articleCount: 10,
|
||||
currentPage: 1,
|
||||
onPageChange: action('onPageChange'),
|
||||
};
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import EditArticleHeader from '../Header/EditArticleHeader';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Header',
|
||||
component: EditArticleHeader,
|
||||
argTypes: {
|
||||
backButtonLabel: {
|
||||
defaultValue: 'Articles',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
draftState: {
|
||||
defaultValue: 'saving',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { EditArticleHeader },
|
||||
template:
|
||||
'<edit-article-header v-bind="$props" @back="onClickGoBack" @show="showPreview" @add="onClickAdd" @open="openSidebar" @close="closeSidebar" ></edit-article-header>',
|
||||
});
|
||||
|
||||
export const EditArticleHeaderView = Template.bind({});
|
||||
EditArticleHeaderView.args = {
|
||||
backButtonLabel: 'Articles',
|
||||
draftState: 'saving',
|
||||
onClickGoBack: action('goBack'),
|
||||
showPreview: action('previewOpened'),
|
||||
onClickAdd: action('added'),
|
||||
openSidebar: action('openedSidebar'),
|
||||
closeSidebar: action('closedSidebar'),
|
||||
};
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import PortalListItem from '../PortalListItem';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Portal List',
|
||||
component: PortalListItem,
|
||||
argTypes: {
|
||||
portals: { control: 'array' },
|
||||
status: {
|
||||
control: {
|
||||
type: 'string',
|
||||
options: ['archived', 'draft', 'published'],
|
||||
},
|
||||
},
|
||||
selectedLocaleCode: { control: 'string' },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { PortalListItem },
|
||||
template:
|
||||
'<portal-list-item v-bind="$props" @add="addLocale" @open-site="openSite" @open="openSettings" @swap="swapLocale" @delete="deleteLocale" ></portal-list-item>',
|
||||
});
|
||||
|
||||
export const PortalListItemView = Template.bind({});
|
||||
PortalListItemView.args = {
|
||||
addLocale: action('addLocale'),
|
||||
openSite: action('openSite'),
|
||||
openSettings: action('openSettings'),
|
||||
swapLocale: action('swapLocale'),
|
||||
deleteLocale: action('deleteLocale'),
|
||||
status: 'published',
|
||||
selectedLocaleCode: 'en-US',
|
||||
portals: [
|
||||
{
|
||||
name: 'Chatwoot Help Center',
|
||||
id: 1,
|
||||
color: 'red',
|
||||
custom_domain: 'help-center.chatwoot.com',
|
||||
articles_count: 123,
|
||||
header_text: 'Help center',
|
||||
homepage_link: null,
|
||||
page_title: 'English',
|
||||
slug: 'help-center',
|
||||
archived: false,
|
||||
config: {
|
||||
allowed_locales: [
|
||||
{
|
||||
code: 'en-US',
|
||||
name: 'English',
|
||||
articles_count: 123,
|
||||
categories_count: 42,
|
||||
},
|
||||
{
|
||||
code: 'fr-FR',
|
||||
name: 'Français',
|
||||
articles_count: 23,
|
||||
categories_count: 11,
|
||||
},
|
||||
{
|
||||
code: 'de-DE',
|
||||
name: 'Deutsch',
|
||||
articles_count: 32,
|
||||
categories_count: 12,
|
||||
},
|
||||
{
|
||||
code: 'es-ES',
|
||||
name: 'Español',
|
||||
articles_count: 12,
|
||||
categories_count: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
locales: [
|
||||
{
|
||||
code: 'en-US',
|
||||
name: 'English',
|
||||
articles_count: 123,
|
||||
categories_count: 42,
|
||||
},
|
||||
{
|
||||
code: 'fr-FR',
|
||||
name: 'Français',
|
||||
articles_count: 23,
|
||||
categories_count: 11,
|
||||
},
|
||||
{
|
||||
code: 'de-DE',
|
||||
name: 'Deutsch',
|
||||
articles_count: 32,
|
||||
categories_count: 12,
|
||||
},
|
||||
{
|
||||
code: 'es-ES',
|
||||
name: 'Español',
|
||||
articles_count: 12,
|
||||
categories_count: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Chatwoot Docs',
|
||||
id: 2,
|
||||
color: 'green',
|
||||
custom_domain: 'doc-chatwoot.com',
|
||||
articles_count: 67,
|
||||
header_text: 'Docs',
|
||||
homepage_link: null,
|
||||
page_title: 'Portal',
|
||||
slug: 'second_portal',
|
||||
archived: false,
|
||||
config: {
|
||||
allowed_locales: [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en-EN',
|
||||
articles_count: 12,
|
||||
categories_count: 66,
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
code: 'ch-CH',
|
||||
articles_count: 6,
|
||||
categories_count: 23,
|
||||
},
|
||||
],
|
||||
},
|
||||
locales: [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en-EN',
|
||||
articles_count: 12,
|
||||
categories_count: 66,
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
code: 'ch-CH',
|
||||
articles_count: 6,
|
||||
categories_count: 23,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
import PortalPopover from '../PortalPopover.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Portal Popover',
|
||||
component: PortalPopover,
|
||||
argTypes: {
|
||||
active: {
|
||||
defaultValue: false,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { PortalPopover },
|
||||
template:
|
||||
'<PortalPopover v-bind="$props" @click="onClick">{{label}}</PortalPopover>',
|
||||
});
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
|
||||
Primary.args = {
|
||||
portals: [
|
||||
{
|
||||
name: 'Chatwoot Help Center',
|
||||
id: 1,
|
||||
color: null,
|
||||
custom_domain: 'doc',
|
||||
articles_count: 123,
|
||||
header_text: null,
|
||||
homepage_link: null,
|
||||
page_title: null,
|
||||
slug: 'first_portal',
|
||||
archived: false,
|
||||
config: {
|
||||
allowed_locales: [
|
||||
{
|
||||
code: 'en',
|
||||
name: 'English',
|
||||
articles_count: 123,
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'Français',
|
||||
articles_count: 123,
|
||||
},
|
||||
{
|
||||
code: 'de',
|
||||
name: 'Deutsch',
|
||||
articles_count: 32,
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
name: 'Español',
|
||||
articles_count: 12,
|
||||
},
|
||||
{
|
||||
code: 'it',
|
||||
name: 'Italiano',
|
||||
articles_count: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
locales: [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en',
|
||||
articles_count: 12,
|
||||
},
|
||||
{
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
articles_count: 42,
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
code: 'fr',
|
||||
articles_count: 29,
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
code: 'it',
|
||||
articles_count: 4,
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
code: 'de',
|
||||
articles_count: 66,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Chatwoot Docs',
|
||||
id: 2,
|
||||
color: null,
|
||||
custom_domain: 'doc',
|
||||
articles_count: 124,
|
||||
header_text: null,
|
||||
homepage_link: null,
|
||||
page_title: null,
|
||||
slug: 'second_portal',
|
||||
archived: false,
|
||||
config: {
|
||||
allowed_locales: [
|
||||
{
|
||||
code: 'en',
|
||||
name: 'English',
|
||||
articles_count: 123,
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'Français',
|
||||
articles_count: 123,
|
||||
},
|
||||
{
|
||||
code: 'de',
|
||||
name: 'Deutsch',
|
||||
articles_count: 32,
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
name: 'Español',
|
||||
articles_count: 12,
|
||||
},
|
||||
{
|
||||
code: 'it',
|
||||
name: 'Italiano',
|
||||
articles_count: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
locales: [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en',
|
||||
articles_count: 12,
|
||||
},
|
||||
{
|
||||
name: 'Japanese',
|
||||
code: 'jp',
|
||||
articles_count: 4,
|
||||
},
|
||||
{
|
||||
name: 'Mandarin',
|
||||
code: 'CH',
|
||||
articles_count: 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import PortalSwitch from '../PortalSwitch.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Portal Switch',
|
||||
component: PortalSwitch,
|
||||
argTypes: {
|
||||
active: {
|
||||
defaultValue: false,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { PortalSwitch },
|
||||
template:
|
||||
'<PortalSwitch v-bind="$props" @click="onClick">{{label}}</PortalSwitch>',
|
||||
});
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
|
||||
Primary.args = {
|
||||
active: false,
|
||||
portal: {
|
||||
name: 'Chatwoot Help Center',
|
||||
articles_count: 124,
|
||||
locales: [
|
||||
{
|
||||
name: 'English',
|
||||
code: 'en',
|
||||
articles_count: 12,
|
||||
},
|
||||
{
|
||||
name: 'Español',
|
||||
code: 'es',
|
||||
articles_count: 42,
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
code: 'fr',
|
||||
articles_count: 29,
|
||||
},
|
||||
{
|
||||
name: 'Italian',
|
||||
code: 'it',
|
||||
articles_count: 4,
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
code: 'de',
|
||||
articles_count: 66,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import Sidebar from '../Sidebar/Sidebar';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/Sidebar',
|
||||
component: { Sidebar, Thumbnail },
|
||||
argTypes: {
|
||||
thumbnailSrc: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
headerTitle: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
subTitle: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
accessibleMenuItems: [],
|
||||
additionalSecondaryMenuItems: [],
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { Sidebar },
|
||||
template: '<sidebar v-bind="$props" @input="onSearch"></sidebar>',
|
||||
});
|
||||
|
||||
export const HelpCenterSidebarView = Template.bind({});
|
||||
HelpCenterSidebarView.args = {
|
||||
onSearch: action('search'),
|
||||
thumbnailSrc: '',
|
||||
headerTitle: 'Help Center',
|
||||
subTitle: 'English',
|
||||
accessibleMenuItems: [
|
||||
{
|
||||
icon: 'book',
|
||||
label: 'HELP_CENTER.ALL_ARTICLES',
|
||||
key: 'helpcenter_all',
|
||||
count: 199,
|
||||
toState: 'accounts/1/articles/all',
|
||||
toolTip: 'All Articles',
|
||||
toStateName: 'helpcenter_all',
|
||||
},
|
||||
{
|
||||
icon: 'pen',
|
||||
label: 'HELP_CENTER.MY_ARTICLES',
|
||||
key: 'helpcenter_mine',
|
||||
count: 112,
|
||||
toState: 'accounts/1/articles/mine',
|
||||
toolTip: 'My articles',
|
||||
toStateName: 'helpcenter_mine',
|
||||
},
|
||||
{
|
||||
icon: 'draft',
|
||||
label: 'HELP_CENTER.DRAFT',
|
||||
key: 'helpcenter_draft',
|
||||
count: 32,
|
||||
toState: 'accounts/1/articles/draft',
|
||||
toolTip: 'Draft',
|
||||
toStateName: 'helpcenter_draft',
|
||||
},
|
||||
{
|
||||
icon: 'archive',
|
||||
label: 'HELP_CENTER.ARCHIVED',
|
||||
key: 'helpcenter_archive',
|
||||
count: 10,
|
||||
toState: 'accounts/1/articles/archived',
|
||||
toolTip: 'Archived',
|
||||
toStateName: 'helpcenter_archive',
|
||||
},
|
||||
],
|
||||
additionalSecondaryMenuItems: [
|
||||
{
|
||||
icon: 'folder',
|
||||
label: 'HELP_CENTER.CATEGORY',
|
||||
hasSubMenu: true,
|
||||
key: 'category',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
label: 'Getting started',
|
||||
count: 12,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/new',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'Channel',
|
||||
count: 19,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/channel',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Feature',
|
||||
count: 24,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/feature',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: 'Advanced',
|
||||
count: 8,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/advanced',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
label: 'Mobile app',
|
||||
count: 3,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/mobile-app',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
label: 'Others',
|
||||
count: 39,
|
||||
truncateLabel: true,
|
||||
toState: 'accounts/1/articles/categories/others',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
+3
-2
@@ -148,7 +148,7 @@ export default {
|
||||
'HELP_CENTER.ARTICLE_SETTINGS.FORM.CATEGORY.SEARCH_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
@click="onClickSelectCategory"
|
||||
@select="onClickSelectCategory"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -168,7 +168,7 @@ export default {
|
||||
:input-placeholder="
|
||||
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.AUTHOR.SEARCH_PLACEHOLDER')
|
||||
"
|
||||
@click="onClickAssignAuthor"
|
||||
@select="onClickAssignAuthor"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -205,6 +205,7 @@ export default {
|
||||
:placeholder="
|
||||
$t('HELP_CENTER.ARTICLE_SETTINGS.FORM.META_TAGS.PLACEHOLDER')
|
||||
"
|
||||
class="min-w-[300px]"
|
||||
label="name"
|
||||
:options="metaOptions"
|
||||
track-by="name"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import EditArticleHeader from '../../components/Header/EditArticleHeader.vue';
|
||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||
import ArticleSettings from './ArticleSettings.vue';
|
||||
@@ -80,7 +80,7 @@ export default {
|
||||
confirmDeletion() {
|
||||
this.closeDeletePopup();
|
||||
this.deleteArticle();
|
||||
this.$track(PORTALS_EVENTS.DELETE_ARTICLE, {
|
||||
useTrack(PORTALS_EVENTS.DELETE_ARTICLE, {
|
||||
status: this.article?.status,
|
||||
});
|
||||
},
|
||||
@@ -136,7 +136,7 @@ export default {
|
||||
status: ARTICLE_STATUS_TYPES.ARCHIVE,
|
||||
});
|
||||
this.alertMessage = this.$t('HELP_CENTER.ARCHIVE_ARTICLE.API.SUCCESS');
|
||||
this.$track(PORTALS_EVENTS.ARCHIVE_ARTICLE, { uiFrom: 'sidebar' });
|
||||
useTrack(PORTALS_EVENTS.ARCHIVE_ARTICLE, { uiFrom: 'sidebar' });
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message || this.$t('HELP_CENTER.ARCHIVE_ARTICLE.API.ERROR');
|
||||
@@ -159,7 +159,7 @@ export default {
|
||||
},
|
||||
showArticleInPortal() {
|
||||
window.open(this.portalLink, '_blank');
|
||||
this.$track(PORTALS_EVENTS.PREVIEW_ARTICLE, {
|
||||
useTrack(PORTALS_EVENTS.PREVIEW_ARTICLE, {
|
||||
status: this.article?.status,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue';
|
||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||
import portalMixin from '../../mixins/portalMixin';
|
||||
@@ -67,7 +67,7 @@ export default {
|
||||
recentlyCreated: true,
|
||||
},
|
||||
});
|
||||
this.$track(PORTALS_EVENTS.CREATE_ARTICLE, {
|
||||
useTrack(PORTALS_EVENTS.CREATE_ARTICLE, {
|
||||
locale: this.locale,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
+7
-7
@@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
import CategoryNameIconInput from './NameEmojiInput.vue';
|
||||
import NameEmojiInput from './NameEmojiInput.vue';
|
||||
|
||||
export default {
|
||||
components: { CategoryNameIconInput },
|
||||
components: { NameEmojiInput },
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
@@ -96,7 +96,7 @@ export default {
|
||||
'HELP_CENTER.CATEGORY.ADD.API.SUCCESS_MESSAGE'
|
||||
);
|
||||
this.onClose();
|
||||
this.$track(PORTALS_EVENTS.CREATE_CATEGORY, {
|
||||
useTrack(PORTALS_EVENTS.CREATE_CATEGORY, {
|
||||
hasDescription: Boolean(description),
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<CategoryNameIconInput
|
||||
<NameEmojiInput
|
||||
:label="$t('HELP_CENTER.CATEGORY.ADD.NAME.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.CATEGORY.ADD.NAME.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.CATEGORY.ADD.NAME.HELP_TEXT')"
|
||||
@@ -144,7 +144,7 @@ export default {
|
||||
@iconChange="onClickInsertEmoji"
|
||||
/>
|
||||
<woot-input
|
||||
v-model.trim="slug"
|
||||
v-model="slug"
|
||||
:class="{ error: v$.slug.$error }"
|
||||
class="w-full"
|
||||
:error="slugError"
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ export default {
|
||||
<td>
|
||||
<span>{{ category.meta.articles_count }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<td class="inline-flex gap-1">
|
||||
<woot-button
|
||||
v-tooltip.top-end="
|
||||
$t(
|
||||
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
import CategoryNameIconInput from './NameEmojiInput.vue';
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
this.alertMessage = this.$t(
|
||||
'HELP_CENTER.CATEGORY.EDIT.API.SUCCESS_MESSAGE'
|
||||
);
|
||||
this.$track(PORTALS_EVENTS.EDIT_CATEGORY);
|
||||
useTrack(PORTALS_EVENTS.EDIT_CATEGORY);
|
||||
this.onClose();
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message;
|
||||
@@ -155,7 +155,7 @@ export default {
|
||||
@iconChange="onClickInsertEmoji"
|
||||
/>
|
||||
<woot-input
|
||||
v-model.trim="slug"
|
||||
v-model="slug"
|
||||
:class="{ error: v$.slug.$error }"
|
||||
class="w-full"
|
||||
:error="slugError"
|
||||
@@ -194,6 +194,7 @@ export default {
|
||||
.article-info {
|
||||
width: 100%;
|
||||
margin: 0 0 var(--space-normal);
|
||||
|
||||
.value {
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
+5
-6
@@ -1,17 +1,17 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { defineComponent, ref, computed } from 'vue';
|
||||
import { defineOptions, ref, computed } from 'vue';
|
||||
|
||||
import CategoryListItem from './CategoryListItem.vue';
|
||||
import AddCategory from './AddCategory.vue';
|
||||
import EditCategory from './EditCategory.vue';
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'ListAllCategories',
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ const showAddCategoryModal = ref(false);
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
const currentPortalSlug = computed(() => {
|
||||
return route.params.portalSlug;
|
||||
@@ -80,7 +79,7 @@ async function deleteCategory(category) {
|
||||
categoryId: category.id,
|
||||
});
|
||||
alertMessage = t('HELP_CENTER.CATEGORY.DELETE.API.SUCCESS_MESSAGE');
|
||||
track(PORTALS_EVENTS.DELETE_CATEGORY, {
|
||||
useTrack(PORTALS_EVENTS.DELETE_CATEGORY, {
|
||||
hasArticles: category?.meta?.articles_count !== 0,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
+6
-2
@@ -1,5 +1,9 @@
|
||||
<script>
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
const EmojiInput = defineAsyncComponent(
|
||||
() => import('shared/components/emoji/EmojiInput.vue')
|
||||
);
|
||||
|
||||
export default {
|
||||
components: { EmojiInput },
|
||||
@@ -96,7 +100,7 @@ export default {
|
||||
<woot-input
|
||||
v-model="name"
|
||||
:class="{ error: hasError }"
|
||||
class="!mt-0 !mb-4 !mx-0 [&>input]:!mb-0 ltr:[&>input]:ml-12 rtl:[&>input]:mr-12 relative w-[calc(100%-3rem)] [&>p]:w-max"
|
||||
class="!mt-0 !mb-4 !mx-0 [&>input]:!mb-0 ltr:[&>input]:!ml-12 rtl:[&>input]:!mr-12 relative w-[calc(100%-3rem)] [&>p]:w-max"
|
||||
:error="nameErrorMessage"
|
||||
:label="label"
|
||||
:placeholder="placeholder"
|
||||
|
||||
@@ -82,8 +82,9 @@ export default {
|
||||
@change="onTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.key"
|
||||
:index="index"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
|
||||
+4
-4
@@ -3,11 +3,11 @@ import PortalSettingsBasicForm from 'dashboard/routes/dashboard/helpcenter/compo
|
||||
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, computed, ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineOptions, computed, ref, onMounted } from 'vue';
|
||||
|
||||
defineComponent({ name: 'EditPortalBasic' });
|
||||
defineOptions({ name: 'EditPortalBasic' });
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const route = useRoute();
|
||||
|
||||
+4
-4
@@ -3,11 +3,11 @@ import PortalSettingsCustomizationForm from 'dashboard/routes/dashboard/helpcent
|
||||
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineOptions, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'EditPortalCustomization',
|
||||
});
|
||||
|
||||
|
||||
+6
-7
@@ -5,11 +5,11 @@ import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, ref, onBeforeMount, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineOptions, ref, onBeforeMount, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'EditPortalLocales',
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ const isAddLocaleModalOpen = ref(false);
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentPortalSlug = computed(() => {
|
||||
@@ -77,7 +76,7 @@ function changeDefaultLocale({ localeCode }) {
|
||||
messageKey: 'CHANGE_DEFAULT_LOCALE',
|
||||
});
|
||||
|
||||
track(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
useTrack(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
newLocale: localeCode,
|
||||
from: route.name,
|
||||
});
|
||||
@@ -95,7 +94,7 @@ function deletePortalLocale({ localeCode }) {
|
||||
messageKey: 'DELETE_LOCALE',
|
||||
});
|
||||
|
||||
track(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
useTrack(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
deletedLocale: localeCode,
|
||||
from: route.name,
|
||||
});
|
||||
|
||||
@@ -12,17 +12,22 @@ export default {
|
||||
globalConfig: 'globalConfig/get',
|
||||
}),
|
||||
items() {
|
||||
const allItems = this.$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW').map(
|
||||
item => ({
|
||||
...item,
|
||||
body: this.useInstallationName(
|
||||
item.body,
|
||||
this.globalConfig.installationName
|
||||
),
|
||||
})
|
||||
);
|
||||
const routes = {
|
||||
BASIC: 'new_portal_information',
|
||||
CUSTOMIZATION: 'portal_customization',
|
||||
FINISH: 'portal_finish',
|
||||
};
|
||||
|
||||
return allItems;
|
||||
const steps = ['BASIC', 'CUSTOMIZATION', 'FINISH'];
|
||||
|
||||
return steps.map(step => ({
|
||||
title: this.$t(`HELP_CENTER.PORTAL.ADD.CREATE_FLOW.${step}.TITLE`),
|
||||
route: routes[step],
|
||||
body: this.useInstallationName(
|
||||
this.$t(`HELP_CENTER.PORTAL.ADD.CREATE_FLOW.${step}.BODY`),
|
||||
this.globalConfig.installationName
|
||||
),
|
||||
}));
|
||||
},
|
||||
portalHeaderText() {
|
||||
if (this.$route.name === 'new_portal_information') {
|
||||
|
||||
+5
-6
@@ -4,11 +4,11 @@ import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, onMounted, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineOptions, onMounted, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
defineOptions({
|
||||
name: 'PortalCustomization',
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@ const getters = useStoreGetters();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
|
||||
const uiFlags = getters['portals/uiFlagsIn'];
|
||||
@@ -42,7 +41,7 @@ async function updatePortalSettings(portalObj) {
|
||||
});
|
||||
alertMessage = t('HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE');
|
||||
|
||||
track(PORTALS_EVENTS.ONBOARD_CUSTOMIZATION, {
|
||||
useTrack(PORTALS_EVENTS.ONBOARD_CUSTOMIZATION, {
|
||||
hasHomePageLink: Boolean(portalObj.homepage_link),
|
||||
hasPageTitle: Boolean(portalObj.page_title),
|
||||
hasHeaderText: Boolean(portalObj.headerText),
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
|
||||
import PortalSettingsBasicForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsBasicForm.vue';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
@@ -43,8 +43,8 @@ export default {
|
||||
const analyticsPayload = {
|
||||
has_custom_domain: portal.domain !== '',
|
||||
};
|
||||
this.$track(PORTALS_EVENTS.ONBOARD_BASIC_INFORMATION, analyticsPayload);
|
||||
this.$track(PORTALS_EVENTS.CREATE_PORTAL, analyticsPayload);
|
||||
useTrack(PORTALS_EVENTS.ONBOARD_BASIC_INFORMATION, analyticsPayload);
|
||||
useTrack(PORTALS_EVENTS.CREATE_PORTAL, analyticsPayload);
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import { defineComponent } from 'vue';
|
||||
defineComponent({
|
||||
import { defineOptions } from 'vue';
|
||||
defineOptions({
|
||||
name: 'PortalSettingsFinish',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
@@ -98,7 +98,7 @@ export default {
|
||||
this.page += 1;
|
||||
},
|
||||
markNotificationAsRead(notification) {
|
||||
this.$track(INBOX_EVENTS.MARK_NOTIFICATION_AS_READ);
|
||||
useTrack(INBOX_EVENTS.MARK_NOTIFICATION_AS_READ);
|
||||
const {
|
||||
id,
|
||||
primary_actor_id: primaryActorId,
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
});
|
||||
},
|
||||
markNotificationAsUnRead(notification) {
|
||||
this.$track(INBOX_EVENTS.MARK_NOTIFICATION_AS_UNREAD);
|
||||
useTrack(INBOX_EVENTS.MARK_NOTIFICATION_AS_UNREAD);
|
||||
this.redirectToInbox();
|
||||
const { id } = notification;
|
||||
this.$store
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
});
|
||||
},
|
||||
deleteNotification(notification) {
|
||||
this.$track(INBOX_EVENTS.DELETE_NOTIFICATION);
|
||||
useTrack(INBOX_EVENTS.DELETE_NOTIFICATION);
|
||||
this.redirectToInbox();
|
||||
this.$store
|
||||
.dispatch('notifications/delete', {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import InboxItemHeader from './components/InboxItemHeader.vue';
|
||||
import ConversationBox from 'dashboard/components/widgets/conversation/ConversationBox.vue';
|
||||
import InboxEmptyState from './InboxEmptyState.vue';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -111,7 +113,7 @@ export default {
|
||||
this.$store
|
||||
.dispatch('setActiveChat', { data: selectedConversation })
|
||||
.then(() => {
|
||||
this.$emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
});
|
||||
},
|
||||
findConversation() {
|
||||
@@ -141,7 +143,7 @@ export default {
|
||||
notification_type: notificationType,
|
||||
} = notification;
|
||||
|
||||
this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
useTrack(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
notificationType,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { dynamicTime, shortTimestamp } from 'shared/helpers/timeHelper';
|
||||
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PriorityIcon,
|
||||
@@ -106,7 +108,7 @@ export default {
|
||||
} = notification;
|
||||
|
||||
if (this.$route.params.notification_id !== id) {
|
||||
this.$track(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
useTrack(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
notificationType,
|
||||
});
|
||||
|
||||
@@ -210,7 +212,7 @@ export default {
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:menu-items="menuItems"
|
||||
@close="closeContextMenu"
|
||||
@click="handleAction"
|
||||
@select-action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script>
|
||||
import ContextMenu from 'dashboard/components/ui/ContextMenu.vue';
|
||||
import MenuItem from './MenuItem.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuItem,
|
||||
ContextMenu,
|
||||
},
|
||||
props: {
|
||||
contextMenuPosition: {
|
||||
@@ -19,7 +22,7 @@ export default {
|
||||
this.$emit('close');
|
||||
},
|
||||
onMenuItemClick(key) {
|
||||
this.$emit('click', key);
|
||||
this.$emit('selectAction', key);
|
||||
this.handleClose();
|
||||
},
|
||||
},
|
||||
@@ -27,7 +30,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-context-menu
|
||||
<ContextMenu
|
||||
:x="contextMenuPosition.x"
|
||||
:y="contextMenuPosition.y"
|
||||
@close="handleClose"
|
||||
@@ -39,8 +42,8 @@ export default {
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
:label="item.label"
|
||||
@click="onMenuItemClick(item.key)"
|
||||
@click.stop="onMenuItemClick(item.key)"
|
||||
/>
|
||||
</div>
|
||||
</woot-context-menu>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/events';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
@@ -8,6 +8,7 @@ import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import PaginationButton from './PaginationButton.vue';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -35,10 +36,10 @@ export default {
|
||||
...mapGetters({ meta: 'notifications/getMeta' }),
|
||||
},
|
||||
mounted() {
|
||||
this.$emitter.on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
|
||||
emitter.on(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
|
||||
},
|
||||
destroyed() {
|
||||
this.$emitter.off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
|
||||
emitter.off(CMD_SNOOZE_NOTIFICATION, this.onCmdSnoozeNotification);
|
||||
},
|
||||
methods: {
|
||||
openSnoozeNotificationModal() {
|
||||
@@ -76,7 +77,7 @@ export default {
|
||||
}
|
||||
},
|
||||
deleteNotification() {
|
||||
this.$track(INBOX_EVENTS.DELETE_NOTIFICATION);
|
||||
useTrack(INBOX_EVENTS.DELETE_NOTIFICATION);
|
||||
this.$store
|
||||
.dispatch('notifications/delete', {
|
||||
notification: this.activeNotification,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import InboxOptionMenu from './InboxOptionMenu.vue';
|
||||
import InboxDisplayMenu from './InboxDisplayMenu.vue';
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
markAllRead() {
|
||||
this.$track(INBOX_EVENTS.MARK_ALL_NOTIFICATIONS_AS_READ);
|
||||
useTrack(INBOX_EVENTS.MARK_ALL_NOTIFICATIONS_AS_READ);
|
||||
this.$store.dispatch('notifications/readAll').then(() => {
|
||||
useAlert(this.$t('INBOX.ALERTS.MARK_ALL_READ'));
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
:label="item.label"
|
||||
@click="onClick(item.key)"
|
||||
@click.stop="onClick(item.key)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,19 +5,12 @@ defineProps({
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['click']);
|
||||
|
||||
const onMenuItemClick = () => {
|
||||
emit('click');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="button"
|
||||
class="flex items-center w-full h-8 px-2 py-1 overflow-hidden text-xs font-medium rounded-md cursor-pointer text-slate-800 dark:text-slate-100 whitespace-nowrap text-ellipsis hover:text-woot-600 dark:hover:text-woot-500"
|
||||
@click.stop="onMenuItemClick"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import NotificationPanelList from './NotificationPanelList.vue';
|
||||
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { ACCOUNT_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
export default {
|
||||
@@ -60,7 +60,7 @@ export default {
|
||||
notification_type: notificationType,
|
||||
} = notification;
|
||||
|
||||
this.$track(ACCOUNT_EVENTS.OPEN_CONVERSATION_VIA_NOTIFICATION, {
|
||||
useTrack(ACCOUNT_EVENTS.OPEN_CONVERSATION_VIA_NOTIFICATION, {
|
||||
notificationType,
|
||||
});
|
||||
this.$store.dispatch('notifications/read', {
|
||||
@@ -100,7 +100,7 @@ export default {
|
||||
}
|
||||
},
|
||||
onMarkAllDoneClick() {
|
||||
this.$track(ACCOUNT_EVENTS.MARK_AS_READ_NOTIFICATIONS);
|
||||
useTrack(ACCOUNT_EVENTS.MARK_AS_READ_NOTIFICATIONS);
|
||||
this.$store.dispatch('notifications/readAll');
|
||||
},
|
||||
openAudioNotificationSettings() {
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/mixins';
|
||||
@import 'dashboard/assets/scss/mixins';
|
||||
|
||||
.notification--title {
|
||||
@apply text-sm m-0 text-slate-800 dark:text-slate-100;
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import TableFooter from 'dashboard/components/widgets/TableFooter.vue';
|
||||
|
||||
import NotificationTable from './NotificationTable.vue';
|
||||
@@ -34,7 +35,7 @@ export default {
|
||||
notification_type: notificationType,
|
||||
} = notification;
|
||||
|
||||
this.$track(ACCOUNT_EVENTS.OPEN_CONVERSATION_VIA_NOTIFICATION, {
|
||||
useTrack(ACCOUNT_EVENTS.OPEN_CONVERSATION_VIA_NOTIFICATION, {
|
||||
notificationType,
|
||||
});
|
||||
this.$store.dispatch('notifications/read', {
|
||||
@@ -49,7 +50,7 @@ export default {
|
||||
);
|
||||
},
|
||||
onMarkAllDoneClick() {
|
||||
this.$track(ACCOUNT_EVENTS.MARK_AS_READ_NOTIFICATIONS);
|
||||
useTrack(ACCOUNT_EVENTS.MARK_AS_READ_NOTIFICATIONS);
|
||||
this.$store.dispatch('notifications/readAll');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full gap-10 font-inter">
|
||||
<div class="flex flex-col w-full h-full gap-8 font-inter">
|
||||
<slot name="header" />
|
||||
<!-- Added to render any templates that should be rendered before body -->
|
||||
<div>
|
||||
|
||||
@@ -12,10 +12,12 @@ defineProps({
|
||||
class="flex flex-col w-full h-full m-0 p-6 sm:py-8 lg:px-16 overflow-auto bg-white dark:bg-slate-900 font-inter"
|
||||
>
|
||||
<div class="flex items-start w-full max-w-6xl mx-auto">
|
||||
<keep-alive v-if="keepAlive">
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
<router-view v-else />
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive v-if="keepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else />
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,48 +1,24 @@
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SettingsHeader from './SettingsHeader.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SettingsHeader,
|
||||
},
|
||||
props: {
|
||||
headerTitle: { type: String, default: '' },
|
||||
headerButtonText: { type: String, default: '' },
|
||||
icon: { type: String, default: '' },
|
||||
keepAlive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
newButtonRoutes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showBackButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
backUrl: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
},
|
||||
showSidemenuIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
currentPage() {
|
||||
return this.$store.state.route.name;
|
||||
},
|
||||
showNewButton() {
|
||||
return this.newButtonRoutes.length !== 0 && !this.showBackButton;
|
||||
},
|
||||
},
|
||||
};
|
||||
const props = defineProps({
|
||||
headerTitle: { type: String, default: '' },
|
||||
headerButtonText: { type: String, default: '' },
|
||||
icon: { type: String, default: '' },
|
||||
keepAlive: { type: Boolean, default: true },
|
||||
newButtonRoutes: { type: Array, default: () => [] },
|
||||
showBackButton: { type: Boolean, default: false },
|
||||
backUrl: { type: [String, Object], default: '' },
|
||||
showSidemenuIcon: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const showNewButton = computed(
|
||||
() => props.newButtonRoutes.length !== 0 && !props.showBackButton
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -52,16 +28,18 @@ export default {
|
||||
<SettingsHeader
|
||||
button-route="new"
|
||||
:icon="icon"
|
||||
:header-title="$t(headerTitle)"
|
||||
:button-text="$t(headerButtonText)"
|
||||
:header-title="t(headerTitle)"
|
||||
:button-text="t(headerButtonText)"
|
||||
:show-back-button="showBackButton"
|
||||
:back-url="backUrl"
|
||||
:show-new-button="showNewButton"
|
||||
:show-sidemenu-icon="showSidemenuIcon"
|
||||
/>
|
||||
<keep-alive v-if="keepAlive">
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
<router-view v-else />
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive v-if="keepAlive">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else />
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export default {
|
||||
(<AgentBotType :bot-type="agentBot.bot_type" />)
|
||||
</div>
|
||||
<div class="agent-bot--description">
|
||||
<ShowMore :text="agentBot.description" :limit="120" />
|
||||
<ShowMore :text="agentBot.description || ''" :limit="120" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="flex justify-end gap-1">
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-auto overflow-auto flex flex-col">
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<div class="flex flex-row">
|
||||
<div class="w-[68%]">
|
||||
<div class="h-[calc(100vh-56px)] relative">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, email } from '@vuelidate/validators';
|
||||
@@ -113,7 +113,7 @@ const addAgent = async () => {
|
||||
<label :class="{ error: v$.agentName.$error }">
|
||||
{{ $t('AGENT_MGMT.ADD.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="agentName"
|
||||
v-model="agentName"
|
||||
type="text"
|
||||
:placeholder="$t('AGENT_MGMT.ADD.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.agentName.$touch"
|
||||
@@ -139,7 +139,7 @@ const addAgent = async () => {
|
||||
<label :class="{ error: v$.agentEmail.$error }">
|
||||
{{ $t('AGENT_MGMT.ADD.FORM.EMAIL.LABEL') }}
|
||||
<input
|
||||
v-model.trim="agentEmail"
|
||||
v-model="agentEmail"
|
||||
type="email"
|
||||
:placeholder="$t('AGENT_MGMT.ADD.FORM.EMAIL.PLACEHOLDER')"
|
||||
@input="v$.agentEmail.$touch"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import WootSubmitButton from 'dashboard/components/buttons/FormSubmitButton.vue';
|
||||
import Auth from '../../../../api/auth';
|
||||
@@ -97,14 +97,20 @@ const selectedRole = computed(() =>
|
||||
)
|
||||
);
|
||||
|
||||
const statusList = computed(() => {
|
||||
return [
|
||||
t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.ONLINE'),
|
||||
t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.BUSY'),
|
||||
t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUS.OFFLINE'),
|
||||
];
|
||||
});
|
||||
|
||||
const availabilityStatuses = computed(() =>
|
||||
t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUSES_LIST').map(
|
||||
(statusLabel, index) => ({
|
||||
label: statusLabel,
|
||||
value: AVAILABILITY_STATUS_KEYS[index],
|
||||
disabled: props.availability === AVAILABILITY_STATUS_KEYS[index],
|
||||
})
|
||||
)
|
||||
statusList.value.map((statusLabel, index) => ({
|
||||
label: statusLabel,
|
||||
value: AVAILABILITY_STATUS_KEYS[index],
|
||||
disabled: props.availability === AVAILABILITY_STATUS_KEYS[index],
|
||||
}))
|
||||
);
|
||||
|
||||
const editAgent = async () => {
|
||||
@@ -151,7 +157,7 @@ const resetPassword = async () => {
|
||||
<label :class="{ error: v$.agentName.$error }">
|
||||
{{ $t('AGENT_MGMT.EDIT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="agentName"
|
||||
v-model="agentName"
|
||||
type="text"
|
||||
:placeholder="$t('AGENT_MGMT.EDIT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.agentName.$touch"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {
|
||||
useStoreGetters,
|
||||
useStore,
|
||||
|
||||
@@ -7,15 +7,12 @@ export default {
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/agents'),
|
||||
component: SettingsWrapper,
|
||||
props: {
|
||||
headerTitle: 'AGENT_MGMT.HEADER',
|
||||
icon: 'people',
|
||||
showNewButton: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: 'list',
|
||||
redirect: to => {
|
||||
return { name: 'agent_list', params: to.params };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAlert } from 'dashboard/composables';
|
||||
import EditAttribute from './EditAttribute.vue';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const props = defineProps({
|
||||
attributeModel: {
|
||||
type: String,
|
||||
@@ -75,6 +75,15 @@ const openDelete = value => {
|
||||
showDeletePopup.value = true;
|
||||
selectedAttribute.value = value;
|
||||
};
|
||||
|
||||
const tableHeaders = computed(() => {
|
||||
return [
|
||||
t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER.NAME'),
|
||||
t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER.DESCRIPTION'),
|
||||
t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER.TYPE'),
|
||||
t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER.KEY'),
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -82,7 +91,7 @@ const openDelete = value => {
|
||||
<table class="min-w-full overflow-x-auto">
|
||||
<thead>
|
||||
<th
|
||||
v-for="tableHeader in $t('ATTRIBUTES_MGMT.LIST.TABLE_HEADER')"
|
||||
v-for="tableHeader in tableHeaders"
|
||||
:key="tableHeader"
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 text-left font-semibold text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
@@ -112,7 +121,7 @@ const openDelete = value => {
|
||||
{{ attribute.attribute_key }}
|
||||
</td>
|
||||
<td class="py-4 min-w-xs">
|
||||
<div class="flex gap-1">
|
||||
<div class="flex gap-1 justify-end">
|
||||
<woot-button
|
||||
v-tooltip.top="$t('ATTRIBUTES_MGMT.LIST.BUTTONS.EDIT')"
|
||||
variant="smooth"
|
||||
|
||||
@@ -169,7 +169,7 @@ export default {
|
||||
<form class="flex flex-col w-full" @submit.prevent="editAttributes">
|
||||
<div class="w-full">
|
||||
<woot-input
|
||||
v-model.trim="displayName"
|
||||
v-model="displayName"
|
||||
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.LABEL')"
|
||||
type="text"
|
||||
:class="{ error: v$.displayName.$error }"
|
||||
@@ -182,7 +182,7 @@ export default {
|
||||
@blur="v$.displayName.$touch"
|
||||
/>
|
||||
<woot-input
|
||||
v-model.trim="attributeKey"
|
||||
v-model="attributeKey"
|
||||
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL')"
|
||||
type="text"
|
||||
:class="{ error: v$.attributeKey.$error }"
|
||||
@@ -194,7 +194,7 @@ export default {
|
||||
<label :class="{ error: v$.description.$error }">
|
||||
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.LABEL') }}
|
||||
<textarea
|
||||
v-model.trim="description"
|
||||
v-model="description"
|
||||
rows="5"
|
||||
type="text"
|
||||
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.DESC.PLACEHOLDER')"
|
||||
@@ -277,13 +277,16 @@ export default {
|
||||
padding: 0 var(--space-small) var(--space-small) 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.multiselect--wrap {
|
||||
margin-bottom: var(--space-normal);
|
||||
|
||||
.error-message {
|
||||
color: var(--r-400);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.invalid {
|
||||
::v-deep {
|
||||
.multiselect__tags {
|
||||
@@ -292,13 +295,16 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.multiselect {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__tags {
|
||||
border-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||
import AddAttribute from './AddAttribute.vue';
|
||||
import CustomAttribute from './CustomAttribute.vue';
|
||||
import SettingsLayout from '../SettingsLayout.vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -85,8 +85,9 @@ const onClickTabChange = index => {
|
||||
@change="onClickTabChange"
|
||||
>
|
||||
<woot-tabs-item
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.key"
|
||||
:index="index"
|
||||
:name="tab.name"
|
||||
:show-badge="false"
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,9 @@ export default {
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: 'list',
|
||||
redirect: to => {
|
||||
return { name: 'attributes_list', params: to.params };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
generateLogActionKey,
|
||||
} from 'dashboard/helper/auditlogHelper';
|
||||
import { computed, onMounted, watch } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
@@ -58,6 +58,14 @@ watch(routerPage, (newPage, oldPage) => {
|
||||
fetchAuditLogs(newPage);
|
||||
}
|
||||
});
|
||||
|
||||
const tableHeaders = computed(() => {
|
||||
return [
|
||||
t('AUDIT_LOGS.LIST.TABLE_HEADER.ACTIVITY'),
|
||||
t('AUDIT_LOGS.LIST.TABLE_HEADER.TIME'),
|
||||
t('AUDIT_LOGS.LIST.TABLE_HEADER.IP_ADDRESS'),
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,7 +92,7 @@ watch(routerPage, (newPage, oldPage) => {
|
||||
<table class="divide-y divide-slate-75 dark:divide-slate-700">
|
||||
<thead>
|
||||
<th
|
||||
v-for="thHeader in $t('AUDIT_LOGS.LIST.TABLE_HEADER')"
|
||||
v-for="thHeader in tableHeaders"
|
||||
:key="thHeader"
|
||||
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
|
||||
@@ -11,7 +11,9 @@ export default {
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: 'list',
|
||||
redirect: to => {
|
||||
return { name: 'auditlogs_list', params: to.params };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
|
||||
@@ -33,7 +33,7 @@ const toggle = () => {
|
||||
<td class="py-4 ltr:pr-4 rtl:pl-4 min-w-[200px]">{{ automation.name }}</td>
|
||||
<td class="py-4 ltr:pr-4 rtl:pl-4">{{ automation.description }}</td>
|
||||
<td class="py-4 ltr:pr-4 rtl:pl-4">
|
||||
<woot-switch :value="automation.active" @input="toggle" />
|
||||
<woot-switch :model-value="automation.active" @input="toggle" />
|
||||
</td>
|
||||
<td
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 min-w-[12px]"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user