diff --git a/package.json b/package.json index c884a4e4..c1394986 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", - "eslint": "9.35.0", + "eslint": "9.36.0", "eslint-config-next": "15.5.3", "firebase": "^12.0.0", "form-data": "^4.0.0", diff --git a/src/app/(protected)/extra-credit/page.tsx b/src/app/(protected)/extra-credit/page.tsx new file mode 100644 index 00000000..fe4e3455 --- /dev/null +++ b/src/app/(protected)/extra-credit/page.tsx @@ -0,0 +1,230 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useFirebase } from "@/lib/providers/FirebaseProvider"; +import { useUserInfoMe } from "@/lib/api/user/hook"; +import { useAllExtraCreditClasses } from "@/lib/api/extra-credit/hook"; +import { + useUserExtraCreditClasses, + useAssignExtraCreditClass, + useUnassignExtraCreditClass, +} from "@/lib/api/user/hook"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { toast } from "sonner"; +import { GraduationCap, Loader2, ArrowLeft, Check, Plus, X } from "lucide-react"; + +export default function ExtraCredit() { + const { user, isAuthenticated, isLoading: authLoading } = useFirebase(); + const router = useRouter(); + const { data: userInfo, isLoading: userInfoLoading } = useUserInfoMe(); + const { data: allClasses, isLoading: allClassesLoading } = + useAllExtraCreditClasses(); + const { data: assignedClasses, isLoading: assignedClassesLoading } = + useUserExtraCreditClasses(user?.uid || ""); + + const { mutateAsync: assignClass, isPending: isAssigning } = + useAssignExtraCreditClass(); + const { mutateAsync: unassignClass, isPending: isUnassigning } = + useUnassignExtraCreditClass(); + + const [processingClassId, setProcessingClassId] = useState( + null + ); + + const isClassAssigned = (classId: number) => { + return assignedClasses?.some((c) => c.id === classId) || false; + }; + + const handleToggleClass = async ( + classId: number, + isCurrentlyAssigned: boolean + ) => { + if (!user?.uid) { + toast.error("Please sign in to manage extra credit classes"); + return; + } + + setProcessingClassId(classId); + try { + if (isCurrentlyAssigned) { + await unassignClass({ userId: user.uid, classId }); + toast.success("Extra credit class removed"); + } else { + await assignClass({ userId: user.uid, classId }); + toast.success("Extra credit class assigned"); + } + } catch (error: any) { + console.error("Error toggling class:", error); + const errorMessage = + error?.message || "Failed to update extra credit class"; + toast.error(errorMessage); + } finally { + setProcessingClassId(null); + } + }; + + if (authLoading || userInfoLoading) { + return ( +
+
+ + Loading... +
+
+ ); + } + + if (!isAuthenticated || !user) { + router.push("/"); + return null; + } + + return ( +
+
+ + + + +
+ +
+ + Extra Credit Classes + + + Select the classes you want to receive extra credit for attending + HackPSU + +
+
+ + + +
+
+ + + Extra Credit Classes + + + Click to add or remove classes for extra credit + +
+ {assignedClasses && assignedClasses.length > 0 && ( + + {assignedClasses.length} selected + + )} +
+
+ + {allClassesLoading || assignedClassesLoading ? ( +
+ + Loading classes... +
+ ) : allClasses && allClasses.length > 0 ? ( +
+ {allClasses.map((classItem) => { + const assigned = isClassAssigned(classItem.id); + const isProcessing = processingClassId === classItem.id; + + return ( +
+ !isProcessing && + !isAssigning && + !isUnassigning && + handleToggleClass(classItem.id, assigned) + } + > +
+
+ {assigned && } +
+
+
+ {classItem.name} +
+
+
+
+ {isProcessing ? ( + + ) : ( + + )} +
+
+ ); + })} +
+ ) : ( +
+ +

No extra credit classes available at this time

+
+ )} +
+
+
+
+ ); +} diff --git a/src/app/(protected)/profile/page.tsx b/src/app/(protected)/profile/page.tsx index 5c55e10b..23ff82f0 100644 --- a/src/app/(protected)/profile/page.tsx +++ b/src/app/(protected)/profile/page.tsx @@ -35,6 +35,7 @@ import { FolderOpen, Users, Lock, + GraduationCap, } from "lucide-react"; export default function Profile() { @@ -141,6 +142,10 @@ export default function Profile() { router.push("/project"); }; + const handleExtraCredit = () => { + router.push("/extra-credit"); + }; + // Find user's team const userTeam = teams?.find((team) => [ @@ -431,6 +436,16 @@ export default function Profile() { Submit Reimbursement Form + +