From e733dc5a138aa2d4ddcd4a886a9a5ee9f5027677 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Tue, 8 Sep 2026 19:06:07 -0500 Subject: [PATCH] Wire scanner feedback toasts and restore rescan uploads. Show scan notices and camera/identify errors in the page toast, patch scanImageUrl after background upload completes, and fix debug upload logging. Co-authored-by: Cursor --- lib/use-scanner-identification.js | 14 ++++++++---- lib/use-scanner-queue.js | 1 + pages/scanner.js | 36 ++++++++++++++++++++++--------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/use-scanner-identification.js b/lib/use-scanner-identification.js index aeed2c0..9f043f4 100644 --- a/lib/use-scanner-identification.js +++ b/lib/use-scanner-identification.js @@ -63,6 +63,7 @@ function readFileToImageData(file) { export function useScannerIdentification({ onCardScanned, + onScanImageUploaded, onError, onTrackerComplete, onTrackerReset, @@ -102,15 +103,20 @@ export function useScannerIdentification({ }, 3000); // 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); // Upload capture in background (fire-and-forget) - if (imageData) { + if (imageData && queueCardId) { uploadScanCapture(imageData) .then((url) => { - if (debugLog && isDebugMode) { - debugLog('📤', `Background upload complete: ${url ? 'success' : '429'}`); + if (url) { + onScanImageUploaded?.(queueCardId, url); + } + if (isDebugMode()) { + debugLog('📤', `Background upload complete: ${url ? 'success' : 'skipped'}`); } }) .catch((uploadError) => { diff --git a/lib/use-scanner-queue.js b/lib/use-scanner-queue.js index 120298d..173aea2 100644 --- a/lib/use-scanner-queue.js +++ b/lib/use-scanner-queue.js @@ -176,6 +176,7 @@ export function useScannerQueue({ user, sessionDestination, scanDefaults, deckMo persistCart(nextQueue, next); return next; }); + return cardEntry.id; }; const incrementCardQuantity = (cardId) => { diff --git a/pages/scanner.js b/pages/scanner.js index 9a8e1f9..1e9afc0 100644 --- a/pages/scanner.js +++ b/pages/scanner.js @@ -22,6 +22,8 @@ import { clearScannerCartStorage } from '../lib/scanner-session.js'; import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js'; const TOAST_DURATION_MS = 2500; +const NOTICE_TOAST_DURATION_MS = 8000; +const ERROR_TOAST_DURATION_MS = 5000; export default function Scanner() { const { user, loading: authLoading } = useAuth(); @@ -53,13 +55,24 @@ export default function Scanner() { const batchInputRef = useRef(null); 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 queue = useScannerQueue({ user, scanDefaults, deckMode: null }); const onVerifyCardRef = useRef(null); 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), verificationPausedRef, autoDetectPausedRef, @@ -67,7 +80,13 @@ export default function Scanner() { const identification = useScannerIdentification({ 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, onTrackerReset: camera.resetTrackedCard, videoRef: camera.videoRef, @@ -102,6 +121,11 @@ export default function Scanner() { disambiguationActiveRef.current = Boolean(identification.disambiguation); }, [identification.disambiguation]); + useEffect(() => { + if (!identification.scanNotice) return; + showPageToast(identification.scanNotice, 'info', NOTICE_TOAST_DURATION_MS); + }, [identification.scanNotice, showPageToast]); + useEffect(() => { const mobileCheckoutPauses = typeof window !== 'undefined' && @@ -137,14 +161,6 @@ export default function Scanner() { 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 = () => { clearScannerCartStorage(); queue.clearScannedCards();