Files
Nas-Notification/pages/notification/templates/edit/[id].vue

765 lines
24 KiB
Vue

<template>
<div>
<LayoutsBreadcrumb />
<!-- Info Card -->
<rs-card class="mb-5">
<template #header>
<div class="flex">
<Icon
class="mr-2 flex justify-center"
name="material-symbols:edit-outline"
></Icon>
Edit Template
</div>
</template>
<template #body>
<p class="mb-4">
Edit and update your notification template. Modify content, settings, and
channel configurations to improve effectiveness.
</p>
</template>
</rs-card>
<!-- Loading State -->
<div v-if="isLoading" class="text-center py-12">
<div class="flex justify-center mb-4">
<Icon name="ic:outline-refresh" size="2rem" class="text-primary animate-spin" />
</div>
<p class="text-gray-600">Loading template details...</p>
</div>
<!-- Main Form Card -->
<rs-card v-else>
<template #header>
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold">Edit Notification Template</h2>
<div class="flex gap-3">
<rs-button @click="$router.push('/notification/templates')" variant="outline">
<Icon name="ic:outline-arrow-back" class="mr-1"></Icon>
Back to Templates
</rs-button>
<rs-button
@click="previewTemplate"
variant="info-outline"
:disabled="!templateForm.content || isSubmitting"
>
<Icon name="material-symbols:preview-outline" class="mr-1"></Icon>
Preview
</rs-button>
</div>
</div>
</template>
<template #body>
<div class="pt-2">
<FormKit
type="form"
@submit="updateTemplate"
:actions="false"
class="w-full max-w-6xl mx-auto"
>
<!-- Basic Information Section -->
<div class="space-y-6 mb-8">
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
Basic Information
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Configure the basic template settings and metadata
</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Left Column -->
<div class="space-y-4">
<FormKit
type="text"
name="title"
label="Template Name"
placeholder="e.g., Welcome Email Template"
validation="required"
v-model="templateForm.title"
help="Internal name for identifying this template"
/>
<FormKit
type="textarea"
name="description"
label="Description"
placeholder="Brief description of template purpose and usage"
v-model="templateForm.description"
rows="3"
help="Optional description to help team members understand the template's use"
/>
<FormKit
type="select"
name="category"
label="Category"
placeholder="Select category"
:options="categoryOptions"
validation="required"
v-model="templateForm.category"
help="Organize templates by category for better management"
/>
</div>
<!-- Right Column -->
<div class="space-y-4">
<FormKit
type="checkbox"
name="channels"
label="Supported Channels"
:options="channelOptions"
validation="required|min:1"
v-model="templateForm.channels"
decorator-icon="material-symbols:check"
options-class="grid grid-cols-2 gap-x-3 gap-y-2 pt-2"
help="Select which channels this template supports"
/>
<div class="grid grid-cols-2 gap-4">
<FormKit
type="select"
name="status"
label="Status"
placeholder="Select status"
:options="statusOptions"
validation="required"
v-model="templateForm.status"
help="Template status - only active templates can be used"
outer-class="mb-0"
/>
<FormKit
type="text"
name="version"
label="Version"
placeholder="1.0"
v-model="templateForm.version"
help="Version number for tracking template changes"
outer-class="mb-0"
/>
</div>
</div>
</div>
</div>
<!-- Content Configuration Section -->
<div class="space-y-6 mb-8">
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
Content Configuration
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Define subject lines and content for different channels
</p>
</div>
<!-- Subject/Title Section -->
<div class="space-y-4">
<FormKit
type="text"
name="subject"
label="Subject Line / Notification Title"
placeholder="e.g., Welcome to {{company_name}}, {{first_name}}!"
validation="required"
v-model="templateForm.subject"
help="Subject for emails or title for push notifications. Use {{variable}} for dynamic content"
/>
<FormKit
type="text"
name="preheader"
label="Email Preheader Text (Optional)"
placeholder="Additional preview text for emails"
v-model="templateForm.preheader"
help="Preview text shown in email clients alongside the subject line"
/>
</div>
<!-- Content Editor Section -->
<div class="space-y-4">
<FormKit
type="textarea"
name="content"
label="Template Content"
validation="required"
v-model="templateForm.content"
help="Main content body. Use HTML for rich formatting and {{variable}} for dynamic content"
rows="10"
/>
</div>
</div>
<!-- Channel-Specific Settings -->
<div class="space-y-6 mb-8">
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
Channel-Specific Settings
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Configure settings specific to each channel
</p>
</div>
<rs-tab fill>
<!-- Email Settings Tab -->
<rs-tab-item
title="Email Settings"
v-if="templateForm.channels.includes('email')"
>
<div class="space-y-4 pt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormKit
type="text"
name="fromName"
label="From Name"
placeholder="Your Company Name"
v-model="templateForm.fromName"
help="Name shown as sender in email clients"
/>
<FormKit
type="email"
name="replyTo"
label="Reply-To Email"
placeholder="noreply@yourcompany.com"
v-model="templateForm.replyTo"
help="Email address for replies"
/>
</div>
<FormKit
type="checkbox"
name="trackOpens"
label="Track Email Opens"
v-model="templateForm.trackOpens"
help="Enable tracking of email opens for analytics"
/>
</div>
</rs-tab-item>
<!-- Push Notification Settings Tab -->
<rs-tab-item
title="Push Settings"
v-if="templateForm.channels.includes('push')"
>
<div class="space-y-4 pt-4">
<FormKit
type="text"
name="pushTitle"
label="Push Notification Title"
placeholder="Custom push title (optional)"
v-model="templateForm.pushTitle"
help="Leave empty to use the main subject line"
/>
<FormKit
type="url"
name="pushIcon"
label="Push Notification Icon URL"
placeholder="https://yoursite.com/icon.png"
v-model="templateForm.pushIcon"
help="URL to icon for push notifications"
/>
<FormKit
type="url"
name="pushUrl"
label="Push Notification Action URL"
placeholder="https://yoursite.com/action"
v-model="templateForm.pushUrl"
help="URL to open when push notification is clicked"
/>
</div>
</rs-tab-item>
<!-- SMS Settings Tab -->
<rs-tab-item
title="SMS Settings"
v-if="templateForm.channels.includes('sms')"
>
<div class="space-y-4 pt-4">
<FormKit
type="textarea"
name="smsContent"
label="SMS Content"
placeholder="SMS version of your message (160 characters max)"
v-model="templateForm.smsContent"
help="Text-only version for SMS. Variables like {{name}} are supported"
rows="4"
maxlength="160"
/>
<p class="text-sm text-gray-500">
Characters: {{ templateForm.smsContent?.length || 0 }}/160
</p>
</div>
</rs-tab-item>
</rs-tab>
</div>
<!-- Additional Settings -->
<div class="space-y-6 mb-8">
<div class="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
Additional Settings
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Configure additional template options
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FormKit
type="text"
name="tags"
label="Tags (comma-separated)"
placeholder="welcome, onboarding, email"
v-model="templateForm.tags"
help="Tags for organizing and filtering templates"
/>
<div class="space-y-4">
<FormKit
type="checkbox"
name="isPersonal"
label="Personal Template"
v-model="templateForm.isPersonal"
help="Mark as personal template (visible only to you)"
/>
</div>
</div>
</div>
<!-- Action Buttons -->
<div
class="flex gap-3 justify-end pt-6 border-t border-gray-200 dark:border-gray-700"
>
<rs-button
@click="$router.push('/notification/templates')"
variant="outline"
>
Cancel
</rs-button>
<rs-button
@click="testTemplate"
variant="info-outline"
:disabled="!isFormValid || isSubmitting"
>
<Icon name="material-symbols:send" class="mr-1"></Icon>
Send Test
</rs-button>
<rs-button
@click="updateTemplate"
:disabled="!isFormValid || isSubmitting"
:loading="isSubmitting"
>
<Icon name="material-symbols:save" class="mr-1"></Icon>
{{ isSubmitting ? "Updating..." : "Update Template" }}
</rs-button>
</div>
</FormKit>
</div>
</template>
</rs-card>
<!-- Preview Modal -->
<rs-modal v-model="showPreview" title="Template Preview" size="lg">
<div class="space-y-4 p-1">
<div class="flex gap-4 mb-4">
<FormKit
type="select"
name="previewChannel"
label="Preview Channel"
:options="
channelOptions.filter(
(c) => c.value !== '' && templateForm.channels.includes(c.value)
)
"
v-model="previewChannel"
outer-class="flex-1"
/>
</div>
<div class="border rounded-lg p-4 bg-gray-50 dark:bg-gray-800">
<h3 class="font-semibold mb-2 text-gray-800 dark:text-gray-200">
{{ templateForm.subject }}
</h3>
<div
class="prose prose-sm max-w-none dark:prose-invert"
v-html="templateForm.content"
></div>
</div>
</div>
<template #footer>
<rs-button @click="showPreview = false">Close</rs-button>
</template>
</rs-modal>
</div>
</template>
<script setup>
definePageMeta({
title: "Edit Template",
middleware: ["auth"],
requiresAuth: true,
});
const { $swal } = useNuxtApp();
const router = useRouter();
const route = useRoute();
// Get template ID from route params
const templateId = computed(() => route.params.id);
// Reactive data
const isLoading = ref(false);
const isSubmitting = ref(false);
const showPreview = ref(false);
const previewChannel = ref("email");
// Form data
const templateForm = ref({
title: "",
description: "",
subject: "",
preheader: "",
category: "",
channels: [],
status: "Draft",
version: "1.0",
content: "",
tags: "",
isPersonal: false,
// Email settings
fromName: "",
replyTo: "",
trackOpens: true,
// Push settings
pushTitle: "",
pushIcon: "",
pushUrl: "",
// SMS settings
smsContent: "",
});
// Form options
const categoryOptions = [
{ label: "User Management", value: "user_management" },
{ label: "Orders & Transactions", value: "orders" },
{ label: "Security & Authentication", value: "security" },
{ label: "Marketing & Promotions", value: "marketing" },
{ label: "System Updates", value: "system" },
{ label: "General Information", value: "general" },
];
const channelOptions = [
{ label: "Email", value: "email" },
{ label: "SMS", value: "sms" },
{ label: "Push Notification", value: "push" },
];
const statusOptions = [
{ label: "Draft", value: "Draft" },
{ label: "Active", value: "Active" },
{ label: "Archived", value: "Archived" },
];
// Computed properties
const isFormValid = computed(() => {
return (
templateForm.value.title &&
templateForm.value.subject &&
templateForm.value.content &&
templateForm.value.category &&
templateForm.value.channels.length > 0
);
});
// Load template data
const loadTemplate = async () => {
if (!templateId.value) return;
isLoading.value = true;
try {
console.log("Loading template for editing:", templateId.value);
const response = await $fetch(`/api/notifications/templates/${templateId.value}`);
if (response.success) {
const template = response.data.template;
console.log("Template loaded:", template);
// Map API response to form data
Object.assign(templateForm.value, {
title: template.title || "",
description: template.description || "",
subject: template.subject || "",
preheader: template.preheader || "",
category: template.category || "",
channels: template.channels || [],
status: template.status || "Draft",
version: template.version || "1.0",
content: template.content || "",
tags: template.tags || "",
isPersonal: template.isPersonal || false,
fromName: template.fromName || "",
replyTo: template.replyTo || "",
trackOpens: template.trackOpens !== false,
pushTitle: template.pushTitle || "",
pushIcon: template.pushIcon || "",
pushUrl: template.pushUrl || "",
smsContent: template.smsContent || "",
});
}
} catch (error) {
console.error("Error loading template:", error);
let errorMessage = "Failed to load template data";
if (error.data?.error) {
errorMessage = error.data.error;
}
await $swal.fire({
title: "Error",
text: errorMessage,
icon: "error",
});
// Navigate back to templates list
router.push("/notification/templates");
} finally {
isLoading.value = false;
}
};
// Preview template
const previewTemplate = () => {
showPreview.value = true;
};
// Test template
const testTemplate = async () => {
if (!isFormValid.value) {
$swal.fire("Error", "Please fill in all required fields", "error");
return;
}
const { value: email } = await $swal.fire({
title: "Send Test Notification",
text: "Enter email address to send test notification:",
input: "email",
inputPlaceholder: "your-email@example.com",
showCancelButton: true,
confirmButtonText: "Send Test",
cancelButtonText: "Cancel",
inputValidator: (value) => {
if (!value) {
return "Please enter a valid email address";
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return "Please enter a valid email address";
}
},
});
if (email) {
// Show loading state
const loadingSwal = $swal.fire({
title: "Sending Test...",
text: "Please wait while we send your test notification",
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
didOpen: () => {
$swal.showLoading();
},
});
try {
const testData = {
subject: templateForm.value.subject,
emailContent: templateForm.value.content,
pushTitle: templateForm.value.pushTitle || templateForm.value.subject,
pushBody: templateForm.value.content
? templateForm.value.content.replace(/<[^>]*>/g, "").substring(0, 300)
: "",
callToActionText: "",
callToActionUrl: "",
};
console.log("Sending test notification with data:", testData);
const response = await $fetch("/api/notifications/test-send", {
method: "POST",
body: {
email: email,
testData: testData,
},
});
console.log("Test API Response:", response);
loadingSwal.close();
let successMessage = `Test notification sent to ${email}`;
if (response.data?.results) {
const results = response.data.results;
const successfulChannels = results
.filter((r) => r.status === "sent")
.map((r) => r.channel);
const failedChannels = results.filter((r) => r.status === "failed");
if (successfulChannels.length > 0) {
successMessage += `\n\nSuccessfully sent via: ${successfulChannels.join(", ")}`;
}
if (failedChannels.length > 0) {
successMessage += `\n\nFailed channels: ${failedChannels
.map((f) => `${f.channel} (${f.message})`)
.join(", ")}`;
}
}
await $swal.fire({
title: "Test Sent!",
text: successMessage,
icon: "success",
timer: 4000,
});
} catch (error) {
console.error("Error sending test notification:", error);
loadingSwal.close();
let errorMessage = "Failed to send test notification. Please try again.";
if (error.data?.error) {
errorMessage = error.data.error;
} else if (error.message) {
errorMessage = error.message;
}
await $swal.fire({
title: "Test Failed",
text: errorMessage,
icon: "error",
confirmButtonText: "OK",
});
}
}
};
// Update template
const updateTemplate = async () => {
if (!isFormValid.value) {
$swal.fire("Error", "Please fill in all required fields", "error");
return;
}
if (isSubmitting.value) {
return;
}
isSubmitting.value = true;
// Show loading state
const loadingSwal = $swal.fire({
title: "Updating Template...",
text: "Please wait while we update your template",
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
didOpen: () => {
$swal.showLoading();
},
});
try {
// Prepare the data for API submission
const apiData = {
title: templateForm.value.title,
description: templateForm.value.description || "",
subject: templateForm.value.subject,
preheader: templateForm.value.preheader || "",
category: templateForm.value.category,
channels: templateForm.value.channels,
status: templateForm.value.status,
version: templateForm.value.version,
content: templateForm.value.content,
tags: templateForm.value.tags || "",
isPersonal: templateForm.value.isPersonal,
// Email specific settings
fromName: templateForm.value.fromName || "",
replyTo: templateForm.value.replyTo || "",
trackOpens: templateForm.value.trackOpens,
// Push notification specific settings
pushTitle: templateForm.value.pushTitle || "",
pushIcon: templateForm.value.pushIcon || "",
pushUrl: templateForm.value.pushUrl || "",
// SMS specific settings
smsContent: templateForm.value.smsContent || "",
};
console.log("Updating Template Data:", apiData);
const response = await $fetch(`/api/notifications/templates/${templateId.value}`, {
method: "PUT",
body: apiData,
});
console.log("API Response:", response);
loadingSwal.close();
isSubmitting.value = false;
await $swal.fire({
title: "Template Updated!",
text: `Template "${templateForm.value.title}" has been successfully updated.`,
icon: "success",
timer: 3000,
showConfirmButton: false,
});
// Navigate back to templates list
router.push("/notification/templates");
} catch (error) {
console.error("Error updating template:", error);
loadingSwal.close();
isSubmitting.value = false;
let errorMessage = "Failed to update template. Please try again.";
let errorDetails = "";
if (error.data) {
if (error.data.errors && Array.isArray(error.data.errors)) {
errorMessage = "Please fix the following errors:";
errorDetails = error.data.errors.join("\n• ");
} else if (error.data.error) {
errorMessage = error.data.error;
}
} else if (error.message) {
errorMessage = error.message;
}
await $swal.fire({
title: "Error",
text: errorMessage,
html: errorDetails
? `<p>${errorMessage}</p><ul style="text-align: left; margin-top: 10px;"><li>${errorDetails.replace(
/\n• /g,
"</li><li>"
)}</li></ul>`
: errorMessage,
icon: "error",
confirmButtonText: "OK",
});
}
};
// Load template when component mounts
onMounted(async () => {
await loadTemplate();
});
</script>