From dee7024cb6d3c3c5d66014306bb9883b3de01634 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 1 Nov 2025 14:34:08 +0530 Subject: [PATCH] feat(newsletter): Add comprehensive newsletter management system - Implement admin newsletter page with subscriber management - Create API routes for newsletter subscribers and sending - Add unsubscribe page for newsletter management - Implement subscriber statistics and export functionality - Add tabs for subscribers list and newsletter sending - Include error handling and loading states - Enhance footer with newsletter-related components --- app/admin/newsletter/page.tsx | 271 ++++++++++++++++++ app/api/admin/newsletter/send/route.ts | 210 ++++++++++++++ app/api/admin/newsletter/subscribers/route.ts | 60 ++++ app/api/newsletter/route.ts | 99 +++++++ app/api/newsletter/unsubscribe/route.ts | 43 +++ app/newsletter/unsubscribe/page.tsx | 98 +++++++ components/footer.tsx | 95 +++++- 7 files changed, 862 insertions(+), 14 deletions(-) create mode 100644 app/admin/newsletter/page.tsx create mode 100644 app/api/admin/newsletter/send/route.ts create mode 100644 app/api/admin/newsletter/subscribers/route.ts create mode 100644 app/api/newsletter/route.ts create mode 100644 app/api/newsletter/unsubscribe/route.ts create mode 100644 app/newsletter/unsubscribe/page.tsx diff --git a/app/admin/newsletter/page.tsx b/app/admin/newsletter/page.tsx new file mode 100644 index 00000000..6b78b709 --- /dev/null +++ b/app/admin/newsletter/page.tsx @@ -0,0 +1,271 @@ +"use client" + +import { useEffect, useState } from "react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Download, Send, Loader2, Users, Mail } from "lucide-react" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +interface Subscriber { + id: number + email: string + status: string + created_at: string +} + +export default function NewsletterAdminPage() { + const [subscribers, setSubscribers] = useState([]) + const [loading, setLoading] = useState(true) + const [stats, setStats] = useState({ total: 0, subscribed: 0, unsubscribed: 0 }) + const [error, setError] = useState("") + + // Newsletter sending state + const [subject, setSubject] = useState("") + const [content, setContent] = useState("") + const [sending, setSending] = useState(false) + + useEffect(() => { + fetchSubscribers() + }, []) + + const fetchSubscribers = async () => { + try { + const response = await fetch("/api/admin/newsletter/subscribers") + const data = await response.json() + + if (response.ok) { + setSubscribers(data.subscribers || []) + setStats(data.stats || { total: 0, subscribed: 0, unsubscribed: 0 }) + } else { + setError(data.error || "Failed to fetch subscribers") + console.error("API Error:", data) + } + } catch (err) { + console.error("Failed to fetch subscribers:", err) + setError("Network error - check console") + } finally { + setLoading(false) + } + } + + const exportSubscribers = () => { + const csv = [ + ["Email", "Status", "Subscribed Date"], + ...subscribers.map(sub => [ + sub.email, + sub.status, + new Date(sub.created_at).toLocaleDateString() + ]) + ].map(row => row.join(",")).join("\n") + + const blob = new Blob([csv], { type: "text/csv" }) + const url = window.URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `newsletter-subscribers-${new Date().toISOString().split("T")[0]}.csv` + a.click() + } + + const sendNewsletter = async () => { + if (!subject || !content) { + alert("Please fill in both subject and content") + return + } + + if (!confirm(`Send newsletter to ${stats.subscribed} subscribers?`)) { + return + } + + setSending(true) + try { + const response = await fetch("/api/admin/newsletter/send", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ subject, content }), + }) + + const data = await response.json() + + if (response.ok) { + alert(`Newsletter sent successfully to ${data.sent} subscribers!`) + setSubject("") + setContent("") + } else { + alert(`Failed to send newsletter: ${data.error}`) + } + } catch { + alert("Failed to send newsletter") + } finally { + setSending(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+
+

Error Loading Subscribers

+

{error}

+

+ Make sure you're logged in as an admin and RLS policies are configured correctly. +

+
+
+ ) + } + + return ( +
+
+
+

Newsletter Management

+

Manage subscribers and send newsletters

+
+ + {/* Stats */} +
+
+
+
+

Total Subscribers

+

{stats.total}

+
+ +
+
+
+
+
+

Active

+

{stats.subscribed}

+
+ +
+
+
+
+
+

Unsubscribed

+

{stats.unsubscribed}

+
+ +
+
+
+ + + + Subscribers + Send Newsletter + + + +
+

All Subscribers

+ +
+ +
+
+ + + + + + + + + + {subscribers.map((sub) => ( + + + + + + ))} + +
EmailStatusSubscribed Date
{sub.email} + + {sub.status} + + + {new Date(sub.created_at).toLocaleDateString()} +
+
+
+
+ + +
+

Compose Newsletter

+ +
+ + setSubject(e.target.value)} + disabled={sending} + /> +
+ +
+ +