From 54c30b1d8a52c437a189e91f145ee8691251e043 Mon Sep 17 00:00:00 2001 From: Don Strawsburg Date: Thu, 11 Dec 2025 22:11:55 -0500 Subject: [PATCH] adding email, not in menus yet --- .env.local | 5 + .idea/dataSources.xml | 17 +++ .idea/data_source_mapping.xml | 6 + .idea/queries/Query.sql | 0 .idea/sqldialects.xml | 2 + app/admin/email/page.tsx | 273 ++++++++++++++++++++++++++++++++++ lib/api/emailRequests.ts | 80 ++++++++++ 7 files changed, 383 insertions(+) create mode 100644 .env.local create mode 100644 .idea/dataSources.xml create mode 100644 .idea/data_source_mapping.xml create mode 100644 .idea/queries/Query.sql create mode 100644 app/admin/email/page.tsx create mode 100644 lib/api/emailRequests.ts diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..6425cca --- /dev/null +++ b/.env.local @@ -0,0 +1,5 @@ + +NEXT_PUBLIC_API_BASE_URL=http://localhost:8080 + +BREVO_API_KEY=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 +BREVO_LIST_ID=9 diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..4a3f760 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://r710.dev.gofwd.group:5433/postgres + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..50be4eb --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/queries/Query.sql b/.idea/queries/Query.sql new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 83c12bd..bbd4f7d 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,6 +1,8 @@ + + \ No newline at end of file diff --git a/app/admin/email/page.tsx b/app/admin/email/page.tsx new file mode 100644 index 0000000..ac8d0b7 --- /dev/null +++ b/app/admin/email/page.tsx @@ -0,0 +1,273 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useAuth } from "@/context/AuthContext"; +import { + fetchEmailRequests, + createEmailRequest, + updateEmailRequest, + deleteEmailRequest, + type EmailRequest, + type CreateEmailRequestDto, + type UpdateEmailRequestDto, +} from "@/lib/api/emailRequests"; +import { Pencil, Trash2, Plus, X } from "lucide-react"; + +export default function AdminEmailPage() { + const { token } = useAuth(); + const [emails, setEmails] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showModal, setShowModal] = useState(false); + const [modalMode, setModalMode] = useState<"create" | "edit">("create"); + const [selectedEmail, setSelectedEmail] = useState(null); + const [formData, setFormData] = useState({ + email: "", + status: "", + }); + const [submitting, setSubmitting] = useState(false); + + const loadEmails = async () => { + if (!token) return; + + try { + setLoading(true); + const data = await fetchEmailRequests(token); + setEmails(data); + setError(null); + } catch (e: any) { + console.error(e); + setError(e?.message ?? "Failed to load email requests"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadEmails(); + }, [token]); + + const handleCreate = () => { + setModalMode("create"); + setFormData({ email: "", status: "" }); + setSelectedEmail(null); + setShowModal(true); + }; + + const handleEdit = (email: EmailRequest) => { + setModalMode("edit"); + setFormData({ email: email.email, status: email.status }); + setSelectedEmail(email); + setShowModal(true); + }; + + const handleDelete = async (id: number) => { + if (!token) return; + if (!confirm("Are you sure you want to delete this email request?")) return; + + try { + await deleteEmailRequest(token, id); + await loadEmails(); + } catch (e: any) { + alert(e?.message ?? "Failed to delete email request"); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!token) return; + + try { + setSubmitting(true); + if (modalMode === "create") { + await createEmailRequest(token, formData); + } else if (selectedEmail) { + const updateData: UpdateEmailRequestDto = {}; + if (formData.email !== selectedEmail.email) updateData.email = formData.email; + if (formData.status !== selectedEmail.status) updateData.status = formData.status; + await updateEmailRequest(token, selectedEmail.id, updateData); + } + setShowModal(false); + await loadEmails(); + } catch (e: any) { + alert(e?.message ?? "Failed to save email request"); + } finally { + setSubmitting(false); + } + }; + + if (!token) { + return ( +
+ You must be logged in to view this page. +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ Email Requests +

+

+ Manage email requests and their statuses. +

+
+ +
+ + {/* Error */} + {error && ( +
+ {error} +
+ )} + + {/* Table */} + {loading ? ( +
Loading…
+ ) : ( +
+ + + + + + + + + + + + + {emails.map((email) => ( + + + + + + + + + ))} + + {emails.length === 0 && ( + + + + )} + +
IDEmailStatusCreated AtUpdated AtActions
{email.id}{email.email} + + {email.status} + + + {new Date(email.createdAt).toLocaleString()} + + {new Date(email.updatedAt).toLocaleString()} + +
+ + +
+
+ No email requests found. +
+
+ )} + + {/* Modal */} + {showModal && ( +
+
+
+

+ {modalMode === "create" ? "Create Email Request" : "Edit Email Request"} +

+ +
+ +
+
+ + setFormData({ ...formData, email: e.target.value })} + className="w-full rounded-md border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder-zinc-500 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" + placeholder="user@example.com" + required + /> +
+ +
+ + setFormData({ ...formData, status: e.target.value })} + className="w-full rounded-md border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder-zinc-500 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500" + placeholder="pending" + /> +
+ +
+ + +
+
+
+
+ )} +
+ ); +} diff --git a/lib/api/emailRequests.ts b/lib/api/emailRequests.ts new file mode 100644 index 0000000..4e6e325 --- /dev/null +++ b/lib/api/emailRequests.ts @@ -0,0 +1,80 @@ +export interface EmailRequest { + id: number; + email: string; + status: string; + createdAt: string; + updatedAt: string; +} + +export interface CreateEmailRequestDto { + email: string; + status?: string; +} + +export interface UpdateEmailRequestDto { + email?: string; + status?: string; +} + +const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080'; + +export async function fetchEmailRequests(token: string): Promise { + const response = await fetch(`${API_BASE}/api/email`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch email requests: ${response.statusText}`); + } + + return response.json(); +} + +export async function createEmailRequest(token: string, data: CreateEmailRequestDto): Promise { + const response = await fetch(`${API_BASE}/api/email`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`Failed to create email request: ${response.statusText}`); + } + + return response.json(); +} + +export async function updateEmailRequest(token: string, id: number, data: UpdateEmailRequestDto): Promise { + const response = await fetch(`${API_BASE}/api/email/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`Failed to update email request: ${response.statusText}`); + } + + return response.json(); +} + +export async function deleteEmailRequest(token: string, id: number): Promise { + const response = await fetch(`${API_BASE}/api/email/${id}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Failed to delete email request: ${response.statusText}`); + } +}