From 3d6bb1d2df50ee01b895cb82fef97a6b7b652c2b Mon Sep 17 00:00:00 2001 From: dominiccreates Date: Sun, 26 Jul 2026 10:36:28 -0700 Subject: [PATCH 1/2] fix(audio): add error state and handle autoplay re-init on src change --- src/components/audio/AudioPlayer.tsx | 38 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/components/audio/AudioPlayer.tsx b/src/components/audio/AudioPlayer.tsx index 3661274e..632ba904 100644 --- a/src/components/audio/AudioPlayer.tsx +++ b/src/components/audio/AudioPlayer.tsx @@ -27,26 +27,45 @@ export const AudioPlayer: React.FC = ({ }) => { const audioRef = useRef(null); const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + + // Reset state when source changes + useEffect(() => { + setIsLoading(true); + setHasError(false); + }, [src]); // When the audio can start playing, hide the loader. useEffect(() => { const audio = audioRef.current; if (!audio) return; - const handleCanPlay = () => setIsLoading(false); + const handleCanPlay = () => { + setIsLoading(false); + setHasError(false); + }; const handleStalled = () => setIsLoading(true); + const handleError = () => { + setIsLoading(false); + setHasError(true); + }; + audio.addEventListener('canplay', handleCanPlay); audio.addEventListener('stalled', handleStalled); + audio.addEventListener('error', handleError); // Cleanup listeners on unmount. return () => { audio.removeEventListener('canplay', handleCanPlay); audio.removeEventListener('stalled', handleStalled); + audio.removeEventListener('error', handleError); }; }, [src]); // Auto‑play if requested. useEffect(() => { if (autoPlay && audioRef.current) { - // eslint‑disable-next-line @typescript-eslint/no-floating-promises + // Reset autoplay correctly on source change + audioRef.current.load(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises audioRef.current.play().catch(() => { // Fail silently – the user may need to interact first. }); @@ -57,7 +76,7 @@ export const AudioPlayer: React.FC = ({
{/* Loading animation – visible only while isLoading is true */} - {isLoading && ( + {isLoading && !hasError && ( = ({
)} + {hasError && ( + + Failed to load audio + + )} {/* Native audio element – always present but hidden behind the loader */} @@ -94,7 +124,7 @@ export const AudioPlayer: React.FC = ({ src={src} controls={controls} className="w-full" - style={{ opacity: isLoading ? 0 : 1, transition: 'opacity 0.2s' }} + style={{ opacity: isLoading || hasError ? 0 : 1, transition: 'opacity 0.2s' }} /> ); From 7065bac6b4f752cceb5a3bfc0b053be1e6722cec Mon Sep 17 00:00:00 2001 From: dominiccreates Date: Sun, 26 Jul 2026 10:45:41 -0700 Subject: [PATCH 2/2] fix(qrcode): reserve stable dimensions for wrapper to prevent layout shift --- src/components/QRCode.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/QRCode.tsx b/src/components/QRCode.tsx index eeefcdd7..cd6bf105 100644 --- a/src/components/QRCode.tsx +++ b/src/components/QRCode.tsx @@ -73,7 +73,7 @@ export const QRCodeComponent = forwardRef +