Merge branch 'develop' into feature/enhanced-template-support
This commit is contained in:
@@ -28,6 +28,10 @@ class Inboxes extends CacheEnabledApiClient {
|
||||
agent_bot: botId,
|
||||
});
|
||||
}
|
||||
|
||||
syncTemplates(inboxId) {
|
||||
return axios.post(`${this.url}/${inboxId}/sync_templates`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Inboxes();
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('#InboxesAPI', () => {
|
||||
expect(inboxesAPI).toHaveProperty('getCampaigns');
|
||||
expect(inboxesAPI).toHaveProperty('getAgentBot');
|
||||
expect(inboxesAPI).toHaveProperty('setAgentBot');
|
||||
expect(inboxesAPI).toHaveProperty('syncTemplates');
|
||||
});
|
||||
|
||||
describe('API calls', () => {
|
||||
@@ -40,5 +41,12 @@ describe('#InboxesAPI', () => {
|
||||
inboxesAPI.deleteInboxAvatar(2);
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/inboxes/2/avatar');
|
||||
});
|
||||
|
||||
it('#syncTemplates', () => {
|
||||
inboxesAPI.syncTemplates(2);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/inboxes/2/sync_templates'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import DropdownBody from './base/DropdownBody.vue';
|
||||
import DropdownSection from './base/DropdownSection.vue';
|
||||
import DropdownItem from './base/DropdownItem.vue';
|
||||
import DropdownSeparator from './base/DropdownSeparator.vue';
|
||||
import WootSwitch from 'components/ui/Switch.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
const currentUserAutoOffline = ref(false);
|
||||
|
||||
@@ -61,7 +61,7 @@ const menuItems = ref([
|
||||
<DropdownItem label="Contact Support" class="justify-between">
|
||||
<span>{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}</span>
|
||||
<div class="flex-shrink-0">
|
||||
<WootSwitch v-model="currentUserAutoOffline" />
|
||||
<ToggleSwitch v-model="currentUserAutoOffline" />
|
||||
</div>
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'next/dropdown-menu/base';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import Button from 'next/button/Button.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
@@ -48,6 +49,16 @@ const activeStatus = computed(() => {
|
||||
return availabilityStatuses.value.find(status => status.active);
|
||||
});
|
||||
|
||||
const autoOfflineToggle = computed({
|
||||
get: () => currentUserAutoOffline.value,
|
||||
set: autoOffline => {
|
||||
store.dispatch('updateAutoOffline', {
|
||||
accountId: currentAccountId.value,
|
||||
autoOffline,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function changeAvailabilityStatus(availability) {
|
||||
if (isImpersonating.value) {
|
||||
useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.IMPERSONATING_ERROR'));
|
||||
@@ -62,13 +73,6 @@ function changeAvailabilityStatus(availability) {
|
||||
useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.SET_AVAILABILITY_ERROR'));
|
||||
}
|
||||
}
|
||||
|
||||
function updateAutoOffline(autoOffline) {
|
||||
store.dispatch('updateAutoOffline', {
|
||||
accountId: currentAccountId.value,
|
||||
autoOffline,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -118,11 +122,7 @@ function updateAutoOffline(autoOffline) {
|
||||
class="size-4 text-n-slate-10"
|
||||
/>
|
||||
</div>
|
||||
<woot-switch
|
||||
class="flex-shrink-0"
|
||||
:model-value="currentUserAutoOffline"
|
||||
@input="updateAutoOffline"
|
||||
/>
|
||||
<ToggleSwitch v-model="autoOfflineToggle" />
|
||||
</DropdownItem>
|
||||
</div>
|
||||
</DropdownSection>
|
||||
|
||||
@@ -19,8 +19,8 @@ const updateValue = () => {
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="relative h-4 transition-colors duration-200 ease-in-out rounded-full w-7 focus:outline-none focus:ring-1 focus:ring-n-brand focus:ring-offset-n-slate-2 focus:ring-offset-2"
|
||||
:class="modelValue ? 'bg-n-brand' : 'bg-n-alpha-1 dark:bg-n-alpha-2'"
|
||||
class="relative h-4 transition-colors duration-200 ease-in-out rounded-full w-7 focus:outline-none focus:ring-1 focus:ring-n-brand focus:ring-offset-n-slate-2 focus:ring-offset-2 flex-shrink-0"
|
||||
:class="modelValue ? 'bg-n-brand' : 'bg-n-slate-6 disabled:bg-n-slate-6/60'"
|
||||
role="switch"
|
||||
:aria-checked="modelValue"
|
||||
@click="updateValue"
|
||||
|
||||
@@ -61,7 +61,9 @@ const onCopy = async e => {
|
||||
|
||||
<template>
|
||||
<div class="relative text-left">
|
||||
<div class="top-1.5 absolute right-1.5 flex items-center gap-1">
|
||||
<div
|
||||
class="top-1.5 absolute ltr:right-1.5 rtl:left-1.5 flex backdrop-blur-sm rounded-lg items-center gap-1"
|
||||
>
|
||||
<form
|
||||
v-if="enableCodePen"
|
||||
class="flex items-center"
|
||||
@@ -86,6 +88,11 @@ const onCopy = async e => {
|
||||
@click="onCopy"
|
||||
/>
|
||||
</div>
|
||||
<highlightjs v-if="script" :language="lang" :code="scrubbedScript" />
|
||||
<highlightjs
|
||||
v-if="script"
|
||||
:language="lang"
|
||||
:code="scrubbedScript"
|
||||
class="[&_code]:text-start"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Boolean, default: false },
|
||||
size: { type: String, default: '' },
|
||||
},
|
||||
emits: ['update:modelValue', 'input'],
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('update:modelValue', !this.modelValue);
|
||||
this.$emit('input', !this.modelValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button p-0"
|
||||
:class="{ active: modelValue, small: size === 'small' }"
|
||||
role="switch"
|
||||
:aria-checked="modelValue.toString()"
|
||||
@click="onClick"
|
||||
>
|
||||
<span aria-hidden="true" :class="{ active: modelValue }" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toggle-button {
|
||||
@apply bg-n-slate-5;
|
||||
--toggle-button-box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px,
|
||||
rgba(59, 130, 246, 0.5) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px,
|
||||
rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
|
||||
border-radius: 0.5625rem;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: 1.188rem;
|
||||
position: relative;
|
||||
transition-duration: 200ms;
|
||||
transition-property: background-color;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
width: 2.125rem;
|
||||
|
||||
&.active {
|
||||
@apply bg-n-brand;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 1.375rem;
|
||||
height: 0.875rem;
|
||||
|
||||
span {
|
||||
@apply size-2.5;
|
||||
|
||||
&.active {
|
||||
@apply ltr:translate-x-[0.5rem] ltr:translate-y-0 rtl:translate-x-[-0.5rem] rtl:translate-y-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
@apply bg-n-background;
|
||||
|
||||
border-radius: 100%;
|
||||
box-shadow: var(--toggle-button-box-shadow);
|
||||
display: inline-block;
|
||||
height: 0.9375rem;
|
||||
transform: translate(0, 0);
|
||||
transition-duration: 200ms;
|
||||
transition-property: transform;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
width: 0.9375rem;
|
||||
|
||||
&.active {
|
||||
@apply ltr:translate-x-[0.9375rem] ltr:translate-y-0 rtl:translate-x-[-0.9375rem] rtl:translate-y-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasWhatsappTemplates: {
|
||||
enableWhatsAppTemplates: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
@@ -333,7 +333,7 @@ export default {
|
||||
@click="toggleMessageSignature"
|
||||
/>
|
||||
<NextButton
|
||||
v-if="hasWhatsappTemplates"
|
||||
v-if="enableWhatsAppTemplates"
|
||||
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
|
||||
icon="i-ph-whatsapp-logo"
|
||||
slate
|
||||
|
||||
@@ -184,9 +184,8 @@ export default {
|
||||
|
||||
return false;
|
||||
},
|
||||
hasWhatsappTemplates() {
|
||||
return !!this.$store.getters['inboxes/getWhatsAppTemplates'](this.inboxId)
|
||||
.length;
|
||||
showWhatsappTemplates() {
|
||||
return this.isAWhatsAppCloudChannel && !this.isPrivate;
|
||||
},
|
||||
isPrivate() {
|
||||
if (this.currentChat.can_reply || this.isAWhatsAppChannel) {
|
||||
@@ -1212,7 +1211,7 @@ export default {
|
||||
<ReplyBottomPanel
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:has-whatsapp-templates="hasWhatsappTemplates"
|
||||
:enable-whats-app-templates="showWhatsappTemplates"
|
||||
:inbox="inbox"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
|
||||
+53
-16
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
// Support all template formats including media headers
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
inboxId: {
|
||||
type: Number,
|
||||
@@ -12,6 +16,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
isRefreshing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -82,22 +87,47 @@ export default {
|
||||
header.format === 'DOCUMENT')
|
||||
);
|
||||
},
|
||||
async refreshTemplates() {
|
||||
this.isRefreshing = true;
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/syncTemplates', this.inboxId);
|
||||
useAlert(this.$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_SUCCESS'));
|
||||
} catch (error) {
|
||||
useAlert(this.$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_ERROR'));
|
||||
} finally {
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div
|
||||
class="flex gap-1 items-center px-2.5 py-0 mb-2.5 rounded-lg bg-n-alpha-black2 outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 focus-within:outline-n-brand dark:focus-within:outline-n-brand"
|
||||
>
|
||||
<fluent-icon icon="search" class="text-n-slate-12" size="16" />
|
||||
<input
|
||||
v-model="query"
|
||||
type="search"
|
||||
:placeholder="$t('WHATSAPP_TEMPLATES.PICKER.SEARCH_PLACEHOLDER')"
|
||||
class="reset-base w-full h-9 bg-transparent text-n-slate-12 !text-sm !outline-0"
|
||||
/>
|
||||
<div class="flex gap-2 mb-2.5">
|
||||
<div
|
||||
class="flex flex-1 gap-1 items-center px-2.5 py-0 rounded-lg bg-n-alpha-black2 outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 focus-within:outline-n-brand dark:focus-within:outline-n-brand"
|
||||
>
|
||||
<fluent-icon icon="search" class="text-n-slate-12" size="16" />
|
||||
<input
|
||||
v-model="query"
|
||||
type="search"
|
||||
:placeholder="$t('WHATSAPP_TEMPLATES.PICKER.SEARCH_PLACEHOLDER')"
|
||||
class="reset-base w-full h-9 bg-transparent text-n-slate-12 !text-sm !outline-0"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
:disabled="isRefreshing"
|
||||
class="flex justify-center items-center w-9 h-9 rounded-lg bg-n-alpha-black2 outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 hover:bg-n-alpha-2 dark:hover:bg-n-solid-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:title="$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_BUTTON')"
|
||||
@click="refreshTemplates"
|
||||
>
|
||||
<Icon
|
||||
icon="i-lucide-refresh-ccw"
|
||||
class="text-n-slate-12 size-4"
|
||||
:class="{ 'animate-spin': isRefreshing }"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="bg-n-background outline-n-container outline outline-1 rounded-lg max-h-[18.75rem] overflow-y-auto p-2.5"
|
||||
@@ -190,11 +220,18 @@ export default {
|
||||
class="border-b border-solid border-n-weak my-2.5 mx-auto max-w-[95%]"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!filteredTemplateMessages.length">
|
||||
<p>
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_FOUND') }}
|
||||
<strong>{{ query }}</strong>
|
||||
</p>
|
||||
<div v-if="!filteredTemplateMessages.length" class="py-8 text-center">
|
||||
<div v-if="query && whatsAppTemplateMessages.length">
|
||||
<p>
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_FOUND') }}
|
||||
<strong>{{ query }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="!whatsAppTemplateMessages.length" class="space-y-4">
|
||||
<p class="text-n-slate-11">
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_AVAILABLE') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -600,6 +600,10 @@
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
|
||||
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
|
||||
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
|
||||
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
|
||||
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
|
||||
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
|
||||
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
|
||||
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
|
||||
},
|
||||
"HELP_CENTER": {
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
"BUTTONS": "Buttons",
|
||||
"CATEGORY": "Category",
|
||||
"MEDIA_CONTENT": "Media Content",
|
||||
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
|
||||
"REFRESH_BUTTON": "Refresh templates",
|
||||
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
|
||||
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
|
||||
"LABELS": {
|
||||
"LANGUAGE": "Language",
|
||||
"TEMPLATE_BODY": "Template Body",
|
||||
|
||||
+14
-9
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { messageStamp } from 'shared/helpers/timeHelper';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
const props = defineProps({
|
||||
automation: {
|
||||
@@ -19,14 +21,17 @@ const readableDate = date => messageStamp(new Date(date), 'LLL d, yyyy');
|
||||
const readableDateWithTime = date =>
|
||||
messageStamp(new Date(date), 'LLL d, yyyy hh:mm a');
|
||||
|
||||
const toggle = () => {
|
||||
const { id, name, active } = props.automation;
|
||||
emit('toggle', {
|
||||
id,
|
||||
name,
|
||||
status: active,
|
||||
});
|
||||
};
|
||||
const automationActive = computed({
|
||||
get: () => props.automation.active,
|
||||
set: active => {
|
||||
const { id, name } = props.automation;
|
||||
emit('toggle', {
|
||||
id,
|
||||
name,
|
||||
status: !active,
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,7 +39,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 :model-value="automation.active" @input="toggle" />
|
||||
<ToggleSwitch v-model="automationActive" />
|
||||
</td>
|
||||
<td
|
||||
class="py-4 ltr:pr-4 rtl:pl-4 min-w-[12px]"
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
<script>
|
||||
import Draggable from 'vuedraggable';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
export default {
|
||||
components: { Draggable },
|
||||
components: { Draggable, ToggleSwitch },
|
||||
props: {
|
||||
preChatFields: {
|
||||
type: Array,
|
||||
@@ -45,9 +46,9 @@ export default {
|
||||
<tr class="border-b border-n-weak">
|
||||
<td class="pre-chat-field"><fluent-icon icon="drag" /></td>
|
||||
<td class="pre-chat-field">
|
||||
<woot-switch
|
||||
<ToggleSwitch
|
||||
:model-value="item['enabled']"
|
||||
@input="handlePreChatFieldOptions($event, 'enabled', item)"
|
||||
@change="handlePreChatFieldOptions($event, 'enabled', item)"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
|
||||
+30
-4
@@ -29,6 +29,7 @@ export default {
|
||||
return {
|
||||
hmacMandatory: false,
|
||||
whatsAppInboxAPIKey: '',
|
||||
isSyncingTemplates: false,
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
@@ -83,6 +84,19 @@ export default {
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
async syncTemplates() {
|
||||
this.isSyncingTemplates = true;
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/syncTemplates', this.inbox.id);
|
||||
useAlert(
|
||||
this.$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_TEMPLATES_SYNC_SUCCESS')
|
||||
);
|
||||
} catch (error) {
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
this.isSyncingTemplates = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -137,7 +151,7 @@ export default {
|
||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_VERIFICATION')"
|
||||
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_DESCRIPTION')"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="hmacMandatory"
|
||||
v-model="hmacMandatory"
|
||||
@@ -169,7 +183,7 @@ export default {
|
||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_VERIFICATION')"
|
||||
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_DESCRIPTION')"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="hmacMandatory"
|
||||
v-model="hmacMandatory"
|
||||
@@ -215,12 +229,12 @@ export default {
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between flex-1 mt-2 whatsapp-settings--content"
|
||||
class="flex flex-1 justify-between items-center mt-2 whatsapp-settings--content"
|
||||
>
|
||||
<woot-input
|
||||
v-model="whatsAppInboxAPIKey"
|
||||
type="text"
|
||||
class="flex-1 mr-2"
|
||||
class="flex-1 mr-2 [&>input]:!mb-0"
|
||||
:placeholder="
|
||||
$t(
|
||||
'INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_SECTION_UPDATE_PLACEHOLDER'
|
||||
@@ -235,6 +249,18 @@ export default {
|
||||
</NextButton>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_TEMPLATES_SYNC_TITLE')"
|
||||
:sub-title="
|
||||
$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_TEMPLATES_SYNC_SUBHEADER')
|
||||
"
|
||||
>
|
||||
<div class="flex justify-start items-center mt-2">
|
||||
<NextButton :disabled="isSyncingTemplates" @click="syncTemplates">
|
||||
{{ $t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_TEMPLATES_SYNC_BUTTON') }}
|
||||
</NextButton>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+5
-5
@@ -9,13 +9,13 @@ import {
|
||||
verifyServiceWorkerExistence,
|
||||
} from 'dashboard/helper/pushHelper.js';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import FormSwitch from 'v3/components/Form/Switch.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
import { NOTIFICATION_TYPES } from './constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TableHeaderCell,
|
||||
FormSwitch,
|
||||
ToggleSwitch,
|
||||
CheckBox,
|
||||
},
|
||||
data() {
|
||||
@@ -284,9 +284,9 @@ export default {
|
||||
{{ $t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.BROWSER_PERMISSION') }}
|
||||
</span>
|
||||
</div>
|
||||
<FormSwitch
|
||||
:model-value="hasEnabledPushPermissions"
|
||||
@update:model-value="onRequestPermissions"
|
||||
<ToggleSwitch
|
||||
v-model="hasEnabledPushPermissions"
|
||||
@change="onRequestPermissions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-6
@@ -10,6 +10,7 @@ import ReportsFiltersRatings from './Filters/Ratings.vue';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import { DATE_RANGE_OPTIONS } from '../constants';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -21,6 +22,7 @@ export default {
|
||||
ReportsFiltersInboxes,
|
||||
ReportsFiltersTeams,
|
||||
ReportsFiltersRatings,
|
||||
ToggleSwitch,
|
||||
},
|
||||
props: {
|
||||
showGroupByFilter: {
|
||||
@@ -106,11 +108,6 @@ export default {
|
||||
return this.validGroupOptions[0];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
businessHoursSelected() {
|
||||
this.emitChange();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.emitChange();
|
||||
},
|
||||
@@ -224,7 +221,7 @@ export default {
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<woot-switch v-model="businessHoursSelected" />
|
||||
<ToggleSwitch v-model="businessHoursSelected" @change="emitChange" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+9
-4
@@ -5,6 +5,7 @@ import startOfDay from 'date-fns/startOfDay';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
const CUSTOM_DATE_RANGE_ID = 5;
|
||||
@@ -13,6 +14,7 @@ export default {
|
||||
components: {
|
||||
WootDateRangePicker,
|
||||
Thumbnail,
|
||||
ToggleSwitch,
|
||||
},
|
||||
props: {
|
||||
currentFilter: {
|
||||
@@ -125,9 +127,6 @@ export default {
|
||||
groupByFilterItemsList() {
|
||||
this.currentSelectedGroupByFilter = this.selectedGroupByFilter;
|
||||
},
|
||||
businessHoursSelected() {
|
||||
this.$emit('businessHoursToggle', this.businessHoursSelected);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onDateRangeChange();
|
||||
@@ -140,6 +139,9 @@ export default {
|
||||
groupBy: this.groupBy,
|
||||
});
|
||||
},
|
||||
onBusinessHoursToggle() {
|
||||
this.$emit('businessHoursToggle', this.businessHoursSelected);
|
||||
},
|
||||
fromCustomDate(date) {
|
||||
return getUnixTime(startOfDay(date));
|
||||
},
|
||||
@@ -303,7 +305,10 @@ export default {
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<woot-switch v-model="businessHoursSelected" />
|
||||
<ToggleSwitch
|
||||
v-model="businessHoursSelected"
|
||||
@change="onBusinessHoursToggle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ import validations from './validations';
|
||||
import SlaTimeInput from './SlaTimeInput.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SlaTimeInput,
|
||||
NextButton,
|
||||
ToggleSwitch,
|
||||
},
|
||||
props: {
|
||||
selectedResponse: {
|
||||
@@ -203,7 +205,7 @@ export default {
|
||||
<span for="sla_bh" class="text-n-slate-11">
|
||||
{{ $t('SLA.FORM.BUSINESS_HOURS.PLACEHOLDER') }}
|
||||
</span>
|
||||
<woot-switch id="sla_bh" v-model="onlyDuringBusinessHours" />
|
||||
<ToggleSwitch id="sla_bh" v-model="onlyDuringBusinessHours" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end w-full gap-2 mt-8">
|
||||
|
||||
@@ -281,6 +281,13 @@ export const actions = {
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
syncTemplates: async (_, inboxId) => {
|
||||
try {
|
||||
await InboxesAPI.syncTemplates(inboxId);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
|
||||
@@ -231,4 +231,28 @@ describe('#actions', () => {
|
||||
).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#syncTemplates', () => {
|
||||
it('sends correct API call when sync is successful', async () => {
|
||||
axios.post.mockResolvedValue({
|
||||
data: { message: 'Template sync initiated successfully' },
|
||||
});
|
||||
|
||||
await actions.syncTemplates({ commit }, 123);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/inboxes/123/sync_templates'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error when API call fails', async () => {
|
||||
const errorMessage =
|
||||
'Template sync is only available for WhatsApp channels';
|
||||
axios.post.mockRejectedValue(new Error(errorMessage));
|
||||
|
||||
await expect(actions.syncTemplates({ commit }, 123)).rejects.toThrow(
|
||||
errorMessage
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import hljsVuePlugin from '@highlightjs/vue-plugin';
|
||||
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import { plugin, defaultConfig } from '@formkit/vue';
|
||||
import WootSwitch from 'components/ui/Switch.vue';
|
||||
import WootWizard from 'components/ui/Wizard.vue';
|
||||
import FloatingVue from 'floating-vue';
|
||||
import WootUiKit from 'dashboard/components';
|
||||
@@ -90,7 +89,6 @@ app.use(FloatingVue, {
|
||||
app.use(hljsVuePlugin);
|
||||
|
||||
app.component('multiselect', Multiselect);
|
||||
app.component('woot-switch', WootSwitch);
|
||||
app.component('woot-wizard', WootWizard);
|
||||
app.component('fluent-icon', FluentIcon);
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('update:modelValue', !this.modelValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex-shrink-0 h-4 p-0 border-none shadow-inner w-7 rounded-3xl"
|
||||
:class="
|
||||
modelValue ? 'bg-n-brand shadow-n-brand' : 'shadow-n-slate-6 bg-n-slate-5'
|
||||
"
|
||||
role="switch"
|
||||
:aria-checked="modelValue.toString()"
|
||||
@click="onClick"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="rounded-full bg-n-background top-0.5 absolute w-3 h-3 translate-y-0 duration-200 transition-transform ease-in-out"
|
||||
:class="
|
||||
modelValue
|
||||
? 'ltr:translate-x-0 rtl:translate-x-[0.75rem]'
|
||||
: 'ltr:-translate-x-[0.75rem] rtl:translate-x-0'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
Reference in New Issue
Block a user