From e200667611cd8fb4b16169f0624d28f92a9afa28 Mon Sep 17 00:00:00 2001 From: Don Strawsburg Date: Fri, 12 Dec 2025 22:39:59 -0500 Subject: [PATCH] added a feature for sending mail from within the application --- .env.local | 1 + app/actions/sendEmail.ts | 57 ++++++++++++++ app/admin/email/send/page.tsx | 116 +++++++++++++++++++++++++++++ app/admin/layout.tsx | 7 +- app/login/page.tsx | 113 ++++++++++++---------------- app/register/page.tsx | 135 ++++++++++++++-------------------- components/SendEmail.tsx | 117 +++++++++++++++++++++++++++++ components/ui/form.tsx | 72 ++++++++++++++++++ lib/api/actions/emailSend.ts | 0 lib/cn.tsx | 6 ++ 10 files changed, 480 insertions(+), 144 deletions(-) create mode 100644 app/actions/sendEmail.ts create mode 100644 app/admin/email/send/page.tsx create mode 100644 components/SendEmail.tsx create mode 100644 components/ui/form.tsx create mode 100644 lib/api/actions/emailSend.ts create mode 100644 lib/cn.tsx diff --git a/.env.local b/.env.local index dc8fd4e..3224d9e 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,6 @@ NEXT_PUBLIC_API_BASE_URL=http://localhost:8080 +BACKEND_BASE_URL=http://localhost:8080 # Middleware to limited site to only root page NEXT_PUBLIC_LAUNCH_ONLY_ROOT=false diff --git a/app/actions/sendEmail.ts b/app/actions/sendEmail.ts new file mode 100644 index 0000000..1bb8368 --- /dev/null +++ b/app/actions/sendEmail.ts @@ -0,0 +1,57 @@ +"use server"; + +export type SendEmailPayload = { + recipient: string; + subject: string; + body: string; +}; + +export type ApiResponse = { + success?: boolean; + message?: string; + data?: T; +}; + +export type EmailRequest = { + id: number; + recipient: string; + subject: string; + body: string; + sentAt: string | null; + status: "PENDING" | "SENT" | "FAILED" | string; + errorMessage: string | null; + createdAt: string; +}; + +export async function sendEmailAction( + payload: SendEmailPayload +): Promise> { + const baseUrl = process.env.BACKEND_BASE_URL; + if (!baseUrl) { + throw new Error("BACKEND_BASE_URL is not set"); + } + + const res = await fetch(`${baseUrl}/api/email/send`, { + method: "POST", + headers: { + "Content-Type": "application/json", + // Authorization: `Bearer ${process.env.BACKEND_JWT}`, // if needed + }, + body: JSON.stringify(payload), + cache: "no-store", + }); + + const data: ApiResponse | null = await res + .json() + .catch(() => null); + + if (!res.ok) { + throw new Error(data?.message ?? `Request failed with ${res.status}`); + } + + if (!data) { + throw new Error("Empty response body"); + } + + return data; +} \ No newline at end of file diff --git a/app/admin/email/send/page.tsx b/app/admin/email/send/page.tsx new file mode 100644 index 0000000..2151763 --- /dev/null +++ b/app/admin/email/send/page.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { sendEmailAction, type ApiResponse, type EmailRequest } from "/app/actions/sendEmail"; +import { Button, Field, Input, Textarea } from "@/components/ui/form"; + +export default function SendEmailForm(): JSX.Element { + const [result, setResult] = useState< + ApiResponse | { error: string } | null + >(null); + const [submitting, setSubmitting] = useState(false); + + const [showSuccess, setShowSuccess] = useState(false); + const successTimerRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (successTimerRef.current) clearTimeout(successTimerRef.current); + }; + }, []); + + async function onSubmit(e: React.FormEvent): Promise { + e.preventDefault(); + setResult(null); + + setShowSuccess(false); + if (successTimerRef.current) clearTimeout(successTimerRef.current); + + const form = new FormData(e.currentTarget); + + const recipient = String(form.get("recipient") ?? ""); + const subject = String(form.get("subject") ?? ""); + const body = String(form.get("body") ?? ""); + + try { + setSubmitting(true); + const data = await sendEmailAction({ recipient, subject, body }); + setResult(data); + + if (data?.success === true) { + setShowSuccess(true); + + successTimerRef.current = setTimeout(() => { + setShowSuccess(false); + }, 2000); + } + } catch (err) { + const message = err instanceof Error ? err.message : "Unknown error"; + setResult({ error: message }); + } finally { + setSubmitting(false); + } + } + + return ( +
+

+ Send Email +

+

+ Compose a message and send it from the admin panel. +

+ +
+ {result && "error" in result && ( +
+ {result.error} +
+ )} + + + + + + + + + + +