"use client" import { useState, useEffect, useCallback } from "react" import { useSession } from "next-auth/react" import { formatPence } from "@/lib/utils" import { Loader2, Copy, Check, MessageCircle, Mail, Trophy, Plus, Link2, Download, QrCode as QrCodeIcon } from "lucide-react" import { QRCodeCanvas } from "@/components/qr-code" /** * /dashboard/community — Community Leader's dashboard * * Who is the Community Leader? * Imam Yusuf. Sister Mariam. Uncle Tariq. The person who rallies * their mosque, WhatsApp group, neighbourhood to pledge. * * Their mental model: * 1. "How are WE doing?" → their community's stats vs the whole appeal * 2. "I need to share the link" → share buttons, front and center * 3. "Who from my group has pledged?" → simple donor list * 4. "How do we compare?" → leaderboard across all communities * * What they DON'T see: * - Bank details, WhatsApp settings, charity config * - Other communities' donor details * - Ability to confirm payments or change statuses * - Full reconciliation * * This is a SCOPED, READ-MOSTLY view. * They can create new links and share. That's it. */ interface EventSummary { id: string; name: string; totalPledged: number; totalCollected: number; pledgeCount: number } interface SourceInfo { id: string; label: string; code: string; volunteerName: string | null scanCount: number; pledgeCount: number; totalPledged: number; totalCollected?: number } interface PledgeInfo { id: string; donorName: string | null; amountPence: number; status: string; createdAt: string } const STATUS: Record = { new: { label: "Waiting", color: "text-gray-600", bg: "bg-gray-100" }, initiated: { label: "Said paid", color: "text-[#F59E0B]", bg: "bg-[#F59E0B]/10" }, paid: { label: "Received ✓", color: "text-[#16A34A]", bg: "bg-[#16A34A]/10" }, overdue: { label: "Overdue", color: "text-[#DC2626]", bg: "bg-[#DC2626]/10" }, cancelled: { label: "Cancelled", color: "text-gray-400", bg: "bg-gray-50" }, } export default function CommunityPage() { const { data: session } = useSession() // eslint-disable-next-line @typescript-eslint/no-explicit-any const user = session?.user as any const [events, setEvents] = useState([]) const [allSources, setAllSources] = useState([]) const [mySources, setMySources] = useState([]) const [myPledges, setMyPledges] = useState([]) const [loading, setLoading] = useState(true) const [copiedCode, setCopiedCode] = useState(null) const [showQr, setShowQr] = useState(null) // Create link const [newLinkName, setNewLinkName] = useState("") const [creating, setCreating] = useState(false) const [showCreate, setShowCreate] = useState(false) const baseUrl = typeof window !== "undefined" ? window.location.origin : "" const userName = user?.name || user?.email?.split("@")[0] || "" const loadData = useCallback(async () => { try { const [evRes, dashRes] = await Promise.all([ fetch("/api/events").then(r => r.json()), fetch("/api/dashboard").then(r => r.json()), ]) if (Array.isArray(evRes)) { setEvents(evRes) // Load sources for first event if (evRes.length > 0) { const srcRes = await fetch(`/api/events/${evRes[0].id}/qr`).then(r => r.json()) if (Array.isArray(srcRes)) { setAllSources(srcRes) // Filter to "my" sources — those with this user's name or created by this user const mine = srcRes.filter((s: SourceInfo) => s.volunteerName?.toLowerCase().includes(userName.toLowerCase()) || s.label.toLowerCase().includes(userName.toLowerCase()) ) setMySources(mine.length > 0 ? mine : srcRes.slice(0, 3)) // fallback: show first 3 } } } // Get pledges (scoped view — show recent) if (dashRes.pledges) { setMyPledges(dashRes.pledges.slice(0, 20)) } } catch { /* */ } setLoading(false) }, [userName]) useEffect(() => { loadData() }, [loadData]) // Actions const copyLink = async (code: string) => { await navigator.clipboard.writeText(`${baseUrl}/p/${code}`) setCopiedCode(code) setTimeout(() => setCopiedCode(null), 2000) } const shareWhatsApp = (code: string, label: string) => { window.open(`https://wa.me/?text=${encodeURIComponent(`Assalamu Alaikum! Please pledge here 🤲\n\n${label}\n${baseUrl}/p/${code}`)}`, "_blank") } const createLink = async () => { if (!newLinkName.trim() || events.length === 0) return setCreating(true) try { const res = await fetch(`/api/events/${events[0].id}/qr`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label: newLinkName.trim(), volunteerName: userName }), }) if (res.ok) { const src = await res.json() const newSrc = { ...src, scanCount: 0, pledgeCount: 0, totalPledged: 0, totalCollected: 0 } setMySources(prev => [newSrc, ...prev]) setAllSources(prev => [newSrc, ...prev]) setNewLinkName("") setShowCreate(false) } } catch { /* */ } setCreating(false) } if (loading) return
const activeEvent = events[0] const myTotal = mySources.reduce((s, l) => s + l.totalPledged, 0) const myPledgeCount = mySources.reduce((s, l) => s + l.pledgeCount, 0) const appealTotal = activeEvent?.totalPledged || 0 const myPct = appealTotal > 0 ? Math.round((myTotal / appealTotal) * 100) : 0 // Leaderboard (all sources, sorted by amount) const leaderboard = [...allSources].sort((a, b) => b.totalPledged - a.totalPledged) return (
{/* ── Header: "How are WE doing?" ── */}

{activeEvent?.name || "Your community"}

Welcome back, {userName.split(" ")[0] || "Leader"}

{/* ── My community's stats vs the whole appeal ── */}

Your community

{formatPence(myTotal)}

{myPledgeCount} pledges from {mySources.length} links

Whole appeal

{formatPence(appealTotal)}

{activeEvent?.pledgeCount || 0} total pledges

{appealTotal > 0 && (
Your contribution {myPct}%
)}
{/* ── Your links + share ── */}

Your links ({mySources.length})

{showCreate && (

Create a link for your community

setNewLinkName(e.target.value)} placeholder='e.g. "Friday Halaqa", "Sisters WhatsApp", "Youth Group"' autoFocus onKeyDown={e => e.key === "Enter" && createLink()} className="flex-1 h-11 px-4 border-2 border-gray-200 text-sm placeholder:text-gray-300 focus:border-[#1E40AF] outline-none" />
)} {mySources.length === 0 ? (

No links yet

Create a link to start collecting pledges for your community

) : (
{mySources.map(src => { const url = `${baseUrl}/p/${src.code}` const isCopied = copiedCode === src.code const isQrOpen = showQr === src.code return (

{src.label}

{src.pledgeCount}

pledges

{formatPence(src.totalPledged)}

raised

{url}

{isQrOpen && ( )}
) })}
)} {/* ── Leaderboard — how do we compare? ── */} {leaderboard.filter(s => s.pledgeCount > 0).length >= 2 && (

How communities compare

{leaderboard.filter(s => s.pledgeCount > 0).slice(0, 10).map((src, i) => { const isMine = mySources.some(m => m.id === src.id) const medals = ["bg-[#F59E0B]", "bg-gray-400", "bg-[#CD7F32]"] return (
{i + 1}

{src.volunteerName || src.label}

{isMine && You}

{src.pledgeCount} pledges

{formatPence(src.totalPledged)}

) })}
)} {/* ── Recent pledges (read-only, no actions) ── */} {myPledges.length > 0 && (

Recent pledges

{myPledges.slice(0, 10).map(p => { const sl = STATUS[p.status] || STATUS.new const days = Math.floor((Date.now() - new Date(p.createdAt).getTime()) / 86400000) const when = days === 0 ? "Today" : days === 1 ? "Yesterday" : `${days}d ago` return (

{p.donorName || "Anonymous"}

{when}

{formatPence(p.amountPence)}

{sl.label}
) })}
)}
) }