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:
2026-08-06 15:00:10 +02:00
co-authored by Claude Sonnet 5
parent d74fec3a5c
commit 8e6cb542d9
119 changed files with 5116 additions and 4316 deletions
+48
View File
@@ -0,0 +1,48 @@
import React from "react";
import { LogIn, UserPlus, KeyRound, ShieldCheck } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content shared by /login, /register, and /forgot-password. */
export const authHelpContent: HelpContent = {
title: "Account help",
subtitle: "Signing in, creating an account, and resetting your password.",
quickLinks: [
{ label: "Log in", href: "/login", icon: LogIn },
{ label: "Create an account", href: "/register", icon: UserPlus },
{ label: "Forgot password", href: "/forgot-password", icon: KeyRound },
],
tabs: [
{
key: "account",
label: "Signing in & registering",
icon: LogIn,
content: (
<div className="space-y-4">
<p>You can log in with either your email address or phone number, whichever you registered with.</p>
<div className="space-y-4">
<GuideItem icon={UserPlus} title="Don't have an account yet?" tone="brand">
You don&apos;t always need one some events let you register as a guest with just your name and email. If an event requires an account, you&apos;ll be prompted to create one right from the registration form.
</GuideItem>
<GuideItem icon={LogIn} title="Already registered as a guest?" tone="green">
If you registered as a guest before, you can still create a full account later using the same email your past registrations will be there once you&apos;re logged in.
</GuideItem>
</div>
</div>
),
},
{
key: "password",
label: "Resetting your password",
icon: KeyRound,
content: (
<div className="space-y-4">
<p>Use &quot;Forgot your password?&quot; on the login page to get a reset link emailed to you it expires after an hour for security.</p>
<GuideItem icon={ShieldCheck} title="Already logged in?" tone="amber">
You can change your password directly from Profile &amp; Security instead, without needing an email link.
</GuideItem>
</div>
),
},
],
};
@@ -0,0 +1,69 @@
import React from "react";
import { Wallet, DollarSign, ClipboardCheck, FileBarChart, Lock } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/admin/cashup (the event list). */
export const dashboardAdminCashupHelpContent: HelpContent = {
title: "Event cashup help",
subtitle: "Set costs, reconcile takings, and close out an event.",
tabs: [
{
key: "overview",
label: "Overview",
icon: Wallet,
content: (
<div className="space-y-4">
<p>This list shows every event you can run a cashup on. Search, or toggle Past, Inactive, and Closed events (closed ones are hidden by default). Each row shows an Open or Closed badge tap a row to open its cashup.</p>
</div>
),
},
],
};
/** Help content for /dashboard/admin/cashup/[id] (the cashup detail page). */
export const dashboardAdminCashupDetailHelpContent: HelpContent = {
title: "Cashup help",
subtitle: "Costs, reconciliation, and the final report — one event at a time.",
tabs: [
{
key: "costs",
label: "Costs",
icon: DollarSign,
content: (
<div className="space-y-4">
<p>Add every cost for the event a label, whether it&apos;s a once-off amount or charged per ticket sold, an amount, and optionally which payment method it was paid from. A running total is shown as you go.</p>
<p className="text-xs text-gray-500">Costs lock once the event is closed.</p>
</div>
),
},
{
key: "reconciliation",
label: "Reconciliation",
icon: ClipboardCheck,
content: (
<div className="space-y-4">
<p>A per-method table (cash, card, EFT, other) compares income, costs charged to that method, and expected vs actual amounts.</p>
<div className="space-y-4">
<GuideItem icon={ClipboardCheck} title="Cash accountability" tone="amber">
Cash&apos;s &quot;actual&quot; figure isn&apos;t typed directly it&apos;s the live total of the staff cash-count panel below, where each staff member enters their physical cash by denomination.
</GuideItem>
<GuideItem icon={Lock} title="Closing the event" tone="rose">
Save draft keeps working on it later; Quick close skips detailed reconciliation; Close event with cashup locks everything in. A closed event can be Reopened if something needs fixing both actions are logged in the History panel.
</GuideItem>
</div>
</div>
),
},
{
key: "report",
label: "Report",
icon: FileBarChart,
content: (
<div className="space-y-4">
<p>A read-only summary of revenue by method, denomination breakdown, staff cash accountability, costs, donations, and net profit. This becomes the default view automatically once the event is closed.</p>
</div>
),
},
],
};
@@ -0,0 +1,40 @@
import React from "react";
import { ClipboardList, Filter, Ban } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/admin/registrations. */
export const dashboardAdminRegistrationsHelpContent: HelpContent = {
title: "Registrations help",
subtitle: "Search, review, and manage every registration.",
tabs: [
{
key: "overview",
label: "Overview",
icon: Filter,
content: (
<div className="space-y-4">
<p>Search by name, email, phone, event, or registration ID, and filter by event or status. The stat tiles above the list total each status for your current filter, plus revenue and outstanding balance (cancelled registrations are excluded from money totals).</p>
</div>
),
},
{
key: "managing",
label: "Managing a registration",
icon: ClipboardList,
content: (
<div className="space-y-4">
<p>Click a row to expand it and see ticket options (with early-bird flags where applied), the payment history, and any submitted form responses.</p>
<div className="space-y-4">
<GuideItem icon={ClipboardList} title="Changing status" tone="brand">
Use the status dropdown inside the expanded row to move a registration between pending, confirmed, partially paid, paid, or cancelled.
</GuideItem>
<GuideItem icon={Ban} title="Cancelling" tone="rose">
Cancelling a registration disables further status changes double-check before confirming.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,47 @@
import React from "react";
import { Users, UserPlus, ShieldAlert } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/admin/users. */
export const dashboardAdminUsersHelpContent: HelpContent = {
title: "Managing users",
subtitle: "Create, edit, and manage accounts and roles.",
tabs: [
{
key: "overview",
label: "Overview",
icon: Users,
content: (
<div className="space-y-4">
<p>Search by name, email, or phone, and filter by role or active status. Use &quot;Create user&quot; to add a new account with a name, email, password, phone, and role.</p>
<div className="space-y-4">
<GuideItem icon={UserPlus} title="Editing a user" tone="brand">
Click a row to open the edit modal you can change name, email, role, phone, notification preference, active status, or set a new password.
</GuideItem>
</div>
</div>
),
},
{
key: "actions",
label: "Row actions",
icon: ShieldAlert,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={Users} title="Sign out everywhere" tone="amber">
Revokes all of that user&apos;s active sessions useful if a device was lost or access needs to be cut immediately.
</GuideItem>
<GuideItem icon={Users} title="Deactivate" tone="gray">
Soft-disables the account (they can&apos;t log in) without removing any data. Reversible at any time.
</GuideItem>
<GuideItem icon={ShieldAlert} title="Delete data" tone="rose">
Anonymises the user&apos;s name, email, and phone and deactivates the account. This cannot be undone use it only for genuine data-removal requests, not as a quick way to disable someone.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,68 @@
import React from "react";
import { TrendingUp, Zap, Shield, Users, Calendar, BarChart2, Settings } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the Admin dashboard root (/dashboard/admin). */
export const dashboardAdminHelpContent: HelpContent = {
title: "Admin dashboard help",
subtitle: "An overview of what's happening across all your events.",
quickLinks: [
{ label: "Manage users", href: "/dashboard/admin/users", icon: Users },
{ label: "Manage events", href: "/dashboard/supervisor/events", icon: Calendar },
{ label: "Reports", href: "/dashboard/supervisor/reports", icon: BarChart2 },
{ label: "Site Settings", href: "/dashboard/admin/settings", icon: Settings },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: TrendingUp,
content: (
<div className="space-y-4">
<p>The top row and the sections below it summarise your events at a glance no need to open Reports for a quick check.</p>
<div className="space-y-4">
<GuideItem icon={TrendingUp} title="Past month's KPIs" tone="brand">
Active events, revenue, donations, registrations, and tickets sold each covers a rolling 30-ish days (today back one month), compared against the equivalent month before that, for an apples-to-apples comparison.
</GuideItem>
<GuideItem icon={BarChart2} title="Revenue trend & top events" tone="green">
A day-by-day revenue chart for the past month, and a ranking of your best-performing events by revenue, registrations, and tickets sold.
</GuideItem>
<GuideItem icon={Zap} title="Payments overview" tone="amber">
Today, past week, and past month totals a quicker check than opening Reports for a simple "how much came in" question.
</GuideItem>
</div>
<p className="text-xs text-gray-500">For a deeper breakdown, filters, and exports, use Reports instead this page is a summary, not a substitute.</p>
</div>
),
},
{
key: "actions",
label: "Quick actions",
icon: Zap,
content: (
<div className="space-y-4">
<p>The Quick actions grid is a shortcut to every admin tool managing users and events, taking payments, scanning tickets, reports, messaging attendees, WhatsApp API setup, and post-event cashup.</p>
</div>
),
},
{
key: "roles",
label: "Roles & permissions",
icon: Shield,
content: (
<div className="space-y-4">
<p>As an admin you can also open Supervisor and Staff tools nothing is hidden from you. Other roles see less by design:</p>
<div className="space-y-4">
<GuideItem icon={Shield} title="Supervisor" tone="green">
Sees the same financial overview you do (revenue, donations, top events), but can&apos;t manage users, WhatsApp API settings, or close out event cashups.
</GuideItem>
<GuideItem icon={Shield} title="Staff" tone="gray">
Scan-tickets and event-tickets tools only no revenue or donation figures at all. That&apos;s intentional, not a bug, if a staff member asks why their dashboard looks different.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,45 @@
import React from "react";
import { QrCode, Ticket, Lock } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the Staff dashboard root (/dashboard/staff). */
export const dashboardStaffHelpContent: HelpContent = {
title: "Staff dashboard help",
subtitle: "Ticket scanning tools and today's activity.",
quickLinks: [
{ label: "Scan tickets", href: "/dashboard/staff/ticket-scanning", icon: QrCode },
{ label: "Event tickets & printing", href: "/dashboard/staff/event-tickets", icon: Ticket },
],
tabs: [
{
key: "scanning",
label: "Scanning tickets",
icon: QrCode,
content: (
<div className="space-y-4">
<p>Use Scan tickets to validate attendees at the door with your device&apos;s camera, and Event tickets & printing to browse an event&apos;s tickets or print a list.</p>
<div className="space-y-4">
<GuideItem icon={QrCode} title="Scans today / My scans / Last hour" tone="brand">
Live counts of ticket scans across everyone, just you, and the last hour.
</GuideItem>
<GuideItem icon={Ticket} title="Today by staff & Recent scans" tone="green">
See who else is scanning and the most recent scans as they happen, useful for spotting duplicate or unexpected activity quickly.
</GuideItem>
</div>
</div>
),
},
{
key: "why-no-money",
label: "Why don't I see payments?",
icon: Lock,
content: (
<div className="space-y-4">
<p>Staff accounts are scoped to ticket scanning only no revenue, donation, or payment figures appear anywhere on this dashboard. That&apos;s intentional, not a bug.</p>
<p className="text-xs text-gray-500">If you need to check or take a payment, ask a supervisor or admin.</p>
</div>
),
},
],
};
@@ -0,0 +1,59 @@
import React from "react";
import { TrendingUp, Zap, QrCode, UserPlus, BarChart2, DoorOpen } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the Supervisor dashboard root (/dashboard/supervisor). */
export const dashboardSupervisorHelpContent: HelpContent = {
title: "Supervisor dashboard help",
subtitle: "An overview of what's happening across all your events.",
quickLinks: [
{ label: "Manual registration", href: "/dashboard/supervisor/manual", icon: UserPlus },
{ label: "Reports", href: "/dashboard/supervisor/reports", icon: BarChart2 },
{ label: "At the door", href: "/dashboard/supervisor/at-the-door", icon: DoorOpen },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: TrendingUp,
content: (
<div className="space-y-4">
<p>The top row and the sections below it summarise your events at a glance no need to open Reports for a quick check.</p>
<div className="space-y-4">
<GuideItem icon={TrendingUp} title="Past month's KPIs" tone="brand">
Active events, revenue, donations, registrations, and tickets sold each covers a rolling 30-ish days (today back one month), compared against the equivalent month before that, for an apples-to-apples comparison.
</GuideItem>
<GuideItem icon={BarChart2} title="Revenue trend & top events" tone="green">
A day-by-day revenue chart for the past month, and a ranking of your best-performing events by revenue, registrations, and tickets sold.
</GuideItem>
</div>
<p className="text-xs text-gray-500">For a deeper breakdown, filters, and exports, use Reports instead this page is a summary, not a substitute.</p>
</div>
),
},
{
key: "actions",
label: "Quick actions",
icon: Zap,
content: (
<div className="space-y-4">
<p>The Quick actions grid covers everything you need day-to-day: manual registrations, managing events and sections, payments and donations, ticket scanning, at-the-door, reports, and messaging attendees.</p>
<GuideItem icon={Zap} title="Missing something?" tone="gray">
Managing users, WhatsApp API configuration, and post-event cashup close-out are admin-only ask an admin if you need one of those.
</GuideItem>
</div>
),
},
{
key: "scanning",
label: "Scanner activity",
icon: QrCode,
content: (
<div className="space-y-4">
<p>The Scanner activity panel tracks ticket scanning in real time: today&apos;s total, your own scans, and scans in the last hour, plus a breakdown of who scanned what.</p>
</div>
),
},
],
};
@@ -0,0 +1,28 @@
import React from "react";
import { HandHeart, CreditCard } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/user/donate. */
export const dashboardUserDonateHelpContent: HelpContent = {
title: "Donations help",
subtitle: "Support an event or the ministry directly.",
quickLinks: [
{ label: "My Payments", href: "/dashboard/user/payments", icon: CreditCard },
],
tabs: [
{
key: "overview",
label: "Making a donation",
icon: HandHeart,
content: (
<div className="space-y-4">
<p>Pick an event to support and an amount (minimum R15), then you&apos;ll be redirected to a secure Yoco checkout to complete payment.</p>
<GuideItem icon={HandHeart} title="Not tied to a registration" tone="rose">
A donation is separate from paying for your own ticket it&apos;s a direct contribution, and it isn&apos;t required to register for or attend an event.
</GuideItem>
</div>
),
},
],
};
@@ -0,0 +1,28 @@
import React from "react";
import { Receipt, Filter } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/user/payments. */
export const dashboardUserPaymentsHelpContent: HelpContent = {
title: "My Payments help",
subtitle: "Every payment you've made, across all your registrations.",
quickLinks: [
{ label: "My Events", href: "/dashboard/user", icon: Receipt },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: Receipt,
content: (
<div className="space-y-4">
<p>This page lists every payment and refund on your account, newest first, with the event, amount, and method for each.</p>
<GuideItem icon={Filter} title="Filtering" tone="brand">
Narrow the list by date range, payment method, or whether you&apos;re looking for payments or refunds specifically.
</GuideItem>
</div>
),
},
],
};
@@ -0,0 +1,83 @@
import React from "react";
import { Calendar, Ticket, CreditCard, HandHeart, Printer, CheckCircle2, ClipboardList, Clock, SquarePen, History, CheckSquare, Receipt } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the "My Events" dashboard (/dashboard/user), reachable by every role. */
export const dashboardUserHelpContent: HelpContent = {
title: "My Events help",
subtitle: "Manage your registrations, tickets, and payments from here.",
quickLinks: [
{ label: "Browse events", href: "/events", icon: Calendar },
{ label: "Payment history", href: "/dashboard/user/payments", icon: CreditCard },
{ label: "Make a donation", href: "/dashboard/user/donate", icon: HandHeart },
],
tabs: [
{
key: "registrations",
label: "Registrations",
icon: Calendar,
content: (
<div className="space-y-4">
<p>My Registrations lists every event you&apos;ve signed up for. Tap one to open its details, where you can edit your items, pay an outstanding balance, or cancel.</p>
<div className="space-y-4">
<GuideItem icon={CheckCircle2} title="Status badges" tone="brand">
Pending (just registered), Confirmed, Partially Paid, Paid, or Cancelled the badge next to each registration shows exactly where it stands.
</GuideItem>
<GuideItem icon={ClipboardList} title="Forms" tone="amber">
Some events need extra details from you (like dietary needs or emergency contacts) before the event. If you see a &quot;forms&quot; badge, tap the registration and fill them in you can save and come back.
</GuideItem>
<GuideItem icon={Clock} title="Early-bird pricing" tone="green">
A notice appears if an event option is still within its early-bird discount window, so you know when the price is about to go up.
</GuideItem>
<GuideItem icon={SquarePen} title="Editing or cancelling" tone="violet">
Tap a registration to change quantities or options, pay what&apos;s outstanding, or cancel it entirely.
</GuideItem>
<GuideItem icon={History} title="Show past events" tone="gray">
Registrations page defaults to upcoming events only tick &quot;Show past events&quot; at the top to bring back ones that have already happened.
</GuideItem>
</div>
</div>
),
},
{
key: "tickets",
label: "Tickets",
icon: Ticket,
content: (
<div className="space-y-4">
<p>My Tickets shows a QR code for each ticket you hold this is what gets scanned at the door.</p>
<div className="space-y-4">
<GuideItem icon={CheckSquare} title="Select multiple" tone="brand">
Tick more than one ticket (or use &quot;Select all&quot;) to send or print them together in one go, instead of one at a time.
</GuideItem>
<GuideItem icon={Printer} title="Print, email, or WhatsApp" tone="green">
Get your selected tickets onto your phone or paper before the event as a printed page, an email, or a WhatsApp message.
</GuideItem>
</div>
</div>
),
},
{
key: "payments",
label: "Payments & donations",
icon: CreditCard,
content: (
<div className="space-y-4">
<p>There are three related but separate things here worth knowing the difference:</p>
<div className="space-y-4">
<GuideItem icon={CreditCard} title="Paying an outstanding balance" tone="brand">
Open a registration with an amount still owing and use Pay Now this pays online for that specific registration only.
</GuideItem>
<GuideItem icon={Receipt} title="Payment history" tone="green">
The Payment history page lists every payment you&apos;ve made across all your registrations, past and present.
</GuideItem>
<GuideItem icon={HandHeart} title="Donations" tone="rose">
A donation isn&apos;t tied to any one registration make one any time to support an event or the ministry directly. It can later be used to help cover an outstanding balance.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,57 @@
import React from "react";
import { Ticket, Tag, Share2, QrCode, Calendar } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for a single public event page (/events/[id]). */
export const eventDetailHelpContent: HelpContent = {
title: "Event details help",
subtitle: "Understanding pricing, availability, and how to register.",
quickLinks: [
{ label: "Browse all events", href: "/events", icon: Calendar },
],
tabs: [
{
key: "tickets",
label: "Tickets & pricing",
icon: Tag,
content: (
<div className="space-y-4">
<p>The tickets panel lists every option available for this event, with live availability.</p>
<div className="space-y-4">
<GuideItem icon={Tag} title="Early-bird pricing" tone="amber">
If a discounted price is shown with the original struck through, an early-bird window is active the price rises automatically once it ends.
</GuideItem>
<GuideItem icon={Ticket} title="Availability badges" tone="brand">
&quot;N remaining&quot; appears once stock runs low, and &quot;Sold out&quot; once an option is fully booked.
</GuideItem>
</div>
</div>
),
},
{
key: "registering",
label: "Registering",
icon: Ticket,
content: (
<div className="space-y-4">
<p>The Register button reflects the event&apos;s current state:</p>
<ul className="list-disc pl-5 space-y-1 text-sm text-gray-600">
<li><strong>Opens &lt;date&gt;</strong> registration hasn&apos;t started yet.</li>
<li><strong>Registration closed</strong> the deadline or event has passed.</li>
<li><strong>Sold Out</strong> every ticket option is fully booked.</li>
<li><strong>Register</strong> you&apos;re good to go.</li>
</ul>
<div className="space-y-4">
<GuideItem icon={Share2} title="Share & Save QR" tone="green">
Use Share to send the event link, or Save QR to download a scannable code for the page handy for posters or flyers.
</GuideItem>
<GuideItem icon={QrCode} title="Downloads" tone="violet">
If the organiser has attached files (like a program or map), they appear in the Downloads section below the description.
</GuideItem>
</div>
</div>
),
},
],
};
+32
View File
@@ -0,0 +1,32 @@
import React from "react";
import { Calendar, LogIn, UserPlus } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the public events list (/events). */
export const eventsHelpContent: HelpContent = {
title: "Browsing events",
subtitle: "Find something coming up and book your spot.",
quickLinks: [
{ label: "Log in", href: "/login", icon: LogIn },
{ label: "Create an account", href: "/register", icon: UserPlus },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: Calendar,
content: (
<div className="space-y-4">
<p>This list only shows upcoming events that are open for viewing, ordered by date closest first.</p>
<div className="space-y-4">
<GuideItem icon={Calendar} title="No events showing?" tone="gray">
If the list is empty, there&apos;s nothing upcoming right now check back later or use the Contact page to ask.
</GuideItem>
</div>
<p className="text-xs text-gray-500">Tap any event card to see full details, pricing, and to register.</p>
</div>
),
},
],
};
+98
View File
@@ -0,0 +1,98 @@
import React from "react";
import { Search, UserPlus, CreditCard, Calendar, Ticket, LogIn, KeyRound, MessageCircleQuestion, Phone } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/**
* Baseline help content shown on any page without its own dedicated guide
* (all public pages, and the ultimate fallback everywhere else). Covers the
* things every visitor needs to know: how to register for an event
* (with or without an account), and how to find/manage a registration
* afterwards.
*/
export const generalHelpContent: HelpContent = {
title: "Need a hand?",
subtitle: "Here's how booking and managing your tickets works.",
quickLinks: [
{ label: "Browse events", href: "/events", icon: Calendar },
{ label: "Log in", href: "/login", icon: LogIn },
{ label: "Create an account", href: "/register", icon: UserPlus },
{ label: "View my tickets", href: "/dashboard/user", icon: Ticket },
{ label: "Contact us", href: "/contact", icon: Phone },
],
tabs: [
{
key: "register",
label: "Booking a ticket",
icon: Calendar,
content: (
<div className="space-y-4">
<p>Booking a spot at an event only takes a few minutes:</p>
<div className="space-y-4">
<GuideItem icon={Search} title="Browse events" tone="brand">
Head to the Events page to see what&apos;s coming up.
</GuideItem>
<GuideItem icon={UserPlus} title="Register" tone="green">
Open an event and tap Register to reserve your spot with your details. Some events let you register as a guest, others ask you to sign in first see the &quot;Accounts &amp; login&quot; tab for the difference.
</GuideItem>
<GuideItem icon={CreditCard} title="Pay if required" tone="amber">
Some events need payment to confirm your spot pay securely online, or come back and pay later from your dashboard.
</GuideItem>
</div>
</div>
),
},
{
key: "accounts",
label: "Accounts & login",
icon: LogIn,
content: (
<div className="space-y-4">
<p>Whether you need an account depends on the event the registration form always makes it clear which applies.</p>
<div className="space-y-4">
<GuideItem icon={Ticket} title="Guest registration" tone="green">
For some events you can register with just your name and email address no password, no account. You&apos;ll get your tickets by email (or WhatsApp, if you provide a number).
</GuideItem>
<GuideItem icon={KeyRound} title="Account required" tone="amber">
Other events ask you to sign in or create a free account before you can register. If you see this, use the &quot;Log in&quot; or &quot;Create an account&quot; quick link above it only takes a minute.
</GuideItem>
<GuideItem icon={LogIn} title="Why create an account?" tone="brand">
An account puts every registration and ticket you&apos;ve ever booked in one place (your dashboard), and you won&apos;t need to retype your details next time. It&apos;s also required to view or manage a registration after you&apos;ve made it, even if that specific event allowed guest booking.
</GuideItem>
</div>
</div>
),
},
{
key: "manage",
label: "Your tickets & registrations",
icon: Ticket,
content: (
<div className="space-y-4">
<p>Once you&apos;re registered, sign in and go to your dashboard to view and manage everything you&apos;ll need to be logged in to see this, even if you registered as a guest.</p>
<div className="space-y-4">
<GuideItem icon={Calendar} title="My Events" tone="brand">
Your dashboard shows every registration and upcoming event in one place.
</GuideItem>
<GuideItem icon={Ticket} title="Tickets" tone="violet">
Each ticket has a QR code print it, email it to yourself, or send it via WhatsApp.
</GuideItem>
<GuideItem icon={CreditCard} title="Payments" tone="green">
Check what you&apos;ve paid and what&apos;s still outstanding right from your dashboard.
</GuideItem>
</div>
</div>
),
},
{
key: "help",
label: "Still need help?",
icon: MessageCircleQuestion,
content: (
<div className="space-y-4">
<p>If you can&apos;t find what you&apos;re looking for, reach out use the Contact link at the top of the site, or the Contact page linked above.</p>
</div>
),
},
],
};
+43
View File
@@ -0,0 +1,43 @@
import React from "react";
import { Compass, Calendar, UserPlus, LogIn } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for the home page (/). */
export const homeHelpContent: HelpContent = {
title: "Welcome",
subtitle: "A quick orientation to the site.",
quickLinks: [
{ label: "Browse all events", href: "/events", icon: Calendar },
{ label: "Log in", href: "/login", icon: LogIn },
{ label: "Create an account", href: "/register", icon: UserPlus },
],
tabs: [
{
key: "overview",
label: "This page",
icon: Compass,
content: (
<div className="space-y-4">
<p>The Upcoming Events section shows the next few events coming up tap &quot;View all events&quot; to see the full list.</p>
<div className="space-y-4">
<GuideItem icon={Calendar} title="Event cards" tone="brand">
Each card shows the date and a short description. Tap one to see full details, pricing, and to register.
</GuideItem>
</div>
</div>
),
},
{
key: "getting-started",
label: "Booking a ticket",
icon: UserPlus,
content: (
<div className="space-y-4">
<p>Open an event and tap Register. Some events let you register as a guest with just a name and email; others ask you to sign in or create a free account first the registration form will make it clear which applies.</p>
<p className="text-xs text-gray-500">Once you have a registration, sign in to view and manage it from your dashboard.</p>
</div>
),
},
],
};
+186
View File
@@ -0,0 +1,186 @@
import type { HelpContent } from "./types";
import { reportsHelpContent } from "./reports";
import { dashboardUserHelpContent } from "./dashboard-user";
import { dashboardAdminHelpContent } from "./dashboard-admin";
import { dashboardSupervisorHelpContent } from "./dashboard-supervisor";
import { dashboardStaffHelpContent } from "./dashboard-staff";
import { siteSettingsHelpContent } from "./site-settings";
import { authHelpContent } from "./auth";
import { dashboardUserPaymentsHelpContent } from "./dashboard-user-payments";
import { dashboardUserDonateHelpContent } from "./dashboard-user-donate";
import { generalHelpContent } from "./general";
import { homeHelpContent } from "./home";
import { eventsHelpContent } from "./events";
import { eventDetailHelpContent } from "./event-detail";
import { dashboardAdminUsersHelpContent } from "./dashboard-admin-users";
import { dashboardAdminRegistrationsHelpContent } from "./dashboard-admin-registrations";
import { dashboardAdminCashupHelpContent, dashboardAdminCashupDetailHelpContent } from "./dashboard-admin-cashup";
import { supervisorEventsHelpContent } from "./supervisor-events";
import { supervisorEventOptionsHelpContent } from "./supervisor-event-options";
import { supervisorManualHelpContent, supervisorManualRegistrationLegacyHelpContent } from "./supervisor-manual";
import { supervisorAtTheDoorHelpContent } from "./supervisor-at-the-door";
import { supervisorPaymentsHelpContent } from "./supervisor-payments";
import { supervisorFormsHelpContent } from "./supervisor-forms";
import { supervisorEmailAttendeesHelpContent } from "./supervisor-email-attendees";
import { supervisorWhatsappAttendeesHelpContent } from "./supervisor-whatsapp-attendees";
import { staffEventTicketsHelpContent } from "./staff-event-tickets";
import { staffTicketScanningHelpContent } from "./staff-ticket-scanning";
import { userFormsHelpContent } from "./user-forms";
type HelpRegistryEntry = {
/** Unique, stable slug — used as the per-page "don't show again" storage key. */
slug: string;
matches: (pathname: string) => boolean;
content: HelpContent;
};
// Most-specific first; the first match wins. Anything that matches nothing
// falls back to `generalHelpContent` below.
const REGISTRY: HelpRegistryEntry[] = [
{
slug: "reports",
matches: pathname => pathname === "/dashboard/supervisor/reports",
content: reportsHelpContent,
},
{
slug: "dashboard-user",
matches: pathname => pathname === "/dashboard/user",
content: dashboardUserHelpContent,
},
{
slug: "dashboard-admin",
matches: pathname => pathname === "/dashboard/admin",
content: dashboardAdminHelpContent,
},
{
slug: "dashboard-supervisor",
matches: pathname => pathname === "/dashboard/supervisor",
content: dashboardSupervisorHelpContent,
},
{
slug: "dashboard-staff",
matches: pathname => pathname === "/dashboard/staff",
content: dashboardStaffHelpContent,
},
{
slug: "site-settings",
matches: pathname => pathname === "/dashboard/admin/settings",
content: siteSettingsHelpContent,
},
{
slug: "dashboard-user-payments",
matches: pathname => pathname === "/dashboard/user/payments",
content: dashboardUserPaymentsHelpContent,
},
{
slug: "dashboard-user-donate",
matches: pathname => pathname === "/dashboard/user/donate",
content: dashboardUserDonateHelpContent,
},
{
slug: "auth",
matches: pathname => pathname === "/login" || pathname === "/register" || pathname === "/forgot-password",
content: authHelpContent,
},
{
slug: "home",
matches: pathname => pathname === "/",
content: homeHelpContent,
},
{
slug: "events",
matches: pathname => pathname === "/events",
content: eventsHelpContent,
},
{
slug: "event-detail",
matches: pathname => /^\/events\/[^/]+$/.test(pathname),
content: eventDetailHelpContent,
},
{
slug: "dashboard-admin-users",
matches: pathname => pathname === "/dashboard/admin/users",
content: dashboardAdminUsersHelpContent,
},
{
slug: "dashboard-admin-registrations",
matches: pathname => pathname === "/dashboard/admin/registrations",
content: dashboardAdminRegistrationsHelpContent,
},
{
slug: "dashboard-admin-cashup-detail",
matches: pathname => /^\/dashboard\/admin\/cashup\/[^/]+$/.test(pathname),
content: dashboardAdminCashupDetailHelpContent,
},
{
slug: "dashboard-admin-cashup",
matches: pathname => pathname === "/dashboard/admin/cashup",
content: dashboardAdminCashupHelpContent,
},
{
slug: "supervisor-events",
matches: pathname => pathname === "/dashboard/supervisor/events" || pathname === "/dashboard/supervisor/sections",
content: supervisorEventsHelpContent,
},
{
slug: "supervisor-event-options",
matches: pathname => pathname === "/dashboard/supervisor/event-options",
content: supervisorEventOptionsHelpContent,
},
{
slug: "supervisor-manual",
matches: pathname => pathname === "/dashboard/supervisor/manual",
content: supervisorManualHelpContent,
},
{
slug: "supervisor-manual-registration-legacy",
matches: pathname => pathname === "/dashboard/supervisor/manual-registration",
content: supervisorManualRegistrationLegacyHelpContent,
},
{
slug: "supervisor-at-the-door",
matches: pathname => pathname === "/dashboard/supervisor/at-the-door",
content: supervisorAtTheDoorHelpContent,
},
{
slug: "supervisor-payments",
matches: pathname => pathname === "/dashboard/supervisor/payments",
content: supervisorPaymentsHelpContent,
},
{
slug: "supervisor-forms",
matches: pathname => pathname === "/dashboard/supervisor/forms" || pathname === "/dashboard/admin/forms",
content: supervisorFormsHelpContent,
},
{
slug: "supervisor-email-attendees",
matches: pathname => pathname === "/dashboard/supervisor/email-attendees",
content: supervisorEmailAttendeesHelpContent,
},
{
slug: "supervisor-whatsapp-attendees",
matches: pathname => pathname === "/dashboard/supervisor/whatsapp-attendees",
content: supervisorWhatsappAttendeesHelpContent,
},
{
slug: "staff-event-tickets",
matches: pathname => pathname === "/dashboard/staff/event-tickets",
content: staffEventTicketsHelpContent,
},
{
slug: "staff-ticket-scanning",
matches: pathname => pathname === "/dashboard/staff/ticket-scanning",
content: staffTicketScanningHelpContent,
},
{
slug: "user-forms",
matches: pathname => pathname === "/dashboard/user/forms",
content: userFormsHelpContent,
},
];
export function resolveHelpContent(pathname: string): { slug: string; content: HelpContent } {
const match = REGISTRY.find(entry => entry.matches(pathname));
if (match) return { slug: match.slug, content: match.content };
return { slug: "general", content: generalHelpContent };
}
+152
View File
@@ -0,0 +1,152 @@
import React from "react";
import {
Home, Filter, ListFilter, Download, BarChart2, MessageCircleQuestion,
Calendar, CalendarClock, EyeOff, Printer, Mail, FileSpreadsheet, MessageCircle,
CreditCard, Gift, Clock, HandHeart, RefreshCw,
} from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
const ADMIN_EMAIL = "admin@crosscode.co.za";
export const reportsHelpContent: HelpContent = {
title: "Reporting guide",
subtitle: "This guide explains how reports work and how to use the available filters.",
supportContact: { label: "Site administrator", email: ADMIN_EMAIL },
tabs: [
{
key: "overview",
label: "Overview",
icon: Home,
content: (
<div className="space-y-4">
<p>Reports help you view key data about your events. You can filter the data, preview it on screen, and export or email it.</p>
<div className="space-y-4">
<GuideItem icon={Filter} title="Use filters" tone="brand">
Apply universal filters (like events and date range) that affect all reports, and report-specific filters for more detailed results.
</GuideItem>
<GuideItem icon={BarChart2} title="Preview & customize" tone="green">
Preview your report, adjust filters, and choose how you want the data to appear.
</GuideItem>
<GuideItem icon={Download} title="Export or email" tone="amber">
Export your report to Excel, PDF, or send it by email or WhatsApp.
</GuideItem>
</div>
</div>
),
},
{
key: "universal",
label: "Universal filters",
icon: Filter,
content: (
<div className="space-y-4">
<p>Universal filters live in the sidebar on the left and apply to whichever report you open you only set them once, not per report.</p>
<div className="space-y-4">
<GuideItem icon={Calendar} title="Events" tone="brand">
Pick one or more events. Every report loads data for exactly these events.
</GuideItem>
<GuideItem icon={EyeOff} title="Include past / inactive / closed events" tone="gray">
Controls which events even appear in the Events list to pick from.
</GuideItem>
<GuideItem icon={CalendarClock} title="Date range" tone="blue">
A preset (This month, Last month, This year) or a custom range. Only applies to reports that are inherently date-based (e.g. Payments between dates, Revenue reports, Cashup audit trail) reports like Attendees or Ticket usage show a live snapshot and ignore the date range.
</GuideItem>
</div>
</div>
),
},
{
key: "specific",
label: "Report-specific filters",
icon: ListFilter,
content: (
<div className="space-y-4">
<p>Some reports have extra options that only make sense for that report these appear at the top of the report popup once it&apos;s open, separate from the universal filters.</p>
<div className="space-y-4">
<GuideItem icon={ListFilter} title="Attendees" tone="violet">
Which single event to show (defaults to the first selected event) and whether to include cancelled registrations.
</GuideItem>
<GuideItem icon={BarChart2} title="Registration status breakdown" tone="green">
Whether to include cancelled registrations in the counts, and whether to count by number of registrations or by ticket quantity (so someone with 3 tickets counts as 3).
</GuideItem>
<GuideItem icon={RefreshCw} title="Refresh" tone="brand">
Adjust a report-specific filter, then use the &quot;Refresh&quot; button inside the popup to re-run the report without closing it.
</GuideItem>
</div>
</div>
),
},
{
key: "exporting",
label: "Exporting reports",
icon: Download,
content: (
<div className="space-y-4">
<p>Every report can be exported straight from its popup:</p>
<div className="space-y-4">
<GuideItem icon={Printer} title="Print" tone="gray">
Opens a print-ready PDF in a new tab; use your browser&apos;s print button from there.
</GuideItem>
<GuideItem icon={Mail} title="Email" tone="blue">
Sends the PDF to your own account email.
</GuideItem>
<GuideItem icon={FileSpreadsheet} title="Excel" tone="green">
Downloads a styled .xlsx workbook colored header, key totals, and a chart section where available matching the on-screen report.
</GuideItem>
<GuideItem icon={MessageCircle} title="WhatsApp" tone="violet">
Sends the PDF to your own account&apos;s WhatsApp number (needs a valid phone number on file).
</GuideItem>
</div>
</div>
),
},
{
key: "fields",
label: "Fields & metrics",
icon: BarChart2,
content: (
<div className="space-y-4">
<p>A few terms come up across several financial reports and are easy to misread here&apos;s what each one actually means:</p>
<div className="space-y-4">
<GuideItem icon={CreditCard} title="Paid" tone="blue">
Money the person paid themselves directly (cash/card/eft/online). Never includes money that reached their order via someone else&apos;s donation.
</GuideItem>
<GuideItem icon={Gift} title="Paid via donation" tone="violet">
The portion of an order that was covered by an assigned donation. This is part of what&apos;s &quot;settled&quot; on the order, but it&apos;s the donor&apos;s money, not the registrant&apos;s so it&apos;s broken out separately and attributed to the donor elsewhere in the report.
</GuideItem>
<GuideItem icon={Clock} title="Outstanding" tone="amber">
What&apos;s still owed on an order, after direct payments and any donation cover.
</GuideItem>
<GuideItem icon={HandHeart} title="Unassigned donations" tone="rose">
Real money already received as a donation that hasn&apos;t been applied to any order yet.
</GuideItem>
<GuideItem icon={BarChart2} title="Donations: Used / Unused" tone="green">
How much of a given donation has been assigned to orders (Used) versus what&apos;s still available to assign (Unused). A donation is never overwritten when assigned the original donation record always keeps its full original amount.
</GuideItem>
</div>
</div>
),
},
{
key: "help",
label: "Need more help?",
icon: MessageCircleQuestion,
content: (
<div className="space-y-4">
<p>Still stuck? Reach out to the site administrator they can check the underlying data with you or flag anything that looks wrong.</p>
<div className="flex items-start gap-3 border border-gray-100 rounded-xl p-4 bg-gray-50">
<div className="w-9 h-9 rounded-full bg-brand-50 flex items-center justify-center shrink-0">
<Mail className="w-4 h-4 text-brand-600" />
</div>
<div>
<div className="font-medium text-gray-800">Site administrator</div>
<a href={`mailto:${ADMIN_EMAIL}`} className="text-sm text-brand-600 hover:underline">{ADMIN_EMAIL}</a>
</div>
</div>
<p className="text-xs text-gray-500">Financial figures matter if a number in a report doesn&apos;t look right, it&apos;s always worth asking rather than assuming.</p>
</div>
),
},
],
};
@@ -0,0 +1,97 @@
import React from "react";
import { Building2, Palette, Bell, Mail, Scale, MessageCircle, LayoutDashboard } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for Site Settings (/dashboard/admin/settings), matching its six tabs. */
export const siteSettingsHelpContent: HelpContent = {
title: "Site Settings help",
subtitle: "Configure your organisation, branding, email, WhatsApp, and legal pages.",
quickLinks: [
{ label: "Back to Admin dashboard", href: "/dashboard/admin", icon: LayoutDashboard },
],
tabs: [
{
key: "organisation",
label: "Organisation",
icon: Building2,
content: (
<div className="space-y-4">
<p>Your organisation&apos;s name, tagline, and contact details.</p>
<div className="space-y-4">
<GuideItem icon={Building2} title="Contact email, phone, and address" tone="brand">
These feed the public Contact page directly update them here and the Contact page updates automatically.
</GuideItem>
<GuideItem icon={Building2} title="Site URL" tone="gray">
The public URL of this site, used in email links like password resets and ticket delivery.
</GuideItem>
</div>
</div>
),
},
{
key: "branding",
label: "Branding",
icon: Palette,
content: (
<div className="space-y-4">
<p>Your logo and accent color.</p>
<GuideItem icon={Palette} title="Accent color" tone="violet">
This only recolors the organisation name next to the logo in the top navigation bar. Every other button, link, and highlight across the site uses a fixed color scheme by design changing this setting won&apos;t change anything else.
</GuideItem>
</div>
),
},
{
key: "notifications",
label: "Notifications",
icon: Bell,
content: (
<div className="space-y-4">
<p>The email address(es) that get notified whenever someone registers for an event separate multiple addresses with commas.</p>
</div>
),
},
{
key: "email",
label: "Email",
icon: Mail,
content: (
<div className="space-y-4">
<p>Outgoing SMTP settings used for tickets, payment confirmations, and account emails.</p>
<GuideItem icon={Mail} title="Leave blank for defaults" tone="gray">
If left blank, the server falls back to its own environment configuration. The SMTP password is stored encrypted.
</GuideItem>
</div>
),
},
{
key: "legal",
label: "Legal",
icon: Scale,
content: (
<div className="space-y-4">
<p>Details used to populate the site&apos;s Privacy Policy and Terms pages operator name, information officer details, website URL, and effective date.</p>
</div>
),
},
{
key: "whatsapp",
label: "WhatsApp",
icon: MessageCircle,
content: (
<div className="space-y-4">
<p>Connects the site to a WhatsApp number (via WAWP) for sending tickets and notifications.</p>
<div className="space-y-4">
<GuideItem icon={MessageCircle} title="Three-step setup" tone="brand">
Enter your WAWP access token, set up a session instance (create new or enter an existing one), then scan the QR code (or use a phone pairing code) to link a WhatsApp number.
</GuideItem>
<GuideItem icon={MessageCircle} title="Session status" tone="green">
Once connected, the status card shows live connection health and auto-refreshes while reconnecting. If it fails, the system attempts to auto-recover and emails an admin alert if that doesn&apos;t work.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,30 @@
import React from "react";
import { Ticket, Printer, Filter } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/staff/event-tickets. */
export const staffEventTicketsHelpContent: HelpContent = {
title: "Event tickets",
subtitle: "Browse and print tickets for an event.",
tabs: [
{
key: "overview",
label: "Overview",
icon: Ticket,
content: (
<div className="space-y-4">
<p>Pick an upcoming event, then filter tickets by user or by ticket variant. The totals at the top show how many tickets are used vs unused.</p>
<div className="space-y-4">
<GuideItem icon={Filter} title="Finding a ticket" tone="brand">
Each ticket shows its own QR code useful for looking one up without needing to scan it.
</GuideItem>
<GuideItem icon={Printer} title="Printing" tone="green">
&quot;Print all&quot; or &quot;Print unused&quot; produces an A4 sheet with 8 tickets per page, ready to hand out physically.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,38 @@
import React from "react";
import { QrCode, Filter, CheckCircle2 } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/staff/ticket-scanning. */
export const staffTicketScanningHelpContent: HelpContent = {
title: "Scanning tickets",
subtitle: "Validate attendees at the door with your camera.",
tabs: [
{
key: "how",
label: "How it works",
icon: QrCode,
content: (
<div className="space-y-4">
<p>Point your camera at a ticket&apos;s QR code. A preview checks it belongs to the selected event (and section, if set), then a confirmation screen shows the holder, ticket type, and remaining quantity you can adjust the redeem quantity before confirming.</p>
<div className="space-y-4">
<GuideItem icon={CheckCircle2} title="Outcomes" tone="green">
You&apos;ll see Success (with redeemed/remaining counts), Already Used, or an Error if something&apos;s wrong with the ticket.
</GuideItem>
</div>
</div>
),
},
{
key: "filters",
label: "Filters",
icon: Filter,
content: (
<div className="space-y-4">
<p>The Event filter defaults to events that ended within the last 12 hours toggle it to see older events. If the event has sections defined, a Section filter restricts which ticket types are valid at this scan point.</p>
<p className="text-xs text-gray-500">Changing the event or section filter stops any scan in progress.</p>
</div>
),
},
],
};
@@ -0,0 +1,49 @@
import React from "react";
import { DoorOpen, UserPlus, CreditCard, CheckSquare, Ticket, RotateCcw } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/at-the-door. */
export const supervisorAtTheDoorHelpContent: HelpContent = {
title: "At the door",
subtitle: "Register, pay, check in, and print tickets on the spot.",
tabs: [
{
key: "modes",
label: "The five modes",
icon: DoorOpen,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={UserPlus} title="Registration" tone="brand">
Search existing registrations or add a new attendee, then pick ticket options and quantities to create a registration.
</GuideItem>
<GuideItem icon={CreditCard} title="Payment" tone="green">
Shows Total/Paid/Due for the selected registration capture a payment, then print or send tickets once fully paid.
</GuideItem>
<GuideItem icon={CheckSquare} title="Check-in" tone="violet">
Per ticket-type totals with a stepper to check attendees in against the scan record.
</GuideItem>
<GuideItem icon={Ticket} title="Tickets" tone="amber">
Browse all tickets for the event; print or send (email/WhatsApp) any of them individually.
</GuideItem>
<GuideItem icon={RotateCcw} title="Refund" tone="rose">
Issue a refund against an existing payment for the selected registration.
</GuideItem>
</div>
</div>
),
},
{
key: "tips",
label: "Good to know",
icon: DoorOpen,
content: (
<div className="space-y-4">
<p>Selecting a registration automatically jumps to Payment mode if it&apos;s unpaid, or Check-in mode if it&apos;s already fully paid.</p>
<p>When editing an existing registration&apos;s items, quantities can&apos;t be reduced below what&apos;s already been printed or paid for.</p>
</div>
),
},
],
};
@@ -0,0 +1,50 @@
import React from "react";
import { Mail, Users, Zap, Send, Clock } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/email-attendees. */
export const supervisorEmailAttendeesHelpContent: HelpContent = {
title: "Emailing attendees",
subtitle: "Reach attendees by event, or send a one-off broadcast.",
tabs: [
{
key: "attendees",
label: "Attendees",
icon: Users,
content: (
<div className="space-y-4">
<p>Pick an event and a template (Custom, Payment reminder, Event reminder, or Tickets-with-attachments), then edit the subject and body. Placeholders like <code>{"{{name}}"}</code>, <code>{"{{event.title}}"}</code>, <code>{"{{balance}}"}</code>, and <code>{"{{payment.link}}"}</code> are filled in per recipient.</p>
<div className="space-y-4">
<GuideItem icon={Users} title="Choosing recipients" tone="brand">
Recipients are pre-selected by their notification preference and payment-status filter a warning shows if you select someone whose preference doesn&apos;t match email.
</GuideItem>
<GuideItem icon={Send} title="Preview, send, or schedule" tone="green">
Preview the recipient list before sending, send immediately, or pick a future date and time.
</GuideItem>
</div>
</div>
),
},
{
key: "automation",
label: "Automations & Broadcasts",
icon: Zap,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={Zap} title="Automations" tone="amber">
Four canned lifecycle emails per event a pre-event reminder, a final reminder, a thank-you, and a next-event promo with send times auto-computed from the event&apos;s dates.
</GuideItem>
<GuideItem icon={Mail} title="Broadcasts" tone="violet">
Send to any selected users or pasted external addresses, independent of a specific event&apos;s attendee list.
</GuideItem>
<GuideItem icon={Clock} title="Scheduled" tone="gray">
Review, edit, or cancel any email that&apos;s scheduled but not yet sent.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,41 @@
import React from "react";
import { Ticket, Tag, Info } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/event-options. */
export const supervisorEventOptionsHelpContent: HelpContent = {
title: "Ticket options",
subtitle: "A simpler, direct editor for an event's ticket types.",
quickLinks: [
{ label: "Manage events", href: "/dashboard/supervisor/events", icon: Ticket },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: Tag,
content: (
<div className="space-y-4">
<p>Pick an event, then edit each option&apos;s name, price, and &quot;Main ticket&quot; flag directly in the list changes save when you click away from the field. Use the form below the list to add a new option.</p>
<div className="space-y-4">
<GuideItem icon={Tag} title="Early-bird tiers" tone="amber">
Expand &quot;Manage early-bird tiers&quot; on an option to add deadline-and-price rows that apply automatically before the cutoff.
</GuideItem>
</div>
</div>
),
},
{
key: "note",
label: "When to use this",
icon: Info,
content: (
<div className="space-y-4">
<p>This page is the older, direct-editing view of ticket options. For new events, the Items &amp; Pricing step inside the main event editor covers the same ground and is the preferred place to work.</p>
<p className="text-xs text-gray-500">Deleting an option is admin-only.</p>
</div>
),
},
],
};
@@ -0,0 +1,61 @@
import React from "react";
import { Calendar, ListChecks, Layers, FileText, Eye, Bell, Paperclip, Lock } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/events. */
export const supervisorEventsHelpContent: HelpContent = {
title: "Managing events",
subtitle: "Create and edit events, ticket options, and forms.",
tabs: [
{
key: "overview",
label: "Overview",
icon: Calendar,
content: (
<div className="space-y-4">
<p>Search or toggle Include past / Show inactive (admin only) / Show closed to find an event, then tap &quot;+ Add Event&quot; or click a card to edit.</p>
<div className="space-y-4">
<GuideItem icon={Lock} title="Closed events" tone="rose">
An event that&apos;s been cashed up shows a banner and can&apos;t be edited it must be reopened from Cashup first.
</GuideItem>
</div>
</div>
),
},
{
key: "wizard",
label: "The event editor",
icon: Layers,
content: (
<div className="space-y-4">
<p>Creating or editing an event walks through several steps:</p>
<div className="space-y-4">
<GuideItem icon={Calendar} title="Basic details" tone="brand">
Title, description, start/end times, registration deadline, go-live time, base price, picture, and a custom URL alias.
</GuideItem>
<GuideItem icon={ListChecks} title="Items & pricing" tone="green">
Ticket options and stock, variants of each option, and early-bird pricing tiers.
</GuideItem>
<GuideItem icon={Layers} title="Sections" tone="violet">
Group ticket options under named headings as shown on the public registration page.
</GuideItem>
<GuideItem icon={FileText} title="Form" tone="amber">
Build a custom registration form text, number, date, yes/no, statement, and heading fields, reorderable, optionally required before tickets are generated.
</GuideItem>
<GuideItem icon={Eye} title="Visibility" tone="gray">
Hide the event, allow guest (no-account) registration, or toggle it active.
</GuideItem>
<GuideItem icon={Bell} title="Notifications" tone="blue">
Choose which staff get registration, payment, and daily-summary emails for this event.
</GuideItem>
<GuideItem icon={Paperclip} title="Attachments" tone="rose">
Upload downloadable files that appear on the public event page.
</GuideItem>
</div>
<p className="text-xs text-gray-500">When creating a new event, later steps unlock as you complete the ones before them; editing an existing event lets you jump to any step freely.</p>
</div>
),
},
],
};
@@ -0,0 +1,40 @@
import React from "react";
import { FileText, Eye, Edit3, ListChecks } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/forms and /dashboard/admin/forms (same page). */
export const supervisorFormsHelpContent: HelpContent = {
title: "Attendee forms",
subtitle: "View responses, fill them in for someone, or edit a form's structure.",
tabs: [
{
key: "view",
label: "View & fill responses",
icon: Eye,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={Eye} title="View responses" tone="brand">
Filter by event, user, or registration ID to see submitted answers. Use &quot;Print forms&quot; for a printable per-registration answer sheet.
</GuideItem>
<GuideItem icon={Edit3} title="Fill / edit responses" tone="green">
Pick an event and registration to fill in or correct attendee answers on someone&apos;s behalf one form block per main ticket held.
</GuideItem>
</div>
</div>
),
},
{
key: "structure",
label: "Edit form structure",
icon: ListChecks,
content: (
<div className="space-y-4">
<p>Pick any event (including past or inactive ones) to add, reorder, or remove its custom registration form fields text, number, date, yes/no, statement, and heading types.</p>
<p className="text-xs text-gray-500">A field can be marked required, or required before tickets are generated for that registration.</p>
</div>
),
},
],
};
@@ -0,0 +1,59 @@
import React from "react";
import { UserPlus, CreditCard, Info } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/manual. */
export const supervisorManualHelpContent: HelpContent = {
title: "Manual registration",
subtitle: "Register someone and take payment on their behalf.",
tabs: [
{
key: "register",
label: "Register",
icon: UserPlus,
content: (
<div className="space-y-4">
<p>Pick an event, then either search for an existing user or enter guest details (name, email, phone) and tick &quot;Guest&quot; to skip linking an account. Choose ticket options and quantities early-bird pricing is applied automatically then create the registration.</p>
<p className="text-xs text-gray-500">After creating a registration, the Record Payment tab is pre-filled with it switch tabs to take payment right away.</p>
</div>
),
},
{
key: "payment",
label: "Record Payment",
icon: CreditCard,
content: (
<div className="space-y-4">
<p>Search for a user, pick one of their registrations with an outstanding balance, and enter the amount, method, and (optionally) a backdated payment time.</p>
</div>
),
},
],
};
/** Help content for /dashboard/supervisor/manual-registration — the older, simpler form. */
export const supervisorManualRegistrationLegacyHelpContent: HelpContent = {
title: "Manual registration (legacy)",
subtitle: "A basic form for creating a registration directly.",
quickLinks: [
{ label: "Manual registration & payment", href: "/dashboard/supervisor/manual", icon: UserPlus },
],
tabs: [
{
key: "overview",
label: "Overview",
icon: Info,
content: (
<div className="space-y-4">
<p>This is an older, simplified registration form it requires typing the exact Event ID and Option ID rather than picking from a list, and doesn&apos;t support payment or early-bird pricing.</p>
<div className="space-y-4">
<GuideItem icon={UserPlus} title="Prefer the newer page" tone="brand">
The Manual Registration &amp; Payment page (linked above) offers event/user search, live pricing, and lets you take payment in the same flow use it unless you specifically need this one.
</GuideItem>
</div>
</div>
),
},
],
};
@@ -0,0 +1,48 @@
import React from "react";
import { Wallet, RotateCcw, HandHeart, ScanLine, Link2 } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/payments. */
export const supervisorPaymentsHelpContent: HelpContent = {
title: "Payments help",
subtitle: "Record payments, refunds, donations, and reconcile card transactions.",
tabs: [
{
key: "modes",
label: "The five modes",
icon: Wallet,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={Wallet} title="Payment" tone="brand">
Record a manual payment against a registration or a donation, with amount, method, and an optional backdated time.
</GuideItem>
<GuideItem icon={RotateCcw} title="Refund" tone="rose">
Refund a specific payment or a registration pick the user, target, amount and method, and a reason.
</GuideItem>
<GuideItem icon={HandHeart} title="Donations" tone="rose">
Assign an unassigned donation to a registration with an outstanding balance. Donations can be partially allocated, leaving the remainder available for another registration.
</GuideItem>
<GuideItem icon={ScanLine} title="Reconcile" tone="green">
Match unreconciled Yoco card transactions to a registration, log them as a donation, or ignore them.
</GuideItem>
<GuideItem icon={Link2} title="Payment Link" tone="violet">
Generate a Yoco checkout link for a user to pay online copy it or send by email/WhatsApp. Generating a new link invalidates the previous one.
</GuideItem>
</div>
</div>
),
},
{
key: "sidebar",
label: "Today panel",
icon: Wallet,
content: (
<div className="space-y-4">
<p>The sidebar shows today&apos;s payment count, revenue, and donation count, plus a recent-payments feed. Internal donation-allocation entries are excluded there to avoid double-counting.</p>
</div>
),
},
],
};
@@ -0,0 +1,44 @@
import React from "react";
import { MessageCircle, Users, Zap, Clock } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/supervisor/whatsapp-attendees. */
export const supervisorWhatsappAttendeesHelpContent: HelpContent = {
title: "WhatsApp attendees",
subtitle: "Message attendees or send tickets over WhatsApp.",
tabs: [
{
key: "attendees",
label: "Attendees",
icon: Users,
content: (
<div className="space-y-4">
<p>Works just like Email Attendees pick an event and a template, edit the message with placeholders like <code>{"{{name}}"}</code> and <code>{"{{payment.link}}"}</code>, and preview, send, or schedule.</p>
<div className="space-y-4">
<GuideItem icon={MessageCircle} title="Who's eligible" tone="brand">
Only recipients with a phone number can be messaged; the list is pre-filtered and colour-coded by notification preference (WhatsApp / Both), though you can still select a mismatch with a warning.
</GuideItem>
</div>
</div>
),
},
{
key: "automation",
label: "Automations & Broadcasts",
icon: Zap,
content: (
<div className="space-y-4">
<div className="space-y-4">
<GuideItem icon={Zap} title="Automations" tone="amber">
Same four lifecycle messages as email (reminder, final reminder, thank-you, next-event promo), auto-timed per event.
</GuideItem>
<GuideItem icon={Clock} title="Broadcasts & Scheduled" tone="gray">
Broadcasts reach arbitrary recipients outside a single event&apos;s list; Scheduled lets you review or cancel anything not yet sent.
</GuideItem>
</div>
</div>
),
},
],
};
+33
View File
@@ -0,0 +1,33 @@
import type { LucideIcon } from "lucide-react";
import type { ReactNode } from "react";
import type { Tone } from "@/components/shared/tones";
export type HelpTab = {
key: string;
label: string;
icon: LucideIcon;
content: ReactNode;
};
/** A fast way out of the guide straight to a relevant page (e.g. "Book a ticket" -> /events). */
export type HelpQuickLink = {
label: string;
description?: string;
href: string;
icon?: LucideIcon;
};
export type HelpSupportContact = {
label: string;
email: string;
};
export type HelpContent = {
title: string;
subtitle: string;
tabs: HelpTab[];
quickLinks?: HelpQuickLink[];
supportContact?: HelpSupportContact;
};
export type { Tone };
+30
View File
@@ -0,0 +1,30 @@
import React from "react";
import { FileText, Save, CheckCircle2 } from "lucide-react";
import { GuideItem } from "@/components/shared/GuideItem";
import type { HelpContent } from "./types";
/** Help content for /dashboard/user/forms. */
export const userFormsHelpContent: HelpContent = {
title: "Attendee forms",
subtitle: "Fill in the details required for your tickets.",
tabs: [
{
key: "overview",
label: "Overview",
icon: FileText,
content: (
<div className="space-y-4">
<p>You&apos;ll see one form to fill in per main ticket on your registration. Already-submitted forms are shown read-only above the remaining ones.</p>
<div className="space-y-4">
<GuideItem icon={CheckCircle2} title="Submitting" tone="brand">
Submit is enabled once every required field across all remaining forms is filled in.
</GuideItem>
<GuideItem icon={Save} title="Save for later" tone="green">
Not ready to finish? Save your progress as a draft and come back to complete it before the event.
</GuideItem>
</div>
</div>
),
},
],
};