The initialization guard was placed after the await I18n.loadModule() call, which could cause race conditions if Alpine calls init() multiple times before the guard is set. This aligns with the pattern used by other pages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
278 lines
9.9 KiB
JavaScript
278 lines
9.9 KiB
JavaScript
/**
|
|
* Email Templates Management Page
|
|
*
|
|
* Handles:
|
|
* - Listing all platform email templates
|
|
* - Editing template content (all languages)
|
|
* - Preview and test email sending
|
|
*/
|
|
|
|
const emailTemplatesLog = window.LogConfig?.createLogger?.('emailTemplates') ||
|
|
{ info: () => {}, debug: () => {}, warn: () => {}, error: () => {} };
|
|
|
|
function emailTemplatesPage() {
|
|
return {
|
|
// Inherit base layout functionality from init-alpine.js
|
|
...data(),
|
|
|
|
// Set current page for navigation
|
|
currentPage: 'email-templates',
|
|
|
|
// Data
|
|
loading: true,
|
|
templates: [],
|
|
categories: [],
|
|
selectedCategory: null,
|
|
|
|
// Edit Modal
|
|
showEditModal: false,
|
|
editingTemplate: null,
|
|
editLanguage: 'en',
|
|
loadingTemplate: false,
|
|
editForm: {
|
|
subject: '',
|
|
body_html: '',
|
|
body_text: '',
|
|
variables: [],
|
|
required_variables: []
|
|
},
|
|
saving: false,
|
|
|
|
// Preview Modal
|
|
showPreviewModal: false,
|
|
previewData: null,
|
|
|
|
// Test Email Modal
|
|
showTestEmailModal: false,
|
|
testEmailAddress: '',
|
|
sendingTest: false,
|
|
|
|
// Computed
|
|
get filteredTemplates() {
|
|
if (!this.selectedCategory) {
|
|
return this.templates;
|
|
}
|
|
return this.templates.filter(t => t.category === this.selectedCategory);
|
|
},
|
|
|
|
// Lifecycle
|
|
async init() {
|
|
// Guard against duplicate initialization
|
|
if (window._adminEmailTemplatesInitialized) return;
|
|
window._adminEmailTemplatesInitialized = true;
|
|
|
|
// Load i18n translations
|
|
await I18n.loadModule('messaging');
|
|
|
|
await this.loadData();
|
|
},
|
|
|
|
// Data Loading
|
|
async loadData() {
|
|
this.loading = true;
|
|
try {
|
|
const [templatesData, categoriesData] = await Promise.all([
|
|
apiClient.get('/admin/email-templates'),
|
|
apiClient.get('/admin/email-templates/categories')
|
|
]);
|
|
|
|
this.templates = templatesData.templates || [];
|
|
this.categories = categoriesData.categories || [];
|
|
} catch (error) {
|
|
emailTemplatesLog.error('Failed to load email templates:', error);
|
|
Utils.showToast(I18n.t('messaging.messages.failed_to_load_templates'), 'error');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// Category styling
|
|
getCategoryClass(category) {
|
|
const classes = {
|
|
'auth': 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
|
|
'orders': 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
|
|
'billing': 'bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200',
|
|
'system': 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200',
|
|
'marketing': 'bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200'
|
|
};
|
|
return classes[category] || 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200';
|
|
},
|
|
|
|
// Edit Template
|
|
async editTemplate(template) {
|
|
this.editingTemplate = template;
|
|
this.editLanguage = 'en';
|
|
this.showEditModal = true;
|
|
await this.loadTemplateLanguage();
|
|
},
|
|
|
|
async loadTemplateLanguage() {
|
|
if (!this.editingTemplate) return;
|
|
|
|
this.loadingTemplate = true;
|
|
try {
|
|
const data = await apiClient.get(
|
|
`/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`
|
|
);
|
|
|
|
this.editForm = {
|
|
subject: data.subject || '',
|
|
body_html: data.body_html || '',
|
|
body_text: data.body_text || '',
|
|
variables: data.variables || [],
|
|
required_variables: data.required_variables || []
|
|
};
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
// Template doesn't exist for this language yet
|
|
this.editForm = {
|
|
subject: '',
|
|
body_html: '',
|
|
body_text: '',
|
|
variables: [],
|
|
required_variables: []
|
|
};
|
|
Utils.showToast(I18n.t('messaging.messages.no_template_for_language', { language: this.editLanguage.toUpperCase() }), 'info');
|
|
} else {
|
|
emailTemplatesLog.error('Failed to load template:', error);
|
|
Utils.showToast(I18n.t('messaging.messages.failed_to_load_template'), 'error');
|
|
}
|
|
} finally {
|
|
this.loadingTemplate = false;
|
|
}
|
|
},
|
|
|
|
closeEditModal() {
|
|
this.showEditModal = false;
|
|
this.editingTemplate = null;
|
|
this.editForm = {
|
|
subject: '',
|
|
body_html: '',
|
|
body_text: '',
|
|
variables: [],
|
|
required_variables: []
|
|
};
|
|
},
|
|
|
|
async saveTemplate() {
|
|
if (!this.editingTemplate) return;
|
|
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.put(
|
|
`/admin/email-templates/${this.editingTemplate.code}/${this.editLanguage}`,
|
|
{
|
|
subject: this.editForm.subject,
|
|
body_html: this.editForm.body_html,
|
|
body_text: this.editForm.body_text
|
|
}
|
|
);
|
|
|
|
Utils.showToast(I18n.t('messaging.messages.template_saved_successfully'), 'success');
|
|
// Refresh templates list
|
|
await this.loadData();
|
|
} catch (error) {
|
|
emailTemplatesLog.error('Failed to save template:', error);
|
|
Utils.showToast(error.detail || I18n.t('messaging.messages.failed_to_save_template'), 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// Preview
|
|
async previewTemplate(template) {
|
|
try {
|
|
// Use sample variables for preview
|
|
const sampleVariables = this.getSampleVariables(template.code);
|
|
|
|
const data = await apiClient.post(
|
|
`/admin/email-templates/${template.code}/preview`,
|
|
{
|
|
template_code: template.code,
|
|
language: 'en',
|
|
variables: sampleVariables
|
|
}
|
|
);
|
|
|
|
this.previewData = data;
|
|
this.showPreviewModal = true;
|
|
} catch (error) {
|
|
emailTemplatesLog.error('Failed to preview template:', error);
|
|
Utils.showToast(I18n.t('messaging.messages.failed_to_load_preview'), 'error');
|
|
}
|
|
},
|
|
|
|
getSampleVariables(templateCode) {
|
|
// Sample variables for common templates
|
|
const samples = {
|
|
'signup_welcome': {
|
|
first_name: 'John',
|
|
company_name: 'Acme Corp',
|
|
email: 'john@example.com',
|
|
vendor_code: 'acme',
|
|
login_url: 'https://example.com/login',
|
|
trial_days: '14',
|
|
tier_name: 'Business'
|
|
},
|
|
'order_confirmation': {
|
|
customer_name: 'Jane Doe',
|
|
order_number: 'ORD-12345',
|
|
order_total: '99.99',
|
|
order_items_count: '3',
|
|
order_date: '2024-01-15',
|
|
shipping_address: '123 Main St, Luxembourg City, L-1234'
|
|
},
|
|
'password_reset': {
|
|
customer_name: 'John Doe',
|
|
reset_link: 'https://example.com/reset?token=abc123',
|
|
expiry_hours: '1'
|
|
},
|
|
'team_invite': {
|
|
invitee_name: 'Jane',
|
|
inviter_name: 'John',
|
|
vendor_name: 'Acme Corp',
|
|
role: 'Admin',
|
|
accept_url: 'https://example.com/accept',
|
|
expires_in_days: '7'
|
|
}
|
|
};
|
|
return samples[templateCode] || { platform_name: 'Wizamart' };
|
|
},
|
|
|
|
// Test Email
|
|
sendTestEmail() {
|
|
this.showTestEmailModal = true;
|
|
},
|
|
|
|
async confirmSendTestEmail() {
|
|
if (!this.testEmailAddress || !this.editingTemplate) return;
|
|
|
|
this.sendingTest = true;
|
|
try {
|
|
const result = await apiClient.post(
|
|
`/admin/email-templates/${this.editingTemplate.code}/test`,
|
|
{
|
|
template_code: this.editingTemplate.code,
|
|
language: this.editLanguage,
|
|
to_email: this.testEmailAddress,
|
|
variables: this.getSampleVariables(this.editingTemplate.code)
|
|
}
|
|
);
|
|
|
|
if (result.success) {
|
|
Utils.showToast(I18n.t('messaging.messages.test_email_sent', { email: this.testEmailAddress }), 'success');
|
|
this.showTestEmailModal = false;
|
|
this.testEmailAddress = '';
|
|
} else {
|
|
Utils.showToast(result.message || I18n.t('messaging.messages.failed_to_send_test_email'), 'error');
|
|
}
|
|
} catch (error) {
|
|
emailTemplatesLog.error('Failed to send test email:', error);
|
|
Utils.showToast(I18n.t('messaging.messages.failed_to_send_test_email'), 'error');
|
|
} finally {
|
|
this.sendingTest = false;
|
|
}
|
|
}
|
|
};
|
|
}
|