chore: add more specs

This commit is contained in:
Muhsin Keloth
2025-07-31 12:55:09 +04:00
parent 6d86c5739c
commit 356946a413
10 changed files with 1115 additions and 484 deletions
@@ -0,0 +1,269 @@
<script setup>
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 {
buildTemplateParameters,
allKeysRequired,
replaceTemplateVariables,
populateAuthenticationButtonParameters,
} from 'dashboard/helper/templateHelper';
const props = defineProps({
template: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(['sendMessage', 'resetTemplate', 'back']);
const { t } = useI18n();
const processedParams = ref({});
const languageLabel = computed(() => {
return `${t('WHATSAPP_TEMPLATES.PARSER.LANGUAGE')}: ${props.template.language || 'en'}`;
});
const categoryLabel = computed(() => {
return `${t('WHATSAPP_TEMPLATES.PARSER.CATEGORY')}: ${props.template.category || 'UTILITY'}`;
});
const headerComponent = computed(() => {
return props.template.components.find(
component => component.type === 'HEADER'
);
});
const bodyComponent = computed(() => {
return props.template.components.find(component => component.type === 'BODY');
});
const bodyText = computed(() => {
return bodyComponent.value.text;
});
const hasMediaHeader = computed(() => {
return (
headerComponent.value &&
headerComponent.value.format &&
['IMAGE', 'VIDEO', 'DOCUMENT'].includes(headerComponent.value.format)
);
});
const hasVariables = computed(() => {
return bodyText.value.match(/{{([^}]+)}}/g);
});
const renderedTemplate = computed(() => {
return replaceTemplateVariables(bodyText.value, processedParams.value);
});
const v$ = useVuelidate(
{
processedParams: {
requiredIfKeysPresent: requiredIf(hasVariables),
allKeysRequired,
},
},
{ processedParams }
);
const initializeTemplateParameters = () => {
const templateParameters = buildTemplateParameters(
props.template,
hasMediaHeader.value
);
processedParams.value = templateParameters;
};
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;
const finalParams = populateAuthenticationButtonParameters(
props.template,
processedParams.value
);
const payload = {
message: renderedTemplate.value,
templateParams: {
name: props.template.name,
category: props.template.category,
language: props.template.language,
namespace: props.template.namespace,
processed_params: finalParams,
},
};
emit('sendMessage', payload);
};
const resetTemplate = () => {
emit('resetTemplate');
};
const goBack = () => {
emit('back');
};
onMounted(initializeTemplateParameters);
defineExpose({
processedParams,
hasVariables,
hasMediaHeader,
headerComponent,
renderedTemplate,
v$,
updateMediaUrl,
sendMessage,
resetTemplate,
goBack,
});
</script>
<template>
<div>
<div class="flex flex-col gap-4 p-4 mb-4 rounded-lg bg-n-alpha-black2">
<div class="flex justify-between items-center">
<h3 class="text-sm font-medium text-n-slate-12">
{{ template.name }}
</h3>
<span class="text-xs text-n-slate-11">
{{ languageLabel }}
</span>
</div>
<div class="flex flex-col gap-2">
<div class="rounded-md bg-n-alpha-black3">
<div class="text-sm whitespace-pre-wrap text-n-slate-12">
{{ renderedTemplate }}
</div>
</div>
</div>
<div class="text-xs text-n-slate-11">
{{ categoryLabel }}
</div>
</div>
<div v-if="hasVariables || hasMediaHeader">
<div v-if="hasMediaHeader" class="mb-4">
<p class="mb-2.5 text-sm font-semibold">
{{
$t('WHATSAPP_TEMPLATES.PARSER.MEDIA_HEADER_LABEL', {
type:
headerComponent.format.charAt(0) +
headerComponent.format.slice(1).toLowerCase(),
}) ||
`${
headerComponent.format.charAt(0) +
headerComponent.format.slice(1).toLowerCase()
} Header`
}}
</p>
<div class="flex items-center mb-2.5">
<Input
:model-value="processedParams.header?.media_url || ''"
type="url"
class="flex-1"
:placeholder="
t('WHATSAPP_TEMPLATES.PARSER.MEDIA_URL_LABEL', {
type:
headerComponent.format.charAt(0) +
headerComponent.format.slice(1).toLowerCase(),
})
"
@update:model-value="updateMediaUrl"
/>
</div>
</div>
<!-- Body Variables Section -->
<div v-if="processedParams.body">
<p class="mb-2.5 text-sm font-semibold">
{{ $t('WHATSAPP_TEMPLATES.PARSER.VARIABLES_LABEL') }}
</p>
<div
v-for="(variable, key) in processedParams.body"
:key="`body-${key}`"
class="flex items-center mb-2.5"
>
<Input
v-model="processedParams.body[key]"
:type="
key === 'otp_code'
? 'tel'
: key === 'expiry_minutes'
? 'number'
: 'text'
"
:maxlength="
key === 'otp_code' ? 8 : key === 'expiry_minutes' ? 3 : null
"
class="flex-1"
:placeholder="
key === 'otp_code'
? t('WHATSAPP_TEMPLATES.PARSER.OTP_CODE')
: key === 'expiry_minutes'
? t('WHATSAPP_TEMPLATES.PARSER.EXPIRY_MINUTES')
: t('WHATSAPP_TEMPLATES.PARSER.VARIABLE_PLACEHOLDER', {
variable: key,
})
"
/>
</div>
</div>
<!-- Button Variables Section -->
<div v-if="processedParams.buttons">
<p class="mb-2.5 text-sm font-semibold">
{{ t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETERS') }}
</p>
<div
v-for="(button, index) in processedParams.buttons"
:key="`button-${index}`"
class="flex items-center mb-2.5"
>
<Input
v-model="processedParams.buttons[index].parameter"
:type="button.type === 'copy_code' ? 'text' : 'text'"
:maxlength="button.type === 'copy_code' ? 15 : 500"
class="flex-1"
:placeholder="
button.type === 'copy_code'
? t('WHATSAPP_TEMPLATES.PARSER.COUPON_CODE')
: t('WHATSAPP_TEMPLATES.PARSER.BUTTON_PARAMETER')
"
/>
</div>
</div>
<p
v-if="v$.$dirty && v$.$invalid"
class="p-2.5 text-center rounded-md bg-n-ruby-9/20 text-n-ruby-9"
>
{{ $t('WHATSAPP_TEMPLATES.PARSER.FORM_ERROR_MESSAGE') }}
</p>
</div>
<slot
name="actions"
:send-message="sendMessage"
:reset-template="resetTemplate"
:go-back="goBack"
:is-valid="!v$.$invalid"
/>
</div>
</template>