Layout: - Removed max-w-6xl from <main> — content now fills available width - Removed padding from <main> — each page manages its own padding - Heroes go edge-to-edge (no inner margins) - Content below heroes has p-4 md:p-6 lg:p-8 padding wrapper - WhatsApp banner has its own margin so it doesn't break hero bleed - overflow-hidden on main prevents horizontal scroll from heroes All 6 pages: - Hero section sits flush against edges (no gaps) - Content below hero wrapped in padding container - Two-column grids now use the FULL available width - On a 1920px screen: sidebar 192px + content fills remaining ~1728px - Right columns are now substantial (5/12 of full width = ~720px)
498 lines
25 KiB
TypeScript
498 lines
25 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, useCallback } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { useSession } from "next-auth/react"
|
|
import Image from "next/image"
|
|
import { formatPence } from "@/lib/utils"
|
|
import { ArrowRight, Loader2, MessageCircle, Copy, Check, Share2, Upload } from "lucide-react"
|
|
import Link from "next/link"
|
|
|
|
/**
|
|
* Context-aware dashboard — feels like a landing page for YOUR data.
|
|
*
|
|
* Every state has:
|
|
* 1. A hero section (brand photography + dark panel with the key metric)
|
|
* 2. Content sections that use landing page patterns (gap-px, border-l-2)
|
|
* 3. Human language throughout
|
|
*/
|
|
|
|
const STATUS_LABELS: Record<string, { label: string; color: string; bg: string }> = {
|
|
new: { label: "Waiting", color: "text-gray-600", bg: "bg-gray-100" },
|
|
initiated: { label: "Said they paid", color: "text-[#F59E0B]", bg: "bg-[#F59E0B]/10" },
|
|
paid: { label: "Received ✓", color: "text-[#16A34A]", bg: "bg-[#16A34A]/10" },
|
|
overdue: { label: "Needs a nudge", color: "text-[#DC2626]", bg: "bg-[#DC2626]/10" },
|
|
cancelled: { label: "Cancelled", color: "text-gray-400", bg: "bg-gray-50" },
|
|
}
|
|
|
|
export default function DashboardPage() {
|
|
const router = useRouter()
|
|
const { data: session } = useSession()
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const userRole = (session?.user as any)?.role
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [data, setData] = useState<any>(null)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [ob, setOb] = useState<any>(null)
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const [events, setEvents] = useState<any[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [waConnected, setWaConnected] = useState<boolean | null>(null)
|
|
const [pledgeLink, setPledgeLink] = useState("")
|
|
const [copied, setCopied] = useState(false)
|
|
|
|
const fetchAll = useCallback(async () => {
|
|
try {
|
|
const [dashRes, obRes, evRes, waRes] = await Promise.all([
|
|
fetch("/api/dashboard").then(r => r.json()),
|
|
fetch("/api/onboarding").then(r => r.json()),
|
|
fetch("/api/events").then(r => r.json()),
|
|
fetch("/api/whatsapp/send").then(r => r.json()).catch(() => ({ connected: false })),
|
|
])
|
|
if (dashRes.summary) setData(dashRes)
|
|
if (obRes.steps) setOb(obRes)
|
|
if (Array.isArray(evRes)) setEvents(evRes)
|
|
setWaConnected(waRes.connected)
|
|
} catch { /* */ }
|
|
setLoading(false)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (userRole === "community_leader" || userRole === "volunteer") {
|
|
router.replace("/dashboard/community")
|
|
}
|
|
}, [userRole, router])
|
|
|
|
useEffect(() => {
|
|
fetchAll()
|
|
const i = setInterval(fetchAll, 15000)
|
|
return () => clearInterval(i)
|
|
}, [fetchAll])
|
|
|
|
useEffect(() => {
|
|
if (events.length > 0) {
|
|
fetch(`/api/events/${events[0].id}/qr`)
|
|
.then(r => r.json())
|
|
.then(qrs => {
|
|
if (Array.isArray(qrs) && qrs.length > 0) {
|
|
const base = typeof window !== "undefined" ? window.location.origin : ""
|
|
setPledgeLink(`${base}/p/${qrs[0].code}`)
|
|
}
|
|
})
|
|
.catch(() => {})
|
|
}
|
|
}, [events])
|
|
|
|
if (loading) {
|
|
return <div className="flex items-center justify-center py-20"><Loader2 className="h-6 w-6 text-[#1E40AF] animate-spin" /></div>
|
|
}
|
|
|
|
const hasEvents = events.length > 0
|
|
if (!hasEvents && ob) {
|
|
const eventDone = ob.steps?.find((s: { id: string; done: boolean }) => s.id === "event" || s.id === "share")?.done
|
|
if (!eventDone) {
|
|
router.replace("/dashboard/welcome")
|
|
return <div className="flex items-center justify-center py-20"><Loader2 className="h-6 w-6 text-[#1E40AF] animate-spin" /></div>
|
|
}
|
|
}
|
|
|
|
const s = data?.summary || { totalPledges: 0, totalPledgedPence: 0, totalCollectedPence: 0, collectionRate: 0 }
|
|
const byStatus = data?.byStatus || {}
|
|
const pledges = data?.pledges || []
|
|
const topSources = data?.topSources || []
|
|
const isEmpty = s.totalPledges === 0
|
|
const hasSaidPaid = (byStatus.initiated || 0) > 0
|
|
const allCollected = !isEmpty && s.collectionRate === 100
|
|
const outstanding = s.totalPledgedPence - s.totalCollectedPence
|
|
|
|
const recentPledges = pledges.filter((p: { status: string }) => p.status !== "cancelled").slice(0, 8)
|
|
const needsAttention = [
|
|
...pledges.filter((p: { status: string }) => p.status === "overdue"),
|
|
...pledges.filter((p: { status: string }) => p.status === "initiated"),
|
|
].slice(0, 5)
|
|
|
|
const activeEvent = events[0]
|
|
|
|
const copyLink = async () => {
|
|
if (!pledgeLink) return
|
|
await navigator.clipboard.writeText(pledgeLink)
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
}
|
|
|
|
// Pick contextual hero photo
|
|
const heroPhoto = allCollected
|
|
? "/images/brand/impact-02-thank-you-letter.jpg"
|
|
: isEmpty
|
|
? "/images/brand/product-setup-05-briefing-volunteers.jpg"
|
|
: "/images/brand/event-03-table-conversation.jpg"
|
|
|
|
const heroAlt = allCollected
|
|
? "Woman reading a thank-you card — the quiet moment when every pledge gets through"
|
|
: isEmpty
|
|
? "Event organiser briefing volunteers before a fundraising dinner"
|
|
: "Friends at a charity dinner — where pledges begin"
|
|
|
|
return (
|
|
<div>
|
|
|
|
{/* ━━ HERO — Brand photography + the one thing that matters ━━━ */}
|
|
<div className="grid md:grid-cols-5 gap-0">
|
|
<div className="md:col-span-3 relative min-h-[180px] md:min-h-[240px] overflow-hidden">
|
|
<Image
|
|
src={heroPhoto}
|
|
alt={heroAlt}
|
|
fill className="object-cover"
|
|
sizes="(max-width: 768px) 100vw, 60vw"
|
|
priority
|
|
/>
|
|
</div>
|
|
<div className={`md:col-span-2 p-6 md:p-8 flex flex-col justify-center ${allCollected ? "bg-[#16A34A]" : "bg-[#111827]"}`}>
|
|
{activeEvent && (
|
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-3">
|
|
{activeEvent.name}
|
|
</p>
|
|
)}
|
|
|
|
{allCollected ? (
|
|
<>
|
|
<h1 className="text-2xl md:text-3xl font-black text-white tracking-tight">
|
|
Every pledge collected
|
|
</h1>
|
|
<p className="text-sm text-white/70 mt-2">
|
|
{formatPence(s.totalCollectedPence)} received from {s.totalPledges} donors
|
|
</p>
|
|
</>
|
|
) : isEmpty ? (
|
|
<>
|
|
<h1 className="text-2xl md:text-3xl font-black text-white tracking-tight">
|
|
Ready to collect pledges
|
|
</h1>
|
|
<p className="text-sm text-gray-400 mt-2">
|
|
Share your link — pledges will appear here as they come in.
|
|
</p>
|
|
{waConnected !== null && (
|
|
<p className="text-xs text-gray-500 mt-3 flex items-center gap-1.5">
|
|
<MessageCircle className={`h-3 w-3 ${waConnected ? "text-[#25D366]" : "text-gray-500"}`} />
|
|
{waConnected ? "WhatsApp connected — reminders active" : "WhatsApp not connected"}
|
|
</p>
|
|
)}
|
|
</>
|
|
) : (
|
|
<>
|
|
<h1 className="text-3xl md:text-4xl font-black text-white tracking-tight">
|
|
{formatPence(outstanding)}
|
|
</h1>
|
|
<p className="text-sm text-gray-400 mt-1">
|
|
still to come · {s.collectionRate}% collected
|
|
</p>
|
|
|
|
{/* Contextual CTA — what should they do RIGHT NOW? */}
|
|
{hasSaidPaid ? (
|
|
<Link href="/dashboard/money" className="mt-4 inline-flex items-center gap-2 bg-white px-4 py-2.5 text-xs font-bold text-[#111827] hover:bg-gray-100 transition-colors self-start">
|
|
<Upload className="h-3.5 w-3.5" /> Upload bank statement
|
|
</Link>
|
|
) : (byStatus.overdue || 0) > 0 ? (
|
|
<Link href="/dashboard/money" className="mt-4 inline-flex items-center gap-2 border border-gray-600 px-4 py-2.5 text-xs font-bold text-gray-300 hover:text-white hover:border-white transition-colors self-start">
|
|
{byStatus.overdue} {byStatus.overdue === 1 ? "pledge needs" : "pledges need"} a nudge <ArrowRight className="h-3 w-3" />
|
|
</Link>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── Content with padding ── */}
|
|
<div className="p-4 md:p-6 lg:p-8 space-y-6">
|
|
|
|
{/* ── Empty state: Share your link ── */}
|
|
{isEmpty && pledgeLink && (
|
|
<div className="space-y-6">
|
|
<div className="border-l-2 border-[#F59E0B] pl-3">
|
|
<p className="text-[11px] font-semibold tracking-[0.15em] uppercase text-gray-500">Your pledge link</p>
|
|
</div>
|
|
|
|
<div className="bg-white border border-gray-200 p-5 space-y-4">
|
|
<p className="text-sm font-mono text-gray-500 break-all">{pledgeLink}</p>
|
|
<div className="grid grid-cols-3 gap-1.5">
|
|
<button onClick={copyLink} className={`py-2.5 text-xs font-bold transition-colors flex items-center justify-center gap-1.5 ${
|
|
copied ? "bg-[#16A34A] text-white" : "bg-[#111827] text-white hover:bg-gray-800"
|
|
}`}>
|
|
{copied ? <><Check className="h-3.5 w-3.5" /> Copied</> : <><Copy className="h-3.5 w-3.5" /> Copy</>}
|
|
</button>
|
|
<button onClick={() => window.open(`https://wa.me/?text=${encodeURIComponent(`Please pledge for ${activeEvent?.name}:\n${pledgeLink}`)}`, "_blank")} className="bg-[#25D366] hover:bg-[#25D366]/90 py-2.5 text-xs font-bold text-white transition-colors flex items-center justify-center gap-1.5">
|
|
<MessageCircle className="h-3.5 w-3.5" /> WhatsApp
|
|
</button>
|
|
<button onClick={() => navigator.share?.({ url: pledgeLink, title: activeEvent?.name })} className="border border-gray-200 hover:bg-gray-50 py-2.5 text-xs font-bold text-[#111827] transition-colors flex items-center justify-center gap-1.5">
|
|
<Share2 className="h-3.5 w-3.5" /> Share
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-l-2 border-[#1E40AF] pl-4">
|
|
<Link href={`/dashboard/events/${activeEvent?.id}`} className="text-sm font-bold text-[#111827] hover:text-[#1E40AF] transition-colors">
|
|
Create more pledge links →
|
|
</Link>
|
|
<p className="text-xs text-gray-500 mt-0.5">Give each volunteer or table their own link to see who brings in the most pledges</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── Empty state: educational guidance ── */}
|
|
{isEmpty && !pledgeLink && (
|
|
<div className="grid md:grid-cols-2 gap-6">
|
|
<div className="border-2 border-dashed border-gray-200 p-8 text-center">
|
|
<h3 className="text-base font-bold text-[#111827]">Share your pledge link to get started</h3>
|
|
<p className="text-sm text-gray-500 mt-2 max-w-xs mx-auto">
|
|
Go to <Link href="/dashboard/collect" className="text-[#1E40AF] font-bold hover:underline">Collect</Link> to
|
|
create an appeal and get your pledge link.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="border border-gray-200 bg-white">
|
|
<div className="border-b border-gray-100 px-5 py-3">
|
|
<h3 className="text-sm font-bold text-[#111827]">How it works</h3>
|
|
</div>
|
|
<div className="divide-y divide-gray-50">
|
|
{[
|
|
{ n: "01", title: "Create a pledge link", desc: "Give it a name like 'Table 5' or 'Ramadan 2026'. Print the QR or share the link." },
|
|
{ n: "02", title: "Donors pledge in 60 seconds", desc: "Name, phone, amount, Gift Aid — done. No account, no app download." },
|
|
{ n: "03", title: "They get your bank details", desc: "Instantly via WhatsApp. With a unique reference so you can match their payment." },
|
|
{ n: "04", title: "Reminders go out automatically", desc: "Day 2, day 7, day 14. Warm and never pushy. They stop when the donor pays." },
|
|
{ n: "05", title: "Upload your bank statement", desc: "We match payments to pledges automatically. No spreadsheet cross-referencing." },
|
|
].map(s => (
|
|
<div key={s.n} className="px-5 py-3 flex gap-3">
|
|
<span className="text-lg font-black text-gray-200 shrink-0 w-6">{s.n}</span>
|
|
<div>
|
|
<p className="text-xs font-bold text-[#111827]">{s.title}</p>
|
|
<p className="text-[11px] text-gray-500 leading-relaxed mt-0.5">{s.desc}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── Has pledges: Stats + Feed + Education ── */}
|
|
{!isEmpty && (
|
|
<>
|
|
{/* Stats — gap-px grid */}
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-px bg-gray-200">
|
|
{[
|
|
{ value: String(s.totalPledges), label: "Pledges" },
|
|
{ value: formatPence(s.totalPledgedPence), label: "Promised" },
|
|
{ value: formatPence(s.totalCollectedPence), label: "Received", accent: "text-[#16A34A]" },
|
|
{ value: `${s.collectionRate}%`, label: "Collected" },
|
|
].map(stat => (
|
|
<div key={stat.label} className="bg-white p-5">
|
|
<p className={`text-2xl md:text-3xl font-black tracking-tight ${stat.accent || "text-[#111827]"}`}>{stat.value}</p>
|
|
<p className="text-[10px] text-gray-500 mt-1">{stat.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Progress bar */}
|
|
<div className="bg-white p-5">
|
|
<div className="flex justify-between items-center mb-3">
|
|
<span className="text-sm font-bold text-[#111827]">Promised → Received</span>
|
|
<span className="text-sm font-black text-[#111827]">{s.collectionRate}%</span>
|
|
</div>
|
|
<div className="h-3 bg-gray-100 overflow-hidden">
|
|
<div className="h-full bg-[#1E40AF] transition-all duration-700" style={{ width: `${s.collectionRate}%` }} />
|
|
</div>
|
|
<div className="flex justify-between mt-2 text-xs text-gray-500">
|
|
<span>{formatPence(s.totalCollectedPence)} received</span>
|
|
<span>{formatPence(outstanding)} still to come</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── "Said they paid" prompt ── */}
|
|
{hasSaidPaid && (
|
|
<Link href="/dashboard/money">
|
|
<div className="border-l-2 border-[#F59E0B] bg-[#F59E0B]/5 p-4 flex items-center gap-3 hover:bg-[#F59E0B]/10 transition-colors cursor-pointer">
|
|
<Upload className="h-5 w-5 text-[#F59E0B] shrink-0" />
|
|
<div className="flex-1">
|
|
<p className="text-sm font-bold text-[#111827]">{byStatus.initiated} {byStatus.initiated === 1 ? "person says" : "people say"} they've paid</p>
|
|
<p className="text-xs text-gray-600">Upload your bank statement to confirm their payments automatically</p>
|
|
</div>
|
|
<ArrowRight className="h-4 w-4 text-[#F59E0B] shrink-0" />
|
|
</div>
|
|
</Link>
|
|
)}
|
|
|
|
<div className="grid lg:grid-cols-12 gap-6">
|
|
{/* LEFT column: Data */}
|
|
<div className="lg:col-span-7 space-y-6">
|
|
{/* Needs attention */}
|
|
{needsAttention.length > 0 && (
|
|
<div className="bg-white border border-gray-200">
|
|
<div className="border-b border-gray-100 px-5 py-3 flex items-center justify-between">
|
|
<h3 className="text-sm font-bold text-[#111827]">Needs attention</h3>
|
|
<span className="text-[10px] font-bold text-white bg-[#DC2626] px-1.5 py-0.5">{needsAttention.length}</span>
|
|
</div>
|
|
<div className="divide-y divide-gray-50">
|
|
{needsAttention.map((p: { id: string; donorName: string | null; amountPence: number; eventName: string; status: string }) => {
|
|
const sl = STATUS_LABELS[p.status] || STATUS_LABELS.new
|
|
return (
|
|
<div key={p.id} className="px-5 py-3 flex items-center justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium text-[#111827]">{p.donorName || "Anonymous"}</p>
|
|
<p className="text-xs text-gray-500">{formatPence(p.amountPence)}</p>
|
|
</div>
|
|
<span className={`text-[10px] font-bold px-2 py-0.5 ${sl.bg} ${sl.color}`}>{sl.label}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
<div className="px-5 py-2 border-t border-gray-50">
|
|
<Link href="/dashboard/money" className="text-xs font-semibold text-[#1E40AF] hover:underline">View all →</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recent pledges */}
|
|
<div className="bg-white border border-gray-200">
|
|
<div className="border-b border-gray-100 px-5 py-3 flex items-center justify-between">
|
|
<h3 className="text-sm font-bold text-[#111827]">Recent pledges</h3>
|
|
<Link href="/dashboard/money" className="text-xs font-semibold text-[#1E40AF] hover:underline">View all</Link>
|
|
</div>
|
|
<div className="divide-y divide-gray-50">
|
|
{recentPledges.map((p: {
|
|
id: string; donorName: string | null; amountPence: number; status: string;
|
|
eventName: string; createdAt: string; donorPhone: string | null;
|
|
installmentNumber: number | null; installmentTotal: number | null;
|
|
}) => {
|
|
const sl = STATUS_LABELS[p.status] || STATUS_LABELS.new
|
|
const initial = (p.donorName || "A")[0].toUpperCase()
|
|
const days = Math.floor((Date.now() - new Date(p.createdAt).getTime()) / 86400000)
|
|
const when = days === 0 ? "Today" : days === 1 ? "Yesterday" : days < 7 ? `${days}d ago` : new Date(p.createdAt).toLocaleDateString("en-GB", { day: "numeric", month: "short" })
|
|
|
|
return (
|
|
<div key={p.id} className="px-5 py-3 flex items-center gap-3">
|
|
<div className="h-8 w-8 bg-[#1E40AF]/10 flex items-center justify-center shrink-0">
|
|
<span className="text-xs font-black text-[#1E40AF]">{initial}</span>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-sm font-medium text-[#111827] truncate">{p.donorName || "Anonymous"}</p>
|
|
{p.donorPhone && <MessageCircle className="h-3 w-3 text-[#25D366] shrink-0" />}
|
|
</div>
|
|
<p className="text-xs text-gray-500 truncate">{when}</p>
|
|
</div>
|
|
<div className="text-right shrink-0">
|
|
<p className="text-sm font-black text-[#111827]">{formatPence(p.amountPence)}</p>
|
|
<span className={`text-[10px] font-bold px-1.5 py-0.5 ${sl.bg} ${sl.color}`}>{sl.label}</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
{recentPledges.length === 0 && (
|
|
<div className="px-5 py-8 text-center text-sm text-gray-400">
|
|
Pledges will appear here as they come in
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* RIGHT column: Status + Sources + Guidance */}
|
|
<div className="lg:col-span-5 space-y-6">
|
|
{/* How pledges are doing */}
|
|
<div className="bg-white border border-gray-200">
|
|
<div className="border-b border-gray-100 px-5 py-3">
|
|
<h3 className="text-sm font-bold text-[#111827]">How pledges are doing</h3>
|
|
</div>
|
|
<div className="divide-y divide-gray-50">
|
|
{Object.entries(byStatus).map(([status, count]) => {
|
|
const sl = STATUS_LABELS[status] || STATUS_LABELS.new
|
|
return (
|
|
<div key={status} className="px-5 py-2.5 flex items-center justify-between">
|
|
<span className={`text-xs font-bold px-2 py-0.5 ${sl.bg} ${sl.color}`}>{sl.label}</span>
|
|
<span className="text-sm font-black text-[#111827]">{count as number}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Where pledges come from */}
|
|
{topSources.length > 0 && (
|
|
<div className="bg-white border border-gray-200">
|
|
<div className="border-b border-gray-100 px-5 py-3">
|
|
<h3 className="text-sm font-bold text-[#111827]">Where pledges come from</h3>
|
|
</div>
|
|
<div className="divide-y divide-gray-50">
|
|
{topSources.slice(0, 5).map((src: { label: string; count: number; amount: number }, i: number) => (
|
|
<div key={i} className="px-5 py-2.5 flex items-center justify-between">
|
|
<div className="flex items-center gap-2.5">
|
|
<span className="text-xs font-black text-gray-300 w-4">{i + 1}</span>
|
|
<span className="text-sm text-[#111827]">{src.label}</span>
|
|
</div>
|
|
<span className="text-sm font-bold text-[#111827]">{formatPence(src.amount)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* What to do next — contextual guidance */}
|
|
<div className="border-l-2 border-[#F59E0B] pl-4 space-y-2">
|
|
<p className="text-xs font-bold text-[#111827]">What to do next</p>
|
|
<div className="space-y-2">
|
|
{s.collectionRate < 100 && (byStatus.initiated || 0) > 0 && (
|
|
<Link href="/dashboard/money" className="flex items-start gap-2 group">
|
|
<span className="text-[#F59E0B] font-bold text-xs shrink-0 mt-0.5">→</span>
|
|
<p className="text-xs text-gray-600 group-hover:text-[#111827] transition-colors">
|
|
<strong>Upload your bank statement</strong> to confirm {byStatus.initiated} {byStatus.initiated === 1 ? "payment" : "payments"} automatically
|
|
</p>
|
|
</Link>
|
|
)}
|
|
{s.collectionRate < 50 && (
|
|
<Link href="/dashboard/collect" className="flex items-start gap-2 group">
|
|
<span className="text-[#F59E0B] font-bold text-xs shrink-0 mt-0.5">→</span>
|
|
<p className="text-xs text-gray-600 group-hover:text-[#111827] transition-colors">
|
|
<strong>Share your link more widely</strong> — WhatsApp groups, social media, or print the QR
|
|
</p>
|
|
</Link>
|
|
)}
|
|
<Link href="/dashboard/automations" className="flex items-start gap-2 group">
|
|
<span className="text-[#F59E0B] font-bold text-xs shrink-0 mt-0.5">→</span>
|
|
<p className="text-xs text-gray-600 group-hover:text-[#111827] transition-colors">
|
|
<strong>Check your messages</strong> — see what donors receive and improve wording
|
|
</p>
|
|
</Link>
|
|
<Link href="/dashboard/reports" className="flex items-start gap-2 group">
|
|
<span className="text-[#F59E0B] font-bold text-xs shrink-0 mt-0.5">→</span>
|
|
<p className="text-xs text-gray-600 group-hover:text-[#111827] transition-colors">
|
|
<strong>Download for your treasurer</strong> — Gift Aid report, full pledge data, HMRC-ready CSV
|
|
</p>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Understanding statuses */}
|
|
<div className="border-l-2 border-[#1E40AF] pl-4 space-y-1.5">
|
|
<p className="text-xs font-bold text-[#111827]">What the statuses mean</p>
|
|
{[
|
|
{ label: "Waiting", desc: "Pledged but hasn't paid yet — reminders are being sent" },
|
|
{ label: "Said they paid", desc: "Donor replied PAID — upload bank statement to confirm" },
|
|
{ label: "Received ✓", desc: "Payment confirmed in your bank account" },
|
|
{ label: "Needs a nudge", desc: "It's been a while — you can send a manual reminder" },
|
|
].map(s => (
|
|
<div key={s.label} className="flex items-start gap-2">
|
|
<span className="w-1.5 h-1.5 bg-[#1E40AF] shrink-0 mt-1.5" />
|
|
<p className="text-[10px] text-gray-500"><strong className="text-[#111827]">{s.label}</strong> — {s.desc}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|