From c71f7dbd877e60e249767afa5b480dd04fc18023 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:53:27 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.smtp.tsx | 293 ++++++++++++++++++++ src/routes/settings.tsx | 1 + supabase/functions/send-smtp-email/index.ts | 104 +++++++ 3 files changed, 398 insertions(+) create mode 100644 src/routes/settings.smtp.tsx create mode 100644 supabase/functions/send-smtp-email/index.ts diff --git a/src/routes/settings.smtp.tsx b/src/routes/settings.smtp.tsx new file mode 100644 index 0000000..742a8a8 --- /dev/null +++ b/src/routes/settings.smtp.tsx @@ -0,0 +1,293 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/lib/auth"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Switch } from "@/components/ui/switch"; +import { Loader2, Mail, Send, KeyRound } from "lucide-react"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/settings/smtp")({ + component: SmtpSettingsPage, +}); + +const EMPTY = { + host: "", + port: "587", + secure: false, + username: "", + from_email: "", + from_name: "", + reply_to: "", + enabled: true, + notes: "", +}; + +function SmtpSettingsPage() { + const { user, isAdmin } = useAuth(); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [testing, setTesting] = useState(false); + const [recordId, setRecordId] = useState(null); + const [form, setForm] = useState({ ...EMPTY }); + const [testTo, setTestTo] = useState(""); + + const load = async () => { + setLoading(true); + const { data } = await supabase + .from("smtp_settings") + .select("*") + .order("updated_at", { ascending: false }) + .limit(1) + .maybeSingle(); + if (data) { + setRecordId(data.id); + setForm({ + host: data.host ?? "", + port: String(data.port ?? 587), + secure: !!data.secure, + username: data.username ?? "", + from_email: data.from_email ?? "", + from_name: data.from_name ?? "", + reply_to: data.reply_to ?? "", + enabled: data.enabled ?? true, + notes: data.notes ?? "", + }); + } + setLoading(false); + }; + + useEffect(() => { + if (isAdmin) load(); + else setLoading(false); + }, [isAdmin]); + + const update = (k: keyof typeof EMPTY, v: string | boolean) => + setForm((f) => ({ ...f, [k]: v })); + + const save = async () => { + if (!form.host || !form.from_email) { + toast.error("Host and From email are required"); + return; + } + setSaving(true); + const payload = { + host: form.host.trim(), + port: parseInt(form.port, 10) || 587, + secure: form.secure, + username: form.username || null, + from_email: form.from_email.trim(), + from_name: form.from_name || null, + reply_to: form.reply_to || null, + enabled: form.enabled, + notes: form.notes || null, + updated_by: user?.id, + }; + const { error } = recordId + ? await supabase.from("smtp_settings").update(payload).eq("id", recordId) + : await supabase.from("smtp_settings").insert(payload); + setSaving(false); + if (error) { + toast.error("Could not save", { description: error.message }); + return; + } + toast.success("SMTP settings saved"); + load(); + }; + + const sendTest = async () => { + if (!testTo) { + toast.error("Enter a test recipient email"); + return; + } + setTesting(true); + const { data, error } = await supabase.functions.invoke("send-smtp-email", { + body: { + to: testTo, + subject: "SMTP test from your firm CRM", + html: `

This is a test email sent from ${form.from_email} via ${form.host}:${form.port}.

If you received this, your SMTP settings are working.

`, + text: `Test email from ${form.from_email} via ${form.host}:${form.port}.`, + }, + }); + setTesting(false); + if (error || (data as any)?.error) { + toast.error("Send failed", { + description: (error as any)?.message || (data as any)?.error, + }); + return; + } + toast.success(`Test email sent to ${testTo}`); + }; + + if (!isAdmin) { + return ( +
+ Only administrators can manage SMTP settings. +
+ ); + } + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ + + + SMTP server + + + + + update("host", e.target.value)} + placeholder="smtp.gmail.com" + /> + + + update("port", e.target.value)} + placeholder="587" + /> + + + update("username", e.target.value)} + placeholder="user@yourdomain.com" + /> + +
+ +
+ + Stored securely as the SMTP_PASSWORD secret. +
+
+
+
+
Use TLS / SSL
+
+ Enable for port 465 (implicit TLS). Leave off for 587 (STARTTLS). +
+
+ update("secure", v)} + /> +
+
+
+ + + + Sender identity + + + + update("from_email", e.target.value)} + placeholder="notifications@yourfirm.com" + /> + + + update("from_name", e.target.value)} + placeholder="Your Firm" + /> + + + update("reply_to", e.target.value)} + /> + + +