feat: add Email and SMS template management configuration page to admin settings
This commit is contained in:
673
src/app/admin/settings/components/EmailSMSTemplates.tsx
Normal file
673
src/app/admin/settings/components/EmailSMSTemplates.tsx
Normal file
@@ -0,0 +1,673 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Mail, MessageSquare, Pencil, Eye, X, Check, Plus } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
name: string;
|
||||
subject?: string;
|
||||
body: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface EmailSMSTemplatesProps {
|
||||
settings: any;
|
||||
setSettings: any;
|
||||
}
|
||||
|
||||
const defaultEmailTemplates: Template[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
name: 'Welcome Email',
|
||||
subject: 'Welcome to JAIBEN Mobility - Your Journey Starts Here!',
|
||||
body: `Dear {name},
|
||||
|
||||
Welcome to JAIBEN Mobility!
|
||||
|
||||
We're thrilled to have you join our community of eco-friendly commuters. Your account has been successfully created.
|
||||
|
||||
Your Login Details:
|
||||
- Phone: {phone}
|
||||
- Email: {email}
|
||||
|
||||
What's Next?
|
||||
1. Complete your KYC verification to unlock all features
|
||||
2. Browse our EV rental plans
|
||||
3. Choose your perfect electric vehicle
|
||||
|
||||
If you have any questions, our support team is here to help!
|
||||
|
||||
Best regards,
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'password_reset',
|
||||
name: 'Password Reset',
|
||||
subject: 'Reset Your JAIBEN Password',
|
||||
body: `Dear {name},
|
||||
|
||||
We received a request to reset your password.
|
||||
|
||||
Your OTP code is: {otp}
|
||||
|
||||
This code will expire in {expiry_minutes} minutes.
|
||||
|
||||
If you didn't request this, please ignore this email or contact support immediately.
|
||||
|
||||
Note: Never share this OTP with anyone.
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'rental_confirmation',
|
||||
name: 'Rental Confirmation',
|
||||
subject: 'Rental Confirmed - Your EV is Ready!',
|
||||
body: `Dear {name},
|
||||
|
||||
Your rental has been confirmed!
|
||||
|
||||
Booking Details:
|
||||
- Bike: {bike}
|
||||
- Plan: {plan}
|
||||
- Start Date: {start_date}
|
||||
- End Date: {end_date}
|
||||
- Amount: {amount}
|
||||
- Deposit: {deposit}
|
||||
|
||||
Pickup Location: {pickup_location}
|
||||
|
||||
Important:
|
||||
- Bring your valid driving license
|
||||
- Carry NID card for verification
|
||||
- Arrive 15 minutes before scheduled time
|
||||
|
||||
Enjoy your ride!
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'payment_reminder',
|
||||
name: 'Payment Reminder',
|
||||
subject: 'Payment Due Soon - {amount}',
|
||||
body: `Dear {name},
|
||||
|
||||
This is a friendly reminder that your payment is due.
|
||||
|
||||
Amount Due: {amount}
|
||||
Due Date: {due_date}
|
||||
Rental: {bike}
|
||||
|
||||
Please make your payment to avoid late fees.
|
||||
|
||||
Payment Methods:
|
||||
- bKash: {bkash_number}
|
||||
- Nagad: {nagad_number}
|
||||
- Bank Transfer: {bank_account}
|
||||
|
||||
Need help? Contact us anytime!
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'due_notice',
|
||||
name: 'Due Notice',
|
||||
subject: 'Payment Overdue - Action Required',
|
||||
body: `Dear {name},
|
||||
|
||||
Your payment for the rental of {bike} is overdue.
|
||||
|
||||
Overdue Amount: {amount}
|
||||
Days Overdue: {days_overdue}
|
||||
Late Fee: {late_fee}
|
||||
|
||||
Please make immediate payment to avoid service interruption.
|
||||
|
||||
Total Due: {total_due}
|
||||
|
||||
If you've already made the payment, please ignore this message or contact us to confirm.
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'kyc_verification',
|
||||
name: 'KYC Verification',
|
||||
subject: 'KYC Verification {status}',
|
||||
body: `Dear {name},
|
||||
|
||||
Your KYC verification has been {status}.
|
||||
|
||||
{status_message}
|
||||
|
||||
{status === 'approved' ? 'You can now access all features and rent EVs!' : 'Please resubmit your documents with correct information.'}
|
||||
|
||||
Documents Submitted:
|
||||
{documents_list}
|
||||
|
||||
If you have questions, contact our support team.
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'rental_termination',
|
||||
name: 'Rental Termination',
|
||||
subject: 'Rental Agreement Terminated',
|
||||
body: `Dear {name},
|
||||
|
||||
Your rental agreement for {bike} has been terminated.
|
||||
|
||||
Termination Details:
|
||||
- End Date: {end_date}
|
||||
- Final Amount: {final_amount}
|
||||
- Refund: {refund_amount}
|
||||
|
||||
Please return the vehicle to {return_location} by {return_date}.
|
||||
|
||||
Any outstanding charges will be deducted from your deposit.
|
||||
|
||||
Thank you for choosing JAIBEN!
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'damage_report',
|
||||
name: 'Damage Report',
|
||||
subject: 'Vehicle Damage Report - {bike}',
|
||||
body: `Dear {name},
|
||||
|
||||
A damage report has been filed for your rented vehicle.
|
||||
|
||||
Incident Details:
|
||||
- Date: {incident_date}
|
||||
- Location: {location}
|
||||
- Description: {description}
|
||||
- Estimated Cost: {repair_cost}
|
||||
|
||||
Your current deposit: {deposit}
|
||||
Remaining after repair: {remaining_deposit}
|
||||
|
||||
Please contact us within 24 hours if you have any concerns.
|
||||
|
||||
JAIBEN Mobility Team`,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultSmsTemplates: Template[] = [
|
||||
{
|
||||
id: 'otp',
|
||||
name: 'OTP Code',
|
||||
body: `JAIBEN: Your OTP is {otp}. Valid for {expiry_minutes} mins. Don't share this code.`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'rental_reminder',
|
||||
name: 'Rental Reminder',
|
||||
body: `JAIBEN: Reminder! Your {bike} rental starts on {start_date}. Pick up from {location}. Reply CONFIRM to proceed.`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'payment_due',
|
||||
name: 'Payment Due',
|
||||
body: `JAIBEN: Payment of {amount} due on {due_date} for your {bike} rental. Pay via bKash: {bkash}. Avoid late fees!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'battery_low',
|
||||
name: 'Battery Low Warning',
|
||||
body: `JAIBEN: Your {bike} battery is low ({battery_level}%). Visit nearest swap station or charge point. Stay safe!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'damage_report_sms',
|
||||
name: 'Damage Report',
|
||||
body: `JAIBEN: Damage reported on your {bike}. Est. cost: {repair_cost}. Contact {support_phone} within 24hrs.`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'welcome_sms',
|
||||
name: 'Welcome Message',
|
||||
body: `Welcome to JAIBEN Mobility {name}! Your account is ready. Download our app or visit {app_link} to rent your first EV!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'rental_start',
|
||||
name: 'Rental Started',
|
||||
body: `JAIBEN: Your {bike} rental has started! Enjoy your ride. Return by {return_time}. Ride safe!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'rental_end',
|
||||
name: 'Rental Ending',
|
||||
body: `JAIBEN: Your {bike} rental ends on {end_date}. Extend at {app_link} or return to {location}. Thanks!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'swap_reminder',
|
||||
name: 'Battery Swap Reminder',
|
||||
body: `JAIBEN: Your battery at {location} is low. Swap station: {station_name}, Distance: {distance}km. Free swap with your plan!`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'kyc_approved',
|
||||
name: 'KYC Approved',
|
||||
body: `JAIBEN: Great news! Your KYC is verified. You can now rent EVs. Download app: {app_link} or visit nearest hub!`,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const commonVariables = [
|
||||
{ name: 'name', label: 'Name' },
|
||||
{ name: 'phone', label: 'Phone' },
|
||||
{ name: 'email', label: 'Email' },
|
||||
{ name: 'amount', label: 'Amount' },
|
||||
{ name: 'date', label: 'Date' },
|
||||
{ name: 'bike', label: 'Bike Name' },
|
||||
{ name: 'plan', label: 'Plan' },
|
||||
{ name: 'otp', label: 'OTP' },
|
||||
{ name: 'deposit', label: 'Deposit' },
|
||||
{ name: 'start_date', label: 'Start Date' },
|
||||
{ name: 'end_date', label: 'End Date' },
|
||||
{ name: 'location', label: 'Location' },
|
||||
];
|
||||
|
||||
export default function EmailSMSTemplates({ settings, setSettings }: EmailSMSTemplatesProps) {
|
||||
const [activeTemplateTab, setActiveTemplateTab] = useState<'email' | 'sms'>('email');
|
||||
const [emailTemplates, setEmailTemplates] = useState<Template[]>(
|
||||
settings.emailTemplates || defaultEmailTemplates
|
||||
);
|
||||
const [smsTemplates, setSmsTemplates] = useState<Template[]>(
|
||||
settings.smsTemplates || defaultSmsTemplates
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingTemplate, setEditingTemplate] = useState<Template | null>(null);
|
||||
const [editSubject, setEditSubject] = useState('');
|
||||
const [editBody, setEditBody] = useState('');
|
||||
const [previewMode, setPreviewMode] = useState(false);
|
||||
|
||||
const handleToggleEnabled = (type: 'email' | 'sms', id: string) => {
|
||||
if (type === 'email') {
|
||||
const updated = emailTemplates.map(t =>
|
||||
t.id === id ? { ...t, enabled: !t.enabled } : t
|
||||
);
|
||||
setEmailTemplates(updated);
|
||||
setSettings({ ...settings, emailTemplates: updated });
|
||||
} else {
|
||||
const updated = smsTemplates.map(t =>
|
||||
t.id === id ? { ...t, enabled: !t.enabled } : t
|
||||
);
|
||||
setSmsTemplates(updated);
|
||||
setSettings({ ...settings, smsTemplates: updated });
|
||||
}
|
||||
toast.success('Template status updated');
|
||||
};
|
||||
|
||||
const handleEdit = (template: Template, isEmail: boolean) => {
|
||||
setEditingTemplate(template);
|
||||
setEditSubject(template.subject || '');
|
||||
setEditBody(template.body);
|
||||
setPreviewMode(false);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!editingTemplate) return;
|
||||
|
||||
const updatedTemplate = {
|
||||
...editingTemplate,
|
||||
subject: editSubject,
|
||||
body: editBody,
|
||||
};
|
||||
|
||||
const newId = updatedTemplate.id || `template_${Date.now()}`;
|
||||
const templateWithId = { ...updatedTemplate, id: newId };
|
||||
|
||||
if (activeTemplateTab === 'email') {
|
||||
if (emailTemplates.find(t => t.id === editingTemplate.id)) {
|
||||
const updated = emailTemplates.map(t =>
|
||||
t.id === editingTemplate.id ? templateWithId : t
|
||||
);
|
||||
setEmailTemplates(updated);
|
||||
setSettings({ ...settings, emailTemplates: updated });
|
||||
} else {
|
||||
const updated = [...emailTemplates, templateWithId];
|
||||
setEmailTemplates(updated);
|
||||
setSettings({ ...settings, emailTemplates: updated });
|
||||
}
|
||||
} else {
|
||||
if (smsTemplates.find(t => t.id === editingTemplate.id)) {
|
||||
const updated = smsTemplates.map(t =>
|
||||
t.id === editingTemplate.id ? templateWithId : t
|
||||
);
|
||||
setSmsTemplates(updated);
|
||||
setSettings({ ...settings, smsTemplates: updated });
|
||||
} else {
|
||||
const updated = [...smsTemplates, templateWithId];
|
||||
setSmsTemplates(updated);
|
||||
setSettings({ ...settings, smsTemplates: updated });
|
||||
}
|
||||
}
|
||||
|
||||
setShowModal(false);
|
||||
toast.success('Template saved successfully');
|
||||
};
|
||||
|
||||
const insertVariable = (varName: string) => {
|
||||
setEditBody(prev => prev + `{${varName}}`);
|
||||
};
|
||||
|
||||
const previewContent = (body: string) => {
|
||||
let preview = body
|
||||
.replace(/{name}/g, 'John Doe')
|
||||
.replace(/{phone}/g, '+880 1234 567890')
|
||||
.replace(/{email}/g, 'john@example.com')
|
||||
.replace(/{amount}/g, '৳12,000')
|
||||
.replace(/{date}/g, '15 May 2026')
|
||||
.replace(/{bike}/g, 'EVO Lite')
|
||||
.replace(/{plan}/g, 'Monthly Premium')
|
||||
.replace(/{otp}/g, '123456')
|
||||
.replace(/{deposit}/g, '৳5,000')
|
||||
.replace(/{start_date}/g, '01 June 2026')
|
||||
.replace(/{end_date}/g, '30 June 2026')
|
||||
.replace(/{location}/g, 'Gulshan Hub')
|
||||
.replace(/{bkash}/g, '01712345678')
|
||||
.replace(/{nagad}/g, '01712345679')
|
||||
.replace(/{expiry_minutes}/g, '10')
|
||||
.replace(/{due_date}/g, '25 May 2026')
|
||||
.replace(/{days_overdue}/g, '3')
|
||||
.replace(/{late_fee}/g, '৳600')
|
||||
.replace(/{total_due}/g, '৳12,600')
|
||||
.replace(/{status}/g, 'Approved')
|
||||
.replace(/{status_message}/g, 'Your documents have been verified successfully.')
|
||||
.replace(/{documents_list}/g, '- NID Card\n- Driving License\n- Photo')
|
||||
.replace(/{final_amount}/g, '৳10,000')
|
||||
.replace(/{refund_amount}/g, '৳3,000')
|
||||
.replace(/{return_date}/g, '20 May 2026')
|
||||
.replace(/{incident_date}/g, '10 May 2026')
|
||||
.replace(/{description}/g, 'Front panel scratch')
|
||||
.replace(/{repair_cost}/g, '৳2,500')
|
||||
.replace(/{remaining_deposit}/g, '৳2,500')
|
||||
.replace(/{battery_level}/g, '15%')
|
||||
.replace(/{return_time}/g, '8:00 PM')
|
||||
.replace(/{app_link}/g, 'jaiben.com/app')
|
||||
.replace(/{support_phone}/g, '+880 9611 222 333')
|
||||
.replace(/{station_name}/g, 'Gulshan Swap Station')
|
||||
.replace(/{distance}/g, '1.2')
|
||||
.replace(/{bank_account}/g, 'AC: 1234567890, Bank: City Bank');
|
||||
return preview;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-bold text-slate-800">Email & SMS Templates</h2>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTemplateTab(activeTemplateTab);
|
||||
setEditingTemplate({ id: '', name: '', subject: '', body: '', enabled: true });
|
||||
setEditSubject('');
|
||||
setEditBody('');
|
||||
setPreviewMode(false);
|
||||
setShowModal(true);
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent/90 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Template
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 mb-6 p-1 bg-slate-100 rounded-lg w-fit">
|
||||
<button
|
||||
onClick={() => setActiveTemplateTab('email')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${activeTemplateTab === 'email'
|
||||
? 'bg-white text-slate-800 shadow-sm'
|
||||
: 'text-slate-500 hover:text-accent'
|
||||
}`}
|
||||
>
|
||||
<Mail className="w-4 h-4" />
|
||||
Email Templates
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTemplateTab('sms')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors ${activeTemplateTab === 'sms'
|
||||
? 'bg-white text-slate-800 shadow-sm'
|
||||
: 'text-slate-500 hover:text-accent'
|
||||
}`}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
SMS Templates
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{activeTemplateTab === 'email' ? (
|
||||
emailTemplates.map(template => (
|
||||
<div
|
||||
key={template.id}
|
||||
className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-200"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="font-semibold text-slate-800">{template.name}</h3>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full ${template.enabled
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-200 text-slate-500'
|
||||
}`}
|
||||
>
|
||||
{template.enabled ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-500 mt-1 truncate">{template.subject || template.body.substring(0, 60)}...</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<button
|
||||
onClick={() => handleToggleEnabled('email', template.id)}
|
||||
className={`relative w-11 h-6 rounded-full transition-colors ${template.enabled ? 'bg-accent' : 'bg-slate-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full shadow transition-transform ${template.enabled ? 'translate-x-5' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setActiveTemplateTab('email'); handleEdit(template, true); }}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded-lg transition-colors"
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setActiveTemplateTab('email'); setEditingTemplate(template); setPreviewMode(true); setShowModal(true); }}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
smsTemplates.map(template => (
|
||||
<div
|
||||
key={template.id}
|
||||
className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-200"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="font-semibold text-slate-800">{template.name}</h3>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full ${template.enabled
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-200 text-slate-500'
|
||||
}`}
|
||||
>
|
||||
{template.enabled ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-500 mt-1 truncate">{template.body.substring(0, 60)}...</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<button
|
||||
onClick={() => handleToggleEnabled('sms', template.id)}
|
||||
className={`relative w-11 h-6 rounded-full transition-colors ${template.enabled ? 'bg-accent' : 'bg-slate-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full shadow transition-transform ${template.enabled ? 'translate-x-5' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setActiveTemplateTab('sms'); handleEdit(template, false); }}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded-lg transition-colors"
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setActiveTemplateTab('sms'); setEditingTemplate(template); setPreviewMode(true); setShowModal(true); }}
|
||||
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded-lg transition-colors"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showModal && editingTemplate && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden">
|
||||
<div className="p-4 border-b border-slate-200 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-bold text-slate-800">
|
||||
{previewMode ? 'Preview' : editingTemplate.id ? 'Edit' : 'Add'} {editingTemplate.id ? '- ' + editingTemplate.name : 'Template'}
|
||||
</h3>
|
||||
{previewMode && (
|
||||
<button
|
||||
onClick={() => setPreviewMode(false)}
|
||||
className="ml-2 px-2 py-1 text-xs bg-accent text-white rounded hover:bg-accent/90"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-lg"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 max-h-[calc(90vh-180px)] overflow-y-auto">
|
||||
{previewMode ? (
|
||||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
|
||||
<div className="flex items-center gap-2 mb-3 text-slate-500 text-sm">
|
||||
<Mail className="w-4 h-4" />
|
||||
<span>Subject: {editingTemplate.subject || 'N/A'}</span>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap text-sm text-slate-700 font-sans">
|
||||
{previewContent(editingTemplate.body)}
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Template Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingTemplate.name}
|
||||
onChange={e => setEditingTemplate({ ...editingTemplate, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent"
|
||||
placeholder="e.g. Welcome Email"
|
||||
/>
|
||||
</div>
|
||||
{activeTemplateTab === 'email' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Subject
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editSubject}
|
||||
onChange={e => setEditSubject(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent"
|
||||
placeholder="Enter email subject..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Body {activeTemplateTab === 'sms' && '(SMS)'}
|
||||
</label>
|
||||
<textarea
|
||||
value={editBody}
|
||||
onChange={e => setEditBody(e.target.value)}
|
||||
rows={12}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent font-mono"
|
||||
placeholder="Enter template body..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
Insert Variable
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{commonVariables.map(v => (
|
||||
<button
|
||||
key={v.name}
|
||||
onClick={() => insertVariable(v.name)}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-sm rounded-lg hover:bg-slate-200 transition-colors"
|
||||
>
|
||||
{`{${v.name}}`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-blue-50 rounded-lg">
|
||||
<p className="text-xs text-blue-700">
|
||||
<strong>Tip:</strong> Variables like {'{name}'}, {'{phone}'}, {'{amount}'} will be replaced with actual values when sending.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="px-4 py-2 bg-white border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{!previewMode && (
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 bg-accent text-white rounded-lg text-sm font-medium hover:bg-accent-dark transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Check className="w-4 h-4" />
|
||||
Save Template
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Settings, Package, Palette, Link2, Mail, Monitor, FileCheck, DollarSign, Zap, Users, Plus, X, Save, Pencil, Trash2 } from 'lucide-react';
|
||||
import { Settings, Package, Palette, Link2, Mail, Monitor, FileCheck, DollarSign, Zap, Users, Plus, X, Save, Pencil, Trash2, FileText } from 'lucide-react';
|
||||
import RichTextEditor from '@/components/RichTextEditor';
|
||||
import GeneralSettings from './components/GeneralSettings';
|
||||
import BrandingSettings from './components/BrandingSettings';
|
||||
@@ -15,6 +15,7 @@ import PlanSelection from './components/PlanSelection';
|
||||
import InvestmentSettings from './components/InvestmentSettings';
|
||||
import SwapStationSettings from './components/SwapStationSettings';
|
||||
import RiderRequestSettings from './components/RiderRequestSettings';
|
||||
import EmailSMSTemplates from './components/EmailSMSTemplates';
|
||||
|
||||
export interface CompanySettings {
|
||||
name: string;
|
||||
@@ -813,7 +814,7 @@ const initialSettings: CompanySettings = {
|
||||
|
||||
export default function CompanySettingsPage() {
|
||||
const [settings, setSettings] = useState<CompanySettings>(initialSettings);
|
||||
const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'companyPolicy' | 'plans' | 'investment' | 'swapstation' | 'riderrequest'>('general');
|
||||
const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'companyPolicy' | 'plans' | 'investment' | 'swapstation' | 'riderrequest' | 'templates'>('general');
|
||||
const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rentalType'>('investor');
|
||||
const [activeRentalTypeTab, setActiveRentalTypeTab] = useState<'single' | 'shared' | 'renttoown'>('single');
|
||||
const [saved, setSaved] = useState(false);
|
||||
@@ -1127,6 +1128,7 @@ export default function CompanySettingsPage() {
|
||||
{ id: 'riderrequest', label: 'Rider Request Plan (P2)', icon: Users },
|
||||
{ id: 'parts', label: 'EV Parts', icon: Package },
|
||||
|
||||
{ id: 'templates', label: 'Templates', icon: FileText },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -1356,6 +1358,10 @@ export default function CompanySettingsPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'templates' && (
|
||||
<EmailSMSTemplates settings={settings} setSettings={updateSettings} />
|
||||
)}
|
||||
|
||||
</div >
|
||||
</div >
|
||||
|
||||
|
||||
Reference in New Issue
Block a user