From eab5157ec53644ef592364430ec8b0f217dc8aca Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Mon, 21 Oct 2024 23:22:27 -0500 Subject: [PATCH 1/6] fixes qr scanner logic --- .../actions/admin/scanner-admin-actions.ts | 12 +- apps/web/src/app/admin/check-in/page.tsx | 8 +- .../admin/scanner/CheckinScanner.tsx | 164 +++++++----------- .../src/components/shared/ProfileButton.tsx | 1 + packages/config/hackkit.config.ts | 2 +- 5 files changed, 83 insertions(+), 104 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index fb5d46a2..5ba4f2db 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -65,12 +65,20 @@ export const getScan = adminAction }, ); -export const checkInUser = adminAction +export const checkInUserToHackathon = adminAction .schema(z.string()) .action(async ({ parsedInput: user }) => { + console.log('Made to server. User is: ', user); // Set checkinTimestamp - return await db + try{ + await db .update(userCommonData) .set({ checkinTimestamp: sql`now()` }) .where(eq(userCommonData.clerkID, user)); + } + catch(e){ + console.log('Error updating checkinTimestamp: ', e); + return { success: false, message: 'Error checking in the user!' }; + } + return { success: true }; }); diff --git a/apps/web/src/app/admin/check-in/page.tsx b/apps/web/src/app/admin/check-in/page.tsx index 0dea1ab8..873c3975 100644 --- a/apps/web/src/app/admin/check-in/page.tsx +++ b/apps/web/src/app/admin/check-in/page.tsx @@ -19,7 +19,9 @@ export default async function Page({ ); const scanUser = await getUser(searchParams.user); - if (!scanUser) + console.log(scanUser); + if (!scanUser){ + console.log("no scan user found"); return (
); - + } + + console.log('using scanner case instead') return (
{ if (hasScanned) { setScanLoading(false); @@ -56,22 +48,48 @@ export default function CheckinScanner({ const path = usePathname(); const router = useRouter(); + const {  execute:runCheckInUserToHackathon, result:checkInResult} = useAction(checkInUserToHackathon); + + useEffect(() => { + if (checkInResult && checkInResult.data && !checkInResult.data.success){ + alert(`${checkInResult.data?.message ?? 'Error checking in user To the hackathon'}`); + } + + },[checkInResult]); function handleScanCreate() { const params = new URLSearchParams(searchParams.toString()); const timestamp = parseInt(params.get("createdAt") as string); + + if (!scanUser) { + return alert("User Not Found"); + } + if (isNaN(timestamp)) { return alert("Invalid QR Code Data (Field: createdAt)"); } if (checkedIn) { return alert("User Already Checked in!"); } else { - // TODO: make this a little more typesafe - checkInUser(scanUser?.clerkID!); + runCheckInUserToHackathon(scanUser.clerkID); + } toast.success("Successfully Scanned User In"); router.replace(`${path}`); } + const drawerTitle = checkedIn + ? "User Already Checked In" + : !hasRSVP ? 'Warning!' + : "New Scan"; + const drawerDescription = checkedIn + ? 'If this is a mistake, please talk to an admin' + : !hasRSVP ? `${scanUser?.firstName} ${scanUser?.lastName} Is not RSVP'd` + : `New scan for ${scanUser?.firstName} ${scanUser?.lastName}`; + const drawerFooterButtonText = checkedIn + ? "Close" + : !hasRSVP ? "Check In Anyways" + : "Scan User In"; + return ( <>
@@ -108,29 +126,21 @@ export default function CheckinScanner({ }} />
- {/*
- - - -
*/}
router.replace(path)} - open={hasScanned || scanLoading} - > + open={hasScanned || scanLoading}> {scanLoading ? ( <> Loading Scan... - {/* */} @@ -138,80 +148,36 @@ export default function CheckinScanner({ ) : ( <> - {checkedIn ? ( - - User already checked in! - - ) : ( - <> - {!proceed ? ( - <> - - Warning! - - - {scanUser?.firstName}{" "} - {scanUser?.lastName} Is not - RSVP'd - - - Do you wish to proceed? - - - - - ) : ( - <> - - New Scan - - - New scan for{" "} - {scanUser?.firstName}{" "} - {scanUser?.lastName} - - - )} - - )} + + {drawerTitle} + - {proceed ? ( - <> - - {!checkedIn && ( - - )} - - - - ) : ( - <> - )} + + {drawerDescription} + + + {!hasRSVP && !checkedIn && ( +
Do you wish to proceed?
+ )} + {!checkedIn && ( + + )} + +
)}
diff --git a/apps/web/src/components/shared/ProfileButton.tsx b/apps/web/src/components/shared/ProfileButton.tsx index 80479377..86410e51 100644 --- a/apps/web/src/components/shared/ProfileButton.tsx +++ b/apps/web/src/components/shared/ProfileButton.tsx @@ -19,6 +19,7 @@ import { DropdownSwitcher } from "@/components/shared/ThemeSwitcher"; import DefaultDropdownTrigger from "../dash/shared/DefaultDropDownTrigger"; import MobileNavBarLinks from "./MobileNavBarLinks"; import { getUser } from "db/functions"; +import { redirect } from "next/navigation"; export default async function ProfileButton() { const clerkUser = await auth(); diff --git a/packages/config/hackkit.config.ts b/packages/config/hackkit.config.ts index 58fa86b0..5c5f93ff 100644 --- a/packages/config/hackkit.config.ts +++ b/packages/config/hackkit.config.ts @@ -852,7 +852,7 @@ const c = { Users: "/admin/users", Events: "/admin/events", Points: "/admin/points", - "Check-in": "/admin/check-in", + "Hackathon Check-in": "/admin/check-in", Toggles: "/admin/toggles", }, // TODO: Can remove days? Pretty sure they're dynamic now. From 5aa3aa44a028b79f069fc23e94a9782758961767 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Mon, 21 Oct 2024 23:23:25 -0500 Subject: [PATCH 2/6] removes debugs --- apps/web/src/actions/admin/scanner-admin-actions.ts | 1 - apps/web/src/app/admin/check-in/page.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 5ba4f2db..6cd24f8b 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -68,7 +68,6 @@ export const getScan = adminAction export const checkInUserToHackathon = adminAction .schema(z.string()) .action(async ({ parsedInput: user }) => { - console.log('Made to server. User is: ', user); // Set checkinTimestamp try{ await db diff --git a/apps/web/src/app/admin/check-in/page.tsx b/apps/web/src/app/admin/check-in/page.tsx index 873c3975..f3ec2bc7 100644 --- a/apps/web/src/app/admin/check-in/page.tsx +++ b/apps/web/src/app/admin/check-in/page.tsx @@ -21,7 +21,6 @@ export default async function Page({ const scanUser = await getUser(searchParams.user); console.log(scanUser); if (!scanUser){ - console.log("no scan user found"); return (
Date: Mon, 21 Oct 2024 23:23:47 -0500 Subject: [PATCH 3/6] prettier write --- .../actions/admin/scanner-admin-actions.ts | 15 +++-- apps/web/src/app/admin/check-in/page.tsx | 4 +- .../admin/scanner/CheckinScanner.tsx | 57 ++++++++++++------- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 6cd24f8b..5b4808f0 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -69,15 +69,14 @@ export const checkInUserToHackathon = adminAction .schema(z.string()) .action(async ({ parsedInput: user }) => { // Set checkinTimestamp - try{ + try { await db - .update(userCommonData) - .set({ checkinTimestamp: sql`now()` }) - .where(eq(userCommonData.clerkID, user)); - } - catch(e){ - console.log('Error updating checkinTimestamp: ', e); - return { success: false, message: 'Error checking in the user!' }; + .update(userCommonData) + .set({ checkinTimestamp: sql`now()` }) + .where(eq(userCommonData.clerkID, user)); + } catch (e) { + console.log("Error updating checkinTimestamp: ", e); + return { success: false, message: "Error checking in the user!" }; } return { success: true }; }); diff --git a/apps/web/src/app/admin/check-in/page.tsx b/apps/web/src/app/admin/check-in/page.tsx index f3ec2bc7..af117b82 100644 --- a/apps/web/src/app/admin/check-in/page.tsx +++ b/apps/web/src/app/admin/check-in/page.tsx @@ -20,7 +20,7 @@ export default async function Page({ const scanUser = await getUser(searchParams.user); console.log(scanUser); - if (!scanUser){ + if (!scanUser) { return (
); } - + return (
{ @@ -48,14 +47,20 @@ export default function CheckinScanner({ const path = usePathname(); const router = useRouter(); - const {  execute:runCheckInUserToHackathon, result:checkInResult} = useAction(checkInUserToHackathon); + const { execute: runCheckInUserToHackathon, result: checkInResult } = + useAction(checkInUserToHackathon); useEffect(() => { - if (checkInResult && checkInResult.data && !checkInResult.data.success){ - alert(`${checkInResult.data?.message ?? 'Error checking in user To the hackathon'}`); + if ( + checkInResult && + checkInResult.data && + !checkInResult.data.success + ) { + alert( + `${checkInResult.data?.message ?? "Error checking in user To the hackathon"}`, + ); } - - },[checkInResult]); + }, [checkInResult]); function handleScanCreate() { const params = new URLSearchParams(searchParams.toString()); const timestamp = parseInt(params.get("createdAt") as string); @@ -71,23 +76,25 @@ export default function CheckinScanner({ return alert("User Already Checked in!"); } else { runCheckInUserToHackathon(scanUser.clerkID); - } toast.success("Successfully Scanned User In"); router.replace(`${path}`); } - const drawerTitle = checkedIn - ? "User Already Checked In" - : !hasRSVP ? 'Warning!' + const drawerTitle = checkedIn + ? "User Already Checked In" + : !hasRSVP + ? "Warning!" : "New Scan"; - const drawerDescription = checkedIn - ? 'If this is a mistake, please talk to an admin' - : !hasRSVP ? `${scanUser?.firstName} ${scanUser?.lastName} Is not RSVP'd` + const drawerDescription = checkedIn + ? "If this is a mistake, please talk to an admin" + : !hasRSVP + ? `${scanUser?.firstName} ${scanUser?.lastName} Is not RSVP'd` : `New scan for ${scanUser?.firstName} ${scanUser?.lastName}`; - const drawerFooterButtonText = checkedIn - ? "Close" - : !hasRSVP ? "Check In Anyways" + const drawerFooterButtonText = checkedIn + ? "Close" + : !hasRSVP + ? "Check In Anyways" : "Scan User In"; return ( @@ -130,7 +137,8 @@ export default function CheckinScanner({
router.replace(path)} - open={hasScanned || scanLoading}> + open={hasScanned || scanLoading} + > {scanLoading ? ( <> @@ -140,7 +148,8 @@ export default function CheckinScanner({ @@ -151,7 +160,8 @@ export default function CheckinScanner({ + })} + > {drawerTitle} @@ -160,7 +170,9 @@ export default function CheckinScanner({ {!hasRSVP && !checkedIn && ( -
Do you wish to proceed?
+
+ Do you wish to proceed? +
)} {!checkedIn && (
From 56591dc665778536f5e19e0e7e9d61924f3c9e99 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Mon, 21 Oct 2024 23:44:47 -0500 Subject: [PATCH 4/6] adds better error handling --- .../actions/admin/scanner-admin-actions.ts | 7 +---- .../admin/scanner/CheckinScanner.tsx | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 5b4808f0..b1507d31 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -69,14 +69,9 @@ export const checkInUserToHackathon = adminAction .schema(z.string()) .action(async ({ parsedInput: user }) => { // Set checkinTimestamp - try { await db .update(userCommonData) .set({ checkinTimestamp: sql`now()` }) .where(eq(userCommonData.clerkID, user)); - } catch (e) { - console.log("Error updating checkinTimestamp: ", e); - return { success: false, message: "Error checking in the user!" }; } - return { success: true }; - }); + ); diff --git a/apps/web/src/components/admin/scanner/CheckinScanner.tsx b/apps/web/src/components/admin/scanner/CheckinScanner.tsx index 470ece15..57df7b53 100644 --- a/apps/web/src/components/admin/scanner/CheckinScanner.tsx +++ b/apps/web/src/components/admin/scanner/CheckinScanner.tsx @@ -47,20 +47,22 @@ export default function CheckinScanner({ const path = usePathname(); const router = useRouter(); + function handleUseActionFeedback(hasErrored = false) { + toast.dismiss(); + (hasErrored) ? toast.error("Failed to Check User Into Hackathon") : toast.success("Successfully Checked User Into Hackathon!"); + router.replace(`${path}`); + } + const { execute: runCheckInUserToHackathon, result: checkInResult } = - useAction(checkInUserToHackathon); + useAction(checkInUserToHackathon,{ + onSuccess: () => { + handleUseActionFeedback(); + }, + onError: () =>{ + handleUseActionFeedback(true); + } + }); - useEffect(() => { - if ( - checkInResult && - checkInResult.data && - !checkInResult.data.success - ) { - alert( - `${checkInResult.data?.message ?? "Error checking in user To the hackathon"}`, - ); - } - }, [checkInResult]); function handleScanCreate() { const params = new URLSearchParams(searchParams.toString()); const timestamp = parseInt(params.get("createdAt") as string); @@ -75,10 +77,10 @@ export default function CheckinScanner({ if (checkedIn) { return alert("User Already Checked in!"); } else { + toast.loading('Checking User In'); runCheckInUserToHackathon(scanUser.clerkID); } - toast.success("Successfully Scanned User In"); - router.replace(`${path}`); + router.replace(path); } const drawerTitle = checkedIn From 4c77c19255f56fa84c29e49a98c686a7dffe33de Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Tue, 22 Oct 2024 00:33:46 -0500 Subject: [PATCH 5/6] adds expired timestamp check --- .../actions/admin/scanner-admin-actions.ts | 16 +++++++-- .../admin/scanner/CheckinScanner.tsx | 35 +++++++++++++------ apps/web/src/lib/constants/index.ts | 1 + 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index b1507d31..0b840c9e 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -5,6 +5,8 @@ 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 .schema( z.object({ @@ -65,13 +67,21 @@ export const getScan = adminAction }, ); +// Schema will be moved over when rewrite of the other scanner happens +const checkInUserSchema = z.object({ + userID:z.string(), + QRTimestamp: z.number().positive().refine((timestamp) => { + return Date.now() - timestamp < 5 * 60 * 1000; + }, "QR Code has expired. Please refresh the QR Code"), +}) + export const checkInUserToHackathon = adminAction - .schema(z.string()) - .action(async ({ parsedInput: user }) => { + .schema(checkInUserSchema) + .action(async ({ parsedInput: {userID} }) => { // Set checkinTimestamp await db .update(userCommonData) .set({ checkinTimestamp: sql`now()` }) - .where(eq(userCommonData.clerkID, user)); + .where(eq(userCommonData.clerkID, userID)); } ); diff --git a/apps/web/src/components/admin/scanner/CheckinScanner.tsx b/apps/web/src/components/admin/scanner/CheckinScanner.tsx index 57df7b53..864d0d42 100644 --- a/apps/web/src/components/admin/scanner/CheckinScanner.tsx +++ b/apps/web/src/components/admin/scanner/CheckinScanner.tsx @@ -8,7 +8,6 @@ import { type QRDataInterface } from "@/lib/utils/shared/qr"; import type { User } from "db/types"; import clsx from "clsx"; import { useAction } from "next-safe-action/hooks"; - import { Drawer, DrawerContent, @@ -20,6 +19,8 @@ import { import { Button } from "@/components/shadcn/ui/button"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { toast } from "sonner"; +import { FIVE_MINUTES_IN_MILLISECONDS } from "@/lib/constants"; +import { ValidationErrors } from "next-safe-action"; interface CheckinScannerProps { hasScanned: boolean; @@ -47,20 +48,30 @@ export default function CheckinScanner({ const path = usePathname(); const router = useRouter(); - function handleUseActionFeedback(hasErrored = false) { + function handleUseActionFeedback(hasErrored=false,message="") { + console.log("called"); toast.dismiss(); - (hasErrored) ? toast.error("Failed to Check User Into Hackathon") : toast.success("Successfully Checked User Into Hackathon!"); + hasErrored + ? toast.error(message || "Failed to Check User Into Hackathon") + : toast.success(message || "Successfully Checked User Into Hackathon!"); router.replace(`${path}`); } - const { execute: runCheckInUserToHackathon, result: checkInResult } = - useAction(checkInUserToHackathon,{ + const { execute: runCheckInUserToHackathon } = + useAction(checkInUserToHackathon, { onSuccess: () => { handleUseActionFeedback(); }, - onError: () =>{ - handleUseActionFeedback(true); - } + onError: ({error,input}) => { + console.log("error is: ", error); + console.log("input is: ", input); + if (error.validationErrors?.QRTimestamp?._errors){ + handleUseActionFeedback(true,error.validationErrors.QRTimestamp._errors[0]); + } + else{ + handleUseActionFeedback(true); + } + }, }); function handleScanCreate() { @@ -74,11 +85,15 @@ export default function CheckinScanner({ if (isNaN(timestamp)) { return alert("Invalid QR Code Data (Field: createdAt)"); } + if (Date.now() - timestamp > FIVE_MINUTES_IN_MILLISECONDS) { + return alert("QR Code has expired. Please refresh the QR Code"); + } + if (checkedIn) { return alert("User Already Checked in!"); } else { - toast.loading('Checking User In'); - runCheckInUserToHackathon(scanUser.clerkID); + toast.loading("Checking User In"); + runCheckInUserToHackathon({userID:scanUser.clerkID, QRTimestamp: timestamp}); } router.replace(path); } diff --git a/apps/web/src/lib/constants/index.ts b/apps/web/src/lib/constants/index.ts index b239662a..f34ca64c 100644 --- a/apps/web/src/lib/constants/index.ts +++ b/apps/web/src/lib/constants/index.ts @@ -1,2 +1,3 @@ export const ONE_HOUR_IN_MILLISECONDS = 3600000; +export const FIVE_MINUTES_IN_MILLISECONDS = 300000; export const VERCEL_IP_TIMEZONE_HEADER_KEY = "x-vercel-ip-timezone"; From 03d94d244e1ced7adc112c9fa7b601894a37b098 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Tue, 22 Oct 2024 00:43:41 -0500 Subject: [PATCH 6/6] update feedback wording --- .../actions/admin/scanner-admin-actions.ts | 27 +++++++------- .../admin/scanner/CheckinScanner.tsx | 35 ++++++++++++------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/apps/web/src/actions/admin/scanner-admin-actions.ts b/apps/web/src/actions/admin/scanner-admin-actions.ts index 0b840c9e..af21fd0e 100644 --- a/apps/web/src/actions/admin/scanner-admin-actions.ts +++ b/apps/web/src/actions/admin/scanner-admin-actions.ts @@ -6,7 +6,6 @@ import { db, sql } from "db"; import { scans, userCommonData } from "db/schema"; import { eq, and } from "db/drizzle"; - export const createScan = adminAction .schema( z.object({ @@ -69,19 +68,21 @@ export const getScan = adminAction // Schema will be moved over when rewrite of the other scanner happens const checkInUserSchema = z.object({ - userID:z.string(), - QRTimestamp: z.number().positive().refine((timestamp) => { - return Date.now() - timestamp < 5 * 60 * 1000; - }, "QR Code has expired. Please refresh the QR Code"), -}) + userID: z.string(), + QRTimestamp: z + .number() + .positive() + .refine((timestamp) => { + return Date.now() - timestamp < 5 * 60 * 1000; + }, "QR Code has expired. Please tell user refresh the QR Code"), +}); export const checkInUserToHackathon = adminAction .schema(checkInUserSchema) - .action(async ({ parsedInput: {userID} }) => { + .action(async ({ parsedInput: { userID } }) => { // Set checkinTimestamp - await db - .update(userCommonData) - .set({ checkinTimestamp: sql`now()` }) - .where(eq(userCommonData.clerkID, userID)); - } - ); + await db + .update(userCommonData) + .set({ checkinTimestamp: sql`now()` }) + .where(eq(userCommonData.clerkID, userID)); + }); diff --git a/apps/web/src/components/admin/scanner/CheckinScanner.tsx b/apps/web/src/components/admin/scanner/CheckinScanner.tsx index 864d0d42..525ad748 100644 --- a/apps/web/src/components/admin/scanner/CheckinScanner.tsx +++ b/apps/web/src/components/admin/scanner/CheckinScanner.tsx @@ -48,31 +48,37 @@ export default function CheckinScanner({ const path = usePathname(); const router = useRouter(); - function handleUseActionFeedback(hasErrored=false,message="") { + function handleUseActionFeedback(hasErrored = false, message = "") { console.log("called"); toast.dismiss(); hasErrored ? toast.error(message || "Failed to Check User Into Hackathon") - : toast.success(message || "Successfully Checked User Into Hackathon!"); + : toast.success( + message || "Successfully Checked User Into Hackathon!", + ); router.replace(`${path}`); } - const { execute: runCheckInUserToHackathon } = - useAction(checkInUserToHackathon, { + const { execute: runCheckInUserToHackathon } = useAction( + checkInUserToHackathon, + { onSuccess: () => { handleUseActionFeedback(); }, - onError: ({error,input}) => { + onError: ({ error, input }) => { console.log("error is: ", error); console.log("input is: ", input); - if (error.validationErrors?.QRTimestamp?._errors){ - handleUseActionFeedback(true,error.validationErrors.QRTimestamp._errors[0]); - } - else{ + if (error.validationErrors?.QRTimestamp?._errors) { + handleUseActionFeedback( + true, + error.validationErrors.QRTimestamp._errors[0], + ); + } else { handleUseActionFeedback(true); } }, - }); + }, + ); function handleScanCreate() { const params = new URLSearchParams(searchParams.toString()); @@ -86,14 +92,19 @@ export default function CheckinScanner({ return alert("Invalid QR Code Data (Field: createdAt)"); } if (Date.now() - timestamp > FIVE_MINUTES_IN_MILLISECONDS) { - return alert("QR Code has expired. Please refresh the QR Code"); + return alert( + "QR Code has expired. Please tell user to refresh the QR Code", + ); } if (checkedIn) { return alert("User Already Checked in!"); } else { toast.loading("Checking User In"); - runCheckInUserToHackathon({userID:scanUser.clerkID, QRTimestamp: timestamp}); + runCheckInUserToHackathon({ + userID: scanUser.clerkID, + QRTimestamp: timestamp, + }); } router.replace(path); }