From bbd653709f8cf140c8e608e6cafacdaf00fe5276 Mon Sep 17 00:00:00 2001 From: Austin A Date: Sun, 26 Apr 2026 10:15:50 +0100 Subject: [PATCH] Polish EventSphere web experience --- apps/web/src/app/globals.css | 73 +++- apps/web/src/components/admin/AdminShell.tsx | 242 ++++++++--- .../src/components/admin/AttendeesCrud.tsx | 69 +-- .../admin/CommunicationsConsole.tsx | 23 +- apps/web/src/components/admin/CrudPage.tsx | 33 +- .../src/components/admin/DashboardPage.tsx | 401 ++++++++++++------ apps/web/src/components/admin/EventsCrud.tsx | 77 ++-- .../src/components/public/EventLanding.tsx | 263 ++++++++++-- apps/web/src/components/public/FlowPage.tsx | 18 +- .../web/src/components/public/PublicShell.tsx | 55 ++- apps/web/src/components/ui/BrandMark.tsx | 24 ++ apps/web/src/components/ui/Button.tsx | 16 +- apps/web/src/components/ui/Card.tsx | 2 +- apps/web/tailwind.config.ts | 3 +- 14 files changed, 939 insertions(+), 360 deletions(-) create mode 100644 apps/web/src/components/ui/BrandMark.tsx diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index f4a88de..c6c3362 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -2,6 +2,75 @@ @tailwind components; @tailwind utilities; -:root { color-scheme: light; } -body { margin: 0; background: #f6f8fb; color: #071B3A; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +:root { + color-scheme: light; + --ink: #061a3a; + --muted: #53637c; + --line: #e5ebf3; + --surface: #f6f8fb; + --accent: #1677ff; +} + * { box-sizing: border-box; } + +html { + min-width: 320px; + scroll-behavior: smooth; +} + +body { + margin: 0; + background: var(--surface); + color: var(--ink); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + text-rendering: geometricPrecision; +} + +button, +input, +select, +textarea { + font: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +::selection { + background: rgba(22, 119, 255, 0.18); +} + +:focus-visible { + outline: 3px solid rgba(22, 119, 255, 0.32); + outline-offset: 2px; +} + +.app-scrollbar { + scrollbar-width: thin; + scrollbar-color: rgba(148, 163, 184, 0.55) transparent; +} + +.app-scrollbar::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +.app-scrollbar::-webkit-scrollbar-thumb { + background: rgba(148, 163, 184, 0.5); + border: 3px solid transparent; + border-radius: 999px; + background-clip: padding-box; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.001ms !important; + } +} diff --git a/apps/web/src/components/admin/AdminShell.tsx b/apps/web/src/components/admin/AdminShell.tsx index a15240e..4c2ddae 100644 --- a/apps/web/src/components/admin/AdminShell.tsx +++ b/apps/web/src/components/admin/AdminShell.tsx @@ -1,12 +1,35 @@ "use client"; import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { BarChart3, CalendarDays, CheckCircle2, CreditCard, LayoutDashboard, Mail, QrCode, Settings, Users, Workflow, MessageCircle, Handshake, Contact, Plug } from "lucide-react"; -import { clsx } from "clsx"; -import { useEffect, useState } from "react"; -import { apiFetch, clearAccessToken } from "@/lib/api"; import Image from "next/image"; +import { usePathname } from "next/navigation"; +import { + BarChart3, + Bell, + CalendarDays, + CheckCircle2, + ChevronDown, + Contact, + CreditCard, + Handshake, + LayoutDashboard, + LogOut, + Mail, + Menu, + MessageCircle, + Plug, + QrCode, + Search, + Settings, + ShieldCheck, + Users, + Workflow, + X +} from "lucide-react"; +import { clsx } from "clsx"; +import { useEffect, useMemo, useState } from "react"; +import { apiFetch, clearAccessToken } from "@/lib/api"; +import { BrandMark } from "@/components/ui/BrandMark"; const nav = [ ["Dashboard", "/dashboard", LayoutDashboard, "dashboard"], @@ -15,7 +38,7 @@ const nav = [ ["Invitees", "/invitees", Mail, "invitees"], ["RSVPs", "/rsvps", CheckCircle2, "rsvps"], ["Registrations", "/registrations", Users, "registrations"], - ["Check-in (Live)", "/check-in", QrCode, "checkins"], + ["Check-in (Live)", "/check-in", ShieldCheck, "checkins"], ["QR Codes", "/qr-codes", QrCode, "qrcodes"], ["Forms & Workflows", "/forms-workflows", Workflow, "workflows"], ["Calendar", "/calendar", CalendarDays, "calendar"], @@ -30,11 +53,25 @@ const nav = [ ["Integrations", "/integrations", Plug, "integrations"] ] as const; +function Initials({ name }: { name: string }) { + return ( + + {name + .split(" ") + .map((part) => part[0]) + .join("") + .slice(0, 2) + .toUpperCase() || "SA"} + + ); +} + export function AdminShell({ title, children }: { title: string; children: React.ReactNode }) { const pathname = usePathname(); const [appName, setAppName] = useState("EventSphere"); const [logoUrl, setLogoUrl] = useState(null); const [modules, setModules] = useState | null>(null); + const [sidebarOpen, setSidebarOpen] = useState(false); const [me, setMe] = useState<{ fullName: string; email: string; avatarUrl?: string | null; mustChangePassword?: boolean } | null>(null); useEffect(() => { @@ -59,7 +96,12 @@ export function AdminShell({ title, children }: { title: string; children: React const payload = await apiFetch<{ user: any }>("/auth/me"); const u = payload.user ?? null; if (!u) return; - setMe({ fullName: String(u.fullName ?? "User"), email: String(u.email ?? ""), avatarUrl: u.avatarUrl ?? null, mustChangePassword: Boolean(u.mustChangePassword) }); + setMe({ + fullName: String(u.fullName ?? "User"), + email: String(u.email ?? ""), + avatarUrl: u.avatarUrl ?? null, + mustChangePassword: Boolean(u.mustChangePassword) + }); } catch { setMe(null); } @@ -83,70 +125,154 @@ export function AdminShell({ title, children }: { title: string; children: React } }, [modules, pathname]); - return
-
+ + ); + + return ( +
+ + Skip to content + + +
{sidebar}
+ + {sidebarOpen ? ( +
+ +
+
+ ) : null} + +
+
+
+ + +
+
{title}
+
Live operations command center
+
+
+ +
+ + + + {me?.avatarUrl ? ( + {userName} + ) : ( + + )} + + {userName} + {userEmail} + +
+
+ +
+ {children} +
+
+ + ); } diff --git a/apps/web/src/components/admin/AttendeesCrud.tsx b/apps/web/src/components/admin/AttendeesCrud.tsx index 98990e5..fcb3f3a 100644 --- a/apps/web/src/components/admin/AttendeesCrud.tsx +++ b/apps/web/src/components/admin/AttendeesCrud.tsx @@ -73,18 +73,19 @@ export function AttendeesCrud() { return (
-
+
-

Attendees

-

Profile registered participants with status, tickets, tags, and engagement history.

+
Audience CRM
+

Attendees

+

Profile registered participants with status, tickets, tags, and engagement history.

- -
+ +
setQuery(e.target.value)} @@ -100,37 +101,39 @@ export function AttendeesCrud() { {!loading && !error ? ( filtered.length ? ( - - - - - - - - - - - - {filtered.map((r) => ( - - - - - - +
+
NameEmailPhoneCreated
{r.fullName}{r.email}{r.phone ?? "-"}{new Date(r.createdAt).toLocaleString()}Open
+ + + + + + + - ))} - -
NameEmailPhoneCreated
+ + + {filtered.map((r) => ( + + {r.fullName} + {r.email} + {r.phone ?? "-"} + {new Date(r.createdAt).toLocaleString()} + Open + + ))} + + +
) : ( -
No attendees yet.
+
No attendees yet.
) ) : null}
{open ? ( -
setOpen(false)}> -
e.stopPropagation()}> +
setOpen(false)} role="dialog" aria-modal="true" aria-label="Create attendee"> +
e.stopPropagation()}>
Create
@@ -142,9 +145,9 @@ export function AttendeesCrud() {
- setFullName(e.target.value)} /> - setEmail(e.target.value)} /> - setPhone(e.target.value)} /> + setFullName(e.target.value)} /> + setEmail(e.target.value)} /> + setPhone(e.target.value)} />
{saveError ?
{saveError}
: null} diff --git a/apps/web/src/components/admin/CommunicationsConsole.tsx b/apps/web/src/components/admin/CommunicationsConsole.tsx index bc33b62..3ea4701 100644 --- a/apps/web/src/components/admin/CommunicationsConsole.tsx +++ b/apps/web/src/components/admin/CommunicationsConsole.tsx @@ -40,17 +40,20 @@ export function CommunicationsConsole() { return (
-

Communications

-

Coordinate email, SMS and WhatsApp automation from one command center.

+
Message operations
+

Communications

+

Coordinate email, SMS and WhatsApp automation from one command center.

- +
-
+
{(["email", "sms", "whatsapp"] as Channel[]).map((item) => (
- setTo(e.target.value)} /> - {channel === "email" ? setSubject(e.target.value)} /> : null} -