diff --git a/src/components/admin/logistics/email-templates-tab.tsx b/src/components/admin/logistics/email-templates-tab.tsx new file mode 100644 index 0000000..e109a91 --- /dev/null +++ b/src/components/admin/logistics/email-templates-tab.tsx @@ -0,0 +1,222 @@ +'use client' + +import { useState, useRef } from 'react' +import { trpc } from '@/lib/trpc/client' +import { Switch } from '@/components/ui/switch' +import { Label } from '@/components/ui/label' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Skeleton } from '@/components/ui/skeleton' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { toast } from 'sonner' +import { Plane, Mail, Eye, Loader2 } from 'lucide-react' +import { EmailPreviewDialog } from '@/components/admin/round/email-preview-dialog' + +export function EmailTemplatesTab({ programId }: { programId?: string }) { + const [previewType, setPreviewType] = useState(null) + const [testingType, setTestingType] = useState(null) + + const utils = trpc.useUtils() + + const { data: allSettings, isLoading } = trpc.notification.getEmailSettings.useQuery() + + const settings = (allSettings ?? []).filter((s) => s.category === 'logistics') + + const updateMutation = trpc.notification.updateEmailSetting.useMutation({ + onSuccess: () => { + toast.success('Setting updated') + void utils.notification.getEmailSettings.invalidate() + }, + onError: (error) => { + toast.error(`Failed to update: ${error.message}`) + }, + }) + + const testMutation = trpc.notification.sendTestEmail.useMutation({ + onSuccess: (data) => { + if (data.success) { + toast.success(data.message, { + description: data.hasStyledTemplate ? 'Using styled template' : 'Using generic template', + }) + } else { + toast.error('Failed to send test email', { description: data.message }) + } + setTestingType(null) + }, + onError: (error) => { + toast.error(`Failed to send: ${error.message}`) + setTestingType(null) + }, + }) + + const preview = trpc.notification.previewEmailTemplate.useQuery( + { notificationType: previewType! }, + { enabled: !!previewType }, + ) + + const previewSetting = settings.find((s) => s.notificationType === previewType) + + if (isLoading) { + return ( +
+ {[...Array(4)].map((_, i) => ( + + ))} +
+ ) + } + + if (settings.length === 0) { + return ( +

+ No logistics email types found — run the notification settings seed. +

+ ) + } + + return ( + <> + + + + + Logistics Emails + + {settings.filter((s) => s.sendEmail).length}/{settings.length} enabled + + + + + {settings.map((setting) => ( + { + setTestingType(setting.notificationType) + testMutation.mutate({ notificationType: setting.notificationType }) + }} + onPreview={() => setPreviewType(setting.notificationType)} + onToggle={(checked) => + updateMutation.mutate({ + notificationType: setting.notificationType, + sendEmail: checked, + emailSubject: setting.emailSubject ?? undefined, + }) + } + onSubjectBlur={(subject) => { + if (subject !== (setting.emailSubject ?? '')) { + updateMutation.mutate({ + notificationType: setting.notificationType, + sendEmail: setting.sendEmail, + emailSubject: subject || undefined, + }) + } + }} + /> + ))} + + + + { if (!o) setPreviewType(null) }} + title={previewSetting?.label ?? 'Email Preview'} + description={previewSetting?.description ?? ''} + recipientCount={0} + previewHtml={preview.data?.html} + isPreviewLoading={preview.isLoading} + onSend={() => {}} + isSending={false} + previewOnly + showCustomMessage={false} + /> + + ) +} + +type RowSetting = { + id: string + notificationType: string + category: string + label: string + description: string | null + sendEmail: boolean + emailSubject: string | null +} + +function EmailTemplateRow({ + setting, + isTesting, + isUpdating, + onTest, + onPreview, + onToggle, + onSubjectBlur, +}: { + setting: RowSetting + isTesting: boolean + isUpdating: boolean + onTest: () => void + onPreview: () => void + onToggle: (checked: boolean) => void + onSubjectBlur: (value: string) => void +}) { + const inputRef = useRef(null) + + return ( +
+
+
+ + {setting.description && ( +

{setting.description}

+ )} +
+
+ + + +
+
+
+ + onSubjectBlur(e.target.value)} + /> +
+
+ ) +} diff --git a/src/components/settings/notification-settings-form.tsx b/src/components/settings/notification-settings-form.tsx index 6d1996c..4ee2c8b 100644 --- a/src/components/settings/notification-settings-form.tsx +++ b/src/components/settings/notification-settings-form.tsx @@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { toast } from 'sonner' -import { Users, Scale, GraduationCap, Eye, Shield, Mail, Loader2 } from 'lucide-react' +import { Users, Scale, GraduationCap, Eye, Shield, Mail, Loader2, Plane } from 'lucide-react' // Category icons and labels const CATEGORIES = { @@ -17,6 +17,7 @@ const CATEGORIES = { mentor: { label: 'Mentors', icon: GraduationCap }, observer: { label: 'Observers', icon: Eye }, admin: { label: 'Administrators', icon: Shield }, + logistics: { label: 'Logistics', icon: Plane }, } type NotificationSetting = {