211 lines
8.8 KiB
TypeScript
211 lines
8.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { X, Plus } from 'lucide-react';
|
|
import { toast } from 'react-toastify';
|
|
import notificationService, { NotificationTemplate } from '../services/notificationService';
|
|
|
|
interface NotificationTemplatesModalProps {
|
|
onClose: () => void;
|
|
}
|
|
|
|
const NotificationTemplatesModal: React.FC<NotificationTemplatesModalProps> = ({ onClose }) => {
|
|
const [templates, setTemplates] = useState<NotificationTemplate[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showCreate, setShowCreate] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
notification_type: 'booking_confirmation',
|
|
channel: 'email',
|
|
subject: '',
|
|
content: '',
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadTemplates();
|
|
}, []);
|
|
|
|
const loadTemplates = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await notificationService.getTemplates();
|
|
setTemplates(response.data.data || []);
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to load templates');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCreate = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!formData.name.trim() || !formData.content.trim()) {
|
|
toast.error('Name and content are required');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await notificationService.createTemplate(formData);
|
|
toast.success('Template created successfully');
|
|
setShowCreate(false);
|
|
setFormData({
|
|
name: '',
|
|
notification_type: 'booking_confirmation',
|
|
channel: 'email',
|
|
subject: '',
|
|
content: '',
|
|
});
|
|
loadTemplates();
|
|
} catch (error: any) {
|
|
toast.error(error.message || 'Failed to create template');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
<div className="sticky top-0 bg-white border-b border-gray-200 p-6 flex items-center justify-between">
|
|
<h2 className="text-2xl font-bold text-gray-900">Notification Templates</h2>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => setShowCreate(true)}
|
|
className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors flex items-center gap-2"
|
|
>
|
|
<Plus className="w-4 h-4" />
|
|
Create Template
|
|
</button>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6">
|
|
{showCreate ? (
|
|
<form onSubmit={handleCreate} className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Name *</label>
|
|
<input
|
|
type="text"
|
|
value={formData.name}
|
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Type</label>
|
|
<select
|
|
value={formData.notification_type}
|
|
onChange={(e) => setFormData({ ...formData, notification_type: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
>
|
|
<option value="booking_confirmation">Booking Confirmation</option>
|
|
<option value="payment_receipt">Payment Receipt</option>
|
|
<option value="pre_arrival_reminder">Pre-Arrival Reminder</option>
|
|
<option value="check_in_reminder">Check-In Reminder</option>
|
|
<option value="check_out_reminder">Check-Out Reminder</option>
|
|
<option value="marketing_campaign">Marketing Campaign</option>
|
|
<option value="loyalty_update">Loyalty Update</option>
|
|
<option value="system_alert">System Alert</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Channel</label>
|
|
<select
|
|
value={formData.channel}
|
|
onChange={(e) => setFormData({ ...formData, channel: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
>
|
|
<option value="email">Email</option>
|
|
<option value="sms">SMS</option>
|
|
<option value="push">Push</option>
|
|
<option value="whatsapp">WhatsApp</option>
|
|
<option value="in_app">In-App</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
{(formData.channel === 'email' || formData.channel === 'push') && (
|
|
<div>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Subject</label>
|
|
<input
|
|
type="text"
|
|
value={formData.subject}
|
|
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
placeholder="Use {{variable_name}} for variables"
|
|
/>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Content *</label>
|
|
<textarea
|
|
value={formData.content}
|
|
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
|
|
rows={8}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
placeholder="Use {{variable_name}} for variables (e.g., {{booking_number}}, {{guest_name}})"
|
|
required
|
|
/>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
Available variables: booking_number, guest_name, check_in, check_out, total_price, payment_amount, etc.
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowCreate(false)}
|
|
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"
|
|
>
|
|
Create Template
|
|
</button>
|
|
</div>
|
|
</form>
|
|
) : (
|
|
<>
|
|
{loading ? (
|
|
<div className="text-center py-8">Loading templates...</div>
|
|
) : templates.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-500">
|
|
No templates found. Create your first template.
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{templates.map((template) => (
|
|
<div key={template.id} className="border border-gray-200 rounded-lg p-4">
|
|
<div className="flex items-start justify-between mb-2">
|
|
<div>
|
|
<h3 className="font-semibold text-gray-900">{template.name}</h3>
|
|
<p className="text-sm text-gray-500">
|
|
{template.notification_type.replace('_', ' ')} • {template.channel}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{template.subject && (
|
|
<p className="text-sm font-medium text-gray-700 mb-2">Subject: {template.subject}</p>
|
|
)}
|
|
<p className="text-sm text-gray-600 line-clamp-3">{template.content}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default NotificationTemplatesModal;
|
|
|