Full site redesign, help system, and dashboard stats fixes
Multi-phase visual facelift (design tokens, dashboards, sidebar/navbar shell, per-page help guides, and a layout/content pass across every remaining page) plus backend fixes to the dashboard KPI stats: - Admin/Supervisor dashboard KPIs (revenue, donations, registrations, tickets sold) now use a rolling trailing-month window (today back one calendar month, e.g. 9 May - 8 June if today is 8 June) instead of calendar month-to-date, which under-counted for most of the month. The comparison window shifts the same way, so like is still compared with like. - Reports deep-links from those stat tiles now match the same window (range=trailing_month, replacing range=this_month). - Design tokens (brand-* Tailwind scale + shadcn CSS variables), a site-wide contextual help button, fixed dashboard sidebar/navbar, Admin/Supervisor/Staff/User dashboard rebuilds backed by a new GET /api/stats/overview endpoint, a dedicated Contact page, Site Settings restyle with WhatsApp config folded in, and an Account activity feed backed by a new SecurityEvent model. - Every remaining page (home, events, registration flow, auth, legal, payment results, and every Admin/Supervisor/Staff/User tool page) restyled onto the same design tokens, several with real layout upgrades (home hero, events list/detail, donate page, auth pages). - 20+ new dedicated help guides so the whole site has page-specific help content instead of falling back to a generic guide. - Assorted fixes surfaced along the way: donation-leg double-counting in payment stats, donations not counting toward revenue, refund netting in per-method report breakdowns, and donation over-allocation after a refund. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { X, Mail, MessageCircleQuestion } from "lucide-react";
|
||||
import type { HelpContent } from "@/content/help/types";
|
||||
|
||||
export default function HelpGuideModal({ content, onClose }: { content: HelpContent; onClose: (dontShowAgain: boolean) => void }) {
|
||||
const [tab, setTab] = useState(content.tabs[0]?.key);
|
||||
const [dontShowAgain, setDontShowAgain] = useState(false);
|
||||
const activeTab = content.tabs.find(t => t.key === tab) || content.tabs[0];
|
||||
|
||||
return (
|
||||
// Above the site header (Navbar is `sticky top-0 z-50`) and above any
|
||||
// page-level popup (e.g. the report viewer at z-[60]), since the guide
|
||||
// can be opened from the FAB while another overlay is showing.
|
||||
<div className="fixed inset-0 z-[70]">
|
||||
<div className="absolute inset-0 bg-black/40 animate-in fade-in duration-200" onClick={() => onClose(dontShowAgain)} />
|
||||
<div className="absolute inset-0 flex items-center justify-center p-4">
|
||||
<div
|
||||
className="w-full max-w-3xl bg-white rounded-2xl shadow-2xl animate-in fade-in zoom-in-95 slide-in-from-bottom-2 duration-200 overflow-hidden"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-start justify-between px-5 py-4 border-b bg-gradient-to-r from-brand-50/60 to-white">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-9 h-9 rounded-full bg-brand-50 flex items-center justify-center shrink-0">
|
||||
<MessageCircleQuestion className="w-5 h-5 text-brand-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900">{content.title}</h2>
|
||||
<p className="text-xs text-gray-500">{content.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="p-1.5 rounded-full hover:bg-gray-100 transition-colors" onClick={() => onClose(dontShowAgain)} aria-label="Close">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{content.quickLinks && content.quickLinks.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 px-5 py-3 border-b bg-gray-50">
|
||||
{content.quickLinks.map(link => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => onClose(dontShowAgain)}
|
||||
className="inline-flex items-center gap-1.5 text-xs font-medium px-3 py-1.5 rounded-full border border-brand-200 bg-white text-brand-700 hover:bg-brand-100 hover:border-brand-300 transition-colors"
|
||||
>
|
||||
{link.icon && <link.icon className="w-3.5 h-3.5" />}
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
{content.tabs.length > 1 && (
|
||||
<nav className="sm:w-56 shrink-0 border-b sm:border-b-0 sm:border-r p-3 space-y-1 bg-gray-50/50">
|
||||
{content.tabs.map(t => {
|
||||
const Icon = t.icon;
|
||||
const active = activeTab?.key === t.key;
|
||||
return (
|
||||
<button
|
||||
key={t.key}
|
||||
type="button"
|
||||
onClick={() => setTab(t.key)}
|
||||
className={"w-full flex items-center gap-2 text-left text-sm px-3 py-2 rounded-lg transition-colors " + (active ? "bg-brand-600 text-white font-medium shadow-sm" : "text-gray-600 hover:bg-white hover:shadow-sm")}
|
||||
>
|
||||
<Icon className="w-4 h-4 shrink-0" />
|
||||
{t.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<div className="flex-1 min-w-0 p-5 text-sm text-gray-700 max-h-[60vh] overflow-auto">
|
||||
{activeTab?.content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t px-5 py-3 space-y-2 bg-gray-50/50">
|
||||
{content.supportContact && (
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<Mail className="w-3.5 h-3.5 shrink-0" />
|
||||
<span>
|
||||
{content.supportContact.label}:{" "}
|
||||
<a href={`mailto:${content.supportContact.email}`} className="text-brand-600 hover:underline">
|
||||
{content.supportContact.email}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 text-xs text-gray-600 cursor-pointer">
|
||||
<input type="checkbox" checked={dontShowAgain} onChange={e => setDontShowAgain(e.target.checked)} className="rounded accent-brand-600" />
|
||||
Don't show this again
|
||||
</label>
|
||||
<button className="px-4 py-2 text-sm rounded-lg bg-brand-600 text-white hover:bg-brand-700 transition-colors shadow-sm" onClick={() => onClose(dontShowAgain)}>
|
||||
Got it
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user