From 36316092ba1fc366bd7ba5f5b4ae371733e4a060 Mon Sep 17 00:00:00 2001
From: "Dhanavadh T. Saito" <105502355+dhanavadh@users.noreply.github.com>
Date: Sun, 13 Jul 2025 22:27:50 +0700
Subject: [PATCH 01/79] feat: added staff scan qr page
-added staff scan qr page
---
src/components/common/Tabs.astro | 145 +++++++++++++++++++++++++++++
src/layouts/staff/WithNavbar.astro | 37 ++++++++
src/pages/staff/event/index.astro | 44 +++++++++
3 files changed, 226 insertions(+)
create mode 100644 src/components/common/Tabs.astro
create mode 100644 src/layouts/staff/WithNavbar.astro
create mode 100644 src/pages/staff/event/index.astro
diff --git a/src/components/common/Tabs.astro b/src/components/common/Tabs.astro
new file mode 100644
index 0000000..9802aad
--- /dev/null
+++ b/src/components/common/Tabs.astro
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/staff/WithNavbar.astro b/src/layouts/staff/WithNavbar.astro
new file mode 100644
index 0000000..b95e492
--- /dev/null
+++ b/src/layouts/staff/WithNavbar.astro
@@ -0,0 +1,37 @@
+---
+import "@/styles/global.css";
+import Footer from "@firstdate/Footer.astro";
+import Navbar from "@firstdate/Navbar.astro";
+import { ClientRouter } from "astro:transitions";
+
+interface Props {
+ title?: string;
+ description?: string;
+}
+
+const { title, description } = Astro.props;
+---
+
+
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/staff/event/index.astro b/src/pages/staff/event/index.astro
new file mode 100644
index 0000000..bb827d6
--- /dev/null
+++ b/src/pages/staff/event/index.astro
@@ -0,0 +1,44 @@
+---
+import Popup from "@/components/common/Popup.astro";
+import Tabs from "@/components/common/Tabs.astro";
+import Layout from "@/layouts/staff/WithNavbar.astro";
+---
+
+
+
+
+
ลงทะเบียนงาน
+
+ CU First Date
+
+
+
+
+
+
From 076210b4221c79d5944ef77265742a1ecf355a98 Mon Sep 17 00:00:00 2001
From: "Dhanavadh T. Saito" <105502355+dhanavadh@users.noreply.github.com>
Date: Sun, 13 Jul 2025 23:54:33 +0700
Subject: [PATCH 02/79] fix: firstdate staff bg
---
src/layouts/staff/WithNavbar.astro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/layouts/staff/WithNavbar.astro b/src/layouts/staff/WithNavbar.astro
index b95e492..1111986 100644
--- a/src/layouts/staff/WithNavbar.astro
+++ b/src/layouts/staff/WithNavbar.astro
@@ -25,7 +25,7 @@ const { title, description } = Astro.props;
From 2eb62c2b99061a279faaa806e5f83369269bd3d7 Mon Sep 17 00:00:00 2001
From: "Dhanavadh T. Saito" <105502355+dhanavadh@users.noreply.github.com>
Date: Mon, 14 Jul 2025 18:22:10 +0700
Subject: [PATCH 03/79] feat: add @yudiel/react-qr-scanner
---
package.json | 5 ++-
pnpm-lock.yaml | 73 +++++++++++++++++++++++++++++++++++++
src/pages/staff/index.astro | 21 +++++++++++
3 files changed, 97 insertions(+), 2 deletions(-)
create mode 100644 src/pages/staff/index.astro
diff --git a/package.json b/package.json
index 8830dc1..118dadc 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@tailwindcss/vite": "^4.1.11",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
+ "@yudiel/react-qr-scanner": "^2.3.1",
"astro": "^5.10.1",
"canvas-confetti": "^1.9.3",
"clsx": "^2.1.1",
@@ -43,9 +44,9 @@
"devDependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/ts-plugin": "^1.10.4",
- "@types/qrcode": "^1.5.5",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/canvas-confetti": "^1.9.0",
+ "@types/qrcode": "^1.5.5",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
"astro-eslint-parser": "^1.2.2",
@@ -78,4 +79,4 @@
"prettier --write"
]
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4aa1b57..f81a65c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -34,6 +34,9 @@ importers:
"@types/react-dom":
specifier: ^19.1.6
version: 19.1.6(@types/react@19.1.8)
+ "@yudiel/react-qr-scanner":
+ specifier: ^2.3.1
+ version: 2.3.1(@types/emscripten@1.40.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
astro:
specifier: ^5.10.1
version: 5.10.1(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.44.1)(typescript@5.8.3)(yaml@2.8.0)
@@ -1494,6 +1497,12 @@ packages:
integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==,
}
+ "@types/emscripten@1.40.1":
+ resolution:
+ {
+ integrity: sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg==,
+ }
+
"@types/estree@1.0.8":
resolution:
{
@@ -1898,6 +1907,15 @@ packages:
integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==,
}
+ "@yudiel/react-qr-scanner@2.3.1":
+ resolution:
+ {
+ integrity: sha512-fE7217QvMKT/AxAEeKIheFhkKO13PSGHiqJfg4dLK/SGPpelpXNpZZ0Qeph1Cm08/AqMlGhvzQbCmoPeD/VVIg==,
+ }
+ peerDependencies:
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+
acorn-jsx@5.3.2:
resolution:
{
@@ -2120,6 +2138,12 @@ packages:
integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==,
}
+ barcode-detector@3.0.3:
+ resolution:
+ {
+ integrity: sha512-N07CNbpudOB3oIYm0tvaezCM6zy9HOlYnUCBhX6Q5UGhnqngyBgOf/p/5ZhqHxsf9/QYy5dX95RrblIs0hNaiw==,
+ }
+
base-64@1.0.0:
resolution:
{
@@ -5258,6 +5282,12 @@ packages:
integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==,
}
+ sdp@3.2.1:
+ resolution:
+ {
+ integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==,
+ }
+
semver@6.3.1:
resolution:
{
@@ -6202,6 +6232,13 @@ packages:
integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==,
}
+ webrtc-adapter@9.0.3:
+ resolution:
+ {
+ integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==,
+ }
+ engines: { node: ">=6.0.0", npm: ">=3.10.0" }
+
whatwg-url@5.0.0:
resolution:
{
@@ -6437,6 +6474,14 @@ packages:
integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==,
}
+ zxing-wasm@2.2.0:
+ resolution:
+ {
+ integrity: sha512-RyHxVaAHsLSDzmwcAG05IF8sVOE5Ta2JT1dRDh0mzVZOIiDXZstsjkqvKHasN1n4lvFSbX7ngkHDufnt/XI07Q==,
+ }
+ peerDependencies:
+ "@types/emscripten": ">=1.39.6"
+
snapshots:
"@ampproject/remapping@2.3.0":
dependencies:
@@ -7235,6 +7280,8 @@ snapshots:
dependencies:
"@types/ms": 2.1.0
+ "@types/emscripten@1.40.1": {}
+
"@types/estree@1.0.8": {}
"@types/fontkit@2.0.8":
@@ -7504,6 +7551,15 @@ snapshots:
"@vscode/l10n@0.0.18": {}
+ "@yudiel/react-qr-scanner@2.3.1(@types/emscripten@1.40.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)":
+ dependencies:
+ barcode-detector: 3.0.3(@types/emscripten@1.40.1)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ webrtc-adapter: 9.0.3
+ transitivePeerDependencies:
+ - "@types/emscripten"
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -7758,6 +7814,12 @@ snapshots:
balanced-match@1.0.2: {}
+ barcode-detector@3.0.3(@types/emscripten@1.40.1):
+ dependencies:
+ zxing-wasm: 2.2.0(@types/emscripten@1.40.1)
+ transitivePeerDependencies:
+ - "@types/emscripten"
+
base-64@1.0.0: {}
base64-js@1.5.1: {}
@@ -9873,6 +9935,8 @@ snapshots:
scheduler@0.26.0: {}
+ sdp@3.2.1: {}
+
semver@6.3.1: {}
semver@7.7.2: {}
@@ -10502,6 +10566,10 @@ snapshots:
webidl-conversions@3.0.1: {}
+ webrtc-adapter@9.0.3:
+ dependencies:
+ sdp: 3.2.1
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
@@ -10664,3 +10732,8 @@ snapshots:
zod@4.0.5: {}
zwitch@2.0.4: {}
+
+ zxing-wasm@2.2.0(@types/emscripten@1.40.1):
+ dependencies:
+ "@types/emscripten": 1.40.1
+ type-fest: 4.41.0
diff --git a/src/pages/staff/index.astro b/src/pages/staff/index.astro
new file mode 100644
index 0000000..020c20d
--- /dev/null
+++ b/src/pages/staff/index.astro
@@ -0,0 +1,21 @@
+---
+import Box from "@/components/firstdate/Box.astro";
+import Layout from "@/layouts/firstdate/WithoutNavbar.astro";
+---
+
+
+
+
+
+
From 560d6a0425889ac82a273f32449afdaa902b1f0f Mon Sep 17 00:00:00 2001
From: "Dhanavadh T. Saito" <105502355+dhanavadh@users.noreply.github.com>
Date: Tue, 15 Jul 2025 05:26:02 +0700
Subject: [PATCH 04/79] Feat: added staff-qr page
added staff-qr page
---
src/components/staff/QRScanner.tsx | 209 ++++++++++
src/layouts/staff/WithNavbar.astro | 2 +-
src/pages/staff/event/index.astro | 639 ++++++++++++++++++++++++++++-
src/pages/staff/index.astro | 21 -
4 files changed, 837 insertions(+), 34 deletions(-)
create mode 100644 src/components/staff/QRScanner.tsx
delete mode 100644 src/pages/staff/index.astro
diff --git a/src/components/staff/QRScanner.tsx b/src/components/staff/QRScanner.tsx
new file mode 100644
index 0000000..ac607e8
--- /dev/null
+++ b/src/components/staff/QRScanner.tsx
@@ -0,0 +1,209 @@
+import React, { useCallback, useEffect, useState } from "react";
+
+import { Scanner } from "@yudiel/react-qr-scanner";
+
+
+declare global {
+ interface Window {
+ handleQRScan?: (data: string) => Promise;
+ showErrorModal?: (message: string) => void;
+ }
+}
+
+interface QRScannerProps {
+ onScan: (data: string) => void;
+ onError?: (error: Error) => void;
+}
+
+const QRScanner: React.FC = ({ onScan, onError }) => {
+ const [permission, setPermission] = useState<"granted" | "denied" | "prompt">(
+ "prompt"
+ );
+ const [cameraFacing, setCameraFacing] = useState<"environment" | "user">(
+ "environment"
+ );
+ const [hasCamera, setHasCamera] = useState(true);
+
+ const checkCameraAvailability = useCallback(async (): Promise => {
+ try {
+ if (!navigator.mediaDevices?.getUserMedia) {
+ setHasCamera(false);
+ return;
+ }
+
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ const videoDevices = devices.filter(
+ (device): boolean => device.kind === "videoinput"
+ );
+
+ if (videoDevices.length === 0) {
+ setHasCamera(false);
+ return;
+ }
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ stream.getTracks().forEach((track): void => track.stop());
+ setPermission("granted");
+ setHasCamera(true);
+ } catch {
+ setPermission("denied");
+ setHasCamera(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ const isMobile =
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent
+ );
+ setCameraFacing(isMobile ? "environment" : "user");
+ void checkCameraAvailability();
+ }, [checkCameraAvailability]);
+
+ const handleScan = useCallback(
+ async (result: unknown): Promise => {
+ if (result && Array.isArray(result) && result.length > 0) {
+ const qrData = (result[0] as { rawValue: string }).rawValue;
+ const testQRData =
+ "id=7137ec7e-0f4b-4d65-88c4-982d36f7692f&userId=a8e7e644-ed12-4748-89fc-4599f868ab5d";
+
+ let finalQRData = qrData;
+ if (
+ qrData.includes("id=7137ec7e-0f4b-4d65-88c4-982d36f7692f") &&
+ qrData.includes("userId=a8e7e644-ed12-4748-89fc-4599f868ab5d")
+ ) {
+ finalQRData = testQRData;
+ } else {
+ const error = new Error("QR Code ไม่ถูกต้อง: ไม่ใช่ QR Code ของระบบ");
+ if (onError) {
+ onError(error);
+ }
+ if (window.showErrorModal) {
+ window.showErrorModal("QR Code ไม่ถูกต้อง: ไม่ใช่ QR Code ของระบบ");
+ }
+ return;
+ }
+
+ try {
+ if (typeof onScan === "function") {
+ onScan(finalQRData);
+ } else if (
+ window.handleQRScan &&
+ typeof window.handleQRScan === "function"
+ ) {
+ await window.handleQRScan(finalQRData);
+ }
+ } catch (scanError) {
+ if (onError) {
+ onError(scanError as Error);
+ }
+ }
+ }
+ },
+ [onScan, onError]
+ );
+
+ const handleError = useCallback(
+ (error: unknown): void => {
+ const err = error as Error;
+ if (err.name === "OverconstrainedError") {
+ const fallbackFacing =
+ cameraFacing === "environment" ? "user" : "environment";
+ setCameraFacing(fallbackFacing);
+ return;
+ }
+
+ if (onError) {
+ onError(err);
+ }
+ },
+ [cameraFacing, onError]
+ );
+
+ const requestCameraPermission = useCallback(async (): Promise => {
+ try {
+ await navigator.mediaDevices.getUserMedia({ video: true });
+ setPermission("granted");
+ } catch {
+ setPermission("denied");
+ }
+ }, []);
+
+ const getCameraConstraints = useCallback(() => {
+ return {
+ facingMode: { ideal: cameraFacing },
+ };
+ }, [cameraFacing]);
+
+ if (!hasCamera) {
+ return (
+
+
+ ไม่พบกล้องหรือไม่สามารถเข้าถึงกล้องได้
+
+
กรุณาใช้อุปกรณ์ที่มีกล้อง
+
+
+ );
+ }
+
+ if (permission === "denied") {
+ return (
+
+
ไม่สามารถเข้าถึงกล้องได้
+
+ กรุณาอนุญาตการเข้าถึงกล้องในการตั้งค่าเบราว์เซอร์
+
+
+
+ );
+ }
+
+ if (permission === "prompt") {
+ return (
+
+
กรุณาอนุญาตการเข้าถึงกล้องเพื่อสแกน QR Code
+
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default QRScanner;
diff --git a/src/layouts/staff/WithNavbar.astro b/src/layouts/staff/WithNavbar.astro
index 1111986..dc4a193 100644
--- a/src/layouts/staff/WithNavbar.astro
+++ b/src/layouts/staff/WithNavbar.astro
@@ -31,7 +31,7 @@ const { title, description } = Astro.props;
-
+