Help guide opens on demand only; add At the door to admin quick actions

- The site-wide help guide no longer auto-opens on first visit to a page
  — it only opens when the help button is clicked. Removed the
  now-pointless "Don't show this again" checkbox and the per-page
  dismissal tracking it drove.
- Admin dashboard quick actions was missing "At the door" (walk-in
  registration, payments, ticket printing), which Supervisor already had.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 15:17:12 +02:00
co-authored by Claude Sonnet 5
parent 8e6cb542d9
commit 0f2b5afa74
6 changed files with 19 additions and 48 deletions
+3
View File
@@ -32,6 +32,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
- Admin/Supervisor dashboard "Revenue" figures (today/week/month) were overcounting: they didn't exclude donation-application "legs" (the money was already counted once via the original donation) the way Reports and the admin payments-stats endpoint already did. Both endpoints now use the same exclusion.
- Admin/Supervisor dashboard "Revenue" KPI, trend chart, and top-performing-events table were undercounting the other way — donations themselves weren't being counted as revenue at all, only tracked in the separate "Donations" figure. Donations now count toward revenue (donation-application legs are still excluded either way, since that money was already counted once via the original donation).
- Admin/Supervisor dashboard KPIs (revenue, donations, registrations, tickets sold) used a calendar-month-to-date window, which under-counted for most of the month (e.g. only 3 days' worth of data on the 3rd). They now use a rolling trailing-month window instead — today back one calendar month, e.g. 9 May through 8 June if today is 8 June — compared against the equal-length month before that. The "View report" links from those tiles now deep-link into the same trailing-month range.
- Admin dashboard was missing the "At the door" quick action that Supervisor already had (walk-in registration, payments, and ticket printing at the door) — added.
### Changed
@@ -41,6 +43,7 @@ and this project follows [Semantic Versioning](https://semver.org/).
- Admin/Supervisor dashboard quick actions no longer list "Manage sections" or "Event tickets & printing" (redundant with the Events and At-the-door pages) or "Manage WhatsApp API" (moved into Site Settings). Staff keeps "Event tickets & printing" — their only other tool is ticket scanning, and they can't reach At-the-door.
- Admin dashboard no longer shows ticket-scanning stats ("Recent scans") — kept on Supervisor/Staff, where it's actually actionable.
- The old hand-rolled `Button` component (`components/shared/Button.tsx`) is gone — its one remaining caller now uses the standard `components/ui/button.tsx`.
- The site-wide help guide no longer opens itself automatically on first visit to a page — it only opens when the help button is clicked. The now-pointless "Don't show this again" checkbox was removed along with the per-page dismissal tracking it drove.
### Removed
+2 -1
View File
@@ -8,7 +8,7 @@ import { useStableState } from "@/hooks/useStableState";
import { useVisiblePolling } from "@/hooks/useVisiblePolling";
import {
Calendar, Banknote, Gift, Users, Ticket, QrCode, ClipboardList,
UserPlus, FileText, MessageCircle, BarChart2, Mail, Wallet,
UserPlus, FileText, MessageCircle, BarChart2, Mail, Wallet, DoorOpen,
} from "lucide-react";
import { StatCard, StatCardRow } from "@/components/shared/StatCard";
import { QuickActionTile, QuickActionGrid } from "@/components/shared/QuickActionTile";
@@ -25,6 +25,7 @@ const QUICK_ACTIONS = [
{ href: "/dashboard/supervisor/events", label: "Manage events", description: "Create, edit, and update ticket types", icon: Calendar },
{ href: "/dashboard/admin/registrations", label: "Manage registrations", description: "Cancel, update status, and search registrations", icon: ClipboardList },
{ href: "/dashboard/supervisor/manual", label: "Manual registration", description: "Register a guest and issue tickets", icon: UserPlus },
{ href: "/dashboard/supervisor/at-the-door", label: "At the door", description: "Walk-ins, payments, ticket printing", icon: DoorOpen },
{ href: "/dashboard/supervisor/payments", label: "Payments & donations", description: "Manual payments and assignment", icon: Wallet },
{ href: "/dashboard/staff/ticket-scanning", label: "Open scanner", description: "Use your device camera to validate tickets", icon: QrCode },
{ href: "/dashboard/supervisor/reports", label: "Reports", description: "View, export, and email reports", icon: BarChart2 },
+2 -2
View File
@@ -21,8 +21,8 @@ const EXCLUDED_PREFIXES = ["/self-service", "/setup"];
export default function HelpFab() {
const pathname = usePathname() || "/";
const { slug, content } = resolveHelpContent(pathname);
const { open, openGuide, closeGuide } = useHelpGuide(slug);
const { content } = resolveHelpContent(pathname);
const { open, openGuide, closeGuide } = useHelpGuide();
if (EXCLUDED_PREFIXES.some(prefix => pathname.startsWith(prefix))) {
return null;
@@ -5,9 +5,8 @@ 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 }) {
export default function HelpGuideModal({ content, onClose }: { content: HelpContent; onClose: () => 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 (
@@ -15,7 +14,7 @@ export default function HelpGuideModal({ content, onClose }: { content: HelpCont
// 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 bg-black/40 animate-in fade-in duration-200" onClick={onClose} />
<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"
@@ -31,7 +30,7 @@ export default function HelpGuideModal({ content, onClose }: { content: HelpCont
<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">
<button className="p-1.5 rounded-full hover:bg-gray-100 transition-colors" onClick={onClose} aria-label="Close">
<X className="w-5 h-5" />
</button>
</div>
@@ -42,7 +41,7 @@ export default function HelpGuideModal({ content, onClose }: { content: HelpCont
<Link
key={link.href}
href={link.href}
onClick={() => onClose(dontShowAgain)}
onClick={onClose}
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" />}
@@ -90,12 +89,8 @@ export default function HelpGuideModal({ content, onClose }: { content: HelpCont
</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&apos;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)}>
<div className="flex items-center justify-end">
<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}>
Got it
</button>
</div>
+1 -1
View File
@@ -28,7 +28,7 @@ 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. */
/** Unique, stable identifier for this guide. */
slug: string;
matches: (pathname: string) => boolean;
content: HelpContent;
+5 -33
View File
@@ -1,42 +1,14 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
/**
* Open/close state for a help guide, with per-page "don't show again"
* persistence. `storageKey` should be a stable slug identifying the guide's
* content (e.g. "reports", "dashboard-user", "general") — each slug gets an
* independent dismissal flag, so dismissing one page's guide doesn't
* suppress another's.
* Open/close state for a help guide — opens only when the user explicitly
* asks for it (e.g. clicking the help FAB), never automatically.
*/
export function useHelpGuide(storageKey: string) {
export function useHelpGuide() {
const [open, setOpen] = useState(false);
const dismissedKey = `hope_events_help_dismissed_${storageKey}`;
// Show automatically on first visit to this guide, unless dismissed for good.
useEffect(() => {
try {
if (typeof window !== "undefined" && window.localStorage.getItem(dismissedKey) !== "1") {
setOpen(true);
}
} catch {
// localStorage unavailable (private browsing, etc.) — just don't auto-show.
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dismissedKey]);
const openGuide = () => setOpen(true);
const closeGuide = (dontShowAgain: boolean) => {
setOpen(false);
if (dontShowAgain) {
try {
window.localStorage.setItem(dismissedKey, "1");
} catch {
// ignore
}
}
};
const closeGuide = () => setOpen(false);
return { open, openGuide, closeGuide };
}