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} +
+ )} + + + + + + + + + + +