775 lines
24 KiB
Vue
775 lines
24 KiB
Vue
<script setup>
|
|
import { computed, ref, 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 Button from 'dashboard/components-next/button/Button.vue';
|
|
|
|
const props = defineProps({
|
|
template: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['sendMessage', 'back']);
|
|
|
|
const { t } = useI18n();
|
|
|
|
const processedParams = ref({});
|
|
|
|
const templateName = computed(() => {
|
|
return props.template?.name || '';
|
|
});
|
|
|
|
const templateComponents = computed(() => {
|
|
return props.template?.components || [];
|
|
});
|
|
|
|
const templateString = computed(() => {
|
|
return (
|
|
props.template?.components?.find(component => component.type === 'BODY')
|
|
?.text || ''
|
|
);
|
|
});
|
|
|
|
const headerComponent = computed(() => {
|
|
return templateComponents.value.find(
|
|
component => component.type === 'HEADER'
|
|
);
|
|
});
|
|
|
|
const footerComponent = computed(() => {
|
|
return templateComponents.value.find(
|
|
component => component.type === 'FOOTER'
|
|
);
|
|
});
|
|
|
|
const buttonComponents = computed(() => {
|
|
return templateComponents.value.filter(
|
|
component => component.type === 'BUTTONS'
|
|
);
|
|
});
|
|
|
|
const interactiveComponents = computed(() => {
|
|
return templateComponents.value.filter(
|
|
component => ['LIST', 'PRODUCT', 'CATALOG'].includes(component.type)
|
|
);
|
|
});
|
|
|
|
const isInteractiveTemplate = computed(() => {
|
|
const hasInteractiveButtons = buttonComponents.value.some(component =>
|
|
component.buttons?.some(button =>
|
|
['quick_reply', 'url', 'phone_number', 'catalog_browse'].includes(button.type)
|
|
)
|
|
);
|
|
return hasInteractiveButtons || interactiveComponents.value.length > 0;
|
|
});
|
|
|
|
const legacyParams = computed(() => {
|
|
const params = {};
|
|
Object.keys(processedParams.value).forEach(key => {
|
|
if (!['header', 'body', 'footer', 'buttons'].includes(key)) {
|
|
params[key] = processedParams.value[key];
|
|
}
|
|
});
|
|
return params;
|
|
});
|
|
|
|
const processVariable = str => {
|
|
return str.replace(/{{|}}/g, '');
|
|
};
|
|
|
|
const processedString = computed(() => {
|
|
return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => {
|
|
const variableKey = processVariable(variable);
|
|
return processedParams.value[variableKey] || `{{${variable}}}`;
|
|
});
|
|
});
|
|
|
|
const processedStringWithVariableHighlight = computed(() => {
|
|
const variables = templateString.value.match(/{{([^}]+)}}/g) || [];
|
|
|
|
return variables.reduce((result, variable) => {
|
|
const variableKey = processVariable(variable);
|
|
const value = processedParams.value[variableKey] || variable;
|
|
return result.replace(
|
|
variable,
|
|
`<span class="break-all text-n-slate-12">${value}</span>`
|
|
);
|
|
}, templateString.value);
|
|
});
|
|
|
|
const rules = computed(() => {
|
|
const paramRules = {};
|
|
Object.keys(processedParams.value).forEach(key => {
|
|
if (key === 'header' && processedParams.value.header.location_type === 'location') {
|
|
// Add specific validation for location parameters
|
|
paramRules[key] = {
|
|
location: {
|
|
latitude: { required: requiredIf(true) },
|
|
longitude: { required: requiredIf(true) }
|
|
}
|
|
};
|
|
} else {
|
|
paramRules[key] = { required: requiredIf(true) };
|
|
}
|
|
});
|
|
return {
|
|
processedParams: paramRules,
|
|
};
|
|
});
|
|
|
|
const v$ = useVuelidate(rules, { processedParams });
|
|
|
|
const getFieldErrorType = key => {
|
|
if (!v$.value.processedParams[key]?.$error) return 'info';
|
|
return 'error';
|
|
};
|
|
|
|
const generateVariables = () => {
|
|
const allVariables = {};
|
|
|
|
// Debug: Log template structure
|
|
console.log('Template components:', templateComponents.value);
|
|
console.log('Header component:', headerComponent.value);
|
|
|
|
// Process body variables
|
|
const bodyVars = templateString.value.match(/{{([^}]+)}}/g) || [];
|
|
if (bodyVars.length > 0) {
|
|
allVariables.body = {};
|
|
bodyVars.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] = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Process header variables
|
|
if (headerComponent.value) {
|
|
if (headerComponent.value.text) {
|
|
// Text headers with variables
|
|
const headerVars = headerComponent.value.text.match(/{{([^}]+)}}/g) || [];
|
|
if (headerVars.length > 0) {
|
|
allVariables.header = {};
|
|
headerVars.forEach(variable => {
|
|
const key = processVariable(variable);
|
|
allVariables.header[key] = '';
|
|
});
|
|
}
|
|
} else if (
|
|
headerComponent.value.format &&
|
|
['IMAGE', 'VIDEO', 'DOCUMENT'].includes(headerComponent.value.format)
|
|
) {
|
|
// Media headers need URL input
|
|
allVariables.header = {
|
|
media_url: '',
|
|
media_type: headerComponent.value.format.toLowerCase(),
|
|
};
|
|
} else if (
|
|
headerComponent.value.format &&
|
|
headerComponent.value.format === 'LOCATION'
|
|
) {
|
|
// Location headers need location data
|
|
console.log('Detected LOCATION header template:', headerComponent.value);
|
|
allVariables.header = {
|
|
location: {
|
|
latitude: '',
|
|
longitude: '',
|
|
name: '',
|
|
address: '',
|
|
},
|
|
location_type: 'location',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Process footer variables
|
|
if (footerComponent.value?.text) {
|
|
const footerVars = footerComponent.value.text.match(/{{([^}]+)}}/g) || [];
|
|
if (footerVars.length > 0) {
|
|
allVariables.footer = {};
|
|
footerVars.forEach(variable => {
|
|
const key = processVariable(variable);
|
|
allVariables.footer[key] = '';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Process button variables
|
|
buttonComponents.value.forEach(buttonComponent => {
|
|
if (buttonComponent.buttons) {
|
|
buttonComponent.buttons.forEach((button, index) => {
|
|
// Handle URL buttons with variables
|
|
if (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: '',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Handle copy code buttons
|
|
if (button.type === 'COPY_CODE') {
|
|
if (!allVariables.buttons) allVariables.buttons = [];
|
|
allVariables.buttons[index] = {
|
|
type: 'copy_code',
|
|
parameter: '',
|
|
};
|
|
}
|
|
|
|
// Handle interactive buttons with dynamic text
|
|
if (['quick_reply', 'url', 'phone_number'].includes(button.type)) {
|
|
if (button.text && button.text.includes('{{')) {
|
|
if (!allVariables.buttons) allVariables.buttons = [];
|
|
allVariables.buttons[index] = {
|
|
type: button.type,
|
|
parameter: '',
|
|
text: button.text,
|
|
};
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Process interactive components (LIST, PRODUCT, CATALOG)
|
|
interactiveComponents.value.forEach(component => {
|
|
if (component.type === 'LIST') {
|
|
allVariables.interactive = {
|
|
type: 'list',
|
|
button_text: 'Select Option',
|
|
sections: component.sections || []
|
|
};
|
|
} else if (component.type === 'PRODUCT') {
|
|
allVariables.interactive = {
|
|
type: 'product',
|
|
catalog_id: component.catalog_id || '',
|
|
product_id: component.product_id || ''
|
|
};
|
|
} else if (component.type === 'CATALOG') {
|
|
allVariables.interactive = {
|
|
type: 'catalog',
|
|
catalog_id: component.catalog_id || '',
|
|
title: component.title || 'Browse Products'
|
|
};
|
|
}
|
|
});
|
|
|
|
processedParams.value = allVariables;
|
|
};
|
|
|
|
const validateButtonParameter = (button, index) => {
|
|
const parameter = processedParams.value.buttons[index].parameter;
|
|
|
|
if (button.type === 'copy_code') {
|
|
if (parameter && parameter.length > 15) {
|
|
processedParams.value.buttons[index].parameter = parameter.substring(
|
|
0,
|
|
15
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const getHeaderFieldLabel = key => {
|
|
if (key === 'media_url') {
|
|
const mediaType = processedParams.value.header?.media_type || 'media';
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.MEDIA_URL', {
|
|
type: mediaType.toUpperCase(),
|
|
}) || `${mediaType.toUpperCase()} URL`
|
|
);
|
|
}
|
|
return key;
|
|
};
|
|
|
|
const getHeaderFieldPlaceholder = key => {
|
|
if (key === 'media_url') {
|
|
const mediaType = processedParams.value.header?.media_type || 'media';
|
|
switch (mediaType) {
|
|
case 'image':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.IMAGE_URL_PLACEHOLDER') ||
|
|
'Enter image URL (https://example.com/image.jpg)'
|
|
);
|
|
case 'video':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.VIDEO_URL_PLACEHOLDER') ||
|
|
'Enter video URL (https://example.com/video.mp4)'
|
|
);
|
|
case 'document':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.DOCUMENT_URL_PLACEHOLDER') ||
|
|
'Enter document URL (https://example.com/document.pdf)'
|
|
);
|
|
default:
|
|
return 'Enter media URL';
|
|
}
|
|
}
|
|
return `Enter ${key} value`;
|
|
};
|
|
|
|
const getBodyParameterLabel = key => {
|
|
if (props.template?.category === 'AUTHENTICATION') {
|
|
switch (key) {
|
|
case 'otp_code':
|
|
return t('WHATSAPP_TEMPLATES.PARSER.OTP_CODE') || 'OTP Code';
|
|
case 'expiry_minutes':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.EXPIRY_MINUTES') || 'Expiry (minutes)'
|
|
);
|
|
default:
|
|
return key;
|
|
}
|
|
}
|
|
return key;
|
|
};
|
|
|
|
const getBodyParameterPlaceholder = key => {
|
|
if (props.template?.category === 'AUTHENTICATION') {
|
|
switch (key) {
|
|
case 'otp_code':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.OTP_CODE_PLACEHOLDER') ||
|
|
'Enter 4-8 digit OTP code'
|
|
);
|
|
case 'expiry_minutes':
|
|
return (
|
|
t('WHATSAPP_TEMPLATES.PARSER.EXPIRY_MINUTES_PLACEHOLDER') ||
|
|
'Enter expiry time in minutes'
|
|
);
|
|
default:
|
|
return `Enter ${key} value`;
|
|
}
|
|
}
|
|
return `Enter ${key} value`;
|
|
};
|
|
|
|
const getBodyParameterType = key => {
|
|
if (props.template?.category === 'AUTHENTICATION') {
|
|
switch (key) {
|
|
case 'otp_code':
|
|
return 'tel';
|
|
case 'expiry_minutes':
|
|
return 'number';
|
|
default:
|
|
return 'text';
|
|
}
|
|
}
|
|
return 'text';
|
|
};
|
|
|
|
const getBodyParameterMaxLength = key => {
|
|
if (props.template?.category === 'AUTHENTICATION') {
|
|
switch (key) {
|
|
case 'otp_code':
|
|
return 8;
|
|
case 'expiry_minutes':
|
|
return 3;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const sendMessage = async () => {
|
|
const isValid = await v$.value.$validate();
|
|
if (!isValid) return;
|
|
|
|
const payload = {
|
|
message: processedString.value,
|
|
templateParams: {
|
|
name: props.template.name,
|
|
category: props.template.category,
|
|
language: props.template.language,
|
|
namespace: props.template.namespace,
|
|
processed_params: processedParams.value,
|
|
},
|
|
};
|
|
emit('sendMessage', payload);
|
|
};
|
|
|
|
onMounted(() => {
|
|
generateVariables();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="absolute top-full mt-1.5 max-h-[30rem] overflow-y-auto left-0 flex flex-col gap-4 px-4 pt-6 pb-5 items-start w-[28.75rem] h-auto bg-n-solid-2 border border-n-strong shadow-sm rounded-lg"
|
|
>
|
|
<span class="text-sm text-n-slate-12">
|
|
{{
|
|
t(
|
|
'COMPOSE_NEW_CONVERSATION.FORM.WHATSAPP_OPTIONS.TEMPLATE_PARSER.TEMPLATE_NAME',
|
|
{ templateName: templateName }
|
|
)
|
|
}}
|
|
</span>
|
|
|
|
<p
|
|
v-dompurify-html="processedStringWithVariableHighlight"
|
|
class="mb-0 text-sm text-n-slate-11"
|
|
/>
|
|
|
|
<span
|
|
v-if="Object.keys(processedParams).length"
|
|
class="text-sm font-medium text-n-slate-12"
|
|
>
|
|
{{
|
|
t(
|
|
'COMPOSE_NEW_CONVERSATION.FORM.WHATSAPP_OPTIONS.TEMPLATE_PARSER.VARIABLES'
|
|
)
|
|
}}
|
|
</span>
|
|
|
|
<!-- Header Variables -->
|
|
<div v-if="processedParams.header" class="w-full">
|
|
<h4 class="text-sm font-medium text-n-slate-12 mb-2">
|
|
{{
|
|
t('WHATSAPP_TEMPLATES.PARSER.HEADER_PARAMETERS') ||
|
|
'Header Parameters'
|
|
}}
|
|
</h4>
|
|
<!-- Location Parameters -->
|
|
<div
|
|
v-if="processedParams.header.location_type === 'location'"
|
|
class="w-full space-y-3 mb-4 p-3 bg-n-solid-1 rounded-lg border border-n-weak"
|
|
>
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<div class="w-4 h-4 bg-blue-500 rounded-full flex items-center justify-center">
|
|
<div class="w-2 h-2 bg-white rounded-full"></div>
|
|
</div>
|
|
<span class="text-sm font-medium text-n-slate-12">Location Details</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">
|
|
📍 Latitude <span class="text-red-500">*</span>
|
|
</label>
|
|
<Input
|
|
v-model="processedParams.header.location.latitude"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
type="number"
|
|
step="any"
|
|
placeholder="37.7749 (San Francisco)"
|
|
:message-type="getFieldErrorType('header.location.latitude')"
|
|
/>
|
|
<span class="text-xs text-n-slate-9">Range: -90.0 to 90.0</span>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">
|
|
📍 Longitude <span class="text-red-500">*</span>
|
|
</label>
|
|
<Input
|
|
v-model="processedParams.header.location.longitude"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
type="number"
|
|
step="any"
|
|
placeholder="-122.4194 (San Francisco)"
|
|
:message-type="getFieldErrorType('header.location.longitude')"
|
|
/>
|
|
<span class="text-xs text-n-slate-9">Range: -180.0 to 180.0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">
|
|
🏢 Location Name
|
|
</label>
|
|
<Input
|
|
v-model="processedParams.header.location.name"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Your Business Name"
|
|
:message-type="getFieldErrorType('header.location.name')"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">
|
|
📮 Full Address
|
|
</label>
|
|
<Input
|
|
v-model="processedParams.header.location.address"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="123 Main Street, City, State 12345"
|
|
:message-type="getFieldErrorType('header.location.address')"
|
|
/>
|
|
</div>
|
|
|
|
<div class="text-xs text-n-slate-9 bg-blue-50 p-2 rounded border-l-4 border-blue-400">
|
|
💡 <strong>Tip:</strong> You can get coordinates by searching your location on Google Maps,
|
|
right-clicking the pin, and copying the latitude/longitude values.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Regular Header Parameters -->
|
|
<div
|
|
v-if="processedParams.header.location_type !== 'location'"
|
|
>
|
|
<div
|
|
v-for="(variable, key) in processedParams.header"
|
|
v-if="!['location', 'location_type'].includes(key)"
|
|
:key="`header-${key}`"
|
|
class="flex items-center w-full gap-2 mb-2"
|
|
>
|
|
<span
|
|
class="flex items-center h-8 text-sm min-w-6 ltr:text-left rtl:text-right text-n-slate-10"
|
|
>
|
|
{{ getHeaderFieldLabel(key) }}
|
|
</span>
|
|
<Input
|
|
v-model="processedParams.header[key]"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
:message-type="getFieldErrorType(`header.${key}`)"
|
|
:placeholder="getHeaderFieldPlaceholder(key)"
|
|
:type="key === 'media_url' ? 'url' : 'text'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body Variables -->
|
|
<div v-if="processedParams.body" class="w-full">
|
|
<h4 class="text-sm font-medium text-n-slate-12 mb-2">
|
|
{{
|
|
t('WHATSAPP_TEMPLATES.PARSER.BODY_PARAMETERS') || 'Body Parameters'
|
|
}}
|
|
</h4>
|
|
<div
|
|
v-for="(variable, key) in processedParams.body"
|
|
:key="`body-${key}`"
|
|
class="flex items-center w-full gap-2 mb-2"
|
|
>
|
|
<span
|
|
class="flex items-center h-8 text-sm min-w-6 ltr:text-left rtl:text-right text-n-slate-10"
|
|
>
|
|
{{ getBodyParameterLabel(key) }}
|
|
</span>
|
|
<Input
|
|
v-model="processedParams.body[key]"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
:message-type="getFieldErrorType(`body.${key}`)"
|
|
:placeholder="getBodyParameterPlaceholder(key)"
|
|
:type="getBodyParameterType(key)"
|
|
:maxlength="getBodyParameterMaxLength(key)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer Variables -->
|
|
<div v-if="processedParams.footer" class="w-full">
|
|
<h4 class="text-sm font-medium text-n-slate-12 mb-2">
|
|
{{
|
|
t('WHATSAPP_TEMPLATES.PARSER.FOOTER_PARAMETERS') ||
|
|
'Footer Parameters'
|
|
}}
|
|
</h4>
|
|
<div
|
|
v-for="(variable, key) in processedParams.footer"
|
|
:key="`footer-${key}`"
|
|
class="flex items-center w-full gap-2 mb-2"
|
|
>
|
|
<span
|
|
class="flex items-center h-8 text-sm min-w-6 ltr:text-left rtl:text-right text-n-slate-10"
|
|
>
|
|
{{ key }}
|
|
</span>
|
|
<Input
|
|
v-model="processedParams.footer[key]"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
:message-type="getFieldErrorType(`footer.${key}`)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Interactive Components -->
|
|
<div v-if="processedParams.interactive" class="w-full">
|
|
<h4 class="text-sm font-medium text-n-slate-12 mb-2">
|
|
{{
|
|
t('WHATSAPP_TEMPLATES.PARSER.INTERACTIVE_PARAMETERS') ||
|
|
'Interactive Parameters'
|
|
}}
|
|
</h4>
|
|
|
|
<!-- Product Template -->
|
|
<div v-if="processedParams.interactive.type === 'product'" class="space-y-2 mb-4">
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">Catalog ID</label>
|
|
<Input
|
|
v-model="processedParams.interactive.catalog_id"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Enter catalog ID"
|
|
:message-type="getFieldErrorType('interactive.catalog_id')"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">Product ID</label>
|
|
<Input
|
|
v-model="processedParams.interactive.product_id"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Enter product retailer ID"
|
|
:message-type="getFieldErrorType('interactive.product_id')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Catalog Template -->
|
|
<div v-else-if="processedParams.interactive.type === 'catalog'" class="space-y-2 mb-4">
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">Catalog ID</label>
|
|
<Input
|
|
v-model="processedParams.interactive.catalog_id"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Enter catalog ID"
|
|
:message-type="getFieldErrorType('interactive.catalog_id')"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">Browse Title</label>
|
|
<Input
|
|
v-model="processedParams.interactive.title"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Browse Products"
|
|
:message-type="getFieldErrorType('interactive.title')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List Template -->
|
|
<div v-else-if="processedParams.interactive.type === 'list'" class="space-y-2 mb-4">
|
|
<div>
|
|
<label class="text-xs font-medium text-n-slate-10 mb-1 block">Button Text</label>
|
|
<Input
|
|
v-model="processedParams.interactive.button_text"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
placeholder="Select an Option"
|
|
:message-type="getFieldErrorType('interactive.button_text')"
|
|
/>
|
|
</div>
|
|
<div class="text-xs text-n-slate-10">
|
|
List sections are configured in the template and cannot be modified here.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Button Variables -->
|
|
<div v-if="processedParams.buttons" class="w-full">
|
|
<h4 class="text-sm font-medium text-n-slate-12 mb-2">
|
|
{{
|
|
t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETERS') ||
|
|
'Button Parameters'
|
|
}}
|
|
</h4>
|
|
<div
|
|
v-for="(button, index) in processedParams.buttons"
|
|
:key="`button-${index}`"
|
|
class="flex items-center w-full gap-2 mb-2"
|
|
>
|
|
<span
|
|
class="flex items-center h-8 text-sm min-w-6 ltr:text-left rtl:text-right text-n-slate-10"
|
|
>
|
|
{{
|
|
button.type === 'copy_code'
|
|
? t('WHATSAPP_TEMPLATES.PARSER.COUPON_CODE') || 'Coupon Code'
|
|
: `${t('WHATSAPP_TEMPLATES.PARSER.BUTTON') || 'Button'} ${index + 1}`
|
|
}}
|
|
</span>
|
|
<Input
|
|
v-model="processedParams.buttons[index].parameter"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
:message-type="getFieldErrorType(`buttons.${index}.parameter`)"
|
|
:placeholder="
|
|
button.type === 'copy_code'
|
|
? 'Enter coupon code (max 15 chars)'
|
|
: 'Enter button parameter'
|
|
"
|
|
:max-length="button.type === 'copy_code' ? 15 : 500"
|
|
@input="validateButtonParameter(button, index)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legacy Variables (for backward compatibility) -->
|
|
<div
|
|
v-for="(variable, key) in legacyParams"
|
|
:key="key"
|
|
class="flex items-center w-full gap-2"
|
|
>
|
|
<span
|
|
class="flex items-center h-8 text-sm min-w-6 ltr:text-left rtl:text-right text-n-slate-10"
|
|
>
|
|
{{ key }}
|
|
</span>
|
|
<Input
|
|
v-model="processedParams[key]"
|
|
custom-input-class="!h-8 w-full !bg-transparent"
|
|
class="w-full"
|
|
:message-type="getFieldErrorType(key)"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-end justify-between w-full gap-3 h-14">
|
|
<Button
|
|
:label="
|
|
t(
|
|
'COMPOSE_NEW_CONVERSATION.FORM.WHATSAPP_OPTIONS.TEMPLATE_PARSER.BACK'
|
|
)
|
|
"
|
|
color="slate"
|
|
variant="faded"
|
|
class="w-full font-medium"
|
|
@click="emit('back')"
|
|
/>
|
|
<Button
|
|
:label="
|
|
t(
|
|
'COMPOSE_NEW_CONVERSATION.FORM.WHATSAPP_OPTIONS.TEMPLATE_PARSER.SEND_MESSAGE'
|
|
)
|
|
"
|
|
class="w-full font-medium"
|
|
@click="sendMessage"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|