From d66921d023803c81e05fe7955af369f50ee965d4 Mon Sep 17 00:00:00 2001 From: DVidal1205 Date: Sat, 27 Jun 2026 12:12:53 -0700 Subject: [PATCH] fixed member and hacker dashboard routing Co-authored-by: Codex --- .../dashboard/dashboard-entry-dialogs.tsx | 89 +++++++++++++++++++ .../hacker-dashboard/hacker-dashboard.tsx | 48 ++++++++-- .../hacker/hacker-application-form.tsx | 13 +-- .../current-hackathon-notice.tsx | 73 --------------- .../member-dashboard/member-dashboard.tsx | 23 ++--- .../src/app/_components/user-interface.tsx | 10 ++- apps/blade/src/app/dashboard/page.tsx | 33 ++++++- apps/blade/src/app/hackathon/page.tsx | 4 +- .../application/[hackathon-id]/page.tsx | 2 +- packages/api/src/routers/hackathon.ts | 11 ++- packages/api/src/routers/hackers/queries.ts | 15 ++-- 11 files changed, 197 insertions(+), 124 deletions(-) create mode 100644 apps/blade/src/app/_components/dashboard/dashboard-entry-dialogs.tsx delete mode 100644 apps/blade/src/app/_components/dashboard/member-dashboard/current-hackathon-notice.tsx diff --git a/apps/blade/src/app/_components/dashboard/dashboard-entry-dialogs.tsx b/apps/blade/src/app/_components/dashboard/dashboard-entry-dialogs.tsx new file mode 100644 index 000000000..b6f8e7509 --- /dev/null +++ b/apps/blade/src/app/_components/dashboard/dashboard-entry-dialogs.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; + +import { Button } from "@forge/ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@forge/ui/dialog"; + +import { TacoTuesday } from "../discord-modal"; + +interface DashboardHackathon { + applicationsOpen: boolean; + displayName: string; + isLive: boolean; + name: string; +} + +export function DashboardEntryDialogs({ + currentHackathon, + hasHacker, + hasMember, + showDiscordPrompt, +}: { + currentHackathon: DashboardHackathon | null; + hasHacker: boolean; + hasMember: boolean; + showDiscordPrompt: boolean; +}) { + const [routingOpen, setRoutingOpen] = useState(currentHackathon != null); + + const hackathonAction = hasHacker + ? `Open ${currentHackathon?.displayName ?? "Hackathon"} Dashboard` + : currentHackathon?.applicationsOpen + ? `Register for ${currentHackathon.displayName}` + : `View ${currentHackathon?.displayName ?? "Hackathon"}`; + const memberAction = hasMember + ? "Continue to Member Dashboard" + : "Explore Knight Hacks Membership"; + + return ( + <> + {currentHackathon && ( + + + + + {currentHackathon.displayName} is{" "} + {currentHackathon.isLive ? "live" : "coming up"} + + + {hasHacker + ? `You have a ${currentHackathon.displayName} application. Choose which dashboard you want to open.` + : `Choose whether you are here for ${currentHackathon.displayName} or year-round Knight Hacks membership.`} + + + + + + + + + + + )} + + {!routingOpen && showDiscordPrompt && } + + ); +} diff --git a/apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-dashboard.tsx b/apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-dashboard.tsx index 80e234c55..40e7ea50d 100644 --- a/apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-dashboard.tsx +++ b/apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-dashboard.tsx @@ -21,27 +21,57 @@ export default async function HackerDashboard({ hackathon: SelectHackathon; hacker: Awaited>; }) { - const [resume, pastHackathons] = await Promise.allSettled([ - api.resume.getResume(), - api.hackathon.getPastHackathons(), - ]); - if (!hacker) { + const now = new Date(); + + if (now < hackathon.applicationOpen) { + const applicationOpen = new Intl.DateTimeFormat("en-US", { + dateStyle: "long", + timeZone: "America/New_York", + }).format(hackathon.applicationOpen); + + return ( +
+

+ Applications for {hackathon.displayName} open on {applicationOpen}. +

+

+ Check back then to submit your hacker application. +

+
+ ); + } + + if (now > hackathon.applicationDeadline) { + return ( +
+

+ Applications for {hackathon.displayName} are closed. +

+

+ Existing applicants can still view their application status here. +

+
+ ); + } + return (

Register for {hackathon.displayName} today!

- { - //if there is no current hackathon then this page is never rendered anyway - - } +
); } + const [resume, pastHackathons] = await Promise.allSettled([ + api.resume.getResume(), + api.hackathon.getPastHackathons(), + ]); + return ( <>
diff --git a/apps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsx b/apps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsx index d14275e79..6c4689526 100644 --- a/apps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsx +++ b/apps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsx @@ -920,9 +920,9 @@ export function HackerFormPage({ existingApplicationRedirectedRef.current = true; toast.info("You already submitted an application for this hackathon."); - router.replace("/dashboard"); + router.replace(`/hackathon/${hackathonId}`); router.refresh(); - }, [applicationSubmitted, existingApplication, router]); + }, [applicationSubmitted, existingApplication, hackathonId, router]); useEffect(() => { if (prefillAppliedRef.current) return; @@ -1200,7 +1200,7 @@ export function HackerFormPage({ toast.info( "You already submitted an application for this hackathon.", ); - router.push("/dashboard"); + router.push(`/hackathon/${hackathonId}`); router.refresh(); setLoading(false); return; @@ -1298,13 +1298,14 @@ export function HackerFormPage({ asChild className="h-12 rounded-full bg-white px-6 text-base font-black text-[#21103d] shadow-[0_18px_46px_rgba(0,0,0,0.42)] hover:bg-white/90" > - - Go to dashboard + + View {hackathonName} dashboard

- Your dashboard will show your current application status. + Your hackathon dashboard will show your current + application status.

diff --git a/apps/blade/src/app/_components/dashboard/member-dashboard/current-hackathon-notice.tsx b/apps/blade/src/app/_components/dashboard/member-dashboard/current-hackathon-notice.tsx deleted file mode 100644 index 7ced0c995..000000000 --- a/apps/blade/src/app/_components/dashboard/member-dashboard/current-hackathon-notice.tsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client"; - -import { useState } from "react"; -import Link from "next/link"; -import { Trophy } from "lucide-react"; - -import { cn } from "@forge/ui"; -import { Alert, AlertDescription, AlertTitle } from "@forge/ui/alert"; -import { buttonVariants } from "@forge/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@forge/ui/dialog"; - -export function CurrentHackathonNotice({ - hackathonDisplayName, -}: { - hackathonDisplayName: string; -}) { - const [open, setOpen] = useState(true); - - return ( - <> - - - {hackathonDisplayName} is happening now - - - Head to the hackathon dashboard for your check-in tools, points, - live events, and event-specific info. - - - Open Hackathon Dashboard - - - - - - - - {hackathonDisplayName} is live - - The hackathon dashboard has check-in tools, points, live events, - and event-specific info. - - - - - Stay Here - - - Open Hackathon Dashboard - - - - - - ); -} diff --git a/apps/blade/src/app/_components/dashboard/member-dashboard/member-dashboard.tsx b/apps/blade/src/app/_components/dashboard/member-dashboard/member-dashboard.tsx index 44ee66ad9..323c36953 100644 --- a/apps/blade/src/app/_components/dashboard/member-dashboard/member-dashboard.tsx +++ b/apps/blade/src/app/_components/dashboard/member-dashboard/member-dashboard.tsx @@ -5,7 +5,6 @@ import { MemberAppCard } from "~/app/_components/option-cards"; import { api } from "~/trpc/server"; import { AlumniDiscord } from "./AlumniDiscord"; import { AlumniRecap } from "./AlumniRecap"; -import { CurrentHackathonNotice } from "./current-hackathon-notice"; import DayInHistory from "./day-in-history"; import EarlyAccessVolunteer from "./early-access-volunteer"; import { EventNumber } from "./event/event-number"; @@ -84,13 +83,11 @@ export default async function MemberDashboard({ const isAlumni = calcAlumniStatus(member.gradDate, member); - const [eventsValue, dues, hackathonsValue, currentHackathon] = - await Promise.all([ - api.member.getEvents(), - api.duesPayment.validatePaidDues(), - isAlumni ? api.hackathon.getPastHackathons() : Promise.resolve([]), - api.hackathon.getCurrentHackathon(), - ]); + const [eventsValue, dues, hackathonsValue] = await Promise.all([ + api.member.getEvents(), + api.duesPayment.validatePaidDues(), + isAlumni ? api.hackathon.getPastHackathons() : Promise.resolve([]), + ]); const duesPaid = dues.duesPaid; @@ -103,11 +100,6 @@ export default async function MemberDashboard({ return (
- {currentHackathon && ( - - )} {/* Unified View */}
@@ -139,11 +131,6 @@ export default async function MemberDashboard({ return (
- {currentHackathon && ( - - )} {/* Unified View */}
diff --git a/apps/blade/src/app/_components/user-interface.tsx b/apps/blade/src/app/_components/user-interface.tsx index c8618ccdf..86b42a2ac 100644 --- a/apps/blade/src/app/_components/user-interface.tsx +++ b/apps/blade/src/app/_components/user-interface.tsx @@ -1,10 +1,12 @@ +import type { api as serverCaller } from "~/trpc/server"; import MemberDashboard from "~/app/_components/dashboard/member-dashboard/member-dashboard"; import { MemberAppCard } from "~/app/_components/option-cards"; -import { api } from "~/trpc/server"; - -export async function UserInterface() { - const member = await api.member.getMember(); +export function UserInterface({ + member, +}: { + member: Awaited>; +}) { if (!member) { return (
diff --git a/apps/blade/src/app/dashboard/page.tsx b/apps/blade/src/app/dashboard/page.tsx index 31e14c419..5a79ff79d 100644 --- a/apps/blade/src/app/dashboard/page.tsx +++ b/apps/blade/src/app/dashboard/page.tsx @@ -3,7 +3,7 @@ import { redirect } from "next/navigation"; import { auth } from "@forge/auth"; -import { TacoTuesday } from "~/app/_components/discord-modal"; +import { DashboardEntryDialogs } from "~/app/_components/dashboard/dashboard-entry-dialogs"; import { SessionNavbar } from "~/app/_components/navigation/session-navbar"; import { UserInterface } from "~/app/_components/user-interface"; import { api, HydrateClient } from "~/trpc/server"; @@ -15,18 +15,43 @@ export const metadata: Metadata = { export default async function Dashboard() { const session = await auth(); - const isMember = await api.auth.getDiscordMemberStatus(); if (!session) { redirect("/"); } + const [isDiscordMember, member, currentHackathon] = await Promise.all([ + api.auth.getDiscordMemberStatus(), + api.member.getMember(), + api.hackathon.getCurrentHackathon(), + ]); + const hacker = currentHackathon + ? await api.hackerQuery.getHacker({ + hackathonName: currentHackathon.name, + }) + : null; + const now = new Date(); + return (
- - + = now, + displayName: currentHackathon.displayName, + isLive: currentHackathon.startDate <= now, + name: currentHackathon.name, + } + : null + } + hasHacker={hacker != null} + hasMember={member != null} + showDiscordPrompt={!isDiscordMember} + /> +
); diff --git a/apps/blade/src/app/hackathon/page.tsx b/apps/blade/src/app/hackathon/page.tsx index ef7d6ca68..6589c7f07 100644 --- a/apps/blade/src/app/hackathon/page.tsx +++ b/apps/blade/src/app/hackathon/page.tsx @@ -8,7 +8,7 @@ import { api } from "~/trpc/server"; export const metadata: Metadata = { title: "Blade | Hackathon", - description: "Open the currently running Knight Hacks hackathon dashboard.", + description: "Open the current Knight Hacks hackathon dashboard.", }; export default async function CurrentHackathonPage() { @@ -25,7 +25,7 @@ export default async function CurrentHackathonPage() {

- There's no Hackathon running right now. + There's no upcoming or active hackathon right now.

Stay on the lookout for the next one by joining our{" "} diff --git a/apps/blade/src/app/hacker/application/[hackathon-id]/page.tsx b/apps/blade/src/app/hacker/application/[hackathon-id]/page.tsx index bbba1cc54..bdb42dfe7 100644 --- a/apps/blade/src/app/hacker/application/[hackathon-id]/page.tsx +++ b/apps/blade/src/app/hacker/application/[hackathon-id]/page.tsx @@ -51,7 +51,7 @@ export default async function HackerApplicationPage(props: { }); if (isHacker != null) { - return redirect("/dashboard"); + return redirect(`/hackathon/${params["hackathon-id"]}`); } } catch { return redirect("/dashboard"); diff --git a/packages/api/src/routers/hackathon.ts b/packages/api/src/routers/hackathon.ts index d83d80a8a..08eb58ccb 100644 --- a/packages/api/src/routers/hackathon.ts +++ b/packages/api/src/routers/hackathon.ts @@ -139,8 +139,15 @@ export const hackathonRouter = { getCurrentHackathon: publicProcedure.query(async () => { const now = new Date(); const hackathon = await db.query.Hackathon.findFirst({ - orderBy: (t, { asc }) => asc(t.endDate), - where: and(lte(Hackathon.startDate, now), gte(Hackathon.endDate, now)), + orderBy: (t, { asc }) => [ + asc(t.applicationOpen), + asc(t.startDate), + asc(t.endDate), + ], + where: and( + lte(Hackathon.applicationOpen, now), + gte(Hackathon.endDate, now), + ), }); return hackathon ?? null; diff --git a/packages/api/src/routers/hackers/queries.ts b/packages/api/src/routers/hackers/queries.ts index ffca18b8a..bf99ba031 100644 --- a/packages/api/src/routers/hackers/queries.ts +++ b/packages/api/src/routers/hackers/queries.ts @@ -28,14 +28,19 @@ export const hackerQueryRouter = { }); } } else { - // If not provided, grab a FUTURE hackathon with a start date CLOSEST to now + // Match the participant lifecycle used by getCurrentHackathon. const now = new Date(); - const futureHackathons = await db.query.Hackathon.findMany({ - where: (t, { gt }) => gt(t.endDate, now), - orderBy: (t, { asc }) => [asc(t.startDate)], + const currentHackathons = await db.query.Hackathon.findMany({ + where: (t, { and, gte, lte }) => + and(lte(t.applicationOpen, now), gte(t.endDate, now)), + orderBy: (t, { asc }) => [ + asc(t.applicationOpen), + asc(t.startDate), + asc(t.endDate), + ], limit: 1, }); - hackathon = futureHackathons[0]; + hackathon = currentHackathons[0]; if (!hackathon) { return null;