POSITIONING FIX — PNPL is NOT just 'QR codes at events': - Charities collecting at events (QR per table) - High-net-worth donor outreach (personal links via WhatsApp/email) - Org-to-org pledges (multi-charity projects) - Personal fundraisers (LaunchGood/Enthuse redirect) TERMINOLOGY (throughout app): - Events → Campaigns (sidebar, pages, create dialogs, onboarding) - QR Codes page → Pledge Links (sharing-first, QR is one option) - Scans → Clicks (not just QR scans) - 'New Event' → 'New Campaign' - 'Create QR Code' → 'Create Pledge Link' - Source label: 'Table Name' → 'Source / Channel' SHARING (pledge links page): - 4-button share row: Copy · WhatsApp · Email · More (native share) - Each link shows its full URL - Create dialog suggests: 'WhatsApp Family Group, Table 5, Instagram Bio' - QR code is still shown but as one option, not the hero LANDING PAGE (complete rewrite): - Hero: 'Collect pledges. Convert them into donations.' - 4 use case cards: Events, HNW Donors, Org-to-Org, Personal Fundraisers - 'Share anywhere' section: WhatsApp, QR, Email, Instagram, Twitter, 1-on-1 - Platform support: Bank Transfer, LaunchGood, Enthuse, JustGiving, GoFundMe, Any URL - Islamic fund types section: Zakat, Sadaqah, Sadaqah Jariyah, Lillah, Fitrana ZAKAT & FUND TYPES: - Organization.zakatEnabled toggle in Settings - Pledge.fundType: general, zakat, sadaqah, lillah, fitrana - Identity step: fund type picker (5 options) when org has zakatEnabled - Zakat note: Quran 9:60 categories reference - Settings: toggle card with fund type descriptions FUND ALLOCATION: - Event.fundAllocation: 'Mosque Building Fund', 'Orphan Sponsorship' etc. - Charities can also add external URL for reference/allocation (not just fundraisers) - Shows on campaign cards and pledge flow
141 lines
6.0 KiB
TypeScript
141 lines
6.0 KiB
TypeScript
"use client"
|
|
|
|
import Link from "next/link"
|
|
import { usePathname } from "next/navigation"
|
|
import { useSession, signOut } from "next-auth/react"
|
|
import { LayoutDashboard, Megaphone, FileBarChart, Upload, Download, Settings, Plus, ExternalLink, LogOut, Shield } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
const navItems = [
|
|
{ href: "/dashboard", label: "Overview", icon: LayoutDashboard },
|
|
{ href: "/dashboard/events", label: "Campaigns", icon: Megaphone },
|
|
{ href: "/dashboard/pledges", label: "Pledges", icon: FileBarChart },
|
|
{ href: "/dashboard/reconcile", label: "Reconcile", icon: Upload },
|
|
{ href: "/dashboard/exports", label: "Exports", icon: Download },
|
|
{ href: "/dashboard/settings", label: "Settings", icon: Settings },
|
|
]
|
|
|
|
const adminNav = { href: "/dashboard/admin", label: "Super Admin", icon: Shield }
|
|
|
|
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
const pathname = usePathname()
|
|
const { data: session } = useSession()
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const user = session?.user as any
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50/50">
|
|
{/* Top bar */}
|
|
<header className="sticky top-0 z-40 border-b bg-white/80 backdrop-blur-xl">
|
|
<div className="flex h-14 items-center gap-4 px-4 md:px-6">
|
|
<Link href="/dashboard" className="flex items-center gap-2.5">
|
|
<div className="h-8 w-8 rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center shadow-lg shadow-trust-blue/20">
|
|
<span className="text-white font-bold text-sm">P</span>
|
|
</div>
|
|
<div className="hidden sm:block">
|
|
<span className="font-black text-sm text-gray-900">{user?.orgName || "PNPL"}</span>
|
|
</div>
|
|
</Link>
|
|
<div className="flex-1" />
|
|
<Link href="/dashboard/events" className="hidden md:block">
|
|
<button className="inline-flex items-center gap-1.5 rounded-lg bg-trust-blue px-3 py-1.5 text-xs font-semibold text-white hover:bg-trust-blue/90 transition-colors">
|
|
<Plus className="h-3 w-3" /> New Campaign
|
|
</button>
|
|
</Link>
|
|
<Link href="/" className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1">
|
|
<ExternalLink className="h-3 w-3" />
|
|
</Link>
|
|
{session && (
|
|
<button
|
|
onClick={() => signOut({ callbackUrl: "/login" })}
|
|
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
|
>
|
|
<LogOut className="h-3 w-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</header>
|
|
|
|
<div className="flex">
|
|
{/* Desktop sidebar */}
|
|
<aside className="hidden md:flex w-56 flex-col border-r bg-white min-h-[calc(100vh-3.5rem)] py-3 px-2">
|
|
<nav className="space-y-0.5">
|
|
{navItems.map((item) => {
|
|
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
|
isActive
|
|
? "bg-trust-blue/5 text-trust-blue"
|
|
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground"
|
|
)}
|
|
>
|
|
<item.icon className="h-4 w-4" />
|
|
{item.label}
|
|
</Link>
|
|
)
|
|
})}
|
|
{user?.role === "super_admin" && (
|
|
<>
|
|
<div className="my-2 border-t" />
|
|
<Link
|
|
href={adminNav.href}
|
|
className={cn(
|
|
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
|
|
pathname === adminNav.href
|
|
? "bg-trust-blue/5 text-trust-blue"
|
|
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground"
|
|
)}
|
|
>
|
|
<adminNav.icon className="h-4 w-4" />
|
|
{adminNav.label}
|
|
</Link>
|
|
</>
|
|
)}
|
|
</nav>
|
|
|
|
<div className="mt-auto px-2 pt-4">
|
|
<div className="rounded-xl bg-gradient-to-br from-trust-blue/5 to-warm-amber/5 border p-3 space-y-1.5">
|
|
<p className="text-xs font-semibold">Need help?</p>
|
|
<p className="text-[10px] text-muted-foreground leading-relaxed">
|
|
Get a fractional Head of Technology to optimise your charity's digital stack.
|
|
</p>
|
|
<Link href="/dashboard/apply" className="inline-block text-[10px] font-semibold text-trust-blue hover:underline">
|
|
Learn more →
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Mobile bottom nav */}
|
|
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-40 border-t bg-white/95 backdrop-blur-xl flex justify-around py-1.5 px-1">
|
|
{navItems.slice(0, 5).map((item) => {
|
|
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
"flex flex-col items-center gap-0.5 py-1 px-2 rounded-lg transition-colors",
|
|
isActive ? "text-trust-blue" : "text-muted-foreground"
|
|
)}
|
|
>
|
|
<item.icon className="h-5 w-5" />
|
|
<span className="text-[9px] font-medium">{item.label}</span>
|
|
</Link>
|
|
)
|
|
})}
|
|
</nav>
|
|
|
|
{/* Main content */}
|
|
<main className="flex-1 p-4 md:p-6 pb-20 md:pb-6 max-w-6xl">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|