From 84f80008c682da2cf7f5c1502de04ef914a28fd9 Mon Sep 17 00:00:00 2001
From: TeeGooodGood <122004889+TeeGoood@users.noreply.github.com>
Date: Sat, 1 Feb 2025 17:16:40 +0700
Subject: [PATCH 1/2] feat: scan qr code
---
src/app/config.ts | 2 +-
src/components/profile/Ticket/EditButton.tsx | 2 +-
src/components/staff/qr/Modal.tsx | 9 ++-
src/components/staff/qr/qrbutton.tsx | 79 ++++++++------------
src/const/timer.ts | 3 +-
src/utils/qr.ts | 66 ++++++++++++++++
6 files changed, 108 insertions(+), 53 deletions(-)
create mode 100644 src/utils/qr.ts
diff --git a/src/app/config.ts b/src/app/config.ts
index c273ce4..96a2bd2 100644
--- a/src/app/config.ts
+++ b/src/app/config.ts
@@ -1,4 +1,4 @@
export const config = {
liffId: process.env.NEXT_PUBLIC_LIFF_ID || '',
- baseURL: process.env.NEXT_PUBLIC_BASE_URL || '',
+ baseURL: process.env.NEXT_PUBLIC_BASE_URL || ''
};
diff --git a/src/components/profile/Ticket/EditButton.tsx b/src/components/profile/Ticket/EditButton.tsx
index 5a72f65..bb4675f 100644
--- a/src/components/profile/Ticket/EditButton.tsx
+++ b/src/components/profile/Ticket/EditButton.tsx
@@ -15,7 +15,7 @@ export default function Edit() {
บัญชีของฉัน
diff --git a/src/components/staff/qr/Modal.tsx b/src/components/staff/qr/Modal.tsx
index 98876af..4e8346b 100644
--- a/src/components/staff/qr/Modal.tsx
+++ b/src/components/staff/qr/Modal.tsx
@@ -4,7 +4,7 @@ import { getImageURL } from '@/utils/image';
interface ModalProps {
modalType: 'confirm' | 'invalid' | 'already';
- userInfo: string | null;
+ userInfo: string | undefined;
scanAgain: () => void;
closeFn: () => void;
time?: string; // Optional prop for 'already' type
@@ -111,10 +111,13 @@ const Modal: React.FC
= ({
};
return (
-
+
-
+
e.stopPropagation()}
+ >
{renderContent()}
diff --git a/src/components/staff/qr/qrbutton.tsx b/src/components/staff/qr/qrbutton.tsx
index 3d76321..f8c62bc 100644
--- a/src/components/staff/qr/qrbutton.tsx
+++ b/src/components/staff/qr/qrbutton.tsx
@@ -3,74 +3,61 @@ import { useState } from 'react';
import { useLiff } from '@/contexts/liff';
import Modal from './Modal';
import { ScanLine } from 'lucide-react';
-import { apiClient } from '@/utils/axios';
import { useAuth } from '@/contexts/auth';
+import { scanQR } from '@/utils/qr';
export default function QrButton() {
const { client } = useLiff();
const { token } = useAuth();
- const [qrCodeValue, setQrCodeValue] = useState
(null);
- const [modalType, setModalType] = useState<
- 'confirm' | 'invalid' | 'already' | null
- >(null);
- const [isModalOpen, setIsModalOpen] = useState(true);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [modalType, setModalType] = useState<'confirm' | 'invalid' | 'already'>(
+ 'invalid',
+ );
+ const [userInfo, setUserInfo] = useState();
+ const [time, setTime] = useState();
const openQRScanner = async () => {
if (!client?.isInClient()) {
alert('Please use this feature inside the LINE app.');
return;
}
- try {
- const result = await client.scanCodeV2();
- const value = result.value ?? '';
- // Post the QR code result to the API
- const status = await postQrCodeToApi(value ?? '');
- setModalType(status?.toString() as 'confirm' | 'invalid' | 'already');
- setQrCodeValue(value);
- setIsModalOpen(true);
- } catch (error) {
- console.error('Error scanning QR code:', error);
- alert('Failed to scan QR code. Please try again.');
- setModalType('invalid');
- setIsModalOpen(true);
+ if (!token?.accessToken) {
+ console.error('Failed scaning QR: no accessToken');
+ return;
}
- };
- const postQrCodeToApi = async (value: string) => {
- try {
- const response = await apiClient.post(
- `/users/qr/${value}`,
- {},
- {
- headers: {
- Authorization: `Bearer ${token?.accessToken}`,
- },
- },
- );
- if (response.status === 200) {
- return 'confirm';
- } else if (response.status === 400) {
- return 'already';
- } else if (response.status === 500) {
- return 'invalid';
- } else {
- return 'invalid';
- }
- } catch (error) {
- console.error('Error posting QR code to API:', error);
- return error;
+ const scanResult = await client.scanCodeV2();
+ const userId = scanResult.value;
+
+ if (!userId) {
+ console.error('Failed scaning QR: No userId');
+ return;
}
+
+ const { modalType, time, userInfo } = await scanQR(
+ userId,
+ token.accessToken,
+ );
+
+ setIsModalOpen(true);
+ setModalType(modalType);
+ setTime(time);
+ setUserInfo(userInfo);
};
+
return (
-
+
-
คลิกเพื่อสแกน
+
+ คลิกเพื่อสแกน
+
{isModalOpen && modalType != null && (
setIsModalOpen(false)}
modalType={modalType}
diff --git a/src/const/timer.ts b/src/const/timer.ts
index c0812ba..d317c3f 100644
--- a/src/const/timer.ts
+++ b/src/const/timer.ts
@@ -9,8 +9,7 @@ if (!rawTargetDate) {
throw new Error('Environment variable NEXT_PUBLIC_TARGET_DATE is missing.');
}
-const t = rawTargetDate.split(/[^0-9]/).map(t => parseInt(t));
-const parsedDate = new Date(t[0], t[1] - 1, t[2]);
+const parsedDate = new Date(rawTargetDate);
if (isNaN(parsedDate.getTime())) {
throw new Error(
`Invalid date format in NEXT_PUBLIC_TARGET_DATE: ${rawTargetDate}`,
diff --git a/src/utils/qr.ts b/src/utils/qr.ts
new file mode 100644
index 0000000..7fd0515
--- /dev/null
+++ b/src/utils/qr.ts
@@ -0,0 +1,66 @@
+import axios from 'axios';
+import { apiClient } from './axios';
+
+export interface ScanQRResp {
+ age: 'string';
+ chronicDisease: 'string';
+ drugAllergy: 'string';
+ education: 'studying';
+ email: 'string';
+ faculty: 'string';
+ foodLimitation: 'string';
+ graduatedYear: 'string';
+ id: 'string';
+ imageUrl: 'string';
+ invitationCode: 'string';
+ lastEntered: 'string';
+ name: 'string';
+ phone: 'string';
+ role: 'member';
+ sizeJersey: 'string';
+ status: 'chula_student';
+ uid: 'string';
+ university: 'string';
+}
+
+export interface ScanQRResult {
+ modalType: 'confirm' | 'invalid' | 'already';
+ userInfo?: string;
+ time?: string;
+}
+
+export interface ScanQRError {
+ error: string;
+ message: string;
+}
+
+export async function scanQR(
+ userId: string,
+ accessToken: string,
+): Promise {
+ try {
+ const resp = await apiClient.post(
+ `/users/qr/${userId}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ },
+ );
+ return { modalType: 'confirm', userInfo: resp.data.name };
+ } catch (err: unknown) {
+ if (axios.isAxiosError(err)) {
+ switch (err.response?.status) {
+ case 400:
+ return {
+ modalType: 'already',
+ time: new Date(err.response.data.message).toLocaleString(),
+ };
+ default:
+ return { modalType: 'invalid' };
+ }
+ }
+ return { modalType: 'invalid' };
+ }
+}
From b243c3e832cd33f00a34ccde199db835d77a2177 Mon Sep 17 00:00:00 2001
From: TeeGooodGood <122004889+TeeGoood@users.noreply.github.com>
Date: Sat, 1 Feb 2025 22:17:04 +0700
Subject: [PATCH 2/2] feat: add isAcroPhobia field
---
src/app/(user)/edit/_components/form.tsx | 9 +++++++++
src/app/(user)/register/_components/subpages/two.tsx | 10 +++++-----
src/schema/edit.ts | 1 +
src/schema/register.ts | 1 +
src/schema/user.ts | 1 +
5 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/app/(user)/edit/_components/form.tsx b/src/app/(user)/edit/_components/form.tsx
index b13e4ee..5d0176b 100644
--- a/src/app/(user)/edit/_components/form.tsx
+++ b/src/app/(user)/edit/_components/form.tsx
@@ -22,6 +22,7 @@ import { SubmitHandler, useForm } from 'react-hook-form';
import { useAuth } from '@/contexts/auth';
import { useLiff } from '@/contexts/liff';
import toast from 'react-hot-toast';
+import Consent from '../../register/_components/policy/consent';
export default function Form() {
const { editError, edit, isEditing, user: defaultUser } = useAuth();
@@ -48,6 +49,7 @@ export default function Form() {
graduatedYear: defaultUser.graduatedYear,
faculty: defaultUser.faculty,
age: defaultUser.age,
+ isAcroPhobia: defaultUser.isAcroPhobia,
sizeJersey: defaultUser.sizeJersey,
foodLimitation: defaultUser.foodLimitation,
chronicDisease: defaultUser.chronicDisease,
@@ -252,6 +254,13 @@ export default function Form() {
+
+
setValue('isAcroPhobia', val)}
+ label="คุณกลัวความสูงหรือไม่"
+ isRequired={false}
+ />
{errors.chronicDisease?.message}
diff --git a/src/app/(user)/register/_components/subpages/two.tsx b/src/app/(user)/register/_components/subpages/two.tsx
index 1f9afbb..3d396f9 100644
--- a/src/app/(user)/register/_components/subpages/two.tsx
+++ b/src/app/(user)/register/_components/subpages/two.tsx
@@ -23,7 +23,7 @@ import { universities } from '@/const/universities';
import { faculties } from '@/const/faculties';
import { sizeJersey } from '@/const/size';
import { statusMap } from '@/const/status';
-// import Consent from '../policy/consent';
+import Consent from '../policy/consent';
interface TwoProps {
setStep: (value: number) => void;
@@ -255,13 +255,13 @@ export default function Two({ setStep, form }: TwoProps) {
- {/*
+
{}}
+ value={user.isAcroPhobia}
+ setValue={(val: boolean) => setValue('isAcroPhobia', val)}
label="คุณกลัวความสูงหรือไม่"
isRequired={false}
- /> */}
+ />
{errors.chronicDisease?.message}
diff --git a/src/schema/edit.ts b/src/schema/edit.ts
index e9fced7..9163a53 100644
--- a/src/schema/edit.ts
+++ b/src/schema/edit.ts
@@ -18,6 +18,7 @@ export const EditSchema = z.object({
sizeJersey: z.enum(sizeJersey, {
message: 'กรุณาเลือกขนาดเสื้อ',
}),
+ isAcroPhobia: z.boolean(),
education: z.enum(educations, { message: 'กรุณาเลือกการศึกษา' }),
foodLimitation: z.string(),
chronicDisease: z.string(),
diff --git a/src/schema/register.ts b/src/schema/register.ts
index d2b8686..a5e948a 100644
--- a/src/schema/register.ts
+++ b/src/schema/register.ts
@@ -18,6 +18,7 @@ export const RegisterSchema = z.object({
sizeJersey: z.enum(sizeJersey, {
message: 'กรุณาเลือกขนาดเสื้อ',
}),
+ isAcroPhobia: z.boolean().default(false),
education: z.enum(educations, { message: 'กรุณาเลือกการศึกษา' }),
foodLimitation: z.string(),
chronicDisease: z.string(),
diff --git a/src/schema/user.ts b/src/schema/user.ts
index 3047f4e..0453dac 100644
--- a/src/schema/user.ts
+++ b/src/schema/user.ts
@@ -18,6 +18,7 @@ export const UserSchema = z.object({
imageURL: z.string(),
invitationCode: z.string(),
lastEntered: z.string(),
+ isAcroPhobia: z.boolean(),
phone: z.string(),
role: z.enum(roles),
sizeJersey: z.enum(sizeJersey),