diff --git a/admin-spa/src/routes/Settings/Notifications/TemplateEditor.tsx b/admin-spa/src/routes/Settings/Notifications/TemplateEditor.tsx new file mode 100644 index 0000000..07ba254 --- /dev/null +++ b/admin-spa/src/routes/Settings/Notifications/TemplateEditor.tsx @@ -0,0 +1,203 @@ +import React, { useState, useEffect } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { X, Plus } from 'lucide-react'; +import { toast } from 'sonner'; +import { __ } from '@/lib/i18n'; + +interface TemplateEditorProps { + open: boolean; + onClose: () => void; + eventId: string; + channelId: string; + eventLabel: string; + channelLabel: string; + template?: any; +} + +export default function TemplateEditor({ + open, + onClose, + eventId, + channelId, + eventLabel, + channelLabel, + template: initialTemplate, +}: TemplateEditorProps) { + const queryClient = useQueryClient(); + const [subject, setSubject] = useState(''); + const [body, setBody] = useState(''); + const [variables, setVariables] = useState<{ [key: string]: string }>({}); + + useEffect(() => { + if (initialTemplate) { + setSubject(initialTemplate.subject || ''); + setBody(initialTemplate.body || ''); + setVariables(initialTemplate.variables || {}); + } + }, [initialTemplate]); + + const saveMutation = useMutation({ + mutationFn: async () => { + return api.post('/notifications/templates', { + eventId, + channelId, + subject, + body, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['notification-templates'] }); + toast.success(__('Template saved successfully')); + onClose(); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to save template')); + }, + }); + + const resetMutation = useMutation({ + mutationFn: async () => { + return api.del(`/notifications/templates/${eventId}/${channelId}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['notification-templates'] }); + toast.success(__('Template reset to default')); + onClose(); + }, + onError: (error: any) => { + toast.error(error?.message || __('Failed to reset template')); + }, + }); + + const insertVariable = (variable: string) => { + const textarea = document.querySelector('textarea[name="body"]') as HTMLTextAreaElement; + if (textarea) { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const text = body; + const before = text.substring(0, start); + const after = text.substring(end); + const newText = before + `{${variable}}` + after; + setBody(newText); + + // Set cursor position after inserted variable + setTimeout(() => { + textarea.focus(); + textarea.setSelectionRange(start + variable.length + 2, start + variable.length + 2); + }, 0); + } + }; + + return ( + + + + + {__('Edit Template')}: {eventLabel} - {channelLabel} + + + {__('Customize the notification template. Use variables like {customer_name} to personalize messages.')} + + + +
+ {/* Subject */} +
+ + setSubject(e.target.value)} + placeholder={__('Enter notification subject')} + /> +

+ {channelId === 'email' + ? __('Email subject line') + : __('Push notification title')} +

+
+ + {/* Body */} +
+ +