Wire scanner feedback toasts and restore rescan uploads #157
3 changed files with 37 additions and 14 deletions
|
|
@ -63,6 +63,7 @@ function readFileToImageData(file) {
|
||||||
|
|
||||||
export function useScannerIdentification({
|
export function useScannerIdentification({
|
||||||
onCardScanned,
|
onCardScanned,
|
||||||
|
onScanImageUploaded,
|
||||||
onError,
|
onError,
|
||||||
onTrackerComplete,
|
onTrackerComplete,
|
||||||
onTrackerReset,
|
onTrackerReset,
|
||||||
|
|
@ -102,15 +103,20 @@ export function useScannerIdentification({
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
// Show result immediately — don't block on upload
|
// Show result immediately — don't block on upload
|
||||||
onCardScanned(buildScannedCardPayload(finalCard, { imageData, scanImageUrl: null, ocrMeta }));
|
const queueCardId = onCardScanned(
|
||||||
|
buildScannedCardPayload(finalCard, { imageData, scanImageUrl: null, ocrMeta })
|
||||||
|
);
|
||||||
finishTrackedCard(cardTracker);
|
finishTrackedCard(cardTracker);
|
||||||
|
|
||||||
// Upload capture in background (fire-and-forget)
|
// Upload capture in background (fire-and-forget)
|
||||||
if (imageData) {
|
if (imageData && queueCardId) {
|
||||||
uploadScanCapture(imageData)
|
uploadScanCapture(imageData)
|
||||||
.then((url) => {
|
.then((url) => {
|
||||||
if (debugLog && isDebugMode) {
|
if (url) {
|
||||||
debugLog('📤', `Background upload complete: ${url ? 'success' : '429'}`);
|
onScanImageUploaded?.(queueCardId, url);
|
||||||
|
}
|
||||||
|
if (isDebugMode()) {
|
||||||
|
debugLog('📤', `Background upload complete: ${url ? 'success' : 'skipped'}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((uploadError) => {
|
.catch((uploadError) => {
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ export function useScannerQueue({ user, sessionDestination, scanDefaults, deckMo
|
||||||
persistCart(nextQueue, next);
|
persistCart(nextQueue, next);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
return cardEntry.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementCardQuantity = (cardId) => {
|
const incrementCardQuantity = (cardId) => {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import { clearScannerCartStorage } from '../lib/scanner-session.js';
|
||||||
import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
|
import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
|
||||||
|
|
||||||
const TOAST_DURATION_MS = 2500;
|
const TOAST_DURATION_MS = 2500;
|
||||||
|
const NOTICE_TOAST_DURATION_MS = 8000;
|
||||||
|
const ERROR_TOAST_DURATION_MS = 5000;
|
||||||
|
|
||||||
export default function Scanner() {
|
export default function Scanner() {
|
||||||
const { user, loading: authLoading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
|
|
@ -53,13 +55,24 @@ export default function Scanner() {
|
||||||
const batchInputRef = useRef(null);
|
const batchInputRef = useRef(null);
|
||||||
const disambiguationActiveRef = useRef(false);
|
const disambiguationActiveRef = useRef(false);
|
||||||
|
|
||||||
|
const showPageToast = useCallback((message, type = 'success', durationMs = TOAST_DURATION_MS) => {
|
||||||
|
clearTimeout(toastTimerRef.current);
|
||||||
|
setPageToast({ message, visible: true, type });
|
||||||
|
toastTimerRef.current = setTimeout(() => {
|
||||||
|
setPageToast((current) => ({ ...current, visible: false }));
|
||||||
|
}, durationMs);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { scanDefaults } = useScannerSession();
|
const { scanDefaults } = useScannerSession();
|
||||||
const queue = useScannerQueue({ user, scanDefaults, deckMode: null });
|
const queue = useScannerQueue({ user, scanDefaults, deckMode: null });
|
||||||
|
|
||||||
const onVerifyCardRef = useRef(null);
|
const onVerifyCardRef = useRef(null);
|
||||||
|
|
||||||
const camera = useCameraScanner({
|
const camera = useCameraScanner({
|
||||||
onError: (msg) => console.error('Camera error:', msg),
|
onError: (msg) => {
|
||||||
|
console.error('Camera error:', msg);
|
||||||
|
showPageToast(msg, 'error', ERROR_TOAST_DURATION_MS);
|
||||||
|
},
|
||||||
onVerifyCard: (cardTracker) => onVerifyCardRef.current?.(cardTracker),
|
onVerifyCard: (cardTracker) => onVerifyCardRef.current?.(cardTracker),
|
||||||
verificationPausedRef,
|
verificationPausedRef,
|
||||||
autoDetectPausedRef,
|
autoDetectPausedRef,
|
||||||
|
|
@ -67,7 +80,13 @@ export default function Scanner() {
|
||||||
|
|
||||||
const identification = useScannerIdentification({
|
const identification = useScannerIdentification({
|
||||||
onCardScanned: queue.handleCardScanned,
|
onCardScanned: queue.handleCardScanned,
|
||||||
onError: (msg) => console.error('Identification error:', msg),
|
onScanImageUploaded: (cardId, url) => {
|
||||||
|
queue.updateCardMetadata(cardId, { scanImageUrl: url });
|
||||||
|
},
|
||||||
|
onError: (msg) => {
|
||||||
|
console.error('Identification error:', msg);
|
||||||
|
showPageToast(msg, 'error', ERROR_TOAST_DURATION_MS);
|
||||||
|
},
|
||||||
onTrackerComplete: camera.removeTrackedCard,
|
onTrackerComplete: camera.removeTrackedCard,
|
||||||
onTrackerReset: camera.resetTrackedCard,
|
onTrackerReset: camera.resetTrackedCard,
|
||||||
videoRef: camera.videoRef,
|
videoRef: camera.videoRef,
|
||||||
|
|
@ -102,6 +121,11 @@ export default function Scanner() {
|
||||||
disambiguationActiveRef.current = Boolean(identification.disambiguation);
|
disambiguationActiveRef.current = Boolean(identification.disambiguation);
|
||||||
}, [identification.disambiguation]);
|
}, [identification.disambiguation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!identification.scanNotice) return;
|
||||||
|
showPageToast(identification.scanNotice, 'info', NOTICE_TOAST_DURATION_MS);
|
||||||
|
}, [identification.scanNotice, showPageToast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mobileCheckoutPauses =
|
const mobileCheckoutPauses =
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
|
|
@ -137,14 +161,6 @@ export default function Scanner() {
|
||||||
return () => clearTimeout(toastTimerRef.current);
|
return () => clearTimeout(toastTimerRef.current);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const showPageToast = useCallback((message, type = 'success') => {
|
|
||||||
clearTimeout(toastTimerRef.current);
|
|
||||||
setPageToast({ message, visible: true, type });
|
|
||||||
toastTimerRef.current = setTimeout(() => {
|
|
||||||
setPageToast((current) => ({ ...current, visible: false }));
|
|
||||||
}, TOAST_DURATION_MS);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLeaveConfirm = () => {
|
const handleLeaveConfirm = () => {
|
||||||
clearScannerCartStorage();
|
clearScannerCartStorage();
|
||||||
queue.clearScannedCards();
|
queue.clearScannedCards();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue