From c968fb3ca22e899a357a631aaf75991c454ca768 Mon Sep 17 00:00:00 2001 From: aamoghS Date: Thu, 1 Oct 2026 12:29:29 -0400 Subject: [PATCH 01/15] style(portal): give the hackathon portal the dashboard's look The hackathon pages, submit, judging and their error and loading screens had drifted into a fake-terminal style the rest of the portal does not use: underscored titles ("Auth_Error", "Status_Code: 401"), "System Failure" / "Reboot System" copy, neon glows, gradient text, pulsing dots, blur blobs inside cards, hover lifts and a second green accent. They now use the dashboard and settings conventions: Oswald headings, the teal accent, one set of buttons and inputs, rounded-sm, plain sentence-case copy, and one empty-state pattern. - Registration statuses render through a shared StatusBadge ("Under review", "Accepted", "Checked in") instead of raw enums. The detail page and registration card no longer say "Registered" or "You're in" while an application is still pending. - text-text-muted is not a utility, so "muted" text rendered at full brightness in 89 places across the site. Now text-[var(--text-muted)]. - Hand-pasted SVG icons are lucide icons; icon-only project links have visible labels; external links carry rel="noopener noreferrer". - Empty states for a filter that matches nothing and for judges with no open hackathons; the list page's back link no longer sends hackathon-only users to the members-only club page. - The login footer's "Internal Terminal // Auth Gateway v2.1" is gone. --- .../app/(portal)/admin/analytics/page.tsx | 6 +- .../app/(portal)/admin/attendees/page.tsx | 16 +- .../(portal)/admin/hackathons/[id]/page.tsx | 208 +----- .../app/(portal)/admin/hackathons/error.tsx | 43 +- .../app/(portal)/admin/hackathons/loading.tsx | 2 +- .../app/(portal)/admin/hackathons/page.tsx | 58 +- .../app/(portal)/admin/judging/page.tsx | 8 +- sites/mainweb/app/(portal)/admin/page.tsx | 4 +- .../app/(portal)/admin/projects/page.tsx | 2 +- .../mainweb/app/(portal)/auth/error/page.tsx | 75 +- sites/mainweb/app/(portal)/dashboard/page.tsx | 50 +- .../app/(portal)/hackathons/[id]/error.tsx | 52 +- .../(portal)/hackathons/[id]/judge/page.tsx | 82 ++- .../app/(portal)/hackathons/[id]/loading.tsx | 2 +- .../app/(portal)/hackathons/[id]/page.tsx | 250 ++----- .../mainweb/app/(portal)/hackathons/error.tsx | 49 +- .../app/(portal)/hackathons/loading.tsx | 4 +- .../mainweb/app/(portal)/hackathons/page.tsx | 685 +++++++----------- .../mainweb/app/(portal)/hacklytics/page.tsx | 41 +- sites/mainweb/app/(portal)/judge/page.tsx | 374 +++++----- .../app/(portal)/judge/register/page.tsx | 98 ++- sites/mainweb/app/(portal)/login/page.tsx | 13 - sites/mainweb/app/(portal)/submit/error.tsx | 48 +- sites/mainweb/app/(portal)/submit/loading.tsx | 4 +- sites/mainweb/app/(portal)/submit/page.tsx | 338 ++++----- sites/mainweb/app/(portal)/verify/page.tsx | 4 +- .../components/admin/hackathons/EventsTab.tsx | 4 +- .../admin/hackathons/JudgeLiveBoard.tsx | 6 +- .../components/hackathon/FormComponents.tsx | 68 +- .../hackathon/HackathonUnavailable.tsx | 26 +- .../mainweb/components/hackathon/InfoTab.tsx | 252 ++----- .../components/hackathon/InterestForm.tsx | 24 +- .../components/hackathon/ProjectsTab.tsx | 205 +++--- .../components/hackathon/ResultsTab.tsx | 14 +- .../components/hackathon/ScheduleTab.tsx | 134 ++-- .../components/hackathon/StatusBadge.tsx | 54 ++ .../mainweb/components/hackathon/TeamsTab.tsx | 371 ++++------ .../components/portal/LoadingScreen.tsx | 14 +- 38 files changed, 1398 insertions(+), 2290 deletions(-) create mode 100644 sites/mainweb/components/hackathon/StatusBadge.tsx diff --git a/sites/mainweb/app/(portal)/admin/analytics/page.tsx b/sites/mainweb/app/(portal)/admin/analytics/page.tsx index 00aefe34..29a5be46 100644 --- a/sites/mainweb/app/(portal)/admin/analytics/page.tsx +++ b/sites/mainweb/app/(portal)/admin/analytics/page.tsx @@ -83,12 +83,12 @@ function StatCard({ icon: Icon, title, value, subtitle }: StatCardProps) {
-

{title}

+

{title}

{value}

