From 945eeeec5322892023a229febb6bdbc3e82502c0 Mon Sep 17 00:00:00 2001 From: dvickster Date: Mon, 20 Jul 2026 16:57:32 -0500 Subject: [PATCH 01/10] Removed clerk header --- src/app/layout.tsx | 14 +------ src/components/navbar.tsx | 26 +++++++++++-- src/lib/qrToken.ts | 78 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 src/lib/qrToken.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 29fa20a..4809ab4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -84,22 +84,10 @@ export default function RootLayout({ className={`${tektur.variable} ${jakarta.variable} ${audiowide.variable} ${electrolize.variable} ${ibmPlexMono.variable} ${hanken.variable} ${chakra.variable} ${spaceMono.variable} font-body antialiased`} > -
- - - - - - - - - -
{children}
) } + diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index c4cefbb..668ec64 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { Show, UserButton } from "@clerk/nextjs"; import { cn } from "@/lib/utils"; const NAV_ITEMS = ["Events", "Apply", "Dashboard"] as const; @@ -51,10 +52,27 @@ export function Navbar({ active = "Dashboard" }: NavbarProps) { {/* Account */}
- - - Member - + + + Sign In + + + Sign Up + + + + + + + Member + +
diff --git a/src/lib/qrToken.ts b/src/lib/qrToken.ts new file mode 100644 index 0000000..1a58982 --- /dev/null +++ b/src/lib/qrToken.ts @@ -0,0 +1,78 @@ +import {SignJWT, jwtVerify, type JWTPayload } from "jose"; + +const secret = process.env.QR_TOKEN_SECRET; + +if (!secret) { + throw new Error("QR_TOKEN_SECRET is not set"); +} + +const SECRET_KEY = new TextEncoder().encode(secret); +const ALG = "HS256"; //encoding algorithm +const TTL = 60*15; //time to live for QR tokens - 15min + +//This is what the QR token will carry +export interface QRTokenPayload { + user_id: string; + event_id: string; + iat?: number; //token creation time + exp?: number; //token expiration time +} + +export interface GenerateQRTokenOptions { + userId: string; + eventId: string; + ttl?: number; +} + +export interface VerifiedQRToken { + userId: string; + eventId: string; + issuedAt: Date; + expiresAt: Date; + raw: JWTPayload & QRTokenPayload; +} + +export async function generateQRToken({ + userId, + eventId, + ttl = TTL, +}: GenerateQRTokenOptions): Promise { + if (!userId?.trim()) throw new Error("userId is required"); //Reject empty or whitespace only user IDs to prevent invalid tokens + if (!eventId?.trim()) throw new Error("eventId is required"); //Reject empty or whitespace only event IDs to prevent invalid tokens + if (ttl <= 0) throw new Error("ttlSeconds must be positive"); //TTL must be valid + + const now = Math.floor(Date.now() / 1000); + + return new SignJWT({ + user_id: userId, + event_id: eventId, + } satisfies QRTokenPayload) + .setProtectedHeader({ alg: ALG }) + .setIssuedAt(now) + .setExpirationTime(now + ttl) + .sign(SECRET_KEY); +} + +// Verify the token signature and decode its payload. +// jwtVerify will throw if the token is invalid, expired, +// or signed with an unexpected algorithm/secret. +export async function verifyQRToken(token: string): Promise { + const { payload } = await jwtVerify(token, SECRET_KEY, { + algorithms: [ALG], + }); + + const { user_id, event_id, iat, exp } = payload; + + if (!user_id) throw new Error("Token missing user_id"); + if (!event_id) throw new Error("Token missing event_id"); + if (!iat) throw new Error("Token missing issuedAt time"); + if (!exp) throw new Error("Token missing expiration time"); + + return { + userId: user_id, + eventId: event_id, + issuedAt: new Date(iat * 1000), + expiresAt: new Date(exp * 1000), + raw: payload, + }; +} \ No newline at end of file From c1512a00b9e17f6e17b7201b8a55bdf31420b0c4 Mon Sep 17 00:00:00 2001 From: dvickster Date: Mon, 20 Jul 2026 17:03:34 -0500 Subject: [PATCH 02/10] needed changes for authentication --- src/app/layout.tsx | 3 +- src/components/onboarding/auth-card.tsx | 219 +++++++++++++++++++----- src/proxy.ts | 34 ++-- 3 files changed, 200 insertions(+), 56 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4809ab4..a75c4b9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -89,5 +89,4 @@ export default function RootLayout({ ) -} - +} \ No newline at end of file diff --git a/src/components/onboarding/auth-card.tsx b/src/components/onboarding/auth-card.tsx index e4c86a3..3f60765 100644 --- a/src/components/onboarding/auth-card.tsx +++ b/src/components/onboarding/auth-card.tsx @@ -1,70 +1,211 @@ "use client"; -import { useState } from "react"; +import { Suspense, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useSignIn, useSignUp } from "@clerk/nextjs"; import { Button } from "@/components/ui/button"; import { Field } from "@/components/ui/field"; import { SegmentedTabs } from "@/components/ui/segmented-tabs"; -/** - * The white auth card from the ONBOARDING frame. - * Fixed 400×500 composition (absolute positions taken verbatim from Figma); - * horizontally fluid via left/right insets so it adapts to narrow screens. - */ -export function AuthCard() { - const [tab, setTab] = useState("Sign up"); +function AuthCardInner() { + const searchParams = useSearchParams(); + const router = useRouter(); + const initialTab = searchParams.get("mode") === "login" ? "Log in" : "Sign up"; + + const [tab, setTab] = useState<"Sign up" | "Log in">(initialTab); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [code, setCode] = useState(""); + const [verifying, setVerifying] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const { signUp } = useSignUp(); + const { signIn } = useSignIn(); + + const goToDashboard = () => router.push("/dashboard"); + + const handleSignUpSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!signUp) return; + setSubmitting(true); + setErrorMessage(null); + + const { error } = await signUp.password({ emailAddress: email, password }); + if (error) { + setErrorMessage(error.longMessage ?? "Something went wrong. Please try again."); + setSubmitting(false); + return; + } + + const { error: codeError } = await signUp.verifications.sendEmailCode(); + if (codeError) { + setErrorMessage("Couldn't send a verification code. Please try again."); + setSubmitting(false); + return; + } + + setVerifying(true); + setSubmitting(false); + }; + + const handleVerifySubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!signUp) return; + setSubmitting(true); + setErrorMessage(null); + + const { error } = await signUp.verifications.verifyEmailCode({ code }); + if (error) { + setErrorMessage("That code didn't work — double-check it and try again."); + setSubmitting(false); + return; + } + + if (signUp.status === "complete") { + await signUp.finalize({ navigate: goToDashboard }); + } else { + setErrorMessage("Couldn't complete sign-up. Please try again."); + setSubmitting(false); + } + }; + + const handleLoginSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!signIn) return; + setSubmitting(true); + setErrorMessage(null); + + const { error } = await signIn.password({ identifier: email, password }); + if (error) { + setErrorMessage("Incorrect email or password."); + setSubmitting(false); + return; + } + + if (signIn.status === "complete") { + await signIn.finalize({ navigate: goToDashboard }); + } else { + // MFA / other session steps aren't wired up yet — fine as long as + // you haven't enabled a second factor in the Clerk Dashboard + setErrorMessage("Additional verification is required, which isn't supported yet."); + setSubmitting(false); + } + }; + + if (verifying) { + return ( +
+
+

+ Check your email +

+
+ +
+
+ setCode(e.target.value)} + /> +
+ + {errorMessage && ( +

+ {errorMessage} +

+ )} + +
+ +
+
+ +
+

+ we sent a 6-digit code to {email} +

+
+
+ ); + } + + const isSignUp = tab === "Sign up"; return (
- {/* Sign up / Log in switcher */}
setTab(v as "Sign up" | "Log in")} />
- {/* Heading */}

- Create your account + {isSignUp ? "Create your account" : "Welcome back"}

- {/* Email */} -
- -
+
+
+ setEmail(e.target.value)} + /> +
- {/* Password */} -
- -
+
+ setPassword(e.target.value)} + /> +
- {/* Submit */} -
- -
+ {errorMessage && ( +

+ {errorMessage} +

+ )} + +
+ +
+
- {/* Helper */}

- we'll send a link to validate your email + {isSignUp ? "we'll email you a 6-digit code to verify your account" : "welcome back to AIS"}

); } + +export function AuthCard() { + // useSearchParams() requires a Suspense boundary + return ( + }> + + + ); +} \ No newline at end of file diff --git a/src/proxy.ts b/src/proxy.ts index f95044c..a82bc86 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,22 +1,29 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; import { NextResponse } from "next/server"; +const isPublicRoute = createRouteMatcher([ + '/', + '/events(.*)', + '/applications(.*)', + '/onboarding(.*)', + '/api/webhooks(.*)', +]); + const isAdminRoute = createRouteMatcher(["/admin(.*)", "/api/admin(.*)"]); +const allowedAdminRoles = ['REVIEWER', 'ORGANIZER', 'SUPER_ADMIN']; export default clerkMiddleware(async (auth, req) => { - if (isAdminRoute(req)) { - const session = await auth(); - - // Fallback if not logged in - if (!session.userId) { - return NextResponse.redirect(new URL('/login', req.url)); - } + const session = await auth(); - // Read custom role metadata from Clerk session + // Everything except the public routes above requires sign-in + if (!isPublicRoute(req) && !session.userId) { + return NextResponse.redirect(new URL('/sign-in', req.url)); + } + + // Extra role gate on top of that, for admin routes only + if (isAdminRoute(req)) { const role = session.sessionClaims?.metadata?.role; - const allowedRoles = ['REVIEWER', 'ORGANIZER', 'SUPER_ADMIN']; - - if (!role || !allowedRoles.includes(role as string)) { + if (!role || !allowedAdminRoles.includes(role as string)) { return NextResponse.redirect(new URL('/dashboard', req.url)); } } @@ -24,11 +31,8 @@ export default clerkMiddleware(async (auth, req) => { export const config = { matcher: [ - // Skip Next.js internals and all static files, unless found in search params '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', - // Always run for Clerk's auto-proxy path - '/__clerk/:path*', - // Always run for API routes + '/__clerk/(.*)', '/(api|trpc)(.*)', ], }; \ No newline at end of file From 2732b65654d6c911bbc7f8248b99193adddd38a7 Mon Sep 17 00:00:00 2001 From: dvickster Date: Mon, 20 Jul 2026 18:50:34 -0500 Subject: [PATCH 03/10] Auth working v1 --- src/components/onboarding/auth-card.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/onboarding/auth-card.tsx b/src/components/onboarding/auth-card.tsx index 3f60765..7f13582 100644 --- a/src/components/onboarding/auth-card.tsx +++ b/src/components/onboarding/auth-card.tsx @@ -33,6 +33,7 @@ function AuthCardInner() { const { error } = await signUp.password({ emailAddress: email, password }); if (error) { + console.error("signUp.password error:", error); setErrorMessage(error.longMessage ?? "Something went wrong. Please try again."); setSubmitting(false); return; @@ -40,6 +41,7 @@ function AuthCardInner() { const { error: codeError } = await signUp.verifications.sendEmailCode(); if (codeError) { + console.error("signUp.password error:", error); setErrorMessage("Couldn't send a verification code. Please try again."); setSubmitting(false); return; @@ -57,6 +59,7 @@ function AuthCardInner() { const { error } = await signUp.verifications.verifyEmailCode({ code }); if (error) { + console.error("signUp.password error:", error); // ADD THIS setErrorMessage("That code didn't work — double-check it and try again."); setSubmitting(false); return; @@ -65,6 +68,7 @@ function AuthCardInner() { if (signUp.status === "complete") { await signUp.finalize({ navigate: goToDashboard }); } else { + console.error("signUp.password error:", error); // ADD THIS setErrorMessage("Couldn't complete sign-up. Please try again."); setSubmitting(false); } @@ -78,6 +82,7 @@ function AuthCardInner() { const { error } = await signIn.password({ identifier: email, password }); if (error) { + console.error("signUp.password error:", error); // ADD THIS setErrorMessage("Incorrect email or password."); setSubmitting(false); return; @@ -185,6 +190,8 @@ function AuthCardInner() {

)} +
+
+
+ + {message ?

{message}

: null} +
); diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..c9b98fa --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,15 @@ +import { auth } from "@clerk/nextjs/server"; +import { prisma } from "@/lib/prisma"; + +export async function getAuthenticatedUser() { + const { userId } = await auth(); + + if (!userId) { + return null; + } + + return prisma.user.findUnique({ + where: { clerkId: userId }, + include: { profile: true }, + }); +} diff --git a/src/lib/data.ts b/src/lib/data.ts index f14b1fc..508719d 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -540,23 +540,27 @@ export const browseEvents: EventGridItem[] = [ meta: "ECSW 2.412 · 08/27/26 · 7:00 PM", description: "Learn about & join AIS", tags: [tag("food"), tag("social")], + eventId: "mock-event-upcoming", }, { title: "Intro to PyTorch", meta: "ECSW 1.355 · 09/03/26 · 6:00 PM", description: "Hands-on ML workshop", tags: [tag("workshop"), tag("learn")], + eventId: "mock-event-past", }, { title: "Sponsor Mixer", meta: "TBD · 09/10/26 · 7:00 PM", description: "Network with industry", tags: [tag("networking"), tag("industry")], + eventId: "mock-event-past", }, { title: "AIM Social", meta: "ECSW 1.315 · 09/17/26 · 6:00 PM", description: "Meet your AIM cohort", tags: [tag("food"), tag("social")], + eventId: "mock-event-upcoming", }, ]; From 3244d3a9a26815f72de0276a1c269c204516fc0a Mon Sep 17 00:00:00 2001 From: Kriti Goomer Date: Thu, 23 Jul 2026 17:35:44 -0500 Subject: [PATCH 05/10] auth + onboarding --- package-lock.json | 133 ++++++++++++------ package.json | 1 + src/components/apply/open-app-row.tsx | 15 ++ src/components/apply/program-card.tsx | 16 ++- src/components/dashboard/recommended-card.tsx | 16 ++- src/components/events/event-grid-card.tsx | 9 ++ src/components/events/featured-event-card.tsx | 16 ++- src/components/navbar.tsx | 12 +- 8 files changed, 173 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6dfe80..9d55b9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@prisma/adapter-neon": "^7.8.0", "@prisma/client": "^7.8.0", "@sentry/nextjs": "^10.58.0", + "jose": "^6.2.4", "next": "16.2.9", "react": "19.2.4", "react-dom": "19.2.4", @@ -522,7 +523,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -803,8 +803,7 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -829,6 +828,28 @@ "@electric-sql/pglite": "0.4.1" } }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -1563,6 +1584,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -1828,7 +1850,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -1850,7 +1871,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -1866,7 +1886,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", @@ -1900,7 +1919,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz", "integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", @@ -1918,7 +1936,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" } @@ -2351,7 +2368,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3625,7 +3641,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3634,9 +3649,8 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3696,7 +3710,6 @@ "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.61.0", "@typescript-eslint/types": "8.61.0", @@ -4299,6 +4312,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -4308,25 +4322,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -4337,13 +4355,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -4356,6 +4376,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -4365,6 +4386,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -4373,13 +4395,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -4396,6 +4420,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -4409,6 +4434,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -4421,6 +4447,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -4435,6 +4462,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -4444,20 +4472,21 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4479,6 +4508,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" }, @@ -4530,6 +4560,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -4547,6 +4578,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4562,7 +4594,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ansi-styles": { "version": "4.3.0", @@ -4907,7 +4940,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4926,7 +4958,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/c12": { "version": "3.3.4", @@ -5101,6 +5134,7 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -5141,7 +5175,8 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/commondir": { "version": "1.0.1", @@ -5586,7 +5621,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.2", @@ -5676,7 +5712,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5862,7 +5897,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6087,6 +6121,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -6758,7 +6793,6 @@ "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -7334,6 +7368,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -7348,6 +7383,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7368,6 +7404,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.4.tgz", + "integrity": "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-cookie": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz", @@ -7777,6 +7822,7 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" }, @@ -7875,7 +7921,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge2": { "version": "1.4.1", @@ -7906,6 +7953,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8033,14 +8081,14 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/next": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz", "integrity": "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "16.2.9", "@swc/helpers": "0.5.15", @@ -8564,7 +8612,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -8702,7 +8749,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8712,7 +8758,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8887,7 +8932,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -9024,6 +9068,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -9060,6 +9105,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -9071,7 +9117,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "6.3.1", @@ -9318,6 +9365,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9336,6 +9384,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -9636,6 +9685,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -9654,6 +9704,7 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -9750,7 +9801,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9928,7 +9978,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10084,6 +10133,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2" }, @@ -10102,6 +10152,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz", "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -10148,6 +10199,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -10157,6 +10209,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -10170,6 +10223,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -10369,7 +10423,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 265d228..5a1a20a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@prisma/adapter-neon": "^7.8.0", "@prisma/client": "^7.8.0", "@sentry/nextjs": "^10.58.0", + "jose": "^6.2.4", "next": "16.2.9", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/src/components/apply/open-app-row.tsx b/src/components/apply/open-app-row.tsx index 60e3865..dca6d47 100644 --- a/src/components/apply/open-app-row.tsx +++ b/src/components/apply/open-app-row.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useAuth } from "@clerk/nextjs"; import { Button } from "@/components/ui/button"; type RowAction = { @@ -32,6 +36,15 @@ export function OpenAppRow({ dim = false, actions, }: OpenApp) { + const router = useRouter(); + const { isSignedIn } = useAuth(); + + const handleActionClick = (label: string) => { + if ((label === "Apply" || label === "Remind me") && !isSignedIn) { + router.push("/onboarding?mode=login"); + } + }; + return (
handleActionClick(action.label)} > {action.label} diff --git a/src/components/apply/program-card.tsx b/src/components/apply/program-card.tsx index 76cf8cb..71fbb20 100644 --- a/src/components/apply/program-card.tsx +++ b/src/components/apply/program-card.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useAuth } from "@clerk/nextjs"; import { Button } from "@/components/ui/button"; import { Tag } from "@/components/ui/tag"; @@ -31,6 +35,16 @@ export function ProgramCard({ tags, cta, }: Program) { + const router = useRouter(); + const { isSignedIn } = useAuth(); + + const handleApply = () => { + if (!isSignedIn) { + router.push("/onboarding?mode=login"); + return; + } + }; + const iconChip = ( -
diff --git a/src/components/dashboard/recommended-card.tsx b/src/components/dashboard/recommended-card.tsx index 2e40a3c..4bec0ea 100644 --- a/src/components/dashboard/recommended-card.tsx +++ b/src/components/dashboard/recommended-card.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useAuth } from "@clerk/nextjs"; import { Card } from "@/components/ui/card"; import { SectionHeader } from "@/components/ui/section-header"; import { Button } from "@/components/ui/button"; @@ -10,6 +14,16 @@ export type RecommendedItem = { }; function RecommendedRow({ item }: { item: RecommendedItem }) { + const router = useRouter(); + const { isSignedIn } = useAuth(); + + function handleRsvp() { + if (!isSignedIn) { + router.push("/onboarding?mode=login"); + return; + } + } + return (
@@ -25,7 +39,7 @@ function RecommendedRow({ item }: { item: RecommendedItem }) {
- diff --git a/src/components/events/event-grid-card.tsx b/src/components/events/event-grid-card.tsx index 53a2ffc..2427520 100644 --- a/src/components/events/event-grid-card.tsx +++ b/src/components/events/event-grid-card.tsx @@ -1,5 +1,7 @@ "use client"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@clerk/nextjs"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Tag } from "@/components/ui/tag"; @@ -19,10 +21,17 @@ export type EventGridItem = { * category tags, and an RSVP button. */ export function EventGridCard({ title, meta, description, tags, eventId }: EventGridItem) { + const router = useRouter(); + const { isSignedIn } = useAuth(); const [isSubmitting, setIsSubmitting] = useState(false); const [message, setMessage] = useState(null); async function handleRsvp() { + if (!isSignedIn) { + router.push("/onboarding?mode=login"); + return; + } + setIsSubmitting(true); setMessage(null); diff --git a/src/components/events/featured-event-card.tsx b/src/components/events/featured-event-card.tsx index d4826ed..62a2c83 100644 --- a/src/components/events/featured-event-card.tsx +++ b/src/components/events/featured-event-card.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useAuth } from "@clerk/nextjs"; import { Button } from "@/components/ui/button"; import { Tag } from "@/components/ui/tag"; import type { TagData } from "@/components/dashboard/up-next-card"; @@ -19,6 +23,16 @@ export function FeaturedEventCard({ description, tags, }: FeaturedEvent) { + const router = useRouter(); + const { isSignedIn } = useAuth(); + + function handleRsvpNow() { + if (!isSignedIn) { + router.push("/onboarding?mode=login"); + return; + } + } + return (
@@ -39,7 +53,7 @@ export function FeaturedEventCard({ ))}
-
diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 668ec64..091c33e 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -1,5 +1,7 @@ +"use client"; + import Link from "next/link"; -import { Show, UserButton } from "@clerk/nextjs"; +import { Show, UserButton, useAuth } from "@clerk/nextjs"; import { cn } from "@/lib/utils"; const NAV_ITEMS = ["Events", "Apply", "Dashboard"] as const; @@ -16,6 +18,8 @@ type NavbarProps = { }; export function Navbar({ active = "Dashboard" }: NavbarProps) { + const { isSignedIn } = useAuth(); + return (