diff --git a/README.md b/README.md index c7f7e51..219fe41 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,8 @@ The database will be automatically initialized and populated with games from `ga DB_PATH=./data/jackbox.db JWT_SECRET=your-secret-jwt-key ADMIN_KEY=admin123 + VOTE_APP_URL=https://vote-app.example.com + VOTE_APP_API_KEY=your-vote-app-api-key ``` 4. **Start the backend server** @@ -184,6 +186,13 @@ If `admins.json` is not found, the app falls back to the `ADMIN_KEY` environment To use a custom path for the admins file, set `ADMIN_CONFIG_PATH` in your environment. +### Sticker Wall Preview + +The backend proxies sticker wall images from the vote-app. Set these in `backend/.env`: + +- `VOTE_APP_URL` — base URL of the vote-app (e.g. `https://vote-app.example.com`) +- `VOTE_APP_API_KEY` — API key for the sticker wall render endpoint + For Docker, uncomment the volume mount in `docker-compose.yml`: ```yaml - ./backend/config/admins.json:/app/config/admins.json:ro diff --git a/frontend/src/hooks/useStickerWall.js b/frontend/src/hooks/useStickerWall.js index f25a34b..c3a5955 100644 --- a/frontend/src/hooks/useStickerWall.js +++ b/frontend/src/hooks/useStickerWall.js @@ -6,6 +6,7 @@ export function useStickerWall(sessionId) { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const urlRef = useRef(null); + const generationRef = useRef(0); const revokeUrl = useCallback(() => { if (urlRef.current) { @@ -23,20 +24,28 @@ export function useStickerWall(sessionId) { const refresh = useCallback(async () => { if (!sessionId) return; + const generation = generationRef.current; setLoading(true); setError(false); try { const res = await api.post(`/stickerwalls/${sessionId}/render`, null, { responseType: 'blob' }); + if (generation !== generationRef.current) return; setBlobUrl(res.data); } catch { - setError(true); + if (generation !== generationRef.current) return; + if (!urlRef.current) { + setError(true); + } } finally { - setLoading(false); + if (generation === generationRef.current) { + setLoading(false); + } } }, [sessionId, setBlobUrl]); useEffect(() => { - let stale = false; + generationRef.current += 1; + const generation = generationRef.current; if (!sessionId) { revokeUrl(); @@ -50,17 +59,17 @@ export function useStickerWall(sessionId) { setError(false); try { const res = await api.get(`/stickerwalls/${id}`, { responseType: 'blob' }); - if (stale) return; + if (generation !== generationRef.current) return; setBlobUrl(res.data); } catch (err) { - if (stale) return; + if (generation !== generationRef.current) return; if (err.response?.status === 404) { try { const renderRes = await api.post(`/stickerwalls/${id}/render`, null, { responseType: 'blob' }); - if (stale) return; + if (generation !== generationRef.current) return; setBlobUrl(renderRes.data); } catch { - if (stale) return; + if (generation !== generationRef.current) return; revokeUrl(); setImageUrl(null); setError(true); @@ -71,7 +80,7 @@ export function useStickerWall(sessionId) { setError(true); } } finally { - if (!stale) { + if (generation === generationRef.current) { setLoading(false); } } @@ -80,7 +89,6 @@ export function useStickerWall(sessionId) { fetchWall(sessionId); return () => { - stale = true; revokeUrl(); }; }, [sessionId, setBlobUrl, revokeUrl]);