From 4a107fe39ef4977943e45bbe9ca93f49355c1eae Mon Sep 17 00:00:00 2001 From: TTRAN70 Date: Fri, 25 Apr 2025 05:50:40 -0400 Subject: [PATCH 1/7] Made mobile buttons larger, added necessary routes for hacker dashboard --- apps/blade/src/app/_components/auth-home.tsx | 6 +- apps/blade/src/app/_components/hero.tsx | 7 +- packages/api/src/routers/hacker.ts | 110 ++++++++++++++++++- 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/apps/blade/src/app/_components/auth-home.tsx b/apps/blade/src/app/_components/auth-home.tsx index bc6c7e681..1b7742024 100644 --- a/apps/blade/src/app/_components/auth-home.tsx +++ b/apps/blade/src/app/_components/auth-home.tsx @@ -15,10 +15,10 @@ export function AuthHome() { Manage your Knight Hacks membership, hackathon information, and more with Blade.

- - diff --git a/apps/blade/src/app/_components/hero.tsx b/apps/blade/src/app/_components/hero.tsx index 941920c1c..681dcee67 100644 --- a/apps/blade/src/app/_components/hero.tsx +++ b/apps/blade/src/app/_components/hero.tsx @@ -29,6 +29,7 @@ export function Hero() {
-
+
-
+
- - - - Past Hackathons Attended - -
- {hackathons.slice(1).map((hackathon) => ( - - - {hackathon.name} - - - {hackathon.theme} - - - -
- - - {formatDateRange( - hackathon.startDate, - hackathon.endDate, - )} - -
-
-
- ))} -
- -
- - - - ); -} - -*/ diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx index 601e0b21e..2b3cce6a1 100644 --- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; import type { api as serverCall } from "~/trpc/server"; +import { HackerData } from "./hacker-data"; export const metadata: Metadata = { title: "Hacker Dashboard", @@ -14,8 +15,38 @@ export default function HackerDashboard({ hacker: Awaited>; }) { if (!hacker) { - redirect("/"); + redirect("/hacker/application"); } - return
Work in progress...
; + return ( +
+ {/* Main content */} + + + {/* Transparent Triangle overlay in bottom right corner */} +
+ + {/* Triangle in bottom right corner */} +
+ + {/* Top rectangle */} +
+
+
+ + {/* Bottom rectangle */} +
+
+
+ + {/* Left side rectangle */} +
+
+ ); } diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx new file mode 100644 index 000000000..5af7803f1 --- /dev/null +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx @@ -0,0 +1,236 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Image from "next/image"; +import { CircleCheckBig, Loader2 } from "lucide-react"; + +import { USE_CAUTION } from "@forge/consts/knight-hacks"; +import { Button } from "@forge/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@forge/ui/dialog"; +import { Input } from "@forge/ui/input"; +import { toast } from "@forge/ui/toast"; + +import type { api as serverCall } from "~/trpc/server"; +import { HACKER_STATUS_MAP } from "~/consts"; +import { api } from "~/trpc/react"; +import { HackerQRCodePopup } from "./hacker-qr-button"; + +type StatusKey = keyof typeof HACKER_STATUS_MAP | null | undefined; + +export function HackerData({ + data, +}: { + data: Awaited>; +}) { + const [hackerStatus, setHackerStatus] = useState(""); + const [hackerStatusColor, setHackerStatusColor] = useState(""); + const [loading, setLoading] = useState(false); + const [confirmationText, setConfirmationText] = useState(""); + const [isOpen, setIsOpen] = useState(false); + + const { data: hacker, isError } = api.hacker.getHacker.useQuery(undefined, { + initialData: data, + }); + + const utils = api.useUtils(); + + const handleConfirm = () => { + try { + setLoading(true); + confirmHacker.mutate({ + id: hacker?.id, + }); + } catch (error) { + console.error("Error confirming hacker:", error); + } + }; + + const handleWithdraw = () => { + try { + setLoading(true); + withdrawHacker.mutate({ + id: hacker?.id, + }); + } catch (error) { + console.error("Error withdrawing hacker:", error); + } + }; + + function getStatusName(status: StatusKey) { + if (!status) return ""; + return HACKER_STATUS_MAP[status].name; + } + + function getStatusColor(status: StatusKey) { + if (!status) return ""; + return HACKER_STATUS_MAP[status].color; + } + + const confirmHacker = api.hacker.confirmHacker.useMutation({ + async onSuccess() { + setHackerStatus("Confirmed"); + setHackerStatusColor(getStatusColor("confirmed")); + toast.success("Hacker confirmed successfully!"); + await utils.hacker.getHacker.invalidate(); + }, + onError() { + toast.error("Oops! Something went wrong. Please try again later."); + }, + onSettled() { + setLoading(false); + }, + }); + + const withdrawHacker = api.hacker.withdrawHacker.useMutation({ + async onSuccess() { + setHackerStatus("Withdrawn"); + setHackerStatusColor(getStatusColor("withdrawn")); + toast.success("Hacker withdrawn successfully."); + await utils.hacker.getHacker.invalidate(); + }, + onError() { + toast.error("Oops! Something went wrong. Please try again later."); + }, + onSettled() { + setLoading(false); + }, + }); + + useEffect(() => { + setHackerStatus(getStatusName(hacker?.status)); + setHackerStatusColor(getStatusColor(hacker?.status)); + }, [hacker]); + + if (isError) { + return ( +
+ Something went wrong. Please refresh and try again. +
+ ); + } + + return ( +
+
+ Image of TK +
+
+
+
Status
+
+
+ {hackerStatus} +
+ {hackerStatus === "Confirmed" && ( + + )} +
+
+
+
Class
+
+ TBD +
+
+
+
+ + {/* Confirm Button */} + {hackerStatus === "Accepted" && ( + + )} + {/* Withdraw Button */} + {hackerStatus === "Confirmed" && ( + + + + + + + + Are you sure? + + You are about to withdraw from this hackathon. This action + cannot be undone. Please proceed with caution. + + + +
+

+ Please type "I am absolutely sure" to + confirm: +

+ setConfirmationText(e.target.value)} + onPaste={(e) => { + e.preventDefault(); + toast.info("Please type in the text, do not paste."); + }} + /> +
+ + + + + +
+
+ )} +
+
+ ); +} diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx new file mode 100644 index 000000000..a78651466 --- /dev/null +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx @@ -0,0 +1,85 @@ +"use client"; + +import Image from "next/image"; +import { Loader2, QrCode } from "lucide-react"; + +import { Button } from "@forge/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@forge/ui/dialog"; + +import { api } from "~/trpc/react"; + +export function HackerQRCodePopup() { + const getQR = () => { + const { data: userQR, isLoading, isError } = api.qr.getQRCode.useQuery(); + + if (isError) { + return ( +
+
+ Something went wrong. please try again +
+
+ ); + } + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (userQR?.qrCodeUrl) { + return ( +
+ QR Code +
+ ); + } + + return ( +
+
No QR Code found.
+
+ ); + }; + return ( + + + + + + + Your QR Code + +
+
{getQR()}
+
+
+ +
+ ); +} diff --git a/apps/blade/src/consts/index.ts b/apps/blade/src/consts/index.ts index 55b48b81d..d04db351c 100644 --- a/apps/blade/src/consts/index.ts +++ b/apps/blade/src/consts/index.ts @@ -15,3 +15,13 @@ export const SIDEBAR_NAV_ITEMS = [ export const USER_DROPDOWN_ICON_COLOR = "hsl(263.4 70% 50.4%)"; //lucide only works with HSL values export const USER_DROPDOWN_ICON_SIZE = 20; + +export const HACKER_STATUS_MAP = { + withdrawn: { name: "Withdrawn", color: "text-[#FF6B6B]" }, + pending: { name: "Pending", color: "text-[#FFD93D]" }, + accepted: { name: "Accepted", color: "text-[#6BCB77]" }, + waitlisted: { name: "Waitlisted", color: "text-[#4D96FF]" }, + checkedin: { name: "Checked-in", color: "text-[#845EC2]" }, + confirmed: { name: "Confirmed", color: "text-[#00C9A7]" }, + denied: { name: "Denied", color: "text-[#FF5E5E]" }, +}; From 5fefeb7861cfa6ad2dadc0d21c10f175ef33f152 Mon Sep 17 00:00:00 2001 From: TTRAN70 Date: Mon, 28 Apr 2025 05:14:45 -0400 Subject: [PATCH 3/7] Final touches and animations to hacker dashboard --- .../hacker-dashboard/hacker-dashboard.tsx | 2 +- .../hacker-dashboard/hacker-data.tsx | 56 +++++++++++++++---- .../hacker-dashboard/hacker-qr-button.tsx | 2 +- apps/blade/src/app/globals.css | 29 ++++++++++ 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx index 2b3cce6a1..85954e8f1 100644 --- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-dashboard.tsx @@ -19,7 +19,7 @@ export default function HackerDashboard({ } return ( -
+
{/* Main content */} diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx index 5af7803f1..7dff6d2e0 100644 --- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-data.tsx @@ -35,6 +35,7 @@ export function HackerData({ const [loading, setLoading] = useState(false); const [confirmationText, setConfirmationText] = useState(""); const [isOpen, setIsOpen] = useState(false); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); const { data: hacker, isError } = api.hacker.getHacker.useQuery(undefined, { initialData: data, @@ -78,7 +79,7 @@ export function HackerData({ async onSuccess() { setHackerStatus("Confirmed"); setHackerStatusColor(getStatusColor("confirmed")); - toast.success("Hacker confirmed successfully!"); + setIsConfirmOpen(true); await utils.hacker.getHacker.invalidate(); }, onError() { @@ -93,7 +94,7 @@ export function HackerData({ async onSuccess() { setHackerStatus("Withdrawn"); setHackerStatusColor(getStatusColor("withdrawn")); - toast.success("Hacker withdrawn successfully."); + toast.success("You have withdrawn from the hackathon!"); await utils.hacker.getHacker.invalidate(); }, onError() { @@ -119,7 +120,7 @@ export function HackerData({ return (
-
+
Image of TK
-
Status
+
Status
-
+
{hackerStatus}
{hackerStatus === "Confirmed" && ( - + )}
-
Class
-
+
Class
+
TBD
@@ -154,7 +160,7 @@ export function HackerData({ {hackerStatus === "Accepted" && ( )} + {/* Confirm Dialog */} + + + + + Congratulations! + + +
+ Image of TK + + You have confirmed your attendance for the hackathon! We are + excited to see you there! + +
+ + + +
+
{/* Withdraw Button */} {hackerStatus === "Confirmed" && ( diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx index a78651466..9aae7c6d3 100644 --- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx +++ b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hacker-qr-button.tsx @@ -62,7 +62,7 @@ export function HackerQRCodePopup() {