From a3e0d84b15166ecabb89df68c02730cc5d17295d Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 30 Jul 2025 16:13:15 +0400 Subject: [PATCH] chore: add WhatsAppTemplateParserCore --- .../components/WhatsappTemplateParser.vue | 189 ++-------- .../shared/WhatsAppTemplateParserCore.vue | 355 ++++++++++++++++++ .../WhatsappTemplates/TemplateParser.vue | 347 ++--------------- 3 files changed, 418 insertions(+), 473 deletions(-) create mode 100644 app/javascript/dashboard/components-next/shared/WhatsAppTemplateParserCore.vue diff --git a/app/javascript/dashboard/components-next/NewConversation/components/WhatsappTemplateParser.vue b/app/javascript/dashboard/components-next/NewConversation/components/WhatsappTemplateParser.vue index 6f80ec715..631f4e742 100644 --- a/app/javascript/dashboard/components-next/NewConversation/components/WhatsappTemplateParser.vue +++ b/app/javascript/dashboard/components-next/NewConversation/components/WhatsappTemplateParser.vue @@ -1,16 +1,12 @@ diff --git a/app/javascript/dashboard/components-next/shared/WhatsAppTemplateParserCore.vue b/app/javascript/dashboard/components-next/shared/WhatsAppTemplateParserCore.vue new file mode 100644 index 000000000..acc29ace3 --- /dev/null +++ b/app/javascript/dashboard/components-next/shared/WhatsAppTemplateParserCore.vue @@ -0,0 +1,355 @@ + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue b/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue index af5427050..ff1fe2350 100644 --- a/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue +++ b/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue @@ -8,15 +8,10 @@ * 4. Replaces placeholders with user-provided values. * 5. Emits events to send the processed message or reset the template. */ -import { ref, computed, onMounted } from 'vue'; -import { useVuelidate } from '@vuelidate/core'; -import { requiredIf } from '@vuelidate/validators'; -import { useI18n } from 'vue-i18n'; - -import Input from 'dashboard/components-next/input/Input.vue'; +import WhatsAppTemplateParserCore from 'dashboard/components-next/shared/WhatsAppTemplateParserCore.vue'; import NextButton from 'dashboard/components-next/button/Button.vue'; -const props = defineProps({ +defineProps({ template: { type: Object, default: () => ({}), @@ -25,330 +20,38 @@ const props = defineProps({ const emit = defineEmits(['sendMessage', 'resetTemplate']); -const { t } = useI18n(); - -const processVariable = str => { - return str.replace(/{{|}}/g, ''); -}; - -const allKeysRequired = value => { - const keys = Object.keys(value); - return keys.every(key => value[key]); -}; - -const processedParams = ref({}); - -const templateString = computed(() => { - return props.template.components.find(component => component.type === 'BODY') - .text; -}); - -const headerComponent = computed(() => { - return props.template.components.find( - component => component.type === 'HEADER' - ); -}); - -const hasMediaHeader = computed(() => { - return ( - headerComponent.value && - headerComponent.value.format && - ['IMAGE', 'VIDEO', 'DOCUMENT'].includes(headerComponent.value.format) - ); -}); - -const variables = computed(() => { - return templateString.value.match(/{{([^}]+)}}/g); -}); - -const processedString = computed(() => { - return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => { - const variableKey = processVariable(variable); - return processedParams.value[variableKey] || `{{${variable}}}`; - }); -}); - -const v$ = useVuelidate( - { - processedParams: { - requiredIfKeysPresent: requiredIf(variables), - allKeysRequired, - }, - }, - { processedParams } -); - -const generateVariables = () => { - const allVariables = {}; - - // Process body variables - const matchedVariables = templateString.value.match(/{{([^}]+)}}/g); - if (matchedVariables) { - allVariables.body = {}; - matchedVariables.forEach(variable => { - const key = processVariable(variable); - // Special handling for authentication templates - if (props.template?.category === 'AUTHENTICATION') { - if ( - key === '1' || - key.toLowerCase().includes('otp') || - key.toLowerCase().includes('code') - ) { - allVariables.body.otp_code = ''; - } else if ( - key === '2' || - key.toLowerCase().includes('expiry') || - key.toLowerCase().includes('minute') - ) { - allVariables.body.expiry_minutes = ''; - } else { - allVariables.body[key] = ''; - } - } else { - allVariables.body[key] = ''; - } - }); - } - - // Add media URL field if template has media header - if (hasMediaHeader.value) { - if (!allVariables.header) allVariables.header = {}; - allVariables.header.media_url = ''; - allVariables.header.media_type = headerComponent.value.format.toLowerCase(); - } - - // Process button variables - const buttonComponents = props.template.components.filter( - component => component.type === 'BUTTONS' - ); - - buttonComponents.forEach(buttonComponent => { - if (buttonComponent.buttons) { - buttonComponent.buttons.forEach((button, index) => { - // Skip button parameter inputs for authentication templates - // as they are auto-populated with OTP codes - if (props.template?.category !== 'AUTHENTICATION') { - // Handle URL buttons with variables - if ( - button.type === 'URL' && - button.url && - button.url.includes('{{') - ) { - const buttonVars = button.url.match(/{{([^}]+)}}/g) || []; - if (buttonVars.length > 0) { - if (!allVariables.buttons) allVariables.buttons = []; - allVariables.buttons[index] = { - type: 'url', - parameter: '', - url: button.url, - variables: buttonVars.map(v => processVariable(v)), - }; - } - } - - // Handle copy code buttons - if (button.type === 'COPY_CODE') { - if (!allVariables.buttons) allVariables.buttons = []; - allVariables.buttons[index] = { - type: 'copy_code', - parameter: '', - }; - } - } - }); - } - }); - - processedParams.value = allVariables; -}; - -const resetTemplate = () => { - emit('resetTemplate'); -}; - -const updateMediaUrl = value => { - if (!processedParams.value.header) { - processedParams.value.header = {}; - } - processedParams.value.header.media_url = value; -}; - -const sendMessage = () => { - v$.value.$touch(); - if (v$.value.$invalid) return; - - // Auto-populate button parameters for authentication templates - const finalParams = { ...processedParams.value }; - if (props.template?.category === 'AUTHENTICATION' && finalParams.buttons) { - finalParams.buttons.forEach((button, index) => { - if (button.type === 'url') { - // For authentication templates, auto-populate URL button parameter with OTP - if (finalParams.body?.['1']) { - finalParams.buttons[index].parameter = finalParams.body['1']; - } else if (finalParams.body?.otp_code) { - finalParams.buttons[index].parameter = finalParams.body.otp_code; - } - } - }); - } - - const payload = { - message: processedString.value, - templateParams: { - name: props.template.name, - category: props.template.category, - language: props.template.language, - namespace: props.template.namespace, - processed_params: finalParams, - }, - }; +const handleSendMessage = payload => { emit('sendMessage', payload); }; -onMounted(generateVariables); +const handleResetTemplate = () => { + emit('resetTemplate'); +}; +