diff --git a/app/javascript/histoire.setup.ts b/app/javascript/histoire.setup.ts
index 7642da78d..1c80a9f85 100644
--- a/app/javascript/histoire.setup.ts
+++ b/app/javascript/histoire.setup.ts
@@ -1,6 +1,7 @@
import './design-system/histoire.scss';
import { defineSetupVue3 } from '@histoire/plugin-vue';
-import i18nMessages from 'dashboard/i18n';
+import dashboardI18n from 'dashboard/i18n';
+import widgetI18n from 'widget/i18n';
import { createI18n } from 'vue-i18n';
import { vResizeObserver } from '@vueuse/components';
import store from 'dashboard/store';
@@ -9,10 +10,30 @@ import VueDOMPurifyHTML from 'vue-dompurify-html';
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer.js';
import { directive as onClickaway } from 'vue3-click-away';
+function mergeMessages(...sources) {
+ return sources.reduce((acc, src) => {
+ Object.keys(src).forEach(key => {
+ if (
+ acc[key] &&
+ typeof acc[key] === 'object' &&
+ typeof src[key] === 'object'
+ ) {
+ acc[key] = mergeMessages(acc[key], src[key]);
+ } else {
+ acc[key] = src[key];
+ }
+ });
+ return acc;
+ }, {});
+}
+
const i18n = createI18n({
legacy: false, // https://github.com/intlify/vue-i18n/issues/1902
locale: 'en',
- messages: i18nMessages,
+ messages: mergeMessages(
+ structuredClone(dashboardI18n),
+ structuredClone(widgetI18n)
+ ),
});
export const setupVue3 = defineSetupVue3(({ app }) => {
diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue
index 48409cef2..379b2bc53 100755
--- a/app/javascript/widget/App.vue
+++ b/app/javascript/widget/App.vue
@@ -4,12 +4,10 @@ import { setHeader } from 'widget/helpers/axios';
import addHours from 'date-fns/addHours';
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
import configMixin from './mixins/configMixin';
-import availabilityMixin from 'widget/mixins/availability';
import { getLocale } from './helpers/urlParamsHelper';
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import { isEmptyObject } from 'widget/helpers/utils';
import Spinner from 'shared/components/Spinner.vue';
-import routerMixin from './mixins/routerMixin';
import {
getExtraSpaceToScroll,
loadedEventConfig,
@@ -20,6 +18,8 @@ import {
ON_UNREAD_MESSAGE_CLICK,
} from './constants/widgetBusEvents';
import { useDarkMode } from 'widget/composables/useDarkMode';
+import { useRouter } from 'vue-router';
+import { useAvailability } from 'widget/composables/useAvailability';
import { SDK_SET_BUBBLE_VISIBILITY } from '../shared/constants/sharedFrameEvents';
import { emitter } from 'shared/helpers/mitt';
@@ -28,10 +28,13 @@ export default {
components: {
Spinner,
},
- mixins: [availabilityMixin, configMixin, routerMixin],
+ mixins: [configMixin],
setup() {
const { prefersDarkMode } = useDarkMode();
- return { prefersDarkMode };
+ const router = useRouter();
+ const { isInWorkingHours } = useAvailability();
+
+ return { prefersDarkMode, router, isInWorkingHours };
},
data() {
return {
@@ -157,15 +160,17 @@ export default {
this.setUnreadView();
});
emitter.on(ON_UNREAD_MESSAGE_CLICK, () => {
- this.replaceRoute('messages').then(() => this.unsetUnreadView());
+ this.router
+ .replace({ name: 'messages' })
+ .then(() => this.unsetUnreadView());
});
},
registerCampaignEvents() {
emitter.on(ON_CAMPAIGN_MESSAGE_CLICK, () => {
if (this.shouldShowPreChatForm) {
- this.replaceRoute('prechat-form');
+ this.router.replace({ name: 'prechat-form' });
} else {
- this.replaceRoute('messages');
+ this.router.replace({ name: 'messages' });
emitter.emit('execute-campaign', {
campaignId: this.activeCampaign.id,
});
@@ -176,7 +181,7 @@ export default {
const { customAttributes, campaignId } = campaignDetails;
const { websiteToken } = window.chatwootWebChannel;
this.executeCampaign({ campaignId, websiteToken, customAttributes });
- this.replaceRoute('messages');
+ this.router.replace({ name: 'messages' });
});
emitter.on('snooze-campaigns', () => {
const expireBy = addHours(new Date(), 1);
@@ -192,7 +197,7 @@ export default {
!messageCount &&
!shouldSnoozeCampaign;
if (this.isIFrame && isCampaignReadyToExecute) {
- this.replaceRoute('campaigns').then(() => {
+ this.router.replace({ name: 'campaigns' }).then(() => {
this.setIframeHeight(true);
IFrameHelper.sendMessage({ event: 'setUnreadMode' });
});
@@ -207,7 +212,7 @@ export default {
unreadMessageCount > 0 &&
!this.isWidgetOpen
) {
- this.replaceRoute('unread-messages').then(() => {
+ this.router.replace({ name: 'unread-messages' }).then(() => {
this.setIframeHeight(true);
IFrameHelper.sendMessage({ event: 'setUnreadMode' });
});
@@ -263,7 +268,7 @@ export default {
this.initCampaigns({
currentURL: referrerURL,
websiteToken,
- isInBusinessHours: this.isInBusinessHours,
+ isInBusinessHours: this.isInWorkingHours,
});
window.referrerURL = referrerURL;
this.setReferrerHost(referrerHost);
@@ -314,12 +319,12 @@ export default {
['unread-messages', 'campaigns'].includes(this.$route.name);
if (shouldShowMessageView) {
- this.replaceRoute('messages');
+ this.router.replace({ name: 'messages' });
}
if (shouldShowHomeView) {
this.$store.dispatch('conversation/setUserLastSeen');
this.unsetUnreadView();
- this.replaceRoute('home');
+ this.router.replace({ name: 'home' });
}
if (!message.isOpen) {
this.resetCampaign();
diff --git a/app/javascript/widget/components/Availability/AvailabilityContainer.vue b/app/javascript/widget/components/Availability/AvailabilityContainer.vue
new file mode 100644
index 000000000..367564751
--- /dev/null
+++ b/app/javascript/widget/components/Availability/AvailabilityContainer.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+ {{ headerText }}
+
+
+
+
+
+
+
+
diff --git a/app/javascript/widget/components/Availability/AvailabilityText.story.vue b/app/javascript/widget/components/Availability/AvailabilityText.story.vue
new file mode 100644
index 000000000..aafcb3556
--- /dev/null
+++ b/app/javascript/widget/components/Availability/AvailabilityText.story.vue
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/widget/components/Availability/AvailabilityText.vue b/app/javascript/widget/components/Availability/AvailabilityText.vue
new file mode 100644
index 000000000..649224d20
--- /dev/null
+++ b/app/javascript/widget/components/Availability/AvailabilityText.vue
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+ {{ replyTimeMessage }}
+
+
+
+
+ {{
+ isOnline
+ ? replyTimeMessage
+ : t('TEAM_AVAILABILITY.BACK_AS_SOON_AS_POSSIBLE')
+ }}
+
+
+
+
+
+ {{ t('TEAM_AVAILABILITY.BACK_AS_SOON_AS_POSSIBLE') }}
+
+
+
+
+ {{ t('REPLY_TIME.BACK_IN_SOME_TIME') }}
+
+
+
+
+ {{ t('REPLY_TIME.BACK_TOMORROW') }}
+
+
+
+
+ {{
+ t('REPLY_TIME.BACK_ON_DAY', {
+ day: dayNames[nextSlot.config.dayOfWeek],
+ })
+ }}
+
+
+
+
+ {{
+ t('REPLY_TIME.BACK_IN_MINUTES', {
+ time: `${roundedMinutesUntilOpen}`,
+ })
+ }}
+
+
+
+
+ {{ t('REPLY_TIME.BACK_IN_HOURS', adjustedHoursUntilOpen) }}
+
+
+
+
+ {{
+ t('REPLY_TIME.BACK_AT_TIME', {
+ time: formattedOpeningTime,
+ })
+ }}
+
+
+
diff --git a/app/javascript/widget/components/ChatFooter.vue b/app/javascript/widget/components/ChatFooter.vue
index 2bc6ba7ec..c85727a2b 100755
--- a/app/javascript/widget/components/ChatFooter.vue
+++ b/app/javascript/widget/components/ChatFooter.vue
@@ -6,7 +6,7 @@ import FooterReplyTo from 'widget/components/FooterReplyTo.vue';
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { sendEmailTranscript } from 'widget/api/conversation';
-import routerMixin from 'widget/mixins/routerMixin';
+import { useRouter } from 'vue-router';
import { IFrameHelper } from '../helpers/utils';
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
import { emitter } from 'shared/helpers/mitt';
@@ -17,7 +17,10 @@ export default {
CustomButton,
FooterReplyTo,
},
- mixins: [routerMixin],
+ setup() {
+ const router = useRouter();
+ return { router };
+ },
data() {
return {
inReplyTo: null,
@@ -77,7 +80,7 @@ export default {
this.inReplyTo = null;
},
startNewConversation() {
- this.replaceRoute('prechat-form');
+ this.router.replace({ name: 'prechat-form' });
IFrameHelper.sendMessage({
event: 'onEvent',
eventIdentifier: CHATWOOT_ON_START_CONVERSATION,
diff --git a/app/javascript/widget/components/ChatHeader.vue b/app/javascript/widget/components/ChatHeader.vue
index 7c497b70f..578fb984e 100644
--- a/app/javascript/widget/components/ChatHeader.vue
+++ b/app/javascript/widget/components/ChatHeader.vue
@@ -1,55 +1,26 @@
-
@@ -79,9 +50,12 @@ export default {
${isOnline ? 'bg-n-teal-10' : 'hidden'}`"
/>
-
- {{ replyWaitMessage }}
-
+
diff --git a/app/javascript/widget/components/PreChat/Form.vue b/app/javascript/widget/components/PreChat/Form.vue
index 6fe8e45a1..84f0036fd 100644
--- a/app/javascript/widget/components/PreChat/Form.vue
+++ b/app/javascript/widget/components/PreChat/Form.vue
@@ -6,7 +6,6 @@ import { getContrastingTextColor } from '@chatwoot/utils';
import { isEmptyObject } from 'widget/helpers/utils';
import { getRegexp } from 'shared/helpers/Validators';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
-import routerMixin from 'widget/mixins/routerMixin';
import configMixin from 'widget/mixins/configMixin';
import { FormKit, createInput } from '@formkit/vue';
import PhoneInput from 'widget/components/Form/PhoneInput.vue';
@@ -17,7 +16,7 @@ export default {
Spinner,
FormKit,
},
- mixins: [routerMixin, configMixin],
+ mixins: [configMixin],
props: {
options: {
type: Object,
diff --git a/app/javascript/widget/components/TeamAvailability.vue b/app/javascript/widget/components/TeamAvailability.vue
index 866854c09..cc08d34c2 100644
--- a/app/javascript/widget/components/TeamAvailability.vue
+++ b/app/javascript/widget/components/TeamAvailability.vue
@@ -1,74 +1,27 @@
-
@@ -76,17 +29,8 @@ export default {
-
-
-
- {{ headerMessage }}
-
-
- {{ replyWaitMessage }}
-
-
-
-
+
+