Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: qrscanner send duplicate request #211

Merged
merged 6 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 103 additions & 14 deletions src/app/rpkm/staff/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,110 @@
'use client';

import Navbar from '@/components/rpkm/Navbar';
import Scan from '@/components/rpkm/staff/home/qrscanner/QRScanner';
import { useAuth } from '@/context/AuthContext';
import { createCheckIn, createCheckInByStudentId } from '@/utils/checkin';
import { getCurrentTime } from '@/utils/time';
import React, { useEffect, useState } from 'react';
import FailureModal from '@/components/rpkm/staff/home/qrscanner/failureModal';
import ConfirmationModal from '@/components/rpkm/staff/home/qrscanner/confirmationModal';
import { CheckIn } from '@/types/checkIn';
import { FRESHYNIGHT_EVENT, RPKM_DAY_1, RPKM_DAY_2 } from '@/utils/date';
import dayjs from 'dayjs';
import StudentCodeInput from '@/components/rpkm/staff/home/qrscanner/StudentCodeInput';

function Page() {
const [eventText, setEventText] = useState<string>('');
const { user } = useAuth();

//modal state
const [status, setStatus] = useState<'success' | 'error' | 'idle'>('idle');
const [taken, setTaken] = useState<boolean>(false);
const [error, setError] = useState<string | React.ReactNode>('');
const [errorTopic, setErrorTopic] = useState('');
const [checkInData, setCheckInData] = useState<CheckIn | null>(null);

useEffect(() => {
const initialize = async () => {
const currentTime = (await getCurrentTime()).currentTime;

const freshy_night_time = new Date(
process.env.NEXT_PUBLIC_FRESHY_NIGHT_EVENT as string
);
const rpkm_day_1_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_1 as string
);
const rpkm_day_2_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_2 as string
);

if (currentTime >= freshy_night_time) {
if (currentTime >= FRESHYNIGHT_EVENT) {
setEventText('Freshy Night');
} else if (currentTime >= rpkm_day_2_time) {
} else if (currentTime >= RPKM_DAY_1) {
setEventText('Onsite 4 สิงหาคม 2567');
} else if (currentTime >= rpkm_day_1_time) {
} else if (currentTime >= RPKM_DAY_2) {
setEventText('Onsite 3 สิงหาคม 2567');
}
};

initialize();
localStorage.setItem('enable', 'true');
}, []);

const handleCloseModal = () => {
setStatus('idle');
localStorage.setItem('enable', 'true');
};

const sendCheckInRequest = async (
mode: 'userId' | 'studentId',
id: string
) => {
if (!user || localStorage.getItem('enable') !== 'true') {
return;
}

//need to use localstorage to prevent user scaning multiple time
//don't use useState because it's not working with qrscanner
localStorage.setItem('enable', 'false');

let event = '';
const currentTime = (await getCurrentTime()).currentTime;

//need to check date every time because qr component don't update function when function re-render
if (currentTime >= FRESHYNIGHT_EVENT) {
event = 'freshy-night';
} else if (currentTime >= RPKM_DAY_2) {
event = 'rpkm-day-2';
} else if (currentTime >= RPKM_DAY_1) {
event = 'rpkm-day-1';
} else {
console.log('invalid date');
localStorage.setItem('enable', 'true');
return;
}

const newCheckInData: CheckIn | null =
mode == 'userId'
? await createCheckIn(id, user.email, event)
: await createCheckInByStudentId(id, user.email, event);

if (newCheckInData) {
if (newCheckInData.checkIn.isDuplicate) {
const date = dayjs(newCheckInData.checkIn.timestamp);
setStatus('error');
setError(
<div>
ผู้ใช้สแกน QR-code นี้แล้ว
<br />
{`เมื่อเวลา ${date.format('HH:mm')} น.`}
</div>
);
setErrorTopic('Already taken!');
setTaken(true);
return;
}

setCheckInData(newCheckInData);
setStatus('success');
} else {
setStatus('error');
setError('แสกนไม่สำเร็จ โปรดลองอีกครั้ง');
setErrorTopic('Invalid QR-code');
setTaken(false);
}
};

return (
<div className="bg-[#EAE3C3]">
<div className="bg-[url('/rpkm/staff/background.svg')] w-full bg-cover bg-no-repeat">
Expand Down Expand Up @@ -63,13 +135,30 @@ function Page() {
<div className="bg-[#183F86] w-4/5 text-white text-center rounded-r-full rounded-l-full">
{eventText}
</div>
<StudentCodeInput sendCheckInRequest={sendCheckInRequest} />
<div className="text-2xl font-semibold ">หรือ</div>
<div className="w-4/5 h-fit">
<Scan />
<Scan sendCheckInRequest={sendCheckInRequest} />
</div>
<div className="text-xl font-semibold">QR-Reader</div>
</div>
</div>
</div>

<ConfirmationModal
isOpen={status == 'success'}
userData={checkInData}
message="The check-in was successful."
onClose={handleCloseModal}
/>

<FailureModal
isOpen={status == 'error'}
message={error}
topic={errorTopic}
onClose={handleCloseModal}
taken={taken}
/>
</div>
);
}
Expand Down
130 changes: 21 additions & 109 deletions src/components/rpkm/staff/home/qrscanner/QRScanner.tsx
Original file line number Diff line number Diff line change
@@ -1,133 +1,45 @@
'use client';
import React, { ReactNode, useEffect, useState } from 'react';
import React from 'react';
import { QrReader } from 'react-qr-reader';
import { motion } from 'framer-motion';
import { useAuth } from '@/context/AuthContext';
import ConfirmationModal from './confirmationModal';
import FailureModal from './failureModal';
import { createCheckIn } from '@/utils/checkin';
import { CheckIn } from '@/types/checkIn';
import { getCurrentTime } from '@/utils/time';

