Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ca2afa1
Add notification sounds for QR scan results; add sound toggle
grablair Oct 29, 2024
82199e0
Add scan check in in-progress beep
grablair Oct 29, 2024
4e50de2
Change sound-off icon color to a gray
grablair Oct 29, 2024
fe8b216
Change sound file paths to not use 'public'
grablair Oct 29, 2024
fdcbe56
Add failure sound to when an attendee code was already scanned that s…
grablair Oct 29, 2024
12c049e
Make the already-checked-in sound an error, not a success
grablair Oct 29, 2024
225ed54
Compress audio files and TS fixes
daveearley Oct 30, 2024
f39f020
Merge pull request #275 from grablair/feat/qr-scanning-sounds
daveearley Oct 30, 2024
268cce7
Change collapse arrow to icon to prevent iOS from converting unicode …
grablair Oct 31, 2024
ca4ed77
Do not wrap tier row group elements, ensure ticket counter is on righ…
grablair Oct 31, 2024
bfa934a
Add deploy workflow
daveearley Nov 2, 2024
b606ff1
Merge branch 'develop' into feature/pr-preview-environments
daveearley Nov 2, 2024
d580b8d
update triggers
daveearley Nov 2, 2024
1bba33a
Merge branch 'feature/pr-preview-environments' of github.com:HiEvents…
daveearley Nov 2, 2024
5c17717
yaml formatting
daveearley Nov 2, 2024
7698998
Merge pull request #278 from HiEventsDev/feature/pr-preview-environments
daveearley Nov 2, 2024
00caaf9
remove action
daveearley Nov 3, 2024
cb3cbd7
Change the collapse arrows to chevrons
grablair Nov 5, 2024
03ea042
Merge pull request #277 from grablair/fix/prevent-unicode-emoji-conve…
daveearley Nov 8, 2024
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
Binary file added frontend/public/sounds/scan-error.wav
Binary file not shown.
Binary file added frontend/public/sounds/scan-in-progress.wav
Binary file not shown.
Binary file added frontend/public/sounds/scan-success.wav
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
z-index: 2;
}

.soundToggle {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 2;
}

.closeButton {
position: absolute;
top: 20px;
Expand Down
47 changes: 43 additions & 4 deletions frontend/src/components/common/AttendeeCheckInTable/QrScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -29,6 +29,20 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => {
const [isScanFailed, setIsScanFailed] = useState(false);
const [isScanSucceeded, setIsScanSucceeded] = useState(false);

const scanSuccessAudioRef = useRef<HTMLAudioElement | null>(null);
const scanErrorAudioRef = useRef<HTMLAudioElement | null>(null);
const scanInProgressAudioRef = useRef<HTMLAudioElement | null>(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]);
Expand Down Expand Up @@ -64,30 +78,44 @@ 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]);
setCurrentAttendeeId(null);

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);
Expand Down Expand Up @@ -126,6 +154,10 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => {
}
};

const handleSoundToggle = () => {
setIsSoundOn(!isSoundOn);
};

const requestPermission = async () => {
setPermissionDenied(false);
await startScanner();
Expand Down Expand Up @@ -184,6 +216,13 @@ export const QRScannerComponent = (props: QRScannerComponentProps) => {
{!isFlashAvailable && <IconBulbOff color={'#ffffff95'} size={30}/>}
{isFlashAvailable && <IconBulb color={isFlashOn ? 'yellow' : '#ffffff95'} size={30}/>}
</Button>
<Button onClick={handleSoundToggle} variant={'transparent'} className={classes.soundToggle}>
{isSoundOn && <IconVolume color={'#ffffff95'} size={30}/>}
{!isSoundOn && <IconVolumeOff color={'#ffffff95'} size={30}/>}
</Button>
<audio ref={scanSuccessAudioRef} src="/sounds/scan-success.wav"/>
<audio ref={scanErrorAudioRef} src="/sounds/scan-error.wav"/>
<audio ref={scanInProgressAudioRef} src="/sounds/scan-in-progress.wav"/>
<Button onClick={handleClose} variant={'transparent'} className={classes.closeButton}>
<IconX color={'#ffffff95'} size={30}/>
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const TieredPricing = ({ticket, event, form, ticketIndex}: TieredPricingP
{ticket?.prices?.map((price, index) => {
return (
<div key={index} className={'hi-price-tier-row'}>
<Group justify={'space-between'}>
<Group justify={'space-between'} wrap={'nowrap'}>
<div className={'hi-price-tier'}>
<div className={'hi-price-tier-label'}>{price.label}</div>
<div className={'hi-price-tier-price'}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {PoweredByFooter} from "../../../common/PoweredByFooter";
import {Event} from "../../../../types.ts";
import {eventsClientPublic} from "../../../../api/event.client.ts";
import {promoCodeClientPublic} from "../../../../api/promo-code.client.ts";
import {IconX} from "@tabler/icons-react"
import {IconX, IconChevronRight} from "@tabler/icons-react"
import {getSessionIdentifier} from "../../../../utilites/sessionIdentifier.ts";
import {Constants} from "../../../../constants.ts";

Expand Down Expand Up @@ -297,7 +297,7 @@ const SelectTickets = (props: SelectTicketsProps) => {
.map((n) => n.toString());
quantityRange.unshift("0");

const [ticketIsCollapsed, {toggle: collapseTicket}] = useDisclosure(!ticket.start_collapsed);
const [ticketIsCollapsed, {toggle: collapseTicket}] = useDisclosure(ticket.start_collapsed);

return (
<div key={ticket.id} className={'hi-ticket-row'}>
Expand Down Expand Up @@ -328,15 +328,14 @@ const SelectTickets = (props: SelectTicketsProps) => {
{(!ticket.is_available && ticket.type === 'TIERED') && (
<TicketAvailabilityMessage ticket={ticket} event={event}/>
)}

<span className={'hi-ticket-collapse-arrow'}>
{ticketIsCollapsed ? '\u25BC' : '\u25B6'}
</span>
</div>
<span className={`hi-ticket-collapse-arrow`}>
<IconChevronRight className={ticketIsCollapsed ? "" : "open"} />
</span>
</UnstyledButton>
</div>

<Collapse in={ticketIsCollapsed} className={'hi-ticket-content'}>
<Collapse in={!ticketIsCollapsed} className={'hi-ticket-content'}>
<div className={'hi-price-tiers-rows'}>
<TieredPricing
ticketIndex={ticketIndex}
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/styles/widget/default.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
align-items: center;

h3 {
flex: 1;
margin: 10px 0;

a {
Expand All @@ -48,14 +49,23 @@
}

.hi-ticket-title-metadata {
.hi-ticket-collapse-arrow {
color: var(--widget-secondary-color, var(--tk-primary));
margin-left: 10px;
}

margin-left: 5px;
font-weight: normal;
}

.hi-ticket-collapse-arrow {
display: flex;
color: var(--widget-secondary-color, var(--tk-primary));
margin-left: 10px;

svg {
transition: all 0.1s linear;
}

svg.open {
transform: rotate(90deg);
}
}
}
}

Expand Down