Files
Spanglish/frontend/src/app/admin/scanner/_lib/feedback.ts
T
MichilisandClaude Fable 5.1 515707c9e4 Close door-screen toasts on our own timer so they dismiss on touchscreens.
react-hot-toast pauses every countdown while the pointer is inside the
Toaster and schedules no dismissal for toasts created while paused. On a
touchscreen, tapping the Undo button synthesizes a mouseenter with no
mouseleave, so the "Undone" toast never went away. Every toast on the
door screen now uses duration: Infinity and is closed by dismissAfter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 17:54:49 +00:00

59 lines
1.9 KiB
TypeScript

import toast from 'react-hot-toast';
// Haptic + audio confirmation. At a loud, dark door the sound and the buzz are
// what staff actually register — the green flash is confirmation for the person
// standing in front of them.
export function playSuccessSound() {
try {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = 880;
osc.type = 'sine';
gain.gain.value = 0.3;
osc.start();
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
osc.stop(ctx.currentTime + 0.15);
} catch {}
}
export function playErrorSound() {
try {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = 300;
osc.type = 'square';
gain.gain.value = 0.2;
osc.start();
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3);
osc.stop(ctx.currentTime + 0.3);
} catch {}
}
export function vibrate(pattern: number | number[]) {
try {
if (navigator.vibrate) navigator.vibrate(pattern);
} catch {}
}
/**
* Dismiss a toast after `ms` regardless of react-hot-toast's hover/touch pause.
*
* The <Toaster> pauses every countdown while the pointer is "inside" its
* container, and schedules no dismissal at all for toasts created while paused.
* On a touchscreen, tapping a toast button (the Undo toast) synthesizes a
* mouseenter with no mouseleave until the next tap somewhere else — so the
* "Undone" toast that follows would sit there indefinitely. Callers create the
* toast with `duration: Infinity` and let this timer close it.
*/
export function dismissAfter(id: string, ms: number): string {
window.setTimeout(() => toast.dismiss(id), ms);
return id;
}