const Scan: React.FC = () => {
const [checkInData, setCheckInData] = useState<CheckIn | null>(null);
const [taken, setTaken] = useState<boolean>(false);
const [status, setStatus] = useState<'success' | 'error' | 'idle'>('idle');
const { user } = useAuth();
const [error, setError] = useState<string | ReactNode>('');
const [errorTopic, setErrorTopic] = useState('');
interface ScanProps {
sendCheckInRequest: (
mode: 'userId' | 'studentId',
id: string
) => Promise<void>;
}

const Scan = ({ sendCheckInRequest }: ScanProps) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleScanResult = async (scanRawData: any) => {
if (!scanRawData || !user) {
const handleScanResult = (scanRawData: any) => {
if (!scanRawData || !scanRawData.text) {
return;
}

let event = '';
const currentTime = (await getCurrentTime()).currentTime;

//need to check date every time because qr component don't update function when function re-render
const freshy_night_time = new Date(
process.env.NEXT_PUBLIC_FRESHY_NIGHT_EVENT as string
);
const rpkm_day_1_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_1 as string
);
const rpkm_day_2_time = new Date(
process.env.NEXT_PUBLIC_RPKM_DAY_2 as string
);

if (currentTime > freshy_night_time) {
event = 'freshy-night';
} else if (currentTime > rpkm_day_2_time) {
event = 'rpkm-day-2';
} else if (currentTime > rpkm_day_1_time) {
event = 'rpkm-day-1';
}

//need to use localstorage to prevent user scan qr code multiple time
//i don't useState because it not work with qrscanner
const enable = localStorage.getItem('enable') === 'true';
if (!enable) return;

const userId = scanRawData.text;
const newCheckInData: CheckIn | null = await createCheckIn(
userId,
user.email,
event
);

localStorage.setItem('enable', 'false');

if (newCheckInData) {
/* if (newCheckInData.checkIn.isDuplicate) {
const date = dayjs(newCheckInData.checkIn.timestamp);
setStatus('error');
setError(
<div>
ผู้ใช้สแกน QR-code นี้แล้ว
<br />
{`เมื่อเวลา ${date.format('HH:mm')} น.`}
</div>
);
setErrorTopic('Already taken!');
setTaken(true);
return;
} */

setCheckInData(newCheckInData);
setStatus('success');
} else {
setStatus('error');
setError('แสกนไม่สำเร็จ โปรดลองอีกครั้ง');
setErrorTopic('Invalid QR-code');
setTaken(false);
}
sendCheckInRequest('userId', scanRawData.text);
};

const handleCloseModal = () => {
setStatus('idle');
};

useEffect(() => {
localStorage.setItem('enable', 'true');
}, []);

return (
<div className="flex flex-col items-center justify-center w-full">
<div className="relative w-full h-full">
<QrReader
className="bg-black"
onResult={handleScanResult}
constraints={{ facingMode: 'environment' }}
/>
<div className="overflow-hidden aspect-square">
<QrReader
scanDelay={0}
className="bg-black w-[100vw] aspect-square -mt-[13vw] -ml-[14vw]"
onResult={handleScanResult}
constraints={{ facingMode: 'environment' }}
/>
</div>

<motion.div
className="absolute left-1/2 top-1/2 h-48 w-48 max-w-md -translate-x-1/2 -translate-y-1/2 transform rounded-3xl border-4 border-white"
className="absolute left-1/2 top-1/2 h-[50vw] w-[50vw] max-w-md -translate-x-1/2 -translate-y-1/2 transform rounded-3xl border-4 border-white"
animate={{ opacity: [0.25, 0.5, 1, 0.5, 0.25] }}
transition={{ duration: 1, repeat: Infinity }}
></motion.div>
</div>
<div className="flex w-full items-center justify-center bg-black">
<div className="w-full bg-white text-center">
<ConfirmationModal
isOpen={status == 'success'}
userData={checkInData}
message="The check-in was successful."
onClose={handleCloseModal}
/>

<FailureModal
isOpen={status == 'error'}
message={error}
topic={errorTopic}
onClose={handleCloseModal}
taken={taken}
/>

{status == 'idle' && (
<p className="break-all text-black pt-2">Please scan a QR code</p>
)}
<p className="break-all text-black pt-2">Please scan a QR code</p>
</div>
</div>
</div>
Expand Down
52 changes: 52 additions & 0 deletions src/components/rpkm/staff/home/qrscanner/StudentCodeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';

interface StudentCodeInputProps {
sendCheckInRequest: (
mode: 'userId' | 'studentId',
id: string
) => Promise<void>;
}

const StudentCodeInput = ({ sendCheckInRequest }: StudentCodeInputProps) => {
const [studentId, setStudentId] = useState<string>('');

const handleOnCheckIn = async () => {
if (studentId.length !== 10) {
toast.error('กรุณากรอกรหัสนิสิตให้ครบ 10 หลัก');
return;
}

console.log(studentId);
await sendCheckInRequest('studentId', studentId);
};

const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
if (!'0123456789'.includes(value.at(-1) || '') || value.length > 10) {
return;
}
setStudentId(value);
};

return (
<div className="flex flex-col items-center justify-center gap-2">
<div className="text-lg">กรอกรหัสนิสิต</div>
<input
className="w-full px-4 py-1 text-xl border rounded-md border-project-dark-blue focus:outline-none focus:border-blue-500"
type="text"
value={studentId}
onChange={handleOnChange}
placeholder="รหัสนิสิต"
/>
<button
className="text-xl px-4 bg-rpkm-blue text-white rounded-md focus:outline-none"
onClick={handleOnCheckIn}
>
submit
</button>
</div>
);
};

export default StudentCodeInput;
Loading
Loading