diff --git a/src/components/dashboard-navigation.ts b/src/components/dashboard-navigation.ts index 450e157..428bb3a 100644 --- a/src/components/dashboard-navigation.ts +++ b/src/components/dashboard-navigation.ts @@ -7,6 +7,7 @@ import { Globe, LayoutDashboard, type LucideIcon, + Mail, Settings, ShieldCheck, Tags, @@ -61,6 +62,7 @@ export const dashboardNavigation = [ items: [ { title: "Groups", url: "/dashboard/azure/groups", icon: Database }, { title: "Members", url: "/dashboard/azure/members", icon: UsersRound }, + { title: "Email templates", url: "/dashboard/azure/email-templates", icon: Mail }, ], }, { diff --git a/src/features/email-templates/email-template-dialogs.tsx b/src/features/email-templates/email-template-dialogs.tsx new file mode 100644 index 0000000..a8854b6 --- /dev/null +++ b/src/features/email-templates/email-template-dialogs.tsx @@ -0,0 +1,179 @@ +import { useServerFn } from "@tanstack/react-start" +import { LoaderCircle, OctagonX } from "lucide-react" +import { useState } from "react" +import { toast } from "sonner" + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import type { EmailTemplate } from "@/lib/api/types" +import { errorMessage } from "@/lib/errors" + +import { addEmailTemplate, deleteEmailTemplate, editEmailTemplate } from "./email-templates.functions" + +export function EmailTemplateDialog({ + template, + onClose, + onSaved, +}: { + template: EmailTemplate | null + onClose: () => void + onSaved: (template: EmailTemplate) => void +}) { + const [subject, setSubject] = useState(template?.subject ?? "") + const [body, setBody] = useState(template?.body ?? "") + const [pending, setPending] = useState(false) + const [error, setError] = useState("") + const addEmailTemplateFn = useServerFn(addEmailTemplate) + const editEmailTemplateFn = useServerFn(editEmailTemplate) + + const trimmedSubject = subject.trim() + const trimmedBody = body.trim() + + async function submit(event: React.FormEvent) { + event.preventDefault() + if (!trimmedSubject || !trimmedBody) return + + setPending(true) + setError("") + try { + const saved = template + ? await editEmailTemplateFn({ data: { id: template.id, subject: trimmedSubject, body: trimmedBody } }) + : await addEmailTemplateFn({ data: { subject: trimmedSubject, body: trimmedBody } }) + onSaved(saved) + } catch (cause) { + console.error(cause) + setError(errorMessage(cause, "The template could not be saved. Check your permissions and try again.")) + } finally { + setPending(false) + } + } + + return ( + !open && !pending && onClose()}> + + +

+ AZURE · EMAIL TEMPLATES +

+ + {template ? "Edit email template" : "New email template"} + + + {template + ? "Update the predefined subject and body for this template." + : "Save a predefined subject and body that can be selected when emailing members."} + +
+
void submit(event)}> + + + Subject + setSubject(event.target.value)} + placeholder="e.g. Membership renewal reminder" + required + autoFocus + /> + + + Body +