+ Something went wrong. Please try again later.
+
+ );
+ }
+
+ if (!member.value && !hacker.value) {
return (
@@ -21,7 +31,7 @@ export async function UserInterface() {
return (
@@ -39,10 +49,10 @@ export async function UserInterface() {
-
+
-
-
+
+
diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-number.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-number.tsx
deleted file mode 100644
index daa8f7ce1..000000000
--- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-number.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Trophy } from "lucide-react";
-
-import { Card, CardContent, CardHeader, CardTitle } from "@forge/ui/card";
-
-import { DASHBOARD_ICON_SIZE } from "~/consts";
-
-export function HackathonNumber({ size }: { size: number }) {
- return (
-
-
-
- Hackathons Attended
-
-
-
-
- {size}
- All academic year
-
-
- );
-}
diff --git a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-showcase.tsx b/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-showcase.tsx
deleted file mode 100644
index ecc367443..000000000
--- a/apps/blade/src/app/dashboard/_components/hacker-dashboard/hackathon/hackathon-showcase.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-/* import { CalendarDays, Tag, Trophy } from "lucide-react";
-
-import { Button } from "@forge/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@forge/ui/card";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@forge/ui/dialog";
-
-import type { api } from "~/trpc/server";
-import { DASHBOARD_ICON_SIZE } from "~/consts";
-import { formatDateRange } from "~/lib/utils";
-
-export function HackathonShowcase({
- hackathons,
-}: {
- hackathons: Awaited
>;
-}) {
- hackathons.sort(
- (a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime(),
- );
-
- const mostRecent = hackathons[0];
-
- if (!mostRecent) {
- return (
-
-
-
- Recent Hackathon Attended
-
-
-
-
- No hackathons found
-
-
-
- );
- }
-
- return (
-
-
-
- Recent Hackathon Attended
-
-
-
-
- {mostRecent.name}
-
-
- {mostRecent.theme}
-
-
-
-
-
-
- {formatDateRange(mostRecent.startDate, mostRecent.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 (
+
+
+
+
+
+
+
Status
+
+
+ {hackerStatus}
+
+ {hackerStatus === "Confirmed" && (
+
+ )}
+
+
+
+
+
+
+ {/* Confirm Button */}
+ {hackerStatus === "Accepted" && (
+
+ )}
+ {/* 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
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 (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+ };
+ return (
+
+ );
+}
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 (
-
+
-
Status
+
Status
-
+
{hackerStatus}
{hackerStatus === "Confirmed" && (
-
+
)}
-
Class
-
@@ -154,7 +160,7 @@ export function HackerData({
{hackerStatus === "Accepted" && (
)}
+ {/* Confirm Dialog */}
+
{/* Withdraw Button */}
{hackerStatus === "Confirmed" && (