From 638fd5738a7b40da9f10013aaed19d4d64003f3b Mon Sep 17 00:00:00 2001
From: Christian Walker <76548772+christianhelp@users.noreply.github.com>
Date: Thu, 24 Oct 2024 00:34:03 -0500
Subject: [PATCH] Fix Hackathon Check-In Scanner (#130)
---
.../actions/admin/scanner-admin-actions.ts | 22 +-
apps/web/src/app/admin/check-in/page.tsx | 4 +-
.../admin/scanner/CheckinScanner.tsx | 203 +++++++++---------
.../src/components/shared/ProfileButton.tsx | 1 +
apps/web/src/lib/constants/index.ts | 1 +
packages/config/hackkit.config.ts | 2 +-
6 files changed, 128 insertions(+), 105 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..af21fd0e 100644
--- a/apps/web/src/actions/admin/scanner-admin-actions.ts
+++ b/apps/web/src/actions/admin/scanner-admin-actions.ts
@@ -5,6 +5,7 @@ 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,12 +66,23 @@ export const getScan = adminAction
},
);
-export const checkInUser = adminAction
- .schema(z.string())
- .action(async ({ parsedInput: user }) => {
+// 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 tell user refresh the QR Code"),
+});
+
+export const checkInUserToHackathon = adminAction
+ .schema(checkInUserSchema)
+ .action(async ({ parsedInput: { userID } }) => {
// Set checkinTimestamp
- return await db
+ await db
.update(userCommonData)
.set({ checkinTimestamp: sql`now()` })
- .where(eq(userCommonData.clerkID, user));
+ .where(eq(userCommonData.clerkID, userID));
});
diff --git a/apps/web/src/app/admin/check-in/page.tsx b/apps/web/src/app/admin/check-in/page.tsx
index 0dea1ab8..af117b82 100644
--- a/apps/web/src/app/admin/check-in/page.tsx
+++ b/apps/web/src/app/admin/check-in/page.tsx
@@ -19,7 +19,8 @@ export default async function Page({
);
const scanUser = await getUser(searchParams.user);
- if (!scanUser)
+ console.log(scanUser);
+ if (!scanUser) {
return (
diff --git a/apps/web/src/components/admin/scanner/CheckinScanner.tsx b/apps/web/src/components/admin/scanner/CheckinScanner.tsx
index d13c2be4..525ad748 100644
--- a/apps/web/src/components/admin/scanner/CheckinScanner.tsx
+++ b/apps/web/src/components/admin/scanner/CheckinScanner.tsx
@@ -3,11 +3,11 @@
import { useState, useEffect } from "react";
import { QrScanner } from "@yudiel/react-qr-scanner";
import superjson from "superjson";
-import { checkInUser } from "@/actions/admin/scanner-admin-actions";
-import { useAction } from "next-safe-action/hooks";
+import { checkInUserToHackathon } from "@/actions/admin/scanner-admin-actions";
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,
@@ -19,16 +19,8 @@ import {
import { Button } from "@/components/shadcn/ui/button";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { toast } from "sonner";
-
-/*
-
-Pass Scanner Props:
-
-eventName: name of the event that the user is scanning into
-hasScanned: if the state has eventered one in which a QR has been scanned (whether that scan has scanned before or not)
-scan: the scan object that has been scanned. If they have not scanned before scan will be null leading to a new record or if they have then it will incriment the scan count.
-
-*/
+import { FIVE_MINUTES_IN_MILLISECONDS } from "@/lib/constants";
+import { ValidationErrors } from "next-safe-action";
interface CheckinScannerProps {
hasScanned: boolean;
@@ -43,9 +35,9 @@ export default function CheckinScanner({
scanUser,
hasRSVP,
}: CheckinScannerProps) {
+ console.log("scanner props is: ", hasScanned, checkedIn, scanUser, hasRSVP);
+
const [scanLoading, setScanLoading] = useState(false);
- // const { execute: runScanAction } = useAction(checkInUser, {});
- const [proceed, setProceed] = useState(hasRSVP);
useEffect(() => {
if (hasScanned) {
setScanLoading(false);
@@ -56,22 +48,83 @@ export default function CheckinScanner({
const path = usePathname();
const router = useRouter();
+ 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!",
+ );
+ router.replace(`${path}`);
+ }
+
+ const { execute: runCheckInUserToHackathon } = useAction(
+ checkInUserToHackathon,
+ {
+ onSuccess: () => {
+ handleUseActionFeedback();
+ },
+ 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() {
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 (Date.now() - timestamp > FIVE_MINUTES_IN_MILLISECONDS) {
+ return alert(
+ "QR Code has expired. Please tell user to refresh the QR Code",
+ );
+ }
+
if (checkedIn) {
return alert("User Already Checked in!");
} else {
- // TODO: make this a little more typesafe
- checkInUser(scanUser?.clerkID!);
+ toast.loading("Checking User In");
+ runCheckInUserToHackathon({
+ userID: scanUser.clerkID,
+ QRTimestamp: timestamp,
+ });
}
- toast.success("Successfully Scanned User In");
- router.replace(`${path}`);
+ 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,11 +161,6 @@ export default function CheckinScanner({
}}
/>
- {/*
-
-
-
-
*/}