{subtitle && ( - {subtitle} + {subtitle} )} {/* Decorative accent line */}
@@ -277,7 +277,7 @@ export default function AnalyticsPage() {

Analytics Dashboard

-

+

Membership growth, bootcamp enrolment, and turnout across every event and hackathon.

diff --git a/sites/mainweb/app/(portal)/admin/attendees/page.tsx b/sites/mainweb/app/(portal)/admin/attendees/page.tsx index ec82a665..05dc07c3 100644 --- a/sites/mainweb/app/(portal)/admin/attendees/page.tsx +++ b/sites/mainweb/app/(portal)/admin/attendees/page.tsx @@ -89,7 +89,7 @@ export default function AttendeesPage() { Attendees{" "} Registry -

+

View and manage attendee check-ins for club meetings. Hackathon applications live on each edition's dashboard.

@@ -131,7 +131,7 @@ export default function AttendeesPage() { {isLoading ? (
-

+

Loading event check-ins...

@@ -139,7 +139,7 @@ export default function AttendeesPage() {
No attendees yet -

+

Select an event to view check-ins.

@@ -166,16 +166,16 @@ export default function AttendeesPage() { - - - - diff --git a/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx index a2e76496..ac679d1c 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/[id]/page.tsx @@ -15,7 +15,16 @@ import { EventsTab } from "@/components/admin/hackathons/EventsTab"; import { JudgesTab } from "@/components/admin/hackathons/JudgesTab"; import { AnnouncementsTab } from "@/components/admin/hackathons/AnnouncementsTab"; import { TableCards } from "@/components/admin/hackathons/TableCards"; -import { Gavel, Megaphone, QrCode } from "lucide-react"; +import { + BarChart3, + Calendar, + ChevronLeft, + Gavel, + Megaphone, + QrCode, + ScanLine, + Users, +} from "lucide-react"; type Tab = | "events" @@ -80,18 +89,18 @@ export default function AdminHackathonDashboard() { { id: "attendees", label: "Applications", - icon: , + icon: , }, { id: "events", - label: "Hackathon Events", - icon: , + label: "Events", + icon: , }, - { id: "scanner", label: "Scan", icon: }, + { id: "scanner", label: "Scan", icon: }, { id: "analytics", label: "Stats", - icon: , + icon: , }, { id: "judges", label: "Judges", icon: }, { @@ -107,47 +116,21 @@ export default function AdminHackathonDashboard() { ]; return ( -
- {/* Ambient Background */} -
-
-
-
-
- - {/* HEADER - Enhanced */} +
-
- - - -
- - Hackathons - + + Hackathons -

+

{hackathon.name} - {/* Animated underline */} -

@@ -157,25 +140,10 @@ export default function AdminHackathonDashboard() { )}
@@ -183,32 +151,21 @@ export default function AdminHackathonDashboard() {
- {/* DESKTOP TABS - Enhanced, Hidden on mobile */} -
- {/* Tab background gradients */} -
- + {/* DESKTOP TABS - Hidden on mobile */} +
{tabs.map((tab) => ( ))}
@@ -237,44 +194,28 @@ export default function AdminHackathonDashboard() {
- {/* MOBILE BOTTOM NAVIGATION - Enhanced */} + {/* MOBILE BOTTOM NAVIGATION */} {/* Scrolls horizontally rather than a fixed 5-column grid: there are seven tabs, so Table Cards and Email were laid out off the right edge of the phone an organiser actually carries — unreachable, with nothing to suggest they existed. */}
- {/* Top gradient overlay */} -
- {tabs.map((tab) => ( ))}
@@ -291,11 +232,11 @@ function DashboardUnavailable({ }) { return (
-
-

+
+

Hackathon unavailable

-

+

{message}

@@ -303,14 +244,14 @@ function DashboardUnavailable({ )} Back to hackathons @@ -319,76 +260,3 @@ function DashboardUnavailable({
); } - -// Icons -function IconScanner({ className }: { className?: string }) { - return ( - - - - ); -} - -function IconUsers({ className }: { className?: string }) { - return ( - - - - ); -} - -function IconChart({ className }: { className?: string }) { - return ( - - - - ); -} - -function IconCalendar({ className }: { className?: string }) { - return ( - - - - ); -} diff --git a/sites/mainweb/app/(portal)/admin/hackathons/error.tsx b/sites/mainweb/app/(portal)/admin/hackathons/error.tsx index 3381dcfb..095ee9dd 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/error.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/error.tsx @@ -2,6 +2,7 @@ import { useEffect } from "react"; import Link from "next/link"; +import { AlertTriangle } from "lucide-react"; import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { useChunkErrorRecovery } from "@/lib/chunk-error"; @@ -20,30 +21,16 @@ export default function AdminError({ }, [error]); return ( -
+
- -
- - - -
+ + -

- {isChunkError ? "New Version Available" : "Admin Protocol Error"} +

+ {isChunkError ? "New version available" : "Couldn't load this page"}

-

+

{isChunkError ? ( <> The app was updated while this tab was open, so part of the old @@ -52,14 +39,14 @@ export default function AdminError({ ) : ( <> - The admin module encountered a critical fault during execution. - Please verify your clearance and try again. + Something went wrong. Try again; if it persists, the message + below will help whoever debugs it. )}

- - {error.message || "Unknown execution exception"} + + {error.message || "No error message"}

@@ -69,15 +56,15 @@ export default function AdminError({ onClick={() => isChunkError ? window.location.reload() : reset() } - className="px-6 py-3 bg-accent text-black font-black uppercase tracking-widest text-sm rounded-none hover:bg-white transition-colors" + className="px-6 py-3 bg-accent text-[var(--text-on-accent)] rounded-sm font-bold text-sm uppercase tracking-widest hover:bg-[var(--accent-secondary)] transition-ui disabled:opacity-50" > - {isChunkError ? "Reload Page" : "Retry Execution"} + {isChunkError ? "Reload page" : "Try again"} - Abort to Dashboard + Back to dashboard
diff --git a/sites/mainweb/app/(portal)/admin/hackathons/loading.tsx b/sites/mainweb/app/(portal)/admin/hackathons/loading.tsx index 7c586b1d..04d136ed 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/loading.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/loading.tsx @@ -3,5 +3,5 @@ import { LoadingScreen } from "@/components/portal/LoadingScreen"; export default function Loading() { - return ; + return ; } diff --git a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx index 37a9926c..db610030 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx @@ -13,7 +13,7 @@ import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { HackathonCard } from "@/components/admin/hackathons/HackathonCard"; import { CreateHackathonForm } from "@/components/admin/hackathons/CreateHackathonForm"; import { EditHackathonForm } from "@/components/admin/hackathons/EditHackathonForm"; -import { Zap } from "lucide-react"; +import { Layers, Plus, Zap } from "lucide-react"; export default function AdminHackathonsPage() { const { data: session, status } = useSession(); @@ -37,32 +37,24 @@ export default function AdminHackathonsPage() { return ( <>
- {/* Ambient Background */} -
-
-
-
- -
-
-
- -

- Hackathon Hub +

+

+ Admin

-

- Hackathon Manager +

+ Hackathons

-

+

Manage your hackathons, participants, and event check-in locations.

@@ -96,32 +88,20 @@ export default function AdminHackathonsPage() { {[1, 2, 3].map((n) => (
))}
) : hackathons.length === 0 ? ( - -
- - - + +
+
-

- No hackathons yet -

-

- Create your first hackathon to get started. +

+ No hackathons yet. Create your first one to get started.

) : ( diff --git a/sites/mainweb/app/(portal)/admin/judging/page.tsx b/sites/mainweb/app/(portal)/admin/judging/page.tsx index d2ac785d..12a14643 100644 --- a/sites/mainweb/app/(portal)/admin/judging/page.tsx +++ b/sites/mainweb/app/(portal)/admin/judging/page.tsx @@ -310,7 +310,7 @@ export default function AdminResultsPage() {

Voting Results

-

+

Data Evaluation Layer //{" "} {selectedHackathon ? "SYNC ACTIVE" : "IDLE"} @@ -365,10 +365,10 @@ export default function AdminResultsPage() { {judgingStatus?.active ? ( Active ) : ( - Inactive + Inactive )}

-

+

{judgingStatus?.active ? "Judges are currently scoring projects in real-time" : "Judges are on standby — waiting for you to begin"} @@ -406,7 +406,7 @@ export default function AdminResultsPage() {

Prepare judging

-

+

Syncs submissions into judging and builds every judge's queue. Run it before starting, and again after late submissions. diff --git a/sites/mainweb/app/(portal)/admin/page.tsx b/sites/mainweb/app/(portal)/admin/page.tsx index d357846d..a19603e3 100644 --- a/sites/mainweb/app/(portal)/admin/page.tsx +++ b/sites/mainweb/app/(portal)/admin/page.tsx @@ -177,7 +177,7 @@ export default function AdminPage() { Check-in{" "} Manager -

+

Club meetings and workshops. Bootcamp sessions are managed on the{" "}

{event.description && ( -

+

{event.description}

)} diff --git a/sites/mainweb/app/(portal)/admin/projects/page.tsx b/sites/mainweb/app/(portal)/admin/projects/page.tsx index 76843793..5fbb8d04 100644 --- a/sites/mainweb/app/(portal)/admin/projects/page.tsx +++ b/sites/mainweb/app/(portal)/admin/projects/page.tsx @@ -105,7 +105,7 @@ export default function ProjectsPage() { Projects{" "} Manager -

+

Browse and manage hackathon projects submitted by participants.

diff --git a/sites/mainweb/app/(portal)/auth/error/page.tsx b/sites/mainweb/app/(portal)/auth/error/page.tsx index e8c2d3a5..ef48f363 100644 --- a/sites/mainweb/app/(portal)/auth/error/page.tsx +++ b/sites/mainweb/app/(portal)/auth/error/page.tsx @@ -3,6 +3,8 @@ import React, { Suspense } from "react"; import { useSearchParams } from "next/navigation"; import Link from "next/link"; +import { AlertTriangle } from "lucide-react"; +import { LoadingScreen } from "@/components/portal/LoadingScreen"; function AuthErrorContent() { const searchParams = useSearchParams(); @@ -10,20 +12,20 @@ function AuthErrorContent() { const errorMessages: Record = { Configuration: { - title: "Service_Unavailable", + title: "Sign-in is unavailable", desc: "Sign-in is unavailable right now. Please try again later.", }, AccessDenied: { - title: "Access_Denied", - desc: "You do not have permission to sign in. Your account may not be authorized.", + title: "Access denied", + desc: "You do not have permission to sign in.", }, Verification: { - title: "Verification_Failed", + title: "Link expired", desc: "The sign-in link is no longer valid or has already been used.", }, Default: { - title: "Authentication_Failure", - desc: "An unexpected error occurred during the authentication process.", + title: "Sign-in failed", + desc: "We couldn't sign you in. Try again, or use a different sign-in method.", }, }; @@ -31,64 +33,29 @@ function AuthErrorContent() { (error && errorMessages[error]) || errorMessages.Default; return ( -
-
- - - -
+
+ -
-

- Auth_Error +
+

+ {title}

-

- Status_Code: 401 -

-
-

{title}

-

- > {desc} -

-
- -
- - < Return_To_Base - +

{desc}

-
-

- Query_Security_Protocols_Active -

-
+ + Back to sign in +
); } export default function AuthErrorPage() { return ( - - Loading_Error_Log... -

- } - > + }> ); diff --git a/sites/mainweb/app/(portal)/dashboard/page.tsx b/sites/mainweb/app/(portal)/dashboard/page.tsx index 7ec27ba2..cf1cfd69 100644 --- a/sites/mainweb/app/(portal)/dashboard/page.tsx +++ b/sites/mainweb/app/(portal)/dashboard/page.tsx @@ -13,6 +13,7 @@ import { useEffect, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import LinkStripeAccount from "@/components/portal/LinkStripeAccount"; +import { StatusBadge } from "@/components/hackathon/StatusBadge"; import { MEMBERSHIP_CENTS, SEMESTER_MEMBERSHIP_CENTS, @@ -26,59 +27,10 @@ import { Shield, Users, ArrowRight, - Clock, - CheckCircle, - XCircle, - AlertCircle, Gavel, Rocket, } from "lucide-react"; -function StatusBadge({ status }: { status: string }) { - const map: Record< - string, - { label: string; color: string; icon: React.ReactNode } - > = { - approved: { - label: "Approved", - color: "text-emerald-500 bg-emerald-500/10 border-emerald-500/20", - icon: , - }, - checked_in: { - label: "Checked In", - color: "text-emerald-500 bg-emerald-500/10 border-emerald-500/20", - icon: , - }, - waitlisted: { - label: "Waitlisted", - color: "text-amber-500 bg-amber-500/10 border-amber-500/20", - icon: , - }, - pending: { - label: "Pending", - color: "text-amber-500 bg-amber-500/10 border-amber-500/20", - icon: , - }, - rejected: { - label: "Rejected", - color: "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-medium)]", - icon: , - }, - }; - const cfg = map[status] ?? { - label: status, - color: - "text-[var(--text-subtle)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", - icon: , - }; - return ( - - {cfg.icon} {cfg.label} - - ); -} export default function Dashboard() { const { data: session, status } = useSession(); diff --git a/sites/mainweb/app/(portal)/hackathons/[id]/error.tsx b/sites/mainweb/app/(portal)/hackathons/[id]/error.tsx index 8cc68d3f..2ae3886e 100644 --- a/sites/mainweb/app/(portal)/hackathons/[id]/error.tsx +++ b/sites/mainweb/app/(portal)/hackathons/[id]/error.tsx @@ -1,8 +1,8 @@ "use client"; import { useEffect } from "react"; -import Background from "@/components/Background"; import Link from "next/link"; +import { AlertTriangle } from "lucide-react"; import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { useChunkErrorRecovery } from "@/lib/chunk-error"; @@ -19,53 +19,37 @@ export default function Error({ }, [error]); return ( -
- - +
- -
- - - -
+ + diff --git a/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx b/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx index 3e0f28dc..9357b5d6 100644 --- a/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx +++ b/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx @@ -10,6 +10,7 @@ import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { LoadingScreen } from "@/components/portal/LoadingScreen"; import { QRScannerModal } from "@/components/portal/QRScannerModal"; import { decodeHackathonParam } from "@/lib/hackathon-slug"; +import { ChevronLeft } from "lucide-react"; type Project = { id: string; @@ -262,7 +263,7 @@ export default function JudgeHackathonPage() { }, [status, router]); if (status === "loading") { - return ; + return ; } if (!session) return null; @@ -275,15 +276,15 @@ export default function JudgeHackathonPage() { return (
-

+

Hackathon Not Found

-

+

{resolved.error?.message ?? "That hackathon does not exist."}

Back to Judge Portal @@ -293,22 +294,22 @@ export default function JudgeHackathonPage() { } if (judgeCheck.isPending) { - return ; + return ; } if (judgeCheck.data && !judgeCheck.data.isJudge) { return (
-

+

Not a Judge

-

+

Your judge account for this hackathon has not been approved yet.

Back to Judge Portal @@ -326,10 +327,10 @@ export default function JudgeHackathonPage() { return (
-

+

Judging Not Open

-

+

Scoring has not been opened for this hackathon yet. Your queue is waiting — an organiser will start judging when the expo begins.

@@ -337,13 +338,13 @@ export default function JudgeHackathonPage() { Back to Judge Portal @@ -363,16 +364,16 @@ export default function JudgeHackathonPage() { return (
-

+

Could Not Load Your Queue

-

+

{nextTable.error.message}

@@ -392,24 +393,25 @@ export default function JudgeHackathonPage() { complete.isPending || skip.isPending || forceSkip.isPending; return ( -
+
- ← Judge Portal + + Judging {progress.data && ( -

+

{progress.data.completed} / {progress.data.total} judged

)}
{progress.data && progress.data.total > 0 && ( -
+
-

{error}

+
+

{error}

)} {done || !project ? ( -

+

All Done

-

+

You have judged every project assigned to you. Thank you.

@@ -436,15 +438,15 @@ export default function JudgeHackathonPage() { <>
{project.tableNumber != null && ( -

+

Table {project.tableNumber}

)} -

+

{project.name}

{project.description && ( -

+

{project.description}

)} @@ -461,7 +463,7 @@ export default function JudgeHackathonPage() { href={l.href as string} target="_blank" rel="noopener noreferrer" - className="px-4 py-2 border border-[var(--border-subtle)] text-[var(--text-primary)] font-mono text-[10px] uppercase tracking-widest hover:bg-white/5 transition-colors" + className="px-5 py-2.5 rounded-sm border border-[var(--border-medium)] bg-[var(--bg-secondary)] text-[var(--text-muted)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text-primary)] hover:border-[var(--border-hover)] transition-ui text-xs font-bold uppercase tracking-widest" > {l.label} @@ -474,10 +476,10 @@ export default function JudgeHackathonPage() { and let a judge score a table they never actually reached. */} {!arrived ? ( -

+

Scan the team's code to begin

-

+

Walk to table {project.tableNumber ?? "?"} and scan the card on their desk. Your judging time starts from the scan, not from when this table was assigned to you. @@ -489,7 +491,7 @@ export default function JudgeHackathonPage() { setShowScanner(true); }} disabled={startByQr.isPending} - className="px-10 py-5 bg-accent text-black font-black text-sm uppercase tracking-widest rounded-none hover:bg-white transition-ui active:scale-95 disabled:opacity-40" + className="px-6 py-3 bg-accent text-[var(--text-on-accent)] rounded-sm font-bold text-sm uppercase tracking-widest hover:bg-[var(--accent-secondary)] transition-ui disabled:opacity-50" > {startByQr.isPending ? "Starting…" : "Scan Table Code"} @@ -501,7 +503,7 @@ export default function JudgeHackathonPage() {

@@ -530,7 +532,7 @@ export default function JudgeHackathonPage() {
@@ -541,16 +543,16 @@ export default function JudgeHackathonPage() { rows={4} maxLength={1000} placeholder="What stood out?" - className="w-full px-4 py-3 bg-[var(--bg-primary)]/40 border border-[var(--border-subtle)] text-[var(--text-primary)] font-mono text-sm placeholder:text-gray-600 focus:border-accent/50 focus:outline-none resize-none" + className="w-full bg-[var(--bg-secondary)] border border-[var(--border-subtle)] rounded-sm px-4 py-3 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-subtle)] focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition-ui resize-none" />
{stranded && (
-

+

This is the last table left in your queue, so there is nothing to skip to. If nobody is here, hand it to another judge instead — it will still get scored. @@ -563,7 +565,7 @@ export default function JudgeHackathonPage() { if (current?.queueId) forceSkip.mutate({ queueId: current.queueId }); }} - className="mt-3 px-4 py-2 border border-amber-500/40 bg-amber-500/10 text-amber-200 font-mono text-xs uppercase tracking-widest hover:bg-amber-500/20 transition-colors disabled:opacity-30" + className="mt-3 px-4 py-2 rounded-sm border border-amber-500/40 bg-amber-500/10 text-amber-200 text-xs font-bold uppercase tracking-widest hover:bg-amber-500/20 transition-colors disabled:opacity-30" > {forceSkip.isPending ? "Reassigning…" @@ -573,7 +575,7 @@ export default function JudgeHackathonPage() { )}

-

+

Total {total}{" "} / 50

@@ -586,7 +588,7 @@ export default function JudgeHackathonPage() { if (current?.queueId) skip.mutate({ queueId: current.queueId }); }} - className="px-6 py-3 border border-[var(--border-subtle)] text-text-muted font-mono text-xs uppercase tracking-widest hover:bg-white/5 transition-colors disabled:opacity-30" + className="px-5 py-2.5 rounded-sm border border-[var(--border-medium)] bg-[var(--bg-secondary)] text-[var(--text-muted)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text-primary)] hover:border-[var(--border-hover)] transition-ui text-xs font-bold uppercase tracking-widest disabled:opacity-30" > {skip.isPending ? "Skipping…" : "Skip for now"} @@ -607,7 +609,7 @@ export default function JudgeHackathonPage() { comment: comment || undefined, }); }} - className="px-8 py-3 bg-accent text-black font-black text-xs uppercase tracking-widest hover:bg-white transition-colors disabled:opacity-30" + className="px-6 py-3 bg-accent text-[var(--text-on-accent)] rounded-sm font-bold text-sm uppercase tracking-widest hover:bg-[var(--accent-secondary)] transition-ui disabled:opacity-50" > {complete.isPending ? "Saving…" : "Submit & Next"} diff --git a/sites/mainweb/app/(portal)/hackathons/[id]/loading.tsx b/sites/mainweb/app/(portal)/hackathons/[id]/loading.tsx index c19b4c4b..4c422b12 100644 --- a/sites/mainweb/app/(portal)/hackathons/[id]/loading.tsx +++ b/sites/mainweb/app/(portal)/hackathons/[id]/loading.tsx @@ -3,5 +3,5 @@ import { LoadingScreen } from "@/components/portal/LoadingScreen"; export default function Loading() { - return ; + return ; } diff --git a/sites/mainweb/app/(portal)/hackathons/[id]/page.tsx b/sites/mainweb/app/(portal)/hackathons/[id]/page.tsx index a4df966c..38306b17 100644 --- a/sites/mainweb/app/(portal)/hackathons/[id]/page.tsx +++ b/sites/mainweb/app/(portal)/hackathons/[id]/page.tsx @@ -8,6 +8,7 @@ import { useRouter, useParams, useSearchParams } from "next/navigation"; import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { LoadingScreen } from "@/components/portal/LoadingScreen"; import Link from "next/link"; +import { Calendar, ChevronLeft, MapPin, Users } from "lucide-react"; // Extracted Tab Components import { InfoTab } from "@/components/hackathon/InfoTab"; @@ -15,6 +16,7 @@ import { ScheduleTab } from "@/components/hackathon/ScheduleTab"; import { ProjectsTab } from "@/components/hackathon/ProjectsTab"; import { ResultsTab } from "@/components/hackathon/ResultsTab"; import { TeamsTab } from "@/components/hackathon/TeamsTab"; +import { StatusBadge } from "@/components/hackathon/StatusBadge"; import { HackathonUnavailable } from "@/components/hackathon/HackathonUnavailable"; import { decodeHackathonParam } from "@/lib/hackathon-slug"; @@ -39,74 +41,44 @@ function formatDateRange(start: Date | string, end: Date | string) { } function statusConfig(s: string) { - const map: Record< - string, - { - label: string; - dot: string; - text: string; - bg: string; - border: string; - glow: string; - } - > = { + const map: Record = { draft: { - label: "Not Published", - dot: "bg-white/40", - text: "text-[var(--text-primary)]/60", - bg: "bg-white/5", - border: "border-[var(--border-subtle)]", - glow: "", + label: "Not published", + color: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", + }, + announced: { + label: "Opens soon", + color: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", }, open: { - label: "Registering", - dot: "bg-emerald-400", - text: "text-accent", - bg: "bg-accent/10", - border: "border-accent/30", - glow: "shadow-[0_0_15px_rgba(52,211,153,0.6)]", + label: "Registration open", + color: "text-accent bg-accent/10 border-accent/25", }, in_progress: { - label: "Live Now", - dot: "bg-emerald-400", - text: "text-accent", - bg: "bg-accent/10", - border: "border-accent/30", - glow: "shadow-[0_0_15px_rgba(52,211,153,0.6)]", + label: "Live", + color: "text-accent bg-accent/10 border-accent/25", }, completed: { label: "Completed", - dot: "bg-white/40", - text: "text-[var(--text-primary)]/60", - bg: "bg-white/5", - border: "border-[var(--border-subtle)]", - glow: "", + color: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", }, closed: { - label: "Applications Closed", - dot: "bg-amber-400", - text: "text-amber-400", - bg: "bg-amber-400/10", - border: "border-amber-400/30", - glow: "", + label: "Registration closed", + color: "text-amber-400 bg-amber-400/10 border-amber-400/30", }, cancelled: { label: "Cancelled", - dot: "bg-rose-500", - text: "text-rose-500", - bg: "bg-rose-500/10", - border: "border-rose-500/30", - glow: "", + color: "text-rose-500 bg-rose-500/10 border-rose-500/30", }, }; return ( map[s] ?? { - label: s, - dot: "bg-gray-500", - text: "text-[var(--text-muted)]", - bg: "bg-gray-500/10", - border: "border-gray-500/20", - glow: "", + label: s.replace(/_/g, " "), + color: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", } ); } @@ -114,10 +86,10 @@ function statusConfig(s: string) { type TabType = "INFO" | "SCHEDULE" | "PROJECTS" | "TEAMS" | "RESULTS"; const TABS: { id: TabType; label: string }[] = [ - { id: "INFO", label: "Info & Register" }, - { id: "SCHEDULE", label: "Schedule & QR" }, - { id: "PROJECTS", label: "Project Gallery" }, - { id: "TEAMS", label: "Find Teams" }, + { id: "INFO", label: "Overview" }, + { id: "SCHEDULE", label: "Schedule & pass" }, + { id: "PROJECTS", label: "Projects" }, + { id: "TEAMS", label: "Teams" }, { id: "RESULTS", label: "Results" }, ]; @@ -160,7 +132,7 @@ export default function HackathonDetailPage() { }, [authStatus, router]); if (authStatus === "loading" || isLoading) - return ; + return ; if (!session) return null; // A hackathon can be missing (bad link, deleted event) or hidden. Without // this branch the loading guard below never clears and the page spins @@ -172,154 +144,62 @@ export default function HackathonDetailPage() { const conf = statusConfig(hackathon.status); return ( -
- {/* Animated Ambient Background Glows */} -
-
+
+
+
+
+
-
+
-
- - - -
- All Hackathons + + All hackathons {/* Header Card */} - - {/* Header Background Gradient Overlay */} -
- {/* Subtle Border Glow. group-hover, not hover: the card content sits - above this layer at z-10, so a bare hover: never fired. */} -
- -
- {/* Status Badge */} -
+
+ -
- - {conf.label} - -
+ {conf.label} +
- {/* Registration Indicator */} - {isRegistered && ( -
- - - - - Registered - -
- )} + {myReg && } - {/* Theme */} {hackathon.theme && ( - + {hackathon.theme} )}
-

+

{hackathon.name}

-
-
-
- - - -
- - {formatDateRange(hackathon.startDate, hackathon.endDate)} - +
+
+ + {formatDateRange(hackathon.startDate, hackathon.endDate)}
{hackathon.location && ( -
-
- - - -
- {hackathon.location} +
+ + {hackathon.location}
)} {hackathon.maxParticipants && ( -
-
- - - -
- - {hackathon.currentParticipants} / {hackathon.maxParticipants}{" "} - Spots +
+ + + {hackathon.currentParticipants} of {hackathon.maxParticipants}{" "} + spots taken
)} @@ -330,7 +210,7 @@ export default function HackathonDetailPage() {
{TABS.map((t) => ( @@ -358,7 +237,6 @@ export default function HackathonDetailPage() { role="tabpanel" id={`panel-${tab}`} aria-labelledby={`tab-${tab}`} - className="relative z-10" > {tab === "INFO" ? ( +
- -
- - - -
+ + diff --git a/sites/mainweb/app/(portal)/hackathons/loading.tsx b/sites/mainweb/app/(portal)/hackathons/loading.tsx index 92a95d1b..357b7a54 100644 --- a/sites/mainweb/app/(portal)/hackathons/loading.tsx +++ b/sites/mainweb/app/(portal)/hackathons/loading.tsx @@ -3,7 +3,5 @@ import { LoadingScreen } from "@/components/portal/LoadingScreen"; export default function Loading() { - return ( - - ); + return ; } diff --git a/sites/mainweb/app/(portal)/hackathons/page.tsx b/sites/mainweb/app/(portal)/hackathons/page.tsx index c0a2f816..9c669171 100644 --- a/sites/mainweb/app/(portal)/hackathons/page.tsx +++ b/sites/mainweb/app/(portal)/hackathons/page.tsx @@ -9,14 +9,15 @@ import { useRouter } from "next/navigation"; import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { LoadingScreen } from "@/components/portal/LoadingScreen"; import Link from "next/link"; +import { StatusBadge } from "@/components/hackathon/StatusBadge"; import { Calendar, FolderGit2, Globe, - LayoutGrid, ArrowRight, - FileCode2, ChevronLeft, + MapPin, + Users, } from "lucide-react"; type HackathonStatus = @@ -49,82 +50,43 @@ function formatDateRange(start: Date | string, end: Date | string) { } function statusConfig(s: HackathonStatus | "draft" | "cancelled") { - const map: Record< - string, - { - label: string; - dot: string; - text: string; - bg: string; - border: string; - glow: string; - } - > = { + const map: Record = { announced: { - label: "Opening Soon", - dot: "bg-cyan-400", - text: "text-cyan-400", - bg: "bg-cyan-400/10", - border: "border-cyan-400/30", - glow: "", + label: "Opens soon", + className: "text-cyan-400 bg-cyan-400/10 border-cyan-400/25", }, open: { - label: "Registering", - dot: "bg-emerald-400", - text: "text-accent", - bg: "bg-accent/10", - border: "border-accent/30", - glow: "shadow-[0_0_15px_rgba(52,211,153,0.6)]", + label: "Registration open", + className: "text-accent bg-accent/10 border-accent/25", }, in_progress: { - label: "Live Now", - dot: "bg-emerald-400", - text: "text-accent", - bg: "bg-accent/10", - border: "border-accent/30", - glow: "shadow-[0_0_15px_rgba(52,211,153,0.6)]", + label: "Live", + className: "text-accent bg-accent/10 border-accent/25", }, completed: { label: "Completed", - dot: "bg-white/40", - text: "text-[var(--text-primary)]/60", - bg: "bg-white/5", - border: "border-[var(--border-subtle)]", - glow: "", + className: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", }, closed: { - label: "Applications Closed", - dot: "bg-amber-400", - text: "text-amber-400", - bg: "bg-amber-400/10", - border: "border-amber-400/30", - glow: "", + label: "Registration closed", + className: "text-amber-400 bg-amber-400/10 border-amber-400/25", }, cancelled: { label: "Cancelled", - dot: "bg-rose-500", - text: "text-rose-500", - bg: "bg-rose-500/10", - border: "border-rose-500/30", - glow: "", + className: "text-rose-500 bg-rose-500/10 border-rose-500/25", }, draft: { label: "Draft", - dot: "bg-gray-500", - text: "text-text-muted", - bg: "bg-gray-500/10", - border: "border-gray-500/20", - glow: "", + className: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", }, }; return ( map[s] ?? { label: s, - dot: "bg-gray-500", - text: "text-text-muted", - bg: "bg-gray-500/10", - border: "border-gray-500/20", - glow: "", + className: + "text-[var(--text-muted)] bg-[var(--bg-secondary)] border-[var(--border-subtle)]", } ); } @@ -166,60 +128,77 @@ export default function HackathonsPage() { }, [authStatus, router]); if (authStatus === "loading" || isLoading) - return ; + return ; if (!session) return null; - const registeredIds = new Set(myRegs?.map((r) => r.hackathonId) ?? []); + const regStatusById = new Map( + myRegs?.map((r) => [r.hackathonId, r.registrationStatus]) ?? [], + ); const tabs = [ - { id: "browse", label: "Browse Events", icon: LayoutGrid }, - { id: "registrations", label: "My Registrations", icon: Calendar }, - { id: "projects", label: "My Projects", icon: FolderGit2 }, + { id: "browse", label: "Browse" }, + { id: "registrations", label: "My registrations" }, + { id: "projects", label: "My projects" }, ]; + const filters = [ + { key: "all", label: "All" }, + { key: "open", label: "Open" }, + { key: "live", label: "Live" }, + { key: "completed", label: "Past" }, + ] as const; + + const filtered = (hackathons ?? []).filter((h) => { + if (statusFilter === "all") return true; + if (statusFilter === "open") return h.status === "open"; + if (statusFilter === "live") return h.status === "in_progress"; + return h.status === "completed"; + }); + const filterLabel = + filters.find((f) => f.key === statusFilter)?.label.toLowerCase() ?? ""; + return ( -
- {/* Ambient Background Glows */} -
-
+
+ {/* Ambient glows */} +
+
+
+
-
+
{/* Back nav */} -
- -
- Club Portal + + Dashboard {/* Header */} -
-

- Hackathon Hub +
+

+ Hackathons

-

- Browse upcoming competitions, manage your registrations, and - showcase your project portfolio. +

+ Events you can register for, your registrations, and projects + you've submitted.

{/* Tab Navigation */} -
+
{tabs.map((tab) => ( setActiveTab(tab.id as Tab)} - className={`flex-1 min-w-[120px] flex items-center justify-center gap-2 px-4 py-3 rounded-none text-sm font-semibold transition-ui ${ + className={`rounded-sm px-5 py-2 text-sm font-bold uppercase tracking-wider transition-colors ${ activeTab === tab.id - ? "bg-accent/10 text-accent border border-accent/30" - : "text-text-muted hover:text-[var(--text-primary)] hover:bg-white/5" + ? "bg-accent/15 text-accent" + : "text-[var(--text-muted)] hover:text-[var(--text-primary)]" }`} > - {tab.label} ))} @@ -227,22 +206,17 @@ export default function HackathonsPage() { {/* ── BROWSE TAB ── */} {activeTab === "browse" && ( -
+
{/* Status Filter */} -
- {[ - { key: "all", label: "All" }, - { key: "open", label: "Registering" }, - { key: "live", label: "Live Now" }, - { key: "completed", label: "Completed" }, - ].map(({ key, label }) => ( +
+ {filters.map(({ key, label }) => (
{!hackathons || hackathons.length === 0 ? ( - -
- - - + +
+ +
+

+ No hackathons are listed yet. New events show up here when + they're announced. +

+
+ ) : filtered.length === 0 ? ( + +
+
-

- No Active Hackathons -

-

- There are currently no hackathons available. Check back soon. +

+ No {filterLabel} hackathons right now.

) : ( -
- {hackathons - .filter((h) => { - if (statusFilter === "all") return true; - if (statusFilter === "open") return h.status === "open"; - if (statusFilter === "live") - return h.status === "in_progress"; - return h.status === "completed"; - }) - .map((h) => { - const conf = statusConfig(h.status); - const isRegistered = registeredIds.has(h.id); - return ( - + {filtered.map((h) => { + const conf = statusConfig(h.status); + const regStatus = regStatusById.get(h.id); + return ( + + - -
-
- -
-
-
- - {conf.label} - -
- {isRegistered && ( -
- - - - - Registered - -
- )} -
- -
- {h.theme && ( -

- {h.theme} -

- )} -

- {h.name} -

- {h.description && ( -

- {h.description} -

- )} -
- -
-
-
- - - -
- - {formatDateRange(h.startDate, h.endDate)} - -
- {h.location && ( -
-
- - - -
- - {h.location} - -
- )} - {h.maxParticipants && ( -
-
- - - -
- - {h.currentParticipants} /{" "} - {h.maxParticipants} Spots - -
- )} +
+ + {conf.label} + + {regStatus && } +
+ +
+ {h.theme && ( +

+ {h.theme} +

+ )} +

+ {h.name} +

+ {h.description && ( +

+ {h.description} +

+ )} +
+ +
+
+ + {formatDateRange(h.startDate, h.endDate)} +
+ {h.location && ( +
+ + {h.location}
- -
- - - + )} + {h.maxParticipants && ( +
+ + {h.currentParticipants} of {h.maxParticipants}{" "} + spots taken
-
- - - ); - })} + )} +
+ +
+ View +
+ + + ); + })}
)} {/* Judge Sign-Up CTA */} -
- -
-
-
-

- Want to be a Judge? -

-

- Help evaluate amazing projects, mentor emerging - developers, and be a core part of our hackathon community. - We're always looking for industry professionals and - experienced students to join our judging panels. -

-
- - Apply to Judge - -
- -
+ +
+

+ Judge a hackathon +

+

+ Industry professionals and experienced students score + projects at the expo. You apply per event, and an organiser + reviews each application. +

+
+ + Apply to judge + +
)} {/* ── MY REGISTRATIONS TAB ── */} {activeTab === "registrations" && ( -
+
{myRegs && myRegs.length > 0 ? ( -
+
{myRegs.map((reg) => ( -
- -
- -
- - {reg.registrationStatus} - -
- -

- {reg.hackathon.name} -

- {reg.hackathon.theme && ( -

- Theme: {reg.hackathon.theme} -

- )} + +
+ +
-

- {reg.hackathon.description} -

+
+

+ {reg.hackathon.name} +

+ {reg.hackathon.theme && ( +

+ Theme: {reg.hackathon.theme} +

+ )} +

+ {reg.hackathon.description} +

+
-
- - - {reg.hackathon.startDate - ? new Date( - reg.hackathon.startDate, - ).toLocaleDateString() - : "TBA"} - - - Enter Portal{" "} - - -
+
+ + + {reg.hackathon.startDate + ? new Date( + reg.hackathon.startDate, + ).toLocaleDateString() + : "TBA"} + + + View + +
+
))}
) : ( -
-
- + +
+
-

- No active registrations -

-

- You haven't registered for any upcoming hackathons yet. Browse - events to get started. +

+ You haven't registered for any hackathons yet.

-
+ )}
)} {/* ── MY PROJECTS TAB ── */} {activeTab === "projects" && ( -
+
{projects.length === 0 ? ( -
-
- + +
+
-

- No projects submitted -

-

- Join a hackathon team and submit a project to see your - portfolio grow here. +

+ Projects you submit to a hackathon show up here.

-
+ ) : ( -
+
{projects.map((project) => ( -
-
+ +
+

+ {project.name} +

+ + {project.status === "winner" + ? "Winner" + : project.status === "judging" + ? "In judging" + : "Submitted"} + +
+

+ {project.description} +

-
-
-

- {project.name} -

- + {project.githubUrl && ( + - {project.status} - -
-

- {project.description} -

- -
- {project.githubUrl && ( - - Source Code - - )} - {project.demoUrl && ( - - Live Demo - - )} -
+ Code + + )} + {project.demoUrl && ( + + Demo + + )}
-
+ ))}
)} diff --git a/sites/mainweb/app/(portal)/hacklytics/page.tsx b/sites/mainweb/app/(portal)/hacklytics/page.tsx index 065f07f3..a4db21c2 100644 --- a/sites/mainweb/app/(portal)/hacklytics/page.tsx +++ b/sites/mainweb/app/(portal)/hacklytics/page.tsx @@ -68,7 +68,7 @@ export default function HacklyticsPage() { @@ -83,7 +83,7 @@ export default function HacklyticsPage() { return (
-

+

Nothing announced yet

@@ -104,26 +104,23 @@ export default function HacklyticsPage() { return (

-
- - - {registrationOpen - ? "Registration is open" - : event.status === "in_progress" - ? "The hackathon is under way" - : event.status === "open" - ? "Registration has closed" - : "Registration opens soon"} - -
+

+ {registrationOpen + ? "Registration is open" + : event.status === "in_progress" + ? "The hackathon is under way" + : event.status === "open" + ? "Registration has closed" + : "Registration opens soon"} +

-

+

{event.name}

-
+
When
@@ -132,7 +129,7 @@ export default function HacklyticsPage() {
{event.location ? (
-
+
Where
@@ -142,7 +139,7 @@ export default function HacklyticsPage() { ) : null} {event.theme ? (
-
+
Theme
{event.theme}
@@ -159,17 +156,17 @@ export default function HacklyticsPage() {
{registrationOpen ? (
-

+

Registration is open

{event.registrationDeadline - ? `Applications close ${formatDeadline(event.registrationDeadline)}. Spots are limited.` - : "Spots are limited and applications are reviewed as they arrive."} + ? `Applications close ${formatDeadline(event.registrationDeadline)}.` + : "Applications are reviewed as they arrive."}

Register now diff --git a/sites/mainweb/app/(portal)/judge/page.tsx b/sites/mainweb/app/(portal)/judge/page.tsx index 9ad176b8..68c1fb28 100644 --- a/sites/mainweb/app/(portal)/judge/page.tsx +++ b/sites/mainweb/app/(portal)/judge/page.tsx @@ -9,6 +9,7 @@ import { useIsClient } from "@/lib/use-is-client"; import { LiquidGlass } from "@/components/portal/LiquidGlass"; import { LoadingScreen } from "@/components/portal/LoadingScreen"; import Link from "next/link"; +import { Calendar, ChevronLeft, Gavel, MapPin, Users } from "lucide-react"; type HackathonData = { id: string; @@ -23,6 +24,11 @@ type HackathonData = { theme?: string | null; }; +const BADGE = + "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm text-[11px] font-bold uppercase tracking-wider border"; +const NEUTRAL_BADGE = + "bg-[var(--bg-secondary)] border-[var(--border-subtle)] text-[var(--text-muted)]"; + export default function JudgePage() { const { data: session, status } = useSession(); const router = useRouter(); @@ -33,14 +39,14 @@ export default function JudgePage() { const { data: hackathons, isLoading: hackathonsLoading } = trpc.hackathon.list.useQuery({}); // Applications, approved or not. getMyAssignments only returns approved ones, - // so without this an applicant saw "Apply to Judge" again — and pressing it + // so without this an applicant saw "Apply to judge" again — and pressing it // threw "You have already applied". const { data: applications } = trpc.judge.myApplications.useQuery(undefined, { enabled: !!session, }); // Gated on any approved application, not isJudge: isJudge answers for the // current edition only, while assignments span every edition, so a judge - // approved for another one saw "Application pending review" forever. + // approved for another one saw "Awaiting approval" forever. const { data: assignments } = trpc.judge.getMyAssignments.useQuery( undefined, { @@ -57,39 +63,33 @@ export default function JudgePage() { }, [status, router]); if (!mounted || status === "loading" || checkingJudge || hackathonsLoading) { - return ; + return ; } if (!session) return null; - const confMap: Record = - { - open: { - label: "Accepting Judges", - bg: "bg-accent/10", - border: "border-accent/30", - }, - in_progress: { - label: "Active", - bg: "bg-accent/10", - border: "border-accent/30", - }, - completed: { - label: "Completed", - bg: "bg-white/5", - border: "border-[var(--border-subtle)]", - }, - closed: { - label: "Closed", - bg: "bg-amber-400/10", - border: "border-amber-400/30", - }, - cancelled: { - label: "Cancelled", - bg: "bg-rose-500/10", - border: "border-rose-500/30", - }, - }; + const confMap: Record = { + open: { + label: "Accepting judges", + cls: "bg-accent/10 border-accent/25 text-accent", + }, + in_progress: { + label: "Live", + cls: "bg-accent/10 border-accent/25 text-accent", + }, + completed: { + label: "Completed", + cls: NEUTRAL_BADGE, + }, + closed: { + label: "Closed", + cls: "bg-amber-400/10 border-amber-400/30 text-amber-300", + }, + cancelled: { + label: "Cancelled", + cls: "bg-rose-500/10 border-rose-500/30 text-rose-400", + }, + }; const formatDateRange = (start: Date | string, end: Date | string) => { const s = new Date(start); @@ -104,191 +104,149 @@ export default function JudgePage() { }; return ( -
-
-
-
- -
- - - -
- Nexus - -

- Judge Portal -

-

- Browse upcoming hackathons and apply to become a judge. You must - be approved by the hackathon admin before you can start judging. -

-
+
+
+
+
+
+ +
+
+ + + Dashboard + +

+ Judging +

+

+ Apply to judge an event. An organiser approves you before you can + score. +

-
- {hackathons?.map((h: HackathonData) => { - // draft has no entry, and admins see drafts in this list. - const conf = confMap[h.status] ?? { - label: h.status, - bg: "bg-white/5", - border: "border-[var(--border-subtle)]", - }; - const assignment = assignments?.find((a) => a.hackathonId === h.id); - const isRegistered = !!assignment; - const application = applications?.find( - (a) => a.hackathonId === h.id, - ); - const awaitingApproval = !isRegistered && !!application; + {!hackathons?.length ? ( + +
+ +
+

+ No hackathons are taking judge applications right now. +

+
+ ) : ( +
+ {hackathons.map((h: HackathonData) => { + // draft has no entry, and admins see drafts in this list. + const conf = confMap[h.status] ?? { + label: h.status, + cls: NEUTRAL_BADGE, + }; + const assignment = assignments?.find( + (a) => a.hackathonId === h.id, + ); + const isRegistered = !!assignment; + const application = applications?.find( + (a) => a.hackathonId === h.id, + ); + const awaitingApproval = !isRegistered && !!application; - return ( - - {/* The card is a plain container, not a Link. - It used to wrap the whole thing in an anchor and then put - the "Apply to Judge" / "Ready to Judge" links inside it — - nested anchors, which React warns about and browsers - resolve unpredictably: the inner link often lost its click - to the outer one, so Apply took you to the judging screen - instead of the application form. */} -
-
-
+ {/* The card is a plain container, not a Link. + It used to wrap the whole thing in an anchor and then put + the "Apply to judge" / "Start judging" links inside it — + nested anchors, which React warns about and browsers + resolve unpredictably: the inner link often lost its click + to the outer one, so Apply took you to the judging screen + instead of the application form. */} +
+ {conf.label} +
+
+ {h.theme && ( +

+ {h.theme} +

+ )} +

-
- {conf.label} + {h.name} +

+ {h.description && ( +

+ {h.description} +

+ )} +
+
+
+ + {formatDateRange(h.startDate, h.endDate)}
-
- {h.theme && ( -

- {h.theme} -

- )} -

+ + {h.location} +

+ )} + {h.maxParticipants && ( +
+ + + {h.currentParticipants ?? 0} of {h.maxParticipants}{" "} + spots taken + +
+ )} +
+
+ {isRegistered ? ( + - {h.name} -

- {h.description && ( -

- {h.description} -

- )} -
-
-
- - - - {formatDateRange(h.startDate, h.endDate)} + Start judging + + ) : awaitingApproval ? ( +
+ Awaiting approval
- {h.location && ( -
- - - - {h.location} -
- )} - {h.maxParticipants && ( -
- - - - - {h.currentParticipants ?? 0} / {h.maxParticipants}{" "} - Spots - -
- )} -
-
- {isRegistered ? ( - <> - - Ready to Judge - - - ) : awaitingApproval ? ( -
- Application pending review -
- ) : h.status === "open" || - h.status === "closed" || - h.status === "in_progress" ? ( - - Apply to Judge - - ) : ( - - )} -
+ ) : h.status === "open" || + h.status === "closed" || + h.status === "in_progress" ? ( + + Apply to judge + + ) : ( + + )}
-
- - ); - })} -
+ + ); + })} +
+ )}
); diff --git a/sites/mainweb/app/(portal)/judge/register/page.tsx b/sites/mainweb/app/(portal)/judge/register/page.tsx index e82a2645..928f37f4 100644 --- a/sites/mainweb/app/(portal)/judge/register/page.tsx +++ b/sites/mainweb/app/(portal)/judge/register/page.tsx @@ -18,7 +18,7 @@ import { } from "@/components/hackathon/FormComponents"; import { SHIRT_SIZES, DIETARY_OPTIONS } from "@/components/hackathon/constants"; import Link from "next/link"; -import { ChevronLeft } from "lucide-react"; +import { Check, ChevronLeft } from "lucide-react"; import { LoadingScreen } from "@/components/portal/LoadingScreen"; const JUDGE_REGISTRATION_STEPS = [ @@ -78,7 +78,7 @@ export default function JudgeRegisterPage() { return ; } - if (isLoading) return ; + if (isLoading) return ; // `closed` belongs here: closing participant registration is the natural step // *before* recruiting judges, and leaving it out emptied this page with no @@ -158,37 +158,22 @@ export default function JudgeRegisterPage() { if (success) { return ( -
-
- -
- - - -
-

- Application Submitted +
+ + +

+ Application received

-

- Thank you for applying to be a judge! Our organizing team will - review your application and assign you to a track shortly. You will - be notified via email once approved. + {/* judge.setActive sends the approval email on the transition. */} +

+ An organiser will review it and assign you a track. You'll get + an email once you're approved.

- Return to Hub + Back to hackathons
@@ -196,37 +181,36 @@ export default function JudgeRegisterPage() { } return ( -
-
-
- -
- -
- -
- Back to Hub - +
+
+
+
+
-
-

- Judge Application +
+
+ + + Back to hackathons + +

+ Apply to judge

-

- Join the mentor network. Help evaluate cutting-edge projects and - guide the next generation of builders. +

+ Pick an event, tell us about yourself, and choose a track. Takes + about five minutes.

- + {step === 0 && ( -

+

Which event would you like to judge for?

@@ -239,18 +223,18 @@ export default function JudgeRegisterPage() { @@ -305,7 +289,7 @@ export default function JudgeRegisterPage() { placeholder="(555) 123-4567" /> setSpecialty(e.target.value)} placeholder="e.g. AI, Web3, Design, Product" @@ -364,7 +348,7 @@ export default function JudgeRegisterPage() { allowDeselect /> ) : ( -

+

This hackathon has no tracks published yet — organisers will assign yours.

diff --git a/sites/mainweb/app/(portal)/login/page.tsx b/sites/mainweb/app/(portal)/login/page.tsx index dd9588e5..3a056b49 100644 --- a/sites/mainweb/app/(portal)/login/page.tsx +++ b/sites/mainweb/app/(portal)/login/page.tsx @@ -333,19 +333,6 @@ export default function Home() {

- {/* Footer */} -
-
-
Internal Terminal // Auth Gateway v2.1
-
- - ACCESS NODE: 0812-ATL -
-
-
- {/* Global styles for custom animations */}
+ Name + Email + Status + Check-In Time