From 5c560c762858bf3c77ee5b76307dad8a526fef77 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 12 Aug 2025 18:53:19 +0530 Subject: [PATCH] feat: WhatsApp enhanced templates front end changes (#12117) Part of the https://github.com/chatwoot/chatwoot/pull/11997 Co-authored-by: Sojan Jose Co-authored-by: iamsivin --- .../WhatsAppCampaign/WhatsAppCampaignForm.vue | 121 +----- .../components/ActionButtons.vue | 6 +- .../components/ComposeNewConversationForm.vue | 1 + .../components/WhatsAppOptions.vue | 35 +- .../components/WhatsappTemplate.vue | 64 +++ .../whatsapp/WhatsAppTemplateParser.vue | 279 +++++++++++++ .../WhatsappTemplates/TemplateParser.vue | 179 ++------- .../WhatsappTemplates/TemplatesPicker.vue | 197 ++++++---- .../helper/specs/templateHelper.spec.js | 368 ++++++++++++++++++ .../dashboard/helper/templateHelper.js | 91 +++++ .../i18n/locale/en/whatsappTemplates.json | 69 ++-- .../dashboard/store/modules/inboxes.js | 53 ++- .../modules/specs/inboxes/getters.spec.js | 266 +++++++++++++ .../specs/inboxes/templateFixtures.js} | 281 +++++++++++++ .../whatsappTemplates.spec.js | 61 --- 15 files changed, 1635 insertions(+), 436 deletions(-) create mode 100644 app/javascript/dashboard/components-next/NewConversation/components/WhatsappTemplate.vue create mode 100644 app/javascript/dashboard/components-next/whatsapp/WhatsAppTemplateParser.vue create mode 100644 app/javascript/dashboard/helper/specs/templateHelper.spec.js create mode 100644 app/javascript/dashboard/helper/templateHelper.js rename app/javascript/{shared/mixins/specs/whatsappTemplates/fixtures.js => dashboard/store/modules/specs/inboxes/templateFixtures.js} (50%) delete mode 100644 app/javascript/shared/mixins/specs/whatsappTemplates/whatsappTemplates.spec.js diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue index df76ae901..0babd11ec 100644 --- a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue @@ -9,6 +9,7 @@ import Input from 'dashboard/components-next/input/Input.vue'; import Button from 'dashboard/components-next/button/Button.vue'; import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue'; import TagMultiSelectComboBox from 'dashboard/components-next/combobox/TagMultiSelectComboBox.vue'; +import WhatsAppTemplateParser from 'dashboard/components-next/whatsapp/WhatsAppTemplateParser.vue'; const emit = defineEmits(['submit', 'cancel']); @@ -18,7 +19,9 @@ const formState = { uiFlags: useMapGetter('campaigns/getUIFlags'), labels: useMapGetter('labels/getLabels'), inboxes: useMapGetter('inboxes/getWhatsAppInboxes'), - getWhatsAppTemplates: useMapGetter('inboxes/getWhatsAppTemplates'), + getFilteredWhatsAppTemplates: useMapGetter( + 'inboxes/getFilteredWhatsAppTemplates' + ), }; const initialState = { @@ -30,7 +33,7 @@ const initialState = { }; const state = reactive({ ...initialState }); -const processedParams = ref({}); +const templateParserRef = ref(null); const rules = { title: { required, minLength: minLength(1) }, @@ -67,7 +70,7 @@ const inboxOptions = computed(() => const templateOptions = computed(() => { if (!state.inboxId) return []; - const templates = formState.getWhatsAppTemplates.value(state.inboxId); + const templates = formState.getFilteredWhatsAppTemplates.value(state.inboxId); return templates.map(template => { // Create a more user-friendly label from template name const friendlyName = template.name @@ -88,26 +91,6 @@ const selectedTemplate = computed(() => { ?.template; }); -const templateString = computed(() => { - if (!selectedTemplate.value) return ''; - try { - return ( - selectedTemplate.value.components?.find( - component => component.type === 'BODY' - )?.text || '' - ); - } catch (error) { - return ''; - } -}); - -const processedString = computed(() => { - if (!templateString.value) return ''; - return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => { - return processedParams.value[variable] || `{{${variable}}}`; - }); -}); - const getErrorMessage = (field, errorKey) => { const baseKey = 'CAMPAIGN.WHATSAPP.CREATE.FORM'; return v$.value[field].$error ? t(`${baseKey}.${errorKey}.ERROR`) : ''; @@ -122,8 +105,7 @@ const formErrors = computed(() => ({ })); const hasRequiredTemplateParams = computed(() => { - const params = Object.values(processedParams.value); - return params.length === 0 || params.every(param => param.trim() !== ''); + return templateParserRef.value?.v$?.$invalid === false || true; }); const isSubmitDisabled = computed( @@ -135,32 +117,18 @@ const formatToUTCString = localDateTime => const resetState = () => { Object.assign(state, initialState); - processedParams.value = {}; v$.value.$reset(); }; const handleCancel = () => emit('cancel'); -const generateVariables = () => { - const matchedVariables = templateString.value.match(/{{([^}]+)}}/g); - if (!matchedVariables) { - processedParams.value = {}; - return; - } - - const finalVars = matchedVariables.map(match => match.replace(/{{|}}/g, '')); - processedParams.value = finalVars.reduce((acc, variable) => { - acc[variable] = processedParams.value[variable] || ''; - return acc; - }, {}); -}; - const prepareCampaignDetails = () => { // Find the selected template to get its content const currentTemplate = selectedTemplate.value; + const parserData = templateParserRef.value; // Extract template content - this should be the template message body - const templateContent = templateString.value; + const templateContent = parserData?.renderedTemplate || ''; // Prepare template_params object with the same structure as used in contacts const templateParams = { @@ -168,7 +136,7 @@ const prepareCampaignDetails = () => { namespace: currentTemplate?.namespace || '', category: currentTemplate?.category || 'UTILITY', language: currentTemplate?.language || 'en_US', - processed_params: processedParams.value, + processed_params: parserData?.processedParams || {}, }; return { @@ -198,15 +166,6 @@ watch( () => state.inboxId, () => { state.templateId = null; - processedParams.value = {}; - } -); - -// Generate variables when template changes -watch( - () => state.templateId, - () => { - generateVariables(); } ); @@ -254,62 +213,12 @@ watch(

- -
+ -
-

- {{ selectedTemplate.name }} -

- - {{ t('CAMPAIGN.WHATSAPP.CREATE.FORM.TEMPLATE.LANGUAGE') }}: - {{ selectedTemplate.language || 'en' }} - -
- -
-
-
- {{ processedString }} -
-
-
- -
- {{ t('CAMPAIGN.WHATSAPP.CREATE.FORM.TEMPLATE.CATEGORY') }}: - {{ selectedTemplate.category || 'UTILITY' }} -
-
- - -
- -
-
- -
-
-
+ ref="templateParserRef" + :template="selectedTemplate" + />