diff --git a/frontend/public/sounds/scan-error.wav b/frontend/public/sounds/scan-error.wav new file mode 100644 index 0000000000..38dcd26e00 Binary files /dev/null and b/frontend/public/sounds/scan-error.wav differ diff --git a/frontend/public/sounds/scan-in-progress.wav b/frontend/public/sounds/scan-in-progress.wav new file mode 100644 index 0000000000..f0c4411156 Binary files /dev/null and b/frontend/public/sounds/scan-in-progress.wav differ diff --git a/frontend/public/sounds/scan-success.wav b/frontend/public/sounds/scan-success.wav new file mode 100644 index 0000000000..27ebae4582 Binary files /dev/null and b/frontend/public/sounds/scan-success.wav differ diff --git a/frontend/src/components/common/AttendeeCheckInTable/QrScanner.module.scss b/frontend/src/components/common/AttendeeCheckInTable/QrScanner.module.scss index 3cb3e6fafa..04c370f0be 100644 --- a/frontend/src/components/common/AttendeeCheckInTable/QrScanner.module.scss +++ b/frontend/src/components/common/AttendeeCheckInTable/QrScanner.module.scss @@ -40,6 +40,13 @@ z-index: 2; } + .soundToggle { + position: absolute; + bottom: 20px; + left: 20px; + z-index: 2; + } + .closeButton { position: absolute; top: 20px; diff --git a/frontend/src/components/common/AttendeeCheckInTable/QrScanner.tsx b/frontend/src/components/common/AttendeeCheckInTable/QrScanner.tsx index 6e309ed29a..fd5cf84de2 100644 --- a/frontend/src/components/common/AttendeeCheckInTable/QrScanner.tsx +++ b/frontend/src/components/common/AttendeeCheckInTable/QrScanner.tsx @@ -2,7 +2,7 @@ import {useEffect, useRef, useState} from 'react'; import QrScanner from 'qr-scanner'; import {useDebouncedValue} from '@mantine/hooks'; import classes from './QrScanner.module.scss'; -import {IconBulb, IconBulbOff, IconCameraRotate, IconX} from "@tabler/icons-react"; +import {IconBulb, IconBulbOff, IconCameraRotate, IconVolume, IconVolumeOff, IconX} from "@tabler/icons-react"; import {Anchor, Button, Menu} from "@mantine/core"; import {showError} from "../../../utilites/notifications.tsx"; import {t, Trans} from "@lingui/macro"; @@ -29,6 +29,20 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => { const [isScanFailed, setIsScanFailed] = useState(false); const [isScanSucceeded, setIsScanSucceeded] = useState(false); + const scanSuccessAudioRef = useRef(null); + const scanErrorAudioRef = useRef(null); + const scanInProgressAudioRef = useRef(null); + + const [isSoundOn, setIsSoundOn] = useState(() => { + const storedIsSoundOn = localStorage.getItem("qrScannerSoundOn"); + return storedIsSoundOn === null ? true : JSON.parse(storedIsSoundOn); + }); + + useEffect(() => { + localStorage.setItem("qrScannerSoundOn", JSON.stringify(isSoundOn)); + }, [isSoundOn]); + + useEffect(() => { latestProcessedAttendeeIdsRef.current = processedAttendeeIds; }, [processedAttendeeIds]); @@ -64,15 +78,23 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => { showError(t`You already scanned this ticket`); setIsScanFailed(true); - setInterval(function() { + setInterval(function () { setIsScanFailed(false); }, 500); + if (isSoundOn && scanErrorAudioRef.current) { + scanErrorAudioRef.current.play(); + } return; } if (!isCheckingIn && !alreadyScanned) { setIsCheckingIn(true); + + if (isSoundOn && scanInProgressAudioRef.current) { + scanInProgressAudioRef.current.play(); + } + props.onCheckIn(debouncedAttendeeId, (didSucceed) => { setIsCheckingIn(false); setProcessedAttendeeIds(prevIds => [...prevIds, debouncedAttendeeId]); @@ -80,14 +102,20 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => { if (didSucceed) { setIsScanSucceeded(true); - setInterval(function() { + setInterval(function () { setIsScanSucceeded(false); }, 500); + if (isSoundOn && scanSuccessAudioRef.current) { + scanSuccessAudioRef.current.play(); + } } else { setIsScanFailed(true); - setInterval(function() { + setInterval(function () { setIsScanFailed(false); }, 500); + if (isSoundOn && scanErrorAudioRef.current) { + scanErrorAudioRef.current.play(); + } } }, () => { setIsCheckingIn(false); @@ -126,6 +154,10 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => { } }; + const handleSoundToggle = () => { + setIsSoundOn(!isSoundOn); + }; + const requestPermission = async () => { setPermissionDenied(false); await startScanner(); @@ -184,6 +216,13 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => { {!isFlashAvailable && } {isFlashAvailable && } + +