7.3 KiB
Sticker Wall Preview — Design Spec
Display sticker wall images in two places: as a live preview in the Picker's sticker toggle card, and as a historical record in the SessionDetail view.
Backend — Proxy Endpoints
Two new Express routes in backend/routes/stickerwalls.js, both behind JWT auth middleware.
GET /api/stickerwalls/:sessionId
Proxies to the vote-app's GET /stickerwalls/{sessionID}.png.
- Streams the PNG response through to the client.
- Passes through status codes: 200 with
image/pngbody, 404 if no render exists. - Passes through
Cache-Controlheader from upstream.
POST /api/stickerwalls/:sessionId/render
Proxies to the vote-app's POST /api/stickerwalls/{sessionID}/render.
- Injects the
X-API-Keyheader server-side (from env var). - Streams back the PNG response.
- Forwards the
X-Sticker-Wall-Renderedresponse header (indicates fresh render vs cached). - Passes through status codes: 200 with image, 404 if session has no placed stickers.
Configuration
Two new environment variables:
VOTE_APP_URL— base URL of the vote-app (e.g.https://vote-app.example.com)VOTE_APP_API_KEY— API key for the render endpoint
Frontend — Shared Components
StickerWallThumbnail (frontend/src/components/StickerWallThumbnail.jsx)
Clickable image thumbnail for sticker walls. Used in both Picker and SessionDetail.
Props:
imageUrl— blob URL or null. When set, displays this image.loading— boolean, shows a spinner overlay while fetching.error— boolean, shows placeholder state ("No sticker wall").onClick— callback for enlarging (opens modal).
Rendering:
- Fixed small size, 16:9 aspect ratio. Scaled-down from 1920×1080.
- Rounded corners, subtle border matching surrounding card style.
- Three visual states: loading (spinner), error/empty (muted placeholder), loaded (image).
StickerWallModal (frontend/src/components/StickerWallModal.jsx)
Full-size image viewer modal. Used in both Picker and SessionDetail.
Props:
isOpen— boolean controlling visibility.onClose— callback to close.imageUrl— blob URL of the sticker wall image.
Behavior:
- Displays the sticker wall image at full/large size, constrained to viewport (
max-w-screen,max-h-screen,object-contain). - PNG with transparent background rendered over a dark backdrop so stickers are visible.
- Closes on: Escape key, overlay click, explicit close button (top-right ×).
- Follows existing Tailwind modal pattern (
fixed inset-0,z-50), built as a reusable component likeRoomCodeModal.
Shared Fetch Logic — useStickerWall Hook
frontend/src/hooks/useStickerWall.js — custom hook used by both Picker and SessionDetail.
Input: sessionId (triggers fetch when set/changed).
Returns: { imageUrl, loading, error, refresh } where refresh() manually calls the render endpoint.
Fetch sequence (runs on mount / sessionId change):
GET /api/stickerwalls/:sessionId— try the cached image.- If 404 →
POST /api/stickerwalls/:sessionId/render— attempt to generate one. - If that also 404s (no stickers exist) → set error state.
Both endpoints return image bytes. The hook fetches as blob, creates a blob URL via URL.createObjectURL, and revokes the previous URL to prevent memory leaks. Cleanup on unmount revokes the current URL.
refresh() method: Calls POST /api/stickerwalls/:sessionId/render directly (skipping the GET), updates imageUrl with the result. Used only by the Picker's refresh button.
This catches three cases:
- Sessions with a final rendered image (normal post-feature sessions) — served from step 1.
- Sessions with sticker data but no render yet (played between wall archival and retrieval deployment) — rendered in step 2.
- Sessions with no stickers at all — error state after step 2 fails.
Frontend — Picker (Live Session Sticker Card)
The existing sticker toggle card in Picker.jsx (~line 892) is expanded to include the sticker wall preview.
Layout (left to right)
- Thumbnail —
StickerWallThumbnailon the left side. Sized to fit the card height (~80–90px tall, ~142–160px wide at 16:9). Rounded corners, subtle border. - Refresh button — Immediately right of the thumbnail. Small, subtle icon button (refresh/sync icon). Muted styling (
text-gray-400 hover:text-indigo-500) so it doesn't compete with the mode toggle. Visually separated from the segmented control. - Flex spacer — Pushes the toggle control to the right edge.
- "Stickers" label + auto/show/hide segmented control — Unchanged, stays on the right.
Fetch behavior
- On session load (when
sessionIdis available), run the shared fetch logic. - The refresh button calls
POST /api/stickerwalls/:sessionId/renderand updates the thumbnail with the response. Shows a spinner on the button while rendering.
Click to enlarge
Clicking the thumbnail opens StickerWallModal with the current image.
State
Managed by the useStickerWall(sessionId) hook. Picker destructures { imageUrl, loading, error, refresh } — no additional local state needed beyond the modal open/close boolean.
Frontend — SessionDetail (History View)
A new "Sticker Wall" section in SessionDetail.jsx, positioned after the notes section and before the games list.
Card layout
- Section heading "Sticker Wall" matching existing heading style (Notes, Games, etc.).
- Contains
StickerWallThumbnail, slightly larger than in Picker (~120px tall). - No refresh button — historical sessions have immutable final images; the one-time render-on-404 from the shared fetch logic handles the edge case of pre-existing sessions.
Fetch behavior
On load, run the shared fetch logic (GET, then POST on 404, then placeholder on second 404).
Placeholder state
If no sticker wall exists (both GET and POST return 404), the card displays "No sticker wall for this session" in muted text.
Click to enlarge
Clicking the thumbnail opens StickerWallModal.
Active sessions in SessionDetail
If the session is still active (is_active === 1), the sticker wall section still appears with read-only fetch-on-load behavior. No refresh button — the Picker is the intended live interface.
Error Handling
- Network errors on proxy requests: Show a brief toast or inline error state in the thumbnail. Don't block the rest of the page.
- Render endpoint returns cached (X-Sticker-Wall-Rendered: false): No special UI treatment — just display the image. The admin can infer from visual inspection whether anything changed.
- Blob URL cleanup: Revoke object URLs on unmount and when replaced by a new fetch, to avoid memory leaks.
Files Changed
| File | Change |
|---|---|
backend/routes/stickerwalls.js |
New file — proxy endpoints |
backend/server.js (or wherever routes mount) |
Register stickerwalls routes |
frontend/src/hooks/useStickerWall.js |
New file — shared fetch/render hook |
frontend/src/components/StickerWallThumbnail.jsx |
New file — reusable thumbnail |
frontend/src/components/StickerWallModal.jsx |
New file — reusable full-size modal |
frontend/src/pages/Picker.jsx |
Expand sticker card with thumbnail, refresh button, modal |
frontend/src/pages/SessionDetail.jsx |
Add sticker wall section after notes |