chore: add playground

This commit is contained in:
Muhsin Keloth
2025-08-17 17:55:56 +05:30
parent 51d0acb90c
commit 090d066f9f
@@ -1,5 +1,5 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'dashboard/composables/store';
@@ -13,102 +13,174 @@ const props = defineProps({
const { t } = useI18n();
const store = useStore();
const allCampaigns = computed(() => store.getters['campaigns/getAllCampaigns']);
const campaign = computed(() =>
allCampaigns.value.find(c => c.id === Number(props.campaignId))
);
const previewData = ref({
title: '',
message: '',
template: '',
audience: '',
campaignType: '',
const campaign = computed(() => {
const allCampaigns = store.getters['campaigns/getAllCampaigns'];
return allCampaigns.find(c => c.id === props.campaignId);
});
const updatePreview = () => {
if (campaign.value) {
previewData.value = {
title: campaign.value.title || '',
message: campaign.value.message || '',
template: campaign.value.template || '',
audience: campaign.value.audience || '',
campaignType: campaign.value.campaign_type || '',
};
}
// Template data based on templates.json examples
const templateExamples = {
training_video: {
header: { type: 'VIDEO', content: '🎥 Video Header' },
body: "Hi {{name}}, here's your training video. Please watch by {{date}}.",
variables: { name: 'John', date: ' July 31' },
},
discount_coupon: {
body: '🎉 Special offer for you! Get {{discount_percentage}}% off your next purchase. Use the code below at checkout',
buttons: [{ type: 'COPY_CODE', text: 'Copy offer code' }],
variables: { discount_percentage: '30' },
},
support_callback: {
body: 'Hello {{name}}, our support team will call you regarding ticket # {{ticket_id}}.',
buttons: [{ type: 'PHONE_NUMBER', text: 'Call Support' }],
variables: { name: 'muhsin', ticket_id: '232323' },
},
feedback_request: {
body: "Hey {{name}}, how was your experience with Puma? We'd love your feedback!",
buttons: [{ type: 'URL', text: 'Leave Feedback' }],
variables: { name: 'muhsin' },
},
order_confirmation: {
header: { type: 'IMAGE', content: '🖼️ Product Image' },
body: 'Welcome to our Diwali sale! Get flat 50% off on select items. Hurry now!',
},
technician_visit: {
header: { type: 'TEXT', content: 'Technician visit' },
body: "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.",
buttons: [
{ type: 'QUICK_REPLY', text: 'Confirm' },
{ type: 'QUICK_REPLY', text: 'Reschedule' },
],
variables: {
1: 'John',
2: '123 Maple St',
3: '2025-12-31',
4: '10:00 AM',
5: '2:00 PM',
},
},
};
watch(
() => campaign.value,
() => updatePreview(),
{ immediate: true, deep: true }
);
const getTemplatePreview = () => {
// Default template if none selected
const defaultTemplate = {
body: 'Hey there! Thanks for reaching out to us. Could you let us know what you need to help us better assist you?',
buttons: [{ type: 'URL', text: 'See options' }],
};
if (!campaign.value?.template_params?.name) {
return defaultTemplate;
}
const templateName = campaign.value.template_params.name;
const templateExample = templateExamples[templateName];
if (templateExample) {
let processedBody = templateExample.body;
// Replace variables with example values
if (templateExample.variables) {
Object.entries(templateExample.variables).forEach(([key, value]) => {
// Handle both positional ({{1}}) and named ({{name}}) variables
const pattern = `{{${key}}}`;
processedBody = processedBody.replace(
new RegExp(pattern.replace(/[{}]/g, '\\$&'), 'g'),
value
);
});
}
return {
header: templateExample.header,
body: processedBody,
buttons: templateExample.buttons,
};
}
return defaultTemplate;
};
const templatePreview = computed(() => getTemplatePreview());
</script>
<template>
<div class="p-4 h-full bg-white rounded-lg border border-n-slate-4">
<h3 class="mb-4 text-lg font-semibold">
{{ t('CAMPAIGN.PLAYGROUND.TITLE') }}
</h3>
<div class="h-full bg-white rounded-lg border border-n-slate-4">
<!-- Header -->
<div class="p-4 border-b border-n-slate-4">
<h3 class="text-lg font-semibold text-n-slate-12">
{{ t('CAMPAIGN.PLAYGROUND.TITLE') }}
</h3>
</div>
<div class="space-y-4">
<!-- Campaign Title Preview -->
<div class="p-4 bg-gray-50 rounded-lg border">
<h4 class="mb-2 text-sm font-medium text-gray-700">
{{ t('CAMPAIGN.PLAYGROUND.CAMPAIGN_TITLE') }}
</h4>
<p class="text-sm text-gray-900">
{{ previewData.title || t('CAMPAIGN.PLAYGROUND.NO_TITLE') }}
</p>
<!-- WhatsApp Preview Container -->
<div class="flex flex-col p-4 h-96">
<!-- WhatsApp-style Message Bubble -->
<div class="flex justify-center">
<div class="max-w-xs">
<div
class="p-3 rounded-lg border bg-n-slate-2 text-n-slate-12 border-n-slate-4"
>
<!-- Header Media/Text (if exists) -->
<div v-if="templatePreview.header" class="mb-2">
<div
v-if="templatePreview.header.type === 'IMAGE'"
class="p-2 -m-3 mb-2 text-xs text-center rounded-t-lg bg-n-slate-3"
>
{{ templatePreview.header.content }}
</div>
<div
v-else-if="templatePreview.header.type === 'VIDEO'"
class="p-2 -m-3 mb-2 text-xs text-center rounded-t-lg bg-n-slate-3"
>
{{ templatePreview.header.content }}
</div>
<div
v-else-if="templatePreview.header.type === 'TEXT'"
class="mb-1 text-sm font-semibold"
>
{{ templatePreview.header.content }}
</div>
</div>
<!-- Message Body -->
<div class="text-sm leading-relaxed whitespace-pre-wrap">
{{ templatePreview.body }}
</div>
<!-- Action Buttons -->
<div v-if="templatePreview.buttons" class="-m-3 mt-2 mt-3">
<div class="pt-2 border-t border-n-slate-4">
<div
v-for="(button, index) in templatePreview.buttons"
:key="index"
class="py-2 text-sm font-medium text-center border-b transition-colors cursor-pointer border-n-slate-4 last:border-b-0 hover:bg-n-slate-3 text-n-slate-12"
>
<!-- eslint-disable-next-line vue/no-bare-strings-in-template, @intlify/vue-i18n/no-raw-text -->
<span v-if="button.type === 'URL'">🔗</span>
<!-- eslint-disable-next-line vue/no-bare-strings-in-template, @intlify/vue-i18n/no-raw-text -->
<span v-else-if="button.type === 'PHONE_NUMBER'">📞</span>
<!-- eslint-disable-next-line vue/no-bare-strings-in-template, @intlify/vue-i18n/no-raw-text -->
<span v-else-if="button.type === 'COPY_CODE'">📋</span>
{{ button.text }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Message Preview -->
<div class="p-4 bg-gray-50 rounded-lg border">
<h4 class="mb-2 text-sm font-medium text-gray-700">
{{ t('CAMPAIGN.PLAYGROUND.MESSAGE_PREVIEW') }}
</h4>
<div
class="text-sm text-gray-900 whitespace-pre-wrap"
v-html="previewData.message || t('CAMPAIGN.PLAYGROUND.NO_MESSAGE')"
/>
</div>
<!-- Template Info -->
<div v-if="previewData.template" class="p-4 bg-blue-50 rounded-lg border">
<h4 class="mb-2 text-sm font-medium text-blue-700">
{{ t('CAMPAIGN.PLAYGROUND.TEMPLATE') }}
</h4>
<p class="text-sm text-blue-900">
{{ previewData.template }}
</p>
</div>
<!-- Audience Info -->
<div
v-if="previewData.audience"
class="p-4 bg-green-50 rounded-lg border"
>
<h4 class="mb-2 text-sm font-medium text-green-700">
{{ t('CAMPAIGN.PLAYGROUND.AUDIENCE') }}
</h4>
<p class="text-sm text-green-900">
{{ previewData.audience }}
</p>
</div>
<!-- Campaign Type -->
<div class="p-4 bg-yellow-50 rounded-lg border">
<h4 class="mb-2 text-sm font-medium text-yellow-700">
{{ t('CAMPAIGN.PLAYGROUND.CAMPAIGN_TYPE') }}
</h4>
<p class="text-sm text-yellow-900">
{{ previewData.campaignType || t('CAMPAIGN.PLAYGROUND.NO_TYPE') }}
</p>
</div>
<!-- Spacer -->
<div class="flex-1 min-h-4" />
<!-- Preview Note -->
<div class="mt-4 text-xs italic text-gray-500">
{{ t('CAMPAIGN.PLAYGROUND.PREVIEW_NOTE') }}
<div class="p-3 rounded-lg bg-n-alpha-2">
<!-- eslint-disable-next-line vue/no-bare-strings-in-template, @intlify/vue-i18n/no-raw-text -->
<p class="text-xs leading-relaxed text-center text-n-slate-11">
This is a preview of how your campaign will appear to recipients and
it may vary slightly per OS and channel. Perform a live test to
confirm. Default value for variables highlighted will be stored upon
saving.
</p>
</div>
</div>
</div>