Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
f0b4b37
added RSVP button for accepted participants
Ny1ka Feb 3, 2026
c96f8c6
fix entity name
joeboppell Feb 10, 2026
c826042
Update dependency posthog-node to v5.24.14 (#714)
renovate[bot] Feb 10, 2026
09d1b1c
Update dependency posthog-js to v1.345.2 (#712)
renovate[bot] Feb 10, 2026
10bdd9d
small changes to RSVP
joeboppell Feb 10, 2026
b2b435e
Merge branch 'main' into nyika/registrationrsvp
joeboppell Feb 10, 2026
78bf82b
fix rebase
joeboppell Feb 10, 2026
f19be79
Merge pull request #687 from Hack-PSU/nyika/registrationrsvp
joeboppell Feb 10, 2026
79337ff
change profile page title based on application status
joeboppell Feb 10, 2026
2a99d37
Update dependency @types/node to v24.10.13 (#719)
renovate[bot] Feb 11, 2026
83a9dce
Update dependency @posthog/nextjs-config to v1.8.15 (#720)
renovate[bot] Feb 11, 2026
3be48f6
Update dependency posthog-js to v1.345.3 (#721)
renovate[bot] Feb 11, 2026
55a87ef
Update dependency @tanstack/react-query to v5.90.21 (#723)
renovate[bot] Feb 11, 2026
4a4a466
Update dependency @types/react to v19.2.14 (#724)
renovate[bot] Feb 11, 2026
27c6531
close registrations flag
joeboppell Feb 11, 2026
53e9cae
Update dependency posthog-js to v1.345.5 (#725)
renovate[bot] Feb 12, 2026
7988a62
Update dependency posthog-node to v5.24.15 (#722)
renovate[bot] Feb 12, 2026
5e623ed
Update dependency @posthog/nextjs-config to v1.8.16 (#726)
renovate[bot] Feb 12, 2026
288386d
Update dependency posthog-js to v1.347.0 (#727)
renovate[bot] Feb 13, 2026
c4effef
Update dependency posthog-js to v1.347.1 (#728)
renovate[bot] Feb 13, 2026
0348201
Update dependency lucide-react to ^0.564.0 (#729)
renovate[bot] Feb 13, 2026
d0f2ad2
Update dependency posthog-js to v1.347.2 (#730)
renovate[bot] Feb 13, 2026
9734249
Update dependency swiper to v12.1.1 (#731)
renovate[bot] Feb 14, 2026
18d2a15
easter egg: hero glitches on click
leonac24 Feb 14, 2026
69abb0f
Update dependency tailwind-merge to v3.4.1 (#733)
renovate[bot] Feb 15, 2026
339dc0b
Update dependency framer-motion to v12.34.1 (#735)
renovate[bot] Feb 17, 2026
25ec513
changed glitch duration
joeboppell Feb 17, 2026
c1c58b6
Merge pull request #732 from Hack-PSU/leona-easter-egg-2
joeboppell Feb 17, 2026
85d86b1
Update dependency @posthog/nextjs-config to v1.8.17 (#736)
renovate[bot] Feb 18, 2026
03c108c
Update dependency posthog-node to v5.24.16 (#737)
renovate[bot] Feb 18, 2026
4e0f270
Update dependency lucide-react to ^0.574.0 (#734)
renovate[bot] Feb 18, 2026
c429123
Update dependency posthog-js to v1.350.0 (#738)
renovate[bot] Feb 18, 2026
f994ea8
Update dependency framer-motion to v12.34.2 (#739)
renovate[bot] Feb 18, 2026
097889e
Update dependency @posthog/nextjs-config to v1.8.18 (#741)
renovate[bot] Feb 18, 2026
fd0e33d
Update dependency posthog-node to v5.24.17 (#742)
renovate[bot] Feb 19, 2026
8d78d7a
Update dependency swiper to v12.1.2 (#744)
renovate[bot] Feb 19, 2026
36e846d
Update dependency knip to v5.84.1 (#740)
renovate[bot] Feb 19, 2026
8da44ca
Update dependency posthog-js to v1.351.3 (#743)
renovate[bot] Feb 19, 2026
a99a12e
Update dependency lucide-react to ^0.575.0 (#746)
renovate[bot] Feb 19, 2026
f07c9d2
Update dependency tailwind-merge to v3.5.0 (#745)
renovate[bot] Feb 19, 2026
202e5e7
Drone flies across screen when you click Hero text
vanishagupta05 Feb 19, 2026
a01f7e7
implemented timer for rsvp
dkupper4 Feb 19, 2026
ebe18d7
fixed timer for RSVP
dkupper4 Feb 19, 2026
f751435
Update dependency posthog-js to v1.351.4 (#748)
renovate[bot] Feb 20, 2026
90cc16f
Merge pull request #747 from Hack-PSU/drone-movement
joeboppell Feb 20, 2026
43bbf00
Merge pull request #749 from Hack-PSU/RSVPTimer
joeboppell Feb 20, 2026
f0fadcf
Update dependency @posthog/nextjs-config to v1.8.18 (#753)
renovate[bot] Feb 20, 2026
7060618
Update dependency eslint to v9.39.3 (#752)
renovate[bot] Feb 21, 2026
c68c56d
Update dependency framer-motion to v12.34.3 (#750)
renovate[bot] Feb 21, 2026
4b7b434
Update dependency react-hook-form to v7.71.2 (#754)
renovate[bot] Feb 21, 2026
3e0b66d
Update dependency posthog-js to v1.352.0 (#751)
renovate[bot] Feb 21, 2026
029eb0a
Update dependency knip to v5.85.0 (#756)
renovate[bot] Feb 21, 2026
3188152
Changed color scheme of footer contents
dark-ap1015 Feb 23, 2026
ba61be1
Update dependency posthog-js to v1.352.1 (#758)
renovate[bot] Feb 23, 2026
629b8f7
Update dependency posthog-js to v1.353.0 (#759)
renovate[bot] Feb 24, 2026
603686e
Merge pull request #757 from Hack-PSU/aaren/footer-design
joeboppell Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@mui/icons-material": "^6.0.0",
"@mui/material": "^6.4.4",
"@next/third-parties": "^16.0.0",
"@posthog/nextjs-config": "^1.0.2",
"@posthog/nextjs-config": "1.8.18",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",
Expand All @@ -37,23 +37,23 @@
"@tanstack/react-query": "^5.66.0",
"@types/luxon": "^3.3.2",
"@types/node": "^24.1.0",
"@types/react": "19.2.13",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@vercel/analytics": "^1.5.0",
"@vercel/speed-insights": "^1.2.0",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"eslint": "9.39.2",
"eslint": "9.39.3",
"eslint-config-next": "16.1.6",
"firebase": "^12.0.0",
"form-data": "^4.0.0",
"framer-motion": "^12.0.0",
"ics": "^3.8.1",
"jwt-decode": "^4.0.0",
"lint-staged": "^16.0.0",
"lucide-react": "^0.563.0",
"lucide-react": "^0.575.0",
"luxon": "^3.4.2",
"next": "16.1.6",
"posthog-js": "^1.257.0",
Expand Down
228 changes: 209 additions & 19 deletions src/app/(protected)/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useFlagState } from "@/lib/api/flag/hook";
import Image from "next/image";
import QRCode from "react-qr-code";
import { Button } from "@/components/ui/button";
import { Check, X } from "lucide-react";
import {
Card,
CardContent,
Expand Down Expand Up @@ -49,9 +50,12 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { usePatchApplicationStatus } from "@/lib/api/registration/hook";
import type { RegistrationEntity } from "@/lib/api/registration/entity";

// Role definitions matching AuthGuard
enum Role {
Expand All @@ -65,12 +69,12 @@ enum Role {

// Mapping from application status to its respective color
const applicationStatusColorMap = new Map<string, string>([
['pending', 'text-purple-400'],
['accepted', 'text-blue-400'],
['rejected', 'text-red-600'],
['waitlisted', 'text-orange-300'],
['confirmed', 'text-green-600'],
['declined', 'text-stone-500']
["pending", "text-purple-400"],
["accepted", "text-blue-400"],
["rejected", "text-red-600"],
["waitlisted", "text-orange-300"],
["confirmed", "text-green-600"],
["declined", "text-stone-500"],
]);

// Utility to get user role from token
Expand Down Expand Up @@ -106,6 +110,7 @@ export default function Profile() {
const router = useRouter();
const { isLoading: isUserLoading, data: userData } = useUserInfoMe();
const { data: teams } = useAllTeams();
const [now, setNow] = useState(() => Date.now());

// Mutations for wallet integration
const { mutateAsync: createWalletPass, isPending: isCreatingGoogleWallet } =
Expand All @@ -119,9 +124,18 @@ export default function Profile() {
const { mutateAsync: uploadResume, isPending: isUploadingResume } =
useUpdateUser();

const {
mutateAsync: patchApplicationStatus,
isPending: isPatchingApplicationStatus,
} = usePatchApplicationStatus();

const [showQRCode, setShowQRCode] = useState(false);
const [showResumeModal, setShowResumeModal] = useState(false);
const [resumeFile, setResumeFile] = useState<File | null>(null);
const [rsvpConfirmOpen, setRsvpConfirmOpen] = useState(false);
const [rsvpPendingStatus, setRsvpPendingStatus] = useState<
"confirmed" | "declined" | null
>(null);

// Feature flag checks
const { data: helpDeskFlag } = useFlagState("HelpDesk");
Expand All @@ -132,11 +146,12 @@ export default function Profile() {
const isOrganizer = userRole > Role.NONE;

// Check if user has a confirmed application
const applicationStatus =
(userData?.registration as any)?.applicationStatus;
const applicationStatus = (userData?.registration as any)?.applicationStatus;
const isConfirmed = applicationStatus === "confirmed";
console.log("User Data: " + JSON.stringify(userData));
console.log("Registration: " + JSON.stringify(userData?.registration?.applicationStatus));
console.log(
"Registration: " + JSON.stringify(userData?.registration?.applicationStatus)
);
const toggleQRCode = () => setShowQRCode((prev) => !prev);

useEffect(() => {
Expand All @@ -149,6 +164,13 @@ export default function Profile() {
}
}, [userData, router, isUserLoading, isOrganizer]);

useEffect(() => {
const timer = window.setInterval(() => {
setNow(Date.now());
}, 60_000);
return () => window.clearInterval(timer);
}, []);

// Handle add-to-Google Wallet click
const handleAddToGoogleWallet = async () => {
try {
Expand Down Expand Up @@ -312,6 +334,57 @@ export default function Profile() {
}
};

const registration = userData?.registration as RegistrationEntity | undefined;
const rsvpDeadline = registration?.rsvpDeadline;
const isOnTime = typeof rsvpDeadline === "number" && rsvpDeadline >= now;
const showRsvp = registration?.applicationStatus === "accepted" && isOnTime;
const formattedRsvpDeadline =
typeof rsvpDeadline === "number"
? new Date(rsvpDeadline).toLocaleString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZoneName: "short",
})
: null;

const openRsvpConfirm = (status: "confirmed" | "declined") => {
setRsvpPendingStatus(status);
setRsvpConfirmOpen(true);
};

const handleRsvpConfirm = async () => {
if (!userData?.id || !rsvpPendingStatus) return;
try {
await patchApplicationStatus({
userId: userData.id,
status: rsvpPendingStatus,
});
toast.success(
rsvpPendingStatus === "confirmed"
? "You're attending HackPSU! We can't wait to see you."
: "Your response has been recorded."
);
setRsvpConfirmOpen(false);
setRsvpPendingStatus(null);
} catch (error) {
console.error("RSVP error:", error);
const message =
error instanceof Error ? error.message : "Something went wrong.";
toast.error(
message.includes("400")
? "Invalid request. Please try again."
: message.includes("404")
? "Registration not found."
: "Failed to submit RSVP. Please try again."
);
setRsvpConfirmOpen(false);
setRsvpPendingStatus(null);
}
};

if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
Expand Down Expand Up @@ -360,7 +433,9 @@ export default function Profile() {
{isOrganizer && <Shield className="h-4 w-4" />}
{isOrganizer
? `HackPSU ${getRoleName(userRole)}`
: "HackPSU Participant"}
: isConfirmed
? "HackPSU Participant"
: "HackPSU Applicant"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
Expand All @@ -376,16 +451,66 @@ export default function Profile() {
</p>
</div>
)}
{(!isOrganizer && userData?.registration) && (
{!isOrganizer && userData?.registration && (
<div className="bg-slate-700/50 rounded-lg p-3 mt-4">
<p className={`text-2xl text-slate-200 text-center`}>
Application Status: <span className={`font-bold ${applicationStatusColorMap.get(userData.registration.applicationStatus)}`}>{userData.registration.applicationStatus.toUpperCase()}</span>
Application Status:{" "}
<span
className={`font-bold ${applicationStatusColorMap.get(userData.registration.applicationStatus)}`}
>
{userData.registration.applicationStatus.toUpperCase()}
</span>
</p>
</div>
)}
</CardContent>
</Card>

{/* RSVP Section - only when accepted and not organizer */}
{showRsvp && !isOrganizer && (
<Card>
<CardHeader>
<CardTitle>Will you be attending HackPSU?</CardTitle>
<CardDescription>
Please confirm your attendance so we can plan accordingly.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{formattedRsvpDeadline !== null && (
<div className="rounded-md border border-amber-300 bg-amber-50 px-4 py-3">
<p className="text-sm font-semibold text-amber-900">
You must RSVP by - {formattedRsvpDeadline}
</p>
</div>
)}
<div className="flex flex-col gap-3 w-full">
<Button
variant="success"
onClick={() => openRsvpConfirm("confirmed")}
disabled={isPatchingApplicationStatus}
className="w-full"
>
<Check className="h-4 w-4" />
{isPatchingApplicationStatus ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"Yes"
)}
</Button>
<Button
variant="destructive"
onClick={() => openRsvpConfirm("declined")}
disabled={isPatchingApplicationStatus}
className="w-full"
>
<X className="h-4 w-4" />
No
</Button>
</div>
</CardContent>
</Card>
)}

{/* QR Code Section */}
{isOrganizer || isConfirmed ? (
<>
Expand Down Expand Up @@ -464,7 +589,9 @@ export default function Profile() {
? "opacity-30 cursor-not-allowed"
: "cursor-pointer hover:opacity-80"
}`}
onClick={isOrganizer ? undefined : handleAddToGoogleWallet}
onClick={
isOrganizer ? undefined : handleAddToGoogleWallet
}
priority
/>
)}
Expand All @@ -486,7 +613,9 @@ export default function Profile() {
? "opacity-30 cursor-not-allowed"
: "cursor-pointer hover:opacity-80"
}`}
onClick={isOrganizer ? undefined : handleAddToAppleWallet}
onClick={
isOrganizer ? undefined : handleAddToAppleWallet
}
priority
/>
)}
Expand All @@ -513,7 +642,9 @@ export default function Profile() {
<>
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-lg">{userTeam.name}</h3>
<h3 className="font-semibold text-lg">
{userTeam.name}
</h3>
</div>
{!userTeam.isActive && (
<div className="flex items-center space-x-2 text-yellow-600">
Expand All @@ -528,7 +659,10 @@ export default function Profile() {
</p>
<div className="space-y-1">
{getTeamMembers().map((memberId) => (
<TeamMemberDisplay key={memberId} memberId={memberId!} />
<TeamMemberDisplay
key={memberId}
memberId={memberId!}
/>
))}
</div>
</div>
Expand Down Expand Up @@ -675,9 +809,9 @@ export default function Profile() {
<CardHeader>
<CardTitle>Actions Unavailable</CardTitle>
<CardDescription>
Access to our features is currently unavailable. Once confirmed, you’ll
gain full access to QR check-in, wallet passes, team features, and
project tools.
Access to our features is currently unavailable. Once confirmed,
you’ll gain full access to QR check-in, wallet passes, team
features, and project tools.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
Expand Down Expand Up @@ -766,6 +900,62 @@ export default function Profile() {
</div>
</DialogContent>
</Dialog>

{/* RSVP confirmation modal */}
<Dialog
open={rsvpConfirmOpen}
onOpenChange={(open) => {
setRsvpConfirmOpen(open);
if (!open) setRsvpPendingStatus(null);
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
{rsvpPendingStatus === "confirmed" ? (
<>
<div>
You are confirming that you will attend HackPSU. This
cannot be undone.
</div>
<div className="mt-2">
<strong>IMPORTANT:</strong> If you confirm and do not
attend, you may be banned from the next hackathon.
</div>
</>
) : (
"You are declining your spot. This cannot be undone."
)}
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={() => {
setRsvpConfirmOpen(false);
setRsvpPendingStatus(null);
}}
disabled={isPatchingApplicationStatus}
>
Cancel
</Button>
<Button
variant={
rsvpPendingStatus === "declined" ? "destructive" : "success"
}
onClick={handleRsvpConfirm}
disabled={isPatchingApplicationStatus}
>
{isPatchingApplicationStatus ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"Confirm"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
);
Expand Down
Loading