From f858b949c4eda7cfbaafabe7ce0ead3c38e09c62 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 17:38:12 -0500 Subject: [PATCH 01/13] updates to use bucket-env --- apps/infrastructure-migrator/driver.ts | 2 +- apps/web/src/app/api/upload/pfp/route.ts | 5 ++++- apps/web/src/app/api/upload/resume/register/route.ts | 5 ++++- apps/web/src/app/api/upload/resume/view/route.ts | 2 +- apps/web/src/app/register/page.tsx | 4 ++-- apps/web/src/lib/utils/server/file-upload.ts | 2 +- packages/config/hackkit.config.ts | 1 - 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/infrastructure-migrator/driver.ts b/apps/infrastructure-migrator/driver.ts index ffbab1e7..012bdc1e 100644 --- a/apps/infrastructure-migrator/driver.ts +++ b/apps/infrastructure-migrator/driver.ts @@ -240,7 +240,7 @@ async function migratePostgresSqLite() { const cmd = new PutObjectCommand({ Key: key, - Bucket: staticUploads.bucketName, + Bucket: process.env.R2_BUCKET_NAME, ContentType: "application/pdf", ///@ts-expect-error Body: buffer, diff --git a/apps/web/src/app/api/upload/pfp/route.ts b/apps/web/src/app/api/upload/pfp/route.ts index 194633a3..187c34ae 100644 --- a/apps/web/src/app/api/upload/pfp/route.ts +++ b/apps/web/src/app/api/upload/pfp/route.ts @@ -26,7 +26,10 @@ export async function POST(request: Request): Promise { const randomSeq = crypto.randomUUID(); const [fileName, extension] = body.fileName.split("."); const key = `${body.location}/${fileName}-${randomSeq}.${extension}`; - const url = await getPresignedUploadUrl(staticUploads.bucketName, key); + const url = await getPresignedUploadUrl( + process.env.R2_BUCKET_NAME!, + key, + ); return NextResponse.json({ url, key }); } catch (error) { diff --git a/apps/web/src/app/api/upload/resume/register/route.ts b/apps/web/src/app/api/upload/resume/register/route.ts index 97957372..b05e53e6 100644 --- a/apps/web/src/app/api/upload/resume/register/route.ts +++ b/apps/web/src/app/api/upload/resume/register/route.ts @@ -25,7 +25,10 @@ export async function POST(request: Request): Promise { const randomSeq = crypto.randomUUID(); const [fileName, extension] = body.fileName.split("."); const key = `${body.location}/${fileName}-${randomSeq}.${extension}`; - const url = await getPresignedUploadUrl(staticUploads.bucketName, key); + const url = await getPresignedUploadUrl( + process.env.R2_BUCKET_NAME!, + key, + ); return NextResponse.json({ url, key }); } catch (error) { diff --git a/apps/web/src/app/api/upload/resume/view/route.ts b/apps/web/src/app/api/upload/resume/view/route.ts index 26597166..5e54bf21 100644 --- a/apps/web/src/app/api/upload/resume/view/route.ts +++ b/apps/web/src/app/api/upload/resume/view/route.ts @@ -25,7 +25,7 @@ export async function GET(request: Request) { // Presign the url and return redirect to it. const presignedViewingUrl = await getPresignedViewingUrl( - staticUploads.bucketName, + process.env.R2_BUCKET_NAME!, decodedKey, ); diff --git a/apps/web/src/app/register/page.tsx b/apps/web/src/app/register/page.tsx index 686b872b..d26fb9ae 100644 --- a/apps/web/src/app/register/page.tsx +++ b/apps/web/src/app/register/page.tsx @@ -27,7 +27,7 @@ export default async function Page() { "config:registration:secretRegistrationEnabled", ); - if (parseRedisBoolean(defaultRegistrationEnabled, true) === true) { + // if (parseRedisBoolean(defaultRegistrationEnabled, true) === true) { return ( <> @@ -57,7 +57,7 @@ export default async function Page() { ); - } + // } return (
diff --git a/apps/web/src/lib/utils/server/file-upload.ts b/apps/web/src/lib/utils/server/file-upload.ts index aa18e5d6..ac1c39be 100644 --- a/apps/web/src/lib/utils/server/file-upload.ts +++ b/apps/web/src/lib/utils/server/file-upload.ts @@ -7,7 +7,7 @@ export async function del(url: string): Promise { const key = url.split("=")[1]; const cmd = new DeleteObjectCommand({ - Bucket: staticUploads.bucketName, + Bucket: process.env.R2_BUCKET_NAME, Key: key, }); diff --git a/packages/config/hackkit.config.ts b/packages/config/hackkit.config.ts index e3a37bd8..47182221 100644 --- a/packages/config/hackkit.config.ts +++ b/packages/config/hackkit.config.ts @@ -986,7 +986,6 @@ const c = { } as const; const staticUploads = { - bucketName: "acm-userdata", bucketHost: "/api/upload/resume/view", bucketResumeBaseUploadUrl: `${c.hackathonName}/${c.itteration}/resumes`, } as const; From 7cd32edba4ecfe4d6649aeb79031b3bb9a3495ca Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 18:03:25 -0500 Subject: [PATCH 02/13] fix volunteer permissions --- apps/web/src/actions/admin/user-actions.ts | 2 +- apps/web/src/app/admin/layout.tsx | 7 ++++++- apps/web/src/app/admin/page.tsx | 2 +- apps/web/src/app/register/page.tsx | 4 ++-- apps/web/src/lib/safe-action.ts | 2 +- apps/web/src/lib/utils/server/admin.ts | 2 +- packages/config/hackkit.config.ts | 4 ++-- 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/web/src/actions/admin/user-actions.ts b/apps/web/src/actions/admin/user-actions.ts index 41b46656..89be68af 100644 --- a/apps/web/src/actions/admin/user-actions.ts +++ b/apps/web/src/actions/admin/user-actions.ts @@ -23,7 +23,7 @@ export const updateRole = adminAction }) => { if ( user.role !== "super_admin" && - (roleToSet === "super_admin" || roleToSet === "admin") + (roleToSet === "super_admin" || roleToSet === "admin" || roleToSet === "volunteer") ) { returnValidationErrors(z.null(), { _errors: ["You are not allowed to do this!"], diff --git a/apps/web/src/app/admin/layout.tsx b/apps/web/src/app/admin/layout.tsx index 78698ce1..33375058 100644 --- a/apps/web/src/app/admin/layout.tsx +++ b/apps/web/src/app/admin/layout.tsx @@ -24,7 +24,12 @@ export default async function AdminLayout({ children }: AdminLayoutProps) { const user = await getUser(userId); - if (!user || (user.role !== "admin" && user.role !== "super_admin")) { + if ( + !user || + (user.role !== "admin" && + user.role !== "super_admin" && + user.role !== "volunteer") + ) { console.log("Denying admin access to user", user); return ( @@ -57,7 +57,7 @@ export default async function Page() {
); - // } + } return (
diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index 8755206f..8beedfec 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -23,7 +23,7 @@ export const authenticatedAction = publicAction.use( export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { const user = await getUser(ctx.userId); - if (!user || (user.role !== "admin" && user.role !== "super_admin")) { + if (!user || (user.role !== "admin" && user.role !== "super_admin" && user.role !== "volunteer")) { returnValidationErrors(z.null(), { _errors: ["Unauthorized (Not Admin)"], }); diff --git a/apps/web/src/lib/utils/server/admin.ts b/apps/web/src/lib/utils/server/admin.ts index 440a6c38..bd8032cf 100644 --- a/apps/web/src/lib/utils/server/admin.ts +++ b/apps/web/src/lib/utils/server/admin.ts @@ -1,5 +1,5 @@ import type { User } from "db/types"; export function isUserAdmin(user: User) { - return user.role === "admin" || user.role === "super_admin"; + return user.role === "admin" || user.role === "super_admin" || user.role === "volunteer"; } diff --git a/packages/config/hackkit.config.ts b/packages/config/hackkit.config.ts index 47182221..6dd26376 100644 --- a/packages/config/hackkit.config.ts +++ b/packages/config/hackkit.config.ts @@ -1020,8 +1020,8 @@ const publicRoutes = [ /^\/user\//, "/404", "/bugreport", - "/sign-in", - "/sign-up", + /^\/sign-in(\/.*)?$/, + /^\/sign-up(\/.*)?$/, ]; export default c; From a101fd850095c36497142dfab5cdfb1d0495cee7 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 18:11:30 -0500 Subject: [PATCH 03/13] fix bug with local storage write --- apps/web/src/components/registration/RegisterForm.tsx | 9 +++++++++ apps/web/src/lib/constants/index.ts | 2 +- apps/web/src/validators/shared/registration.ts | 3 --- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/registration/RegisterForm.tsx b/apps/web/src/components/registration/RegisterForm.tsx index 8c37967f..2a177a42 100644 --- a/apps/web/src/components/registration/RegisterForm.tsx +++ b/apps/web/src/components/registration/RegisterForm.tsx @@ -134,6 +134,7 @@ export default function RegisterForm({ const hackerFormData = localStorage.getItem( HACKER_REGISTRATION_STORAGE_KEY, ); + console.log(hackerFormData) if (hackerFormData) { try { const parsed = JSON.parse(hackerFormData); @@ -185,6 +186,14 @@ export default function RegisterForm({ ); } } + // else{ + // localStorage.setItem( + // HACKER_REGISTRATION_STORAGE_KEY, + // JSON.stringify({ + // ...form.getValues(), + // }), + // ); + // } }, []); // seperate useffect for getting the resume file diff --git a/apps/web/src/lib/constants/index.ts b/apps/web/src/lib/constants/index.ts index 4a076d57..2de61d33 100644 --- a/apps/web/src/lib/constants/index.ts +++ b/apps/web/src/lib/constants/index.ts @@ -7,7 +7,7 @@ export const UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE = "23505"; export const UNIQUE_KEY_MAPPER_DEFAULT_KEY = "default" as keyof typeof c.db.uniqueKeyMapper; export const PAYLOAD_TOO_LARGE_CODE = 413; -export const HACKER_REGISTRATION_STORAGE_KEY = "hackerRegistrationData"; +export const HACKER_REGISTRATION_STORAGE_KEY = `${c.hackathonName}_${c.itteration}_hackerRegistrationData`; export const HACKER_REGISTRATION_RESUME_STORAGE_KEY = "hackerRegistrationResume"; export const NOT_LOCAL_SCHOOL = "NOT_LOCAL_SCHOOL"; diff --git a/apps/web/src/validators/shared/registration.ts b/apps/web/src/validators/shared/registration.ts index 83f52c9f..fde03f7f 100644 --- a/apps/web/src/validators/shared/registration.ts +++ b/apps/web/src/validators/shared/registration.ts @@ -204,9 +204,6 @@ export const hackerRegistrationValidatorLocalStorage = text: z.string().min(1).max(50), }), ) - .min(1, { - message: "You must have at least one skill", - }) .max(c.registration.maxNumberOfSkills, { message: `You cannot have more than ${c.registration.maxNumberOfSkills} skills`, }), From 7865ac833ef36a2685d5e40b4fa98f7423c1e884 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 18:28:20 -0500 Subject: [PATCH 04/13] debounce registration write --- .../components/registration/RegisterForm.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/web/src/components/registration/RegisterForm.tsx b/apps/web/src/components/registration/RegisterForm.tsx index 2a177a42..b7df5fc2 100644 --- a/apps/web/src/components/registration/RegisterForm.tsx +++ b/apps/web/src/components/registration/RegisterForm.tsx @@ -81,6 +81,7 @@ import { encodeFileAsBase64, decodeBase64AsFile, } from "@/lib/utils/shared/files"; +import { useDebouncedCallback } from "use-debounce"; export default function RegisterForm({ defaultEmail, @@ -186,14 +187,6 @@ export default function RegisterForm({ ); } } - // else{ - // localStorage.setItem( - // HACKER_REGISTRATION_STORAGE_KEY, - // JSON.stringify({ - // ...form.getValues(), - // }), - // ); - // } }, []); // seperate useffect for getting the resume file @@ -224,16 +217,24 @@ export default function RegisterForm({ } }, []); - // might be good to debounce later on + const debouncedLocalStorageWrite = useDebouncedCallback( + // function + () => { + localStorage.setItem( + HACKER_REGISTRATION_STORAGE_KEY, + JSON.stringify({ + ...form.getValues(), + }), + ); + }, + 1000, + ); + form.watch(() => { - localStorage.setItem( - HACKER_REGISTRATION_STORAGE_KEY, - JSON.stringify({ - ...form.getValues(), - }), - ); + debouncedLocalStorageWrite(); }); + // use action logic const { execute: runRegisterUser, reset: resetRegisterUser } = useAction( registerHacker, From 3828e4043fc867ce764084a0dcacb544b367925f Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 18:35:16 -0500 Subject: [PATCH 05/13] remove silly alert --- apps/web/src/components/registration/RegisterForm.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/web/src/components/registration/RegisterForm.tsx b/apps/web/src/components/registration/RegisterForm.tsx index b7df5fc2..272a8236 100644 --- a/apps/web/src/components/registration/RegisterForm.tsx +++ b/apps/web/src/components/registration/RegisterForm.tsx @@ -317,8 +317,6 @@ export default function RegisterForm({ }, ); - alert(uploadedFileUrl); - resume = uploadedFileUrl; } runRegisterUser({ ...data, resume }); From 891aac42b833aedd55258068b324ca911024c5cf Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 27 Jun 2025 18:35:30 -0500 Subject: [PATCH 06/13] formatter --- apps/web/src/actions/admin/user-actions.ts | 4 +++- apps/web/src/app/admin/page.tsx | 4 +++- apps/web/src/components/registration/RegisterForm.tsx | 3 +-- apps/web/src/lib/safe-action.ts | 7 ++++++- apps/web/src/lib/utils/server/admin.ts | 6 +++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/web/src/actions/admin/user-actions.ts b/apps/web/src/actions/admin/user-actions.ts index 89be68af..919a9320 100644 --- a/apps/web/src/actions/admin/user-actions.ts +++ b/apps/web/src/actions/admin/user-actions.ts @@ -23,7 +23,9 @@ export const updateRole = adminAction }) => { if ( user.role !== "super_admin" && - (roleToSet === "super_admin" || roleToSet === "admin" || roleToSet === "volunteer") + (roleToSet === "super_admin" || + roleToSet === "admin" || + roleToSet === "volunteer") ) { returnValidationErrors(z.null(), { _errors: ["You are not allowed to do this!"], diff --git a/apps/web/src/app/admin/page.tsx b/apps/web/src/app/admin/page.tsx index 06acd700..1914bc73 100644 --- a/apps/web/src/app/admin/page.tsx +++ b/apps/web/src/app/admin/page.tsx @@ -19,7 +19,9 @@ export default async function Page() { const adminUser = await getUser(userId); if ( !adminUser || - (adminUser.role !== "admin" && adminUser.role !== "super_admin" && adminUser.role !== "volunteer") + (adminUser.role !== "admin" && + adminUser.role !== "super_admin" && + adminUser.role !== "volunteer") ) { return notFound(); } diff --git a/apps/web/src/components/registration/RegisterForm.tsx b/apps/web/src/components/registration/RegisterForm.tsx index 272a8236..85abd141 100644 --- a/apps/web/src/components/registration/RegisterForm.tsx +++ b/apps/web/src/components/registration/RegisterForm.tsx @@ -135,7 +135,7 @@ export default function RegisterForm({ const hackerFormData = localStorage.getItem( HACKER_REGISTRATION_STORAGE_KEY, ); - console.log(hackerFormData) + console.log(hackerFormData); if (hackerFormData) { try { const parsed = JSON.parse(hackerFormData); @@ -234,7 +234,6 @@ export default function RegisterForm({ debouncedLocalStorageWrite(); }); - // use action logic const { execute: runRegisterUser, reset: resetRegisterUser } = useAction( registerHacker, diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index 8beedfec..7ee73e3b 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -23,7 +23,12 @@ export const authenticatedAction = publicAction.use( export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { const user = await getUser(ctx.userId); - if (!user || (user.role !== "admin" && user.role !== "super_admin" && user.role !== "volunteer")) { + if ( + !user || + (user.role !== "admin" && + user.role !== "super_admin" && + user.role !== "volunteer") + ) { returnValidationErrors(z.null(), { _errors: ["Unauthorized (Not Admin)"], }); diff --git a/apps/web/src/lib/utils/server/admin.ts b/apps/web/src/lib/utils/server/admin.ts index bd8032cf..b62bf2a1 100644 --- a/apps/web/src/lib/utils/server/admin.ts +++ b/apps/web/src/lib/utils/server/admin.ts @@ -1,5 +1,9 @@ import type { User } from "db/types"; export function isUserAdmin(user: User) { - return user.role === "admin" || user.role === "super_admin" || user.role === "volunteer"; + return ( + user.role === "admin" || + user.role === "super_admin" || + user.role === "volunteer" + ); } From 4b4e46d99f5af2742286185de697578789a81d71 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:36:45 -0600 Subject: [PATCH 07/13] adds voluteer action --- .../src/actions/admin/scanner-admin-actions.ts | 8 ++++---- apps/web/src/lib/safe-action.ts | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 60f1aeed..3447db0d 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -1,12 +1,12 @@ "use server"; -import { adminAction } from "@/lib/safe-action"; +import { volunteerAction } from "@/lib/safe-action"; import { z } from "zod"; import { db, sql } from "db"; import { scans, userCommonData } from "db/schema"; import { eq, and } from "db/drizzle"; -export const createScan = adminAction +export const createScan = volunteerAction .schema( z.object({ eventID: z.number(), @@ -49,7 +49,7 @@ export const createScan = adminAction }, ); -export const getScan = adminAction +export const getScan = volunteerAction .schema(z.object({ eventID: z.number(), userID: z.string() })) .action( async ({ @@ -77,7 +77,7 @@ const checkInUserSchema = z.object({ }, "QR Code has expired. Please tell user refresh the QR Code"), }); -export const checkInUserToHackathon = adminAction +export const checkInUserToHackathon = volunteerAction .schema(checkInUserSchema) .action(async ({ parsedInput: { userID } }) => { // Set checkinTimestamp diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index 7ee73e3b..587fd51d 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -5,6 +5,7 @@ import { import { auth } from "@clerk/nextjs/server"; import { getUser } from "db/functions"; import { z } from "zod"; +import { isUserAdmin } from "./utils/server/admin"; export const publicAction = createSafeActionClient(); @@ -21,13 +22,25 @@ export const authenticatedAction = publicAction.use( }, ); +export const volunteerAction = authenticatedAction.use(async ({ next, ctx }) => { + const user = await getUser(ctx.userId); + if ( + !user || + !["admin", "super_admin", "volunteer"].includes(user.role) + ) { + returnValidationErrors(z.null(), { + _errors: ["Unauthorized (Not Admin)"], + }); + } + return next({ ctx: { user, ...ctx } }); +}); + export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { const user = await getUser(ctx.userId); if ( !user || (user.role !== "admin" && - user.role !== "super_admin" && - user.role !== "volunteer") + user.role !== "super_admin") ) { returnValidationErrors(z.null(), { _errors: ["Unauthorized (Not Admin)"], From dbb2bc7427dd5c75819eb0916f06ae537518cfbf Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:36:50 -0600 Subject: [PATCH 08/13] removes points --- packages/config/hackkit.config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/config/hackkit.config.ts b/packages/config/hackkit.config.ts index 6dd26376..ff57e404 100644 --- a/packages/config/hackkit.config.ts +++ b/packages/config/hackkit.config.ts @@ -912,11 +912,10 @@ const c = { Overview: "/admin", Users: "/admin/users", Events: "/admin/events", - Points: "/admin/points", + // Points: "/admin/points", -- commented out until implemented "Hackathon Check-in": "/admin/check-in", Toggles: "/admin/toggles", }, - // TODO: Can remove days? Pretty sure they're dynamic now. }, eventTypes: { Meal: "#FFC107", From 2023f55f502a99717fbaa59850f468b567fb4584 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:37:07 -0600 Subject: [PATCH 09/13] updates ui for volunteers --- apps/web/src/app/admin/layout.tsx | 9 ++++++--- apps/web/src/app/admin/users/page.tsx | 11 +++++++++++ apps/web/src/lib/utils/server/admin.ts | 4 +--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/admin/layout.tsx b/apps/web/src/app/admin/layout.tsx index 33375058..78f1889b 100644 --- a/apps/web/src/app/admin/layout.tsx +++ b/apps/web/src/app/admin/layout.tsx @@ -85,9 +85,12 @@ export default async function AdminLayout({ children }: AdminLayoutProps) {
- {Object.entries(c.dashPaths.admin).map(([name, path]) => ( - - ))} + {Object.entries(c.dashPaths.admin).map(([name, path]) => + ["Users","Toggles"].includes(name) && + user.role === "volunteer" ? null : ( + + ), + )}
Loading...

}>{children}
diff --git a/apps/web/src/app/admin/users/page.tsx b/apps/web/src/app/admin/users/page.tsx index a800519d..9cde884a 100644 --- a/apps/web/src/app/admin/users/page.tsx +++ b/apps/web/src/app/admin/users/page.tsx @@ -5,9 +5,20 @@ import { Button } from "@/components/shadcn/ui/button"; import { FolderInput } from "lucide-react"; import { getAllUsers } from "db/functions"; import { userCommonData } from "db/schema"; +import { getUser } from "db/functions"; +import { auth } from "@clerk/nextjs/server"; +import { notFound } from "next/navigation"; +import { isUserAdmin } from "@/lib/utils/server/admin"; // This begs a question where we might want to have an option later on to sort by the role as we might want different things export default async function Page() { + const { userId } = await auth(); + + if (!userId) return notFound(); + + const admin = await getUser(userId); + if (!admin || !isUserAdmin(admin)) return notFound(); + const userData = await getAllUsers(); return ( diff --git a/apps/web/src/lib/utils/server/admin.ts b/apps/web/src/lib/utils/server/admin.ts index b62bf2a1..cd9b83db 100644 --- a/apps/web/src/lib/utils/server/admin.ts +++ b/apps/web/src/lib/utils/server/admin.ts @@ -2,8 +2,6 @@ import type { User } from "db/types"; export function isUserAdmin(user: User) { return ( - user.role === "admin" || - user.role === "super_admin" || - user.role === "volunteer" + ["admin", "super_admin",].includes(user.role) ); } From 9bc5007e60633806cb235f87ea32b318c5aa3b83 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:37:45 -0600 Subject: [PATCH 10/13] removes super admin actions --- apps/web/src/actions/admin/event-actions.ts | 2 +- apps/web/src/lib/safe-action.ts | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/web/src/actions/admin/event-actions.ts b/apps/web/src/actions/admin/event-actions.ts index 34f9dbf6..24ee4b71 100644 --- a/apps/web/src/actions/admin/event-actions.ts +++ b/apps/web/src/actions/admin/event-actions.ts @@ -1,6 +1,6 @@ "use server"; -import { adminAction, superAdminAction } from "@/lib/safe-action"; +import { adminAction } from "@/lib/safe-action"; import { newEventFormSchema as editEventFormSchema } from "@/validators/event"; import { editEvent as modifyEvent } from "db/functions"; import { deleteEvent as removeEvent } from "db/functions"; diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index 587fd51d..aa4c0532 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -49,14 +49,3 @@ export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { return next({ ctx: { user, ...ctx } }); }); -export const superAdminAction = authenticatedAction.use( - async ({ next, ctx }) => { - const user = await getUser(ctx.userId); - if (!user || user.role !== "super_admin") { - returnValidationErrors(z.null(), { - _errors: ["Unauthorized (Not Super Admin)"], - }); - } - return next({ ctx: { user, ...ctx } }); - }, -); From 68229736b6456b9fef56b366731c887c07c61166 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:55:02 -0600 Subject: [PATCH 11/13] updates event ui for volunteers --- apps/web/src/app/admin/events/page.tsx | 17 +++++- .../components/events/shared/EventColumns.tsx | 57 ++++++++++++------- apps/web/src/lib/safe-action.ts | 3 +- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/apps/web/src/app/admin/events/page.tsx b/apps/web/src/app/admin/events/page.tsx index f95b1612..2c1eeb12 100644 --- a/apps/web/src/app/admin/events/page.tsx +++ b/apps/web/src/app/admin/events/page.tsx @@ -7,6 +7,8 @@ import { PlusCircle } from "lucide-react"; import Link from "next/link"; import { getAllEvents, getUser } from "db/functions"; import { auth } from "@clerk/nextjs/server"; +import FullScreenMessage from "@/components/shared/FullScreenMessage"; +import { isUserAdmin } from "@/lib/utils/server/admin"; export default async function Page() { const { userId, redirectToSignIn } = await auth(); @@ -15,10 +17,19 @@ export default async function Page() { } const userData = await getUser(userId); - const isSuperAdmin = userData?.role === "super_admin"; + if (!userData){ + return ( + + + ) + } + const events = await getAllEvents(); - + const isUserAuthorized = isUserAdmin(userData); return (
@@ -43,7 +54,7 @@ export default async function Page() {
({ ...ev, isSuperAdmin }))} + data={events.map((ev) => ({ ...ev, isUserAdmin:isUserAuthorized }))} />
); diff --git a/apps/web/src/components/events/shared/EventColumns.tsx b/apps/web/src/components/events/shared/EventColumns.tsx index 68061444..a447c1e9 100644 --- a/apps/web/src/components/events/shared/EventColumns.tsx +++ b/apps/web/src/components/events/shared/EventColumns.tsx @@ -31,8 +31,9 @@ import { useAction } from "next-safe-action/hooks"; import { deleteEventAction } from "@/actions/admin/event-actions"; import { toast } from "sonner"; import { LoaderCircle } from "lucide-react"; +import { error } from "console"; -type EventRow = eventTableValidatorType & { isSuperAdmin: boolean }; +type EventRow = eventTableValidatorType & { isUserAdmin: boolean }; export const columns: ColumnDef[] = [ { @@ -78,6 +79,7 @@ export const columns: ColumnDef[] = [ accessorKey: "endTime", header: "End", cell: ({ row }) => ( + {new Date(row.original.endTime).toLocaleDateString()}{" "} {new Date(row.original.endTime).toLocaleTimeString("en-US", { @@ -104,7 +106,19 @@ export const columns: ColumnDef[] = [ router.refresh(); setOpen(false); }, - onError: (err) => { + onError: ({error:err}) => { + let description: string; + + if (err.validationErrors?._errors) { + // User is not super admin + description = err.validationErrors._errors[0]; + } else { + description = + err.serverError || + "An unknown error occurred"; + } + + toast.error("Unable to edit event", { description }); toast.dismiss(); toast.error("Failed to delete event"); console.log(err); @@ -144,26 +158,31 @@ export const columns: ColumnDef[] = [ - - - Edit - - - - Delete - + + Edit + + + )} + {row.original.isUserAdmin && ( + + + Delete + + + )} - diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index aa4c0532..d9c9b6f6 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -39,8 +39,7 @@ export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { const user = await getUser(ctx.userId); if ( !user || - (user.role !== "admin" && - user.role !== "super_admin") + !isUserAdmin(user) ) { returnValidationErrors(z.null(), { _errors: ["Unauthorized (Not Admin)"], From 84f8646c3e83fa933132c12ec3e534be3d841c30 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 2 Jul 2025 18:55:21 -0600 Subject: [PATCH 12/13] formatter --- apps/web/src/app/admin/events/page.tsx | 19 +++++------ apps/web/src/app/admin/layout.tsx | 2 +- apps/web/src/app/admin/users/page.tsx | 4 +-- .../components/events/shared/EventColumns.tsx | 6 ++-- apps/web/src/lib/safe-action.ts | 32 +++++++++---------- apps/web/src/lib/utils/server/admin.ts | 4 +-- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/admin/events/page.tsx b/apps/web/src/app/admin/events/page.tsx index 2c1eeb12..61ee1167 100644 --- a/apps/web/src/app/admin/events/page.tsx +++ b/apps/web/src/app/admin/events/page.tsx @@ -17,16 +17,14 @@ export default async function Page() { } const userData = await getUser(userId); - if (!userData){ + if (!userData) { return ( - - - ) + + ); } - const events = await getAllEvents(); const isUserAuthorized = isUserAdmin(userData); @@ -54,7 +52,10 @@ export default async function Page() { ({ ...ev, isUserAdmin:isUserAuthorized }))} + data={events.map((ev) => ({ + ...ev, + isUserAdmin: isUserAuthorized, + }))} /> ); diff --git a/apps/web/src/app/admin/layout.tsx b/apps/web/src/app/admin/layout.tsx index 78f1889b..47cd9a1f 100644 --- a/apps/web/src/app/admin/layout.tsx +++ b/apps/web/src/app/admin/layout.tsx @@ -86,7 +86,7 @@ export default async function AdminLayout({ children }: AdminLayoutProps) {
{Object.entries(c.dashPaths.admin).map(([name, path]) => - ["Users","Toggles"].includes(name) && + ["Users", "Toggles"].includes(name) && user.role === "volunteer" ? null : ( ), diff --git a/apps/web/src/app/admin/users/page.tsx b/apps/web/src/app/admin/users/page.tsx index 9cde884a..6e762102 100644 --- a/apps/web/src/app/admin/users/page.tsx +++ b/apps/web/src/app/admin/users/page.tsx @@ -13,9 +13,9 @@ import { isUserAdmin } from "@/lib/utils/server/admin"; // This begs a question where we might want to have an option later on to sort by the role as we might want different things export default async function Page() { const { userId } = await auth(); - + if (!userId) return notFound(); - + const admin = await getUser(userId); if (!admin || !isUserAdmin(admin)) return notFound(); diff --git a/apps/web/src/components/events/shared/EventColumns.tsx b/apps/web/src/components/events/shared/EventColumns.tsx index a447c1e9..fb6ed5a6 100644 --- a/apps/web/src/components/events/shared/EventColumns.tsx +++ b/apps/web/src/components/events/shared/EventColumns.tsx @@ -79,7 +79,6 @@ export const columns: ColumnDef[] = [ accessorKey: "endTime", header: "End", cell: ({ row }) => ( - {new Date(row.original.endTime).toLocaleDateString()}{" "} {new Date(row.original.endTime).toLocaleTimeString("en-US", { @@ -106,7 +105,7 @@ export const columns: ColumnDef[] = [ router.refresh(); setOpen(false); }, - onError: ({error:err}) => { + onError: ({ error: err }) => { let description: string; if (err.validationErrors?._errors) { @@ -114,8 +113,7 @@ export const columns: ColumnDef[] = [ description = err.validationErrors._errors[0]; } else { description = - err.serverError || - "An unknown error occurred"; + err.serverError || "An unknown error occurred"; } toast.error("Unable to edit event", { description }); diff --git a/apps/web/src/lib/safe-action.ts b/apps/web/src/lib/safe-action.ts index d9c9b6f6..822408a9 100644 --- a/apps/web/src/lib/safe-action.ts +++ b/apps/web/src/lib/safe-action.ts @@ -22,29 +22,27 @@ export const authenticatedAction = publicAction.use( }, ); -export const volunteerAction = authenticatedAction.use(async ({ next, ctx }) => { - const user = await getUser(ctx.userId); - if ( - !user || - !["admin", "super_admin", "volunteer"].includes(user.role) - ) { - returnValidationErrors(z.null(), { - _errors: ["Unauthorized (Not Admin)"], - }); - } - return next({ ctx: { user, ...ctx } }); -}); +export const volunteerAction = authenticatedAction.use( + async ({ next, ctx }) => { + const user = await getUser(ctx.userId); + if ( + !user || + !["admin", "super_admin", "volunteer"].includes(user.role) + ) { + returnValidationErrors(z.null(), { + _errors: ["Unauthorized (Not Admin)"], + }); + } + return next({ ctx: { user, ...ctx } }); + }, +); export const adminAction = authenticatedAction.use(async ({ next, ctx }) => { const user = await getUser(ctx.userId); - if ( - !user || - !isUserAdmin(user) - ) { + if (!user || !isUserAdmin(user)) { returnValidationErrors(z.null(), { _errors: ["Unauthorized (Not Admin)"], }); } return next({ ctx: { user, ...ctx } }); }); - diff --git a/apps/web/src/lib/utils/server/admin.ts b/apps/web/src/lib/utils/server/admin.ts index cd9b83db..d91062cb 100644 --- a/apps/web/src/lib/utils/server/admin.ts +++ b/apps/web/src/lib/utils/server/admin.ts @@ -1,7 +1,5 @@ import type { User } from "db/types"; export function isUserAdmin(user: User) { - return ( - ["admin", "super_admin",].includes(user.role) - ); + return ["admin", "super_admin"].includes(user.role); } From a7aeb39b3eb501658d85921999a5de9d8c6d4a61 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Tue, 8 Jul 2025 20:46:27 -0600 Subject: [PATCH 13/13] adds recent registrations --- apps/web/src/app/admin/events/page.tsx | 18 +++--- apps/web/src/app/admin/page.tsx | 64 +++++++++++++++---- .../admin/toggles/RegistrationSettings.tsx | 5 +- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/apps/web/src/app/admin/events/page.tsx b/apps/web/src/app/admin/events/page.tsx index 61ee1167..d43e3b4d 100644 --- a/apps/web/src/app/admin/events/page.tsx +++ b/apps/web/src/app/admin/events/page.tsx @@ -41,14 +41,16 @@ export default async function Page() {

-
- - - -
+ {isUserAuthorized && ( +
+ + + +
+ )} @@ -51,7 +62,6 @@ export default async function Page() {
{allUsers.length}
- {/*

+20.1% from last month

*/} @@ -63,7 +73,6 @@ export default async function Page() {
{0}
- {/*

+20.1% from last month

*/}
@@ -75,7 +84,6 @@ export default async function Page() {
{rsvpCount}
- {/*

+20.1% from last month

*/}
@@ -87,7 +95,6 @@ export default async function Page() {
{checkinCount}
- {/*

+20.1% from last month

*/}
@@ -107,7 +114,6 @@ export default async function Page() { days. - @@ -121,10 +127,32 @@ export default async function Page() { Recent Registrations {" "} - - + +
+ {recentRegisteredUsers.map((user) => ( +
+ + {user.firstName} {user.lastName} + + + {formatInTimeZone( + user.signupTime, + timezone, + "MMMM dd h:mm a", + )} + +
+ ))} +
+
@@ -136,6 +164,9 @@ function getRecentRegistrationData(users: User[]) { let rsvpCount = 0; let checkinCount = 0; + + const recentRegisteredUsers: User[] = []; + let recentRegisteredUsersCount = 0; let recentSignupCount: DateNumberMap = {}; for (let i = 0; i < 7; i++) { @@ -156,10 +187,21 @@ function getRecentRegistrationData(users: User[]) { const stamp = user.signupTime.toISOString().split("T")[0]; - if (recentSignupCount[stamp] != undefined) recentSignupCount[stamp]++; + if (recentSignupCount[stamp] != undefined) { + if (recentRegisteredUsersCount < 10) { + recentRegisteredUsers.push(user); + recentRegisteredUsersCount++; + } + recentSignupCount[stamp]++; + } } - return { rsvpCount, checkinCount, recentSignupCount }; + return { + rsvpCount, + checkinCount, + recentSignupCount, + recentRegisteredUsers, + }; } export const runtime = "edge"; diff --git a/apps/web/src/components/admin/toggles/RegistrationSettings.tsx b/apps/web/src/components/admin/toggles/RegistrationSettings.tsx index 0b415fd6..bbf699ad 100644 --- a/apps/web/src/components/admin/toggles/RegistrationSettings.tsx +++ b/apps/web/src/components/admin/toggles/RegistrationSettings.tsx @@ -93,7 +93,8 @@ export function RegistrationToggles({ }} /> -
+ {/* removed until implemented */} + {/*

Allow Secret Code Sign-up

@@ -111,7 +112,7 @@ export function RegistrationToggles({ }); }} /> -
+
*/}