Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
661f3065fc | ||
|
|
11fb464501 | ||
|
|
02a4fc9e54 | ||
|
|
43395be4d4 | ||
|
|
7b237372b0 | ||
|
|
aba68158bb | ||
|
|
883d80b144 | ||
|
|
43367d57a8 | ||
|
|
250558c2f6 |
+33
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup>
|
||||||
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-2.5 text-n-slate-12 max-w-80">
|
||||||
|
<div class="p-3 rounded-xl bg-n-alpha-2">
|
||||||
|
<span
|
||||||
|
v-dompurify-html="message.content"
|
||||||
|
class="text-sm font-medium prose prose-bubble"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="message.buttons && message.buttons.length > 0"
|
||||||
|
class="flex flex-col gap-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
v-for="(button, index) in message.buttons"
|
||||||
|
:key="index"
|
||||||
|
:label="button.text || 'Button'"
|
||||||
|
slate
|
||||||
|
class="!text-n-blue-text w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import FileIcon from 'dashboard/components-next/icon/FileIcon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine media type based on URL or template data
|
||||||
|
const getMediaType = () => {
|
||||||
|
if (props.message.originalTemplate?.header?.format) {
|
||||||
|
return props.message.originalTemplate.header.format.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = props.message.image_url;
|
||||||
|
if (!url) return 'document';
|
||||||
|
|
||||||
|
if (url.includes('.pdf') || url.includes('pdf')) return 'document';
|
||||||
|
if (url.includes('.mp4') || url.includes('.mov') || url.includes('video'))
|
||||||
|
return 'video';
|
||||||
|
return 'image';
|
||||||
|
};
|
||||||
|
|
||||||
|
const mediaType = getMediaType();
|
||||||
|
|
||||||
|
// Get file type from URL for proper icon
|
||||||
|
const fileType = computed(() => {
|
||||||
|
const url = props.message.image_url;
|
||||||
|
if (!url) return 'pdf';
|
||||||
|
|
||||||
|
if (url.includes('.pdf') || url.includes('pdf')) return 'pdf';
|
||||||
|
if (url.includes('.doc')) return 'doc';
|
||||||
|
return 'pdf'; // Default to PDF for documents
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-2.5 p-3 rounded-xl bg-n-alpha-2 text-n-slate-12 max-w-80"
|
||||||
|
>
|
||||||
|
<!-- Image Media -->
|
||||||
|
<img
|
||||||
|
v-if="mediaType === 'image'"
|
||||||
|
:src="message.image_url"
|
||||||
|
class="object-cover w-full max-h-44 rounded-lg"
|
||||||
|
alt="Template media"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Video Media -->
|
||||||
|
<div
|
||||||
|
v-else-if="mediaType === 'video'"
|
||||||
|
class="overflow-hidden relative bg-gray-100 rounded-lg"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
:src="message.image_url"
|
||||||
|
class="object-cover w-full max-h-44"
|
||||||
|
controls
|
||||||
|
preload="metadata"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Document Media -->
|
||||||
|
<div v-else-if="mediaType === 'document'" class="flex items-center">
|
||||||
|
<FileIcon :file-type="fileType" class="text-2xl text-n-slate-12" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Text -->
|
||||||
|
<span
|
||||||
|
v-if="message.content"
|
||||||
|
v-dompurify-html="message.content"
|
||||||
|
class="text-sm font-medium prose prose-bubble"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup>
|
||||||
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-n-alpha-2 divide-y divide-n-strong text-n-slate-12 rounded-xl max-w-80"
|
||||||
|
>
|
||||||
|
<div class="p-3">
|
||||||
|
<span
|
||||||
|
v-dompurify-html="message.content"
|
||||||
|
class="prose prose-bubble font-medium text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(action, index) in message.actions"
|
||||||
|
:key="index"
|
||||||
|
class="p-3 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="action.title || action.text || 'Button'"
|
||||||
|
link
|
||||||
|
class="hover:!no-underline"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<svg
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
class="stroke-n-blue-text"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M.667 6.654 5.315.667v3.326c7.968 0 8.878 6.46 8.656 10.007l-.005-.027c-.334-1.79-.474-4.658-8.65-4.658v3.327z"
|
||||||
|
stroke-width="1.333"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
<script setup>
|
||||||
|
import TemplatePreview from './TemplatePreview.vue';
|
||||||
|
|
||||||
|
// Sample template data from the JSON files
|
||||||
|
const whatsAppTemplates = {
|
||||||
|
greet: {
|
||||||
|
id: '997298832221901',
|
||||||
|
name: 'greet',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hey {{customer_name}} how may I help you?',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'John',
|
||||||
|
param_name: 'customer_name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
|
||||||
|
eventInvitation: {
|
||||||
|
id: '1381151706284063',
|
||||||
|
name: 'event_invitation_static',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: "You're invited to {{event_name}} at {{location}}, Join us for an amazing experience!",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'F1',
|
||||||
|
param_name: 'event_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'Dubai',
|
||||||
|
param_name: 'location',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
url: 'https://events.example.com/register',
|
||||||
|
text: 'Visit website',
|
||||||
|
type: 'URL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://maps.app.goo.gl/YoWAzRj1GDuxs6qz8',
|
||||||
|
text: 'Get Directions',
|
||||||
|
type: 'URL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
|
||||||
|
orderConfirmation: {
|
||||||
|
id: '1106685194739985',
|
||||||
|
name: 'order_confirmation',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'IMAGE',
|
||||||
|
example: {
|
||||||
|
header_handle: [
|
||||||
|
'https://scontent.whatsapp.net/v/t61.29466-34/518466505_1106685198073318_3569250580697484416_n.jpg',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Hi your order {{1}} is confirmed. Please wait for further updates',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [['blue canvas shoes']],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
|
||||||
|
discountCoupon: {
|
||||||
|
id: '1469258364071127',
|
||||||
|
name: 'discount_coupon',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: '🎉 Special offer for you! Get {{discount_percentage}}% off your next purchase. Use the code below at checkout',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: '30',
|
||||||
|
param_name: 'discount_percentage',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Copy offer code',
|
||||||
|
type: 'COPY_CODE',
|
||||||
|
example: ['SAVE30OFF'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
|
||||||
|
trainingVideo: {
|
||||||
|
id: '1023596726651144',
|
||||||
|
name: 'training_video',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'VIDEO',
|
||||||
|
example: {
|
||||||
|
header_handle: [
|
||||||
|
'https://scontent.whatsapp.net/v/t61.29466-34/521582686_1023596729984477_1872358575355618432_n.mp4',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Hi {{name}}, here's your training video. Please watch by {{date}}.",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'john',
|
||||||
|
param_name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'July 31',
|
||||||
|
param_name: 'date',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const twilioTemplates = {
|
||||||
|
greet: {
|
||||||
|
body: 'Hey {{1}}, how may I help you?',
|
||||||
|
types: {
|
||||||
|
'twilio/text': {
|
||||||
|
body: 'Hey {{1}}, how may I help you?',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'John',
|
||||||
|
},
|
||||||
|
content_sid: 'HXee240fd3a8b5045dba057feda5173e55',
|
||||||
|
friendly_name: 'greet',
|
||||||
|
template_type: 'text',
|
||||||
|
},
|
||||||
|
|
||||||
|
shoeLaunch: {
|
||||||
|
body: '👟 Introducing our latest release — the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
types: {
|
||||||
|
'twilio/media': {
|
||||||
|
body: '👟 Introducing our latest release — the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
media: ['https://vite-five-phi.vercel.app/jordan-shoes.jpg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'Jordan',
|
||||||
|
2: '100$',
|
||||||
|
},
|
||||||
|
content_sid: 'HX4b1ff075f097ccdf7f274b6af4d7be02',
|
||||||
|
friendly_name: 'shoe_launch',
|
||||||
|
template_type: 'media',
|
||||||
|
},
|
||||||
|
|
||||||
|
welcomeMessage: {
|
||||||
|
body: 'Thanks for reaching out to us. Before we proceed, we would like to get some information from you to assist you better. To whom would you like to connect?',
|
||||||
|
types: {
|
||||||
|
'twilio/quick-reply': {
|
||||||
|
body: 'Thanks for reaching out to us. Before we proceed, we would like to get some information from you to assist you better. To whom would you like to connect?',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'Sales_payload',
|
||||||
|
title: 'Sales',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Support_payload',
|
||||||
|
title: 'Support',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en_US',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HX778677f5867f96175ab4b7efb9a5bee6',
|
||||||
|
friendly_name: 'welcome_message_new',
|
||||||
|
template_type: 'quick_reply',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Story
|
||||||
|
title="Components/TemplatePreview"
|
||||||
|
:layout="{ type: 'grid', width: 400 }"
|
||||||
|
>
|
||||||
|
<!-- WhatsApp Text Templates -->
|
||||||
|
<Variant title="WhatsApp - Simple Text">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.greet"
|
||||||
|
:variables="{ customer_name: 'John' }"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WhatsApp - Simple Text (No Variables)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.greet"
|
||||||
|
:variables="{}"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<!-- WhatsApp Button Templates -->
|
||||||
|
<Variant title="WhatsApp - Call to Action Buttons">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.eventInvitation"
|
||||||
|
:variables="{ event_name: 'F1 Grand Prix', location: 'Dubai' }"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<!-- WhatsApp Media Templates -->
|
||||||
|
<Variant title="WhatsApp - Image Media">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.orderConfirmation"
|
||||||
|
:variables="{ '1': 'Blue Canvas Shoes' }"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WhatsApp - Video Media">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.trainingVideo"
|
||||||
|
:variables="{ name: 'John', date: 'July 31st' }"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<!-- WhatsApp Copy Code Templates -->
|
||||||
|
<Variant title="WhatsApp - Copy Code">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates.discountCoupon"
|
||||||
|
:variables="{ discount_percentage: '30' }"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<!-- Twilio Templates -->
|
||||||
|
<Variant title="Twilio - Text">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates.greet"
|
||||||
|
:variables="{ '1': 'John' }"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio - Media">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates.shoeLaunch"
|
||||||
|
:variables="{ '1': 'Air Jordan', '2': '$150' }"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio - Quick Reply">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates.welcomeMessage"
|
||||||
|
:variables="{}"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
</Story>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { TemplateNormalizer } from 'dashboard/services/TemplateNormalizer';
|
||||||
|
|
||||||
|
import TextTemplate from 'dashboard/components-next/message/bubbles/Template/Text.vue';
|
||||||
|
import CardTemplate from 'dashboard/components-next/message/bubbles/Template/Card.vue';
|
||||||
|
import DynamicCallToActionTemplate from './DynamicCallToActionTemplate.vue';
|
||||||
|
import DynamicMediaTemplate from './DynamicMediaTemplate.vue';
|
||||||
|
import WhatsAppTextTemplate from './WhatsAppTextTemplate.vue';
|
||||||
|
import DynamicQuickReplyTemplate from './DynamicQuickReplyTemplate.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
template: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
platform: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
validator: value => ['whatsapp', 'twilio'].includes(value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Normalize template data and apply variables
|
||||||
|
const processedTemplate = computed(() => {
|
||||||
|
const normalized =
|
||||||
|
props.platform === 'whatsapp'
|
||||||
|
? TemplateNormalizer.normalizeWhatsApp(props.template)
|
||||||
|
: TemplateNormalizer.normalizeTwilio(props.template);
|
||||||
|
|
||||||
|
// Apply variable substitution to content
|
||||||
|
const processText = text => {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.replace(/\{\{([^}]+)\}\}/g, (match, variable) => {
|
||||||
|
const value = props.variables[variable];
|
||||||
|
return value !== undefined && value !== '' ? value : `[${variable}]`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract content based on platform
|
||||||
|
let content = '';
|
||||||
|
let imageUrl = '';
|
||||||
|
let title = '';
|
||||||
|
let footer = '';
|
||||||
|
|
||||||
|
if (props.platform === 'whatsapp') {
|
||||||
|
// WhatsApp: get text from body component
|
||||||
|
content = normalized.body?.text || '';
|
||||||
|
|
||||||
|
// Get header content for media templates
|
||||||
|
if (normalized.header) {
|
||||||
|
if (
|
||||||
|
normalized.header.format === 'IMAGE' ||
|
||||||
|
normalized.header.format === 'VIDEO' ||
|
||||||
|
normalized.header.format === 'DOCUMENT'
|
||||||
|
) {
|
||||||
|
imageUrl = normalized.header.example?.header_handle?.[0] || '';
|
||||||
|
}
|
||||||
|
if (normalized.header.format === 'TEXT') {
|
||||||
|
title = normalized.header.text || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get footer content
|
||||||
|
footer = normalized.footer?.text || '';
|
||||||
|
} else {
|
||||||
|
// Twilio: get body directly
|
||||||
|
content = normalized.body || '';
|
||||||
|
|
||||||
|
// Get media URL if available
|
||||||
|
if (normalized.media && normalized.media.length > 0) {
|
||||||
|
imageUrl = normalized.media[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...normalized,
|
||||||
|
content: processText(content),
|
||||||
|
title: processText(title),
|
||||||
|
footer: processText(footer),
|
||||||
|
image_url: imageUrl,
|
||||||
|
buttons: normalized.buttons || [],
|
||||||
|
actions: normalized.actions || [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Component selection based on template type
|
||||||
|
const previewComponent = computed(() => {
|
||||||
|
const type = processedTemplate.value.type;
|
||||||
|
|
||||||
|
const componentMap = {
|
||||||
|
// WhatsApp components
|
||||||
|
'whatsapp-text': WhatsAppTextTemplate,
|
||||||
|
'whatsapp-text-header': WhatsAppTextTemplate,
|
||||||
|
'whatsapp-media-image': DynamicMediaTemplate,
|
||||||
|
'whatsapp-media-video': DynamicMediaTemplate,
|
||||||
|
'whatsapp-media-document': DynamicMediaTemplate,
|
||||||
|
'whatsapp-interactive': DynamicCallToActionTemplate,
|
||||||
|
'whatsapp-copy-code': DynamicCallToActionTemplate,
|
||||||
|
|
||||||
|
// Twilio components
|
||||||
|
'twilio-text': TextTemplate,
|
||||||
|
'twilio-media': DynamicMediaTemplate,
|
||||||
|
'twilio-quick-reply': DynamicQuickReplyTemplate,
|
||||||
|
'twilio-card': CardTemplate,
|
||||||
|
};
|
||||||
|
|
||||||
|
return componentMap[type] || TextTemplate;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="template-preview">
|
||||||
|
<component :is="previewComponent" :message="processedTemplate" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
<script setup>
|
||||||
|
import TemplatePreview from './TemplatePreview.vue';
|
||||||
|
import {
|
||||||
|
whatsAppTemplates,
|
||||||
|
getWhatsAppVariables,
|
||||||
|
} from './templates/whatsapp-templates.js';
|
||||||
|
import { twilioTemplates } from './templates/twillio-templates.js';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Story
|
||||||
|
title="Components/TemplatePreviewExamples"
|
||||||
|
:layout="{ type: 'grid', width: 400 }"
|
||||||
|
>
|
||||||
|
<!-- WhatsApp Templates -->
|
||||||
|
<Variant title="WA: Event Invitation (Buttons)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[0]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[0])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Purchase Receipt (Document)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[1]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[1])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Discount Coupon (Copy Code)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[2]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[2])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Support Callback (Phone)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[3]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[3])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Training Video (Video)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[4]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[4])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Order Confirmation (Image)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[5]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[5])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Product Launch (Image + Footer)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[6]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[6])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Technician Visit (Header + Buttons)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[7]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[7])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Greet (Simple Text)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[8]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[8])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Hello World (Header + Footer)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[9]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[9])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Feedback Request (Button)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[10]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[10])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Address Update (Header)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[11]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[11])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="WA: Delivery Confirmation">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="whatsAppTemplates[12]"
|
||||||
|
:variables="getWhatsAppVariables(whatsAppTemplates[12])"
|
||||||
|
platform="whatsapp"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<!-- Twilio Templates -->
|
||||||
|
<Variant title="Twilio: Shoe Launch (Media)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[0]"
|
||||||
|
:variables="twilioTemplates[0].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Product Launch Custom Price (Media)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[1]"
|
||||||
|
:variables="twilioTemplates[1].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Product Launch (Media)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[2]"
|
||||||
|
:variables="twilioTemplates[2].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Greet (Text)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[3]"
|
||||||
|
:variables="twilioTemplates[3].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Order Status (Text)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[4]"
|
||||||
|
:variables="twilioTemplates[4].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Hello World (Text)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[5]"
|
||||||
|
:variables="twilioTemplates[5].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Welcome Message New (Quick Reply)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[6]"
|
||||||
|
:variables="twilioTemplates[6].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: SaaS WhatsApp Question (Quick Reply)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[7]"
|
||||||
|
:variables="twilioTemplates[7].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
|
||||||
|
<Variant title="Twilio: Welcome Message (Quick Reply)">
|
||||||
|
<TemplatePreview
|
||||||
|
:template="twilioTemplates[8]"
|
||||||
|
:variables="twilioTemplates[8].variables"
|
||||||
|
platform="twilio"
|
||||||
|
/>
|
||||||
|
</Variant>
|
||||||
|
</Story>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-n-alpha-2 text-n-slate-12 rounded-xl p-3 max-w-80 flex flex-col gap-2"
|
||||||
|
>
|
||||||
|
<!-- Header Text -->
|
||||||
|
<div v-if="message.title" class="font-bold text-base">
|
||||||
|
{{ message.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Body Content -->
|
||||||
|
<div v-if="message.content" class="prose prose-bubble font-medium text-sm">
|
||||||
|
<span v-dompurify-html="message.content" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Text -->
|
||||||
|
<div v-if="message.footer" class="text-xs text-n-slate-11 opacity-70">
|
||||||
|
{{ message.footer }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Main Template Preview Component
|
||||||
|
export { default as TemplatePreview } from './TemplatePreview.vue';
|
||||||
|
|
||||||
|
// Core Services
|
||||||
|
export { TemplateTypeDetector } from 'dashboard/services/TemplateTypeDetector';
|
||||||
|
export { TemplateNormalizer } from 'dashboard/services/TemplateNormalizer';
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
export const twilioTemplates = [
|
||||||
|
{
|
||||||
|
body: '=_ Introducing our latest release the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
types: {
|
||||||
|
'twilio/media': {
|
||||||
|
body: '=_ Introducing our latest release the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
media: ['https://vite-five-phi.vercel.app/jordan-shoes.jpg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'Jordan',
|
||||||
|
2: '100$',
|
||||||
|
},
|
||||||
|
content_sid: 'HX4b1ff075f097ccdf7f274b6af4d7be02',
|
||||||
|
friendly_name: 'shoe_launch',
|
||||||
|
template_type: 'media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: '=_ Introducing our latest release the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
types: {
|
||||||
|
'twilio/media': {
|
||||||
|
body: '=_ Introducing our latest release the {{1}}! Available now for just {{2}}. Be among the first to own this style. Limited stock available!',
|
||||||
|
media: ['https://vite-five-phi.vercel.app/jordan-shoes.jpg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'Jordan',
|
||||||
|
2: '400$',
|
||||||
|
},
|
||||||
|
content_sid: 'HXd5c1f8f8d68976f841c440d5e4b46c2e',
|
||||||
|
friendly_name: 'product_launch_custom_price',
|
||||||
|
template_type: 'media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: '=_ Introducing our latest release the Nike Air Force! Available now for just $129.99',
|
||||||
|
types: {
|
||||||
|
'twilio/media': {
|
||||||
|
body: '=_ Introducing our latest release the Nike Air Force! Available now for just $129.99',
|
||||||
|
media: ['https://vite-five-phi.vercel.app/jordan-shoes.jpg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HX25f6e823f2416ca4b34254d98e916fae',
|
||||||
|
friendly_name: 'product_launch',
|
||||||
|
template_type: 'media',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'Hey {{1}}, how may I help you?',
|
||||||
|
types: {
|
||||||
|
'twilio/text': {
|
||||||
|
body: 'Hey {{1}}, how may I help you?',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'John',
|
||||||
|
},
|
||||||
|
content_sid: 'HXee240fd3a8b5045dba057feda5173e55',
|
||||||
|
friendly_name: 'greet',
|
||||||
|
template_type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: "Hi {{1}} Thanks for placing an order with us. We'll let you know once your order has been processed and delivered. Your order number is {{3}}. Thanks",
|
||||||
|
types: {
|
||||||
|
'twilio/text': {
|
||||||
|
body: "Hi {{1}} Thanks for placing an order with us. We'll let you know once your order has been processed and delivered. Your order number is {{3}}. Thanks",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {
|
||||||
|
1: 'John',
|
||||||
|
3: '12345',
|
||||||
|
},
|
||||||
|
content_sid: 'HX88291ef8d30d7dcd436cbb9b21c236f4',
|
||||||
|
friendly_name: 'order_status',
|
||||||
|
template_type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'Welcome and congratulations!! This message demonstrates your ability to send a WhatsApp message notification from the Cloud API, hosted by Meta. Thank you for taking the time to test with us.',
|
||||||
|
types: {
|
||||||
|
'twilio/text': {
|
||||||
|
body: 'Welcome and congratulations!! This message demonstrates your ability to send a WhatsApp message notification from the Cloud API, hosted by Meta. Thank you for taking the time to test with us.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HXdc6da32d489ee80f67c07d5bb0e7e390',
|
||||||
|
friendly_name: 'hello_world',
|
||||||
|
template_type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'Thanks for reaching out to us. Before we proceed, we would like to get some information from you to assist you better. To whom would you like to connect?',
|
||||||
|
types: {
|
||||||
|
'twilio/quick-reply': {
|
||||||
|
body: 'Thanks for reaching out to us. Before we proceed, we would like to get some information from you to assist you better. To whom would you like to connect?',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'Sales_payload',
|
||||||
|
title: 'Sales',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Support_payload',
|
||||||
|
title: 'Support',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en_US',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HX778677f5867f96175ab4b7efb9a5bee6',
|
||||||
|
friendly_name: 'welcome_message_new',
|
||||||
|
template_type: 'quick_reply',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'What type of Chatwoot installation are you using? Select "Chatwoot Cloud" if you are using app.chatwoot.com, otherwise select "Self-hosted Chatwoot".',
|
||||||
|
types: {
|
||||||
|
'twilio/quick-reply': {
|
||||||
|
body: 'What type of Chatwoot installation are you using? Select "Chatwoot Cloud" if you are using app.chatwoot.com, otherwise select "Self-hosted Chatwoot".',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'Chatwoot Cloud_payload',
|
||||||
|
title: 'Chatwoot Cloud',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Self-hosted Chatwoot_payload',
|
||||||
|
title: 'Self-hosted Chatwoot',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en_US',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HX3a35e5cd76529fd91d19341deb4ef685',
|
||||||
|
friendly_name: 'saas_whatsapp_question',
|
||||||
|
template_type: 'quick_reply',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'Thanks for reaching out to us. Happy to help. What are you looking for?',
|
||||||
|
types: {
|
||||||
|
'twilio/quick-reply': {
|
||||||
|
body: 'Thanks for reaching out to us. Happy to help. What are you looking for?',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'Support_payload',
|
||||||
|
title: 'Support',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Sales_payload',
|
||||||
|
title: 'Sales',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Demo_payload',
|
||||||
|
title: 'Demo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'approved',
|
||||||
|
category: 'utility',
|
||||||
|
language: 'en_US',
|
||||||
|
variables: {},
|
||||||
|
content_sid: 'HX5d8e09f96cee2f7fb7bab223c03cb0a1',
|
||||||
|
friendly_name: 'welcome_message',
|
||||||
|
template_type: 'quick_reply',
|
||||||
|
},
|
||||||
|
];
|
||||||
+408
@@ -0,0 +1,408 @@
|
|||||||
|
export const whatsAppTemplates = [
|
||||||
|
{
|
||||||
|
id: '1381151706284063',
|
||||||
|
name: 'event_invitation_static',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: "You're invited to {{event_name}} at {{location}}, Join us for an amazing experience!",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'F1',
|
||||||
|
param_name: 'event_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'Dubai',
|
||||||
|
param_name: 'location',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
url: 'https://events.example.com/register',
|
||||||
|
text: 'Visit website',
|
||||||
|
type: 'URL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://maps.app.goo.gl/YoWAzRj1GDuxs6qz8',
|
||||||
|
text: 'Get Directions',
|
||||||
|
type: 'URL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '767076159336759',
|
||||||
|
name: 'purchase_receipt',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en_US',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'DOCUMENT',
|
||||||
|
example: {
|
||||||
|
header_handle: [
|
||||||
|
'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Thank you for using your {{1}} card at {{2}}. Your {{3}} is attached as a PDF.',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [['credit', 'CS Mutual', 'receipt']],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1469258364071127',
|
||||||
|
name: 'discount_coupon',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: '<� Special offer for you! Get {{discount_percentage}}% off your next purchase. Use the code below at checkout',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: '30',
|
||||||
|
param_name: 'discount_percentage',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Copy offer code',
|
||||||
|
type: 'COPY_CODE',
|
||||||
|
example: ['SAVE1OFF'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1075221534579807',
|
||||||
|
name: 'support_callback',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hello {{name}}, our support team will call you regarding ticket # {{ticket_id}}.',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'muhsin',
|
||||||
|
param_name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: '232323',
|
||||||
|
param_name: 'ticket_id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Call Support',
|
||||||
|
type: 'PHONE_NUMBER',
|
||||||
|
phone_number: '+16506677566',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1023596726651144',
|
||||||
|
name: 'training_video',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'VIDEO',
|
||||||
|
example: {
|
||||||
|
header_handle: [
|
||||||
|
'https://scontent.whatsapp.net/v/t61.29466-34/521582686_1023596729984477_1872358575355618432_n.mp4',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Hi {{name}}, here's your training video. Please watch by{{date}}.",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'john',
|
||||||
|
param_name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'July 31',
|
||||||
|
param_name: 'date',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1106685194739985',
|
||||||
|
name: 'order_confirmation',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'IMAGE',
|
||||||
|
example: {
|
||||||
|
header_handle: ['https://vite-five-phi.vercel.app/vaporfly.png'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Hi your order {{1}} is confirmed. Please wait for further updates',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [['blue canvas shoes']],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1242180011253003',
|
||||||
|
name: 'product_launch',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'IMAGE',
|
||||||
|
example: {
|
||||||
|
header_handle: ['https://vite-five-phi.vercel.app/coat.png'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'New arrival! Our stunning coat now available in {{color}} color.',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'blue',
|
||||||
|
param_name: 'color',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Free shipping on orders over $100. Limited time offer.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1449876326175680',
|
||||||
|
name: 'technician_visit',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en_US',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Technician visit',
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Hi {{1}}, we're scheduling a technician visit to {{2}} on {{3}} between {{4}} and {{5}}. Please confirm if this time slot works for you.",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [
|
||||||
|
['John', '123 Maple St', '2025-12-31', '10:00 AM', '2:00 PM'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Confirm',
|
||||||
|
type: 'QUICK_REPLY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Reschedule',
|
||||||
|
type: 'QUICK_REPLY',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '997298832221901',
|
||||||
|
name: 'greet',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hey {{customer_name}} how may I help you?',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'John',
|
||||||
|
param_name: 'customer_name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '632315222954611',
|
||||||
|
name: 'hello_world',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en_US',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hello World',
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Welcome and congratulations!! This message demonstrates your ability to send a WhatsApp message notification from the Cloud API, hosted by Meta. Thank you for taking the time to test with us.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'WhatsApp Business Platform sample message',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '787864066907971',
|
||||||
|
name: 'feedback_request',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'MARKETING',
|
||||||
|
language: 'en',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: "Hey {{name}}, how was your experience with Puma? We'd love your feedback!",
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text_named_params: [
|
||||||
|
{
|
||||||
|
example: 'muhsin',
|
||||||
|
param_name: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
url: 'https://feedback.example.com/survey',
|
||||||
|
text: 'Leave Feedback',
|
||||||
|
type: 'URL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sub_category: 'CUSTOM',
|
||||||
|
parameter_format: 'NAMED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1938057163677205',
|
||||||
|
name: 'address_update',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en_US',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Address update',
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Hi {{1}}, your delivery address has been successfully updated to {{2}}. Contact {{3}} for any inquiries.',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [['John', '123 Main St', 'support@telco.com']],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1644094842949394',
|
||||||
|
name: 'delivery_confirmation',
|
||||||
|
status: 'APPROVED',
|
||||||
|
category: 'UTILITY',
|
||||||
|
language: 'en_US',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: '{{1}}, your order was successfully delivered on {{2}}.\\n\\nThank you for your purchase.\\n',
|
||||||
|
type: 'BODY',
|
||||||
|
example: {
|
||||||
|
body_text: [['John', 'Jan 1, 2024']],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parameter_format: 'POSITIONAL',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper function to get variable values from examples
|
||||||
|
export const getWhatsAppVariables = template => {
|
||||||
|
const variables = {};
|
||||||
|
template.components?.forEach(component => {
|
||||||
|
if (component.example?.body_text_named_params) {
|
||||||
|
component.example.body_text_named_params.forEach(param => {
|
||||||
|
variables[param.param_name] = param.example;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (component.example?.body_text) {
|
||||||
|
component.example.body_text[0]?.forEach((value, index) => {
|
||||||
|
variables[(index + 1).toString()] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return variables;
|
||||||
|
};
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { TemplateTypeDetector } from './TemplateTypeDetector';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TemplateNormalizer - Convert platform-specific formats to unified structure
|
||||||
|
*/
|
||||||
|
export class TemplateNormalizer {
|
||||||
|
/**
|
||||||
|
* Normalize WhatsApp template to unified format
|
||||||
|
* @param {Object} template - WhatsApp template object
|
||||||
|
* @returns {Object} - Normalized template
|
||||||
|
*/
|
||||||
|
static normalizeWhatsApp(template) {
|
||||||
|
const components = template.components || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: template.id,
|
||||||
|
name: template.name,
|
||||||
|
platform: 'whatsapp',
|
||||||
|
type: TemplateTypeDetector.detectWhatsAppType(template),
|
||||||
|
parameterFormat: template.parameter_format || 'POSITIONAL',
|
||||||
|
header: components.find(c => c.type === 'HEADER'),
|
||||||
|
body: components.find(c => c.type === 'BODY'),
|
||||||
|
footer: components.find(c => c.type === 'FOOTER'),
|
||||||
|
buttons: components.find(c => c.type === 'BUTTONS')?.buttons || [],
|
||||||
|
variables: this.extractWhatsAppVariables(template),
|
||||||
|
category: template.category,
|
||||||
|
language: template.language,
|
||||||
|
originalTemplate: template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize Twilio template to unified format
|
||||||
|
* @param {Object} template - Twilio template object
|
||||||
|
* @returns {Object} - Normalized template
|
||||||
|
*/
|
||||||
|
static normalizeTwilio(template) {
|
||||||
|
// Convert template_type to match the keys used in the types object
|
||||||
|
const normalizedType = template.template_type.replace('_', '-');
|
||||||
|
const lookupKey = `twilio/${normalizedType}`;
|
||||||
|
const typeData = template.types?.[lookupKey] || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentSid: template.content_sid,
|
||||||
|
name: template.friendly_name,
|
||||||
|
platform: 'twilio',
|
||||||
|
type: TemplateTypeDetector.detectTwilioType(template),
|
||||||
|
body: template.body || typeData.body,
|
||||||
|
media: typeData.media || [],
|
||||||
|
actions: typeData.actions || [],
|
||||||
|
variables: template.variables || {},
|
||||||
|
category: template.category || 'utility',
|
||||||
|
language: template.language || 'en',
|
||||||
|
originalTemplate: template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract variables from WhatsApp template
|
||||||
|
* @param {Object} template - WhatsApp template object
|
||||||
|
* @returns {Object} - Variables object with example values
|
||||||
|
*/
|
||||||
|
static extractWhatsAppVariables(template) {
|
||||||
|
const variables = {};
|
||||||
|
const components = template.components || [];
|
||||||
|
|
||||||
|
components.forEach(component => {
|
||||||
|
// Extract from text content
|
||||||
|
if (component.text) {
|
||||||
|
const matches = component.text.match(/\{\{([^}]+)\}\}/g) || [];
|
||||||
|
matches.forEach(match => {
|
||||||
|
const variable = match.replace(/[{}]/g, '');
|
||||||
|
|
||||||
|
if (template.parameter_format === 'NAMED') {
|
||||||
|
// Named parameters: {{customer_name}}
|
||||||
|
const example =
|
||||||
|
component.example?.body_text_named_params?.find(
|
||||||
|
p => p.param_name === variable
|
||||||
|
)?.example || '';
|
||||||
|
variables[variable] = example;
|
||||||
|
} else {
|
||||||
|
// Positional parameters: {{1}}, {{2}}
|
||||||
|
const position = parseInt(variable, 10) - 1;
|
||||||
|
const example = component.example?.body_text?.[0]?.[position] || '';
|
||||||
|
variables[variable] = example;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract from button URLs
|
||||||
|
if (component.buttons) {
|
||||||
|
component.buttons.forEach(button => {
|
||||||
|
if (button.url) {
|
||||||
|
const matches = button.url.match(/\{\{([^}]+)\}\}/g) || [];
|
||||||
|
matches.forEach(match => {
|
||||||
|
const variable = match.replace(/[{}]/g, '');
|
||||||
|
const example = button.example?.[0] || '';
|
||||||
|
variables[variable] = example;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract variables from Twilio template
|
||||||
|
* @param {Object} template - Twilio template object
|
||||||
|
* @returns {Object} - Variables object with example values
|
||||||
|
*/
|
||||||
|
static extractTwilioVariables(template) {
|
||||||
|
const variables = {};
|
||||||
|
|
||||||
|
// Extract from body text
|
||||||
|
if (template.body) {
|
||||||
|
const matches = template.body.match(/\{\{([^}]+)\}\}/g) || [];
|
||||||
|
matches.forEach(match => {
|
||||||
|
const variable = match.replace(/[{}]/g, '');
|
||||||
|
variables[variable] = template.variables?.[variable] || '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract from media URLs
|
||||||
|
const typeData = template.types?.[`twilio/${template.template_type}`] || {};
|
||||||
|
if (typeData.media) {
|
||||||
|
typeData.media.forEach(mediaUrl => {
|
||||||
|
const matches = mediaUrl.match(/\{\{([^}]+)\}\}/g) || [];
|
||||||
|
matches.forEach(match => {
|
||||||
|
const variable = match.replace(/[{}]/g, '');
|
||||||
|
variables[variable] = template.variables?.[variable] || '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize template from any platform
|
||||||
|
* @param {Object} template - Template object
|
||||||
|
* @param {string} platform - Platform identifier ('whatsapp' | 'twilio')
|
||||||
|
* @returns {Object} - Normalized template
|
||||||
|
*/
|
||||||
|
static normalize(template, platform) {
|
||||||
|
switch (platform) {
|
||||||
|
case 'whatsapp':
|
||||||
|
return this.normalizeWhatsApp(template);
|
||||||
|
case 'twilio':
|
||||||
|
return this.normalizeTwilio(template);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* TemplateTypeDetector - Unified service to identify template types across platforms
|
||||||
|
*/
|
||||||
|
export class TemplateTypeDetector {
|
||||||
|
/**
|
||||||
|
* Detect WhatsApp template type based on components
|
||||||
|
* @param {Object} template - WhatsApp template object
|
||||||
|
* @returns {string} - Template type identifier
|
||||||
|
*/
|
||||||
|
static detectWhatsAppType(template) {
|
||||||
|
const components = template.components || [];
|
||||||
|
const hasHeader = components.find(c => c.type === 'HEADER');
|
||||||
|
const hasButtons = components.find(c => c.type === 'BUTTONS');
|
||||||
|
|
||||||
|
// Media templates (priority: header media)
|
||||||
|
if (hasHeader?.format === 'IMAGE') return 'whatsapp-media-image';
|
||||||
|
if (hasHeader?.format === 'VIDEO') return 'whatsapp-media-video';
|
||||||
|
if (hasHeader?.format === 'DOCUMENT') return 'whatsapp-media-document';
|
||||||
|
|
||||||
|
// Interactive templates (priority: special buttons)
|
||||||
|
if (hasButtons) {
|
||||||
|
const copyCodeButton = hasButtons.buttons?.find(
|
||||||
|
b => b.type === 'COPY_CODE'
|
||||||
|
);
|
||||||
|
if (copyCodeButton) return 'whatsapp-copy-code';
|
||||||
|
return 'whatsapp-interactive';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text templates
|
||||||
|
if (hasHeader?.format === 'TEXT') return 'whatsapp-text-header';
|
||||||
|
return 'whatsapp-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect Twilio template type
|
||||||
|
* @param {Object} template - Twilio template object
|
||||||
|
* @returns {string} - Template type identifier
|
||||||
|
*/
|
||||||
|
static detectTwilioType(template) {
|
||||||
|
switch (template.template_type) {
|
||||||
|
case 'media':
|
||||||
|
return 'twilio-media';
|
||||||
|
case 'quick_reply':
|
||||||
|
return 'twilio-quick-reply';
|
||||||
|
default:
|
||||||
|
return 'twilio-text';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all supported template types
|
||||||
|
* @returns {Array} - Array of supported template types
|
||||||
|
*/
|
||||||
|
static getSupportedTypes() {
|
||||||
|
return [
|
||||||
|
// WhatsApp types
|
||||||
|
'whatsapp-text',
|
||||||
|
'whatsapp-text-header',
|
||||||
|
'whatsapp-media-image',
|
||||||
|
'whatsapp-media-video',
|
||||||
|
'whatsapp-media-document',
|
||||||
|
'whatsapp-interactive',
|
||||||
|
'whatsapp-copy-code',
|
||||||
|
|
||||||
|
// Twilio types
|
||||||
|
'twilio-text',
|
||||||
|
'twilio-media',
|
||||||
|
'twilio-quick-reply',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user