From 98b58cb1178d416088b7a862b0a36e31ee8815b5 Mon Sep 17 00:00:00 2001 From: valeboth <109695782+valeboth@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:29:40 +0300 Subject: [PATCH] feat: multi-select platforms + creator-only media type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two lobby/room fixes: Platforms β the single-select dropdown becomes a multi-select chip group, so someone with e.g. Netflix + Prime gets titles on EITHER. An explicit "Any" chip (default) clears the filter; the old "Any / Plex" label drops the "Plex" wording. platform_filter is now a CSV; the backend maps each key to its TMDb provider id and OR-joins them (with_watch_providers=8|119). Media type (movie/tv) β it's a shared, room-level setting (both users draw from ONE pool), so a joiner picking a different type would break overlap. Now creator-only: PATCH /rooms/:id is gated to user_a, and the swipe-screen toggle is hidden for the joiner (they inherit the creator's choice). New pure helper platformProviderParam (CSV β provider param) + rejectβ¦ unchanged; both unit-tested. typecheck, lint, vitest (12/12), build all pass. --- public/app.js | 31 ++++++++++++++++++++++++++++++- public/index.html | 18 +++++++++--------- public/style.css | 5 +++++ src/routes/rooms.ts | 6 ++++-- src/services/tmdb.ts | 20 +++++++++++++++++--- test/unit.test.ts | 19 +++++++++++++++++++ 6 files changed, 84 insertions(+), 15 deletions(-) diff --git a/public/app.js b/public/app.js index 80f9f72..a9589ca 100644 --- a/public/app.js +++ b/public/app.js @@ -42,6 +42,7 @@ const state = { seeds: [], // [{ tmdb_id, media_type, title }] editMode: false, // editing an existing profile (vs. first-time onboarding) mediaType: "movie", + platforms: new Set(), // selected streaming platforms (empty = any); creator-only room: null, soloMode: false, deck: [], @@ -321,6 +322,26 @@ function setupMediaToggle() { }); } +// Multi-select streaming platforms; empty set = "Any". Results include titles on ANY +// picked platform. The "Any" chip (data-platform="") clears the selection. +function syncPlatformChips() { + document.querySelectorAll("#platform-chips .chip").forEach((b) => { + const p = b.dataset.platform; + b.classList.toggle("on", p === "" ? state.platforms.size === 0 : state.platforms.has(p)); + }); +} +function setupPlatformChips() { + document.querySelectorAll("#platform-chips .chip").forEach((btn) => { + btn.onclick = () => { + const p = btn.dataset.platform; + if (p === "") state.platforms.clear(); // "Any" resets to no filter + else if (state.platforms.has(p)) state.platforms.delete(p); + else state.platforms.add(p); + syncPlatformChips(); + }; + }); +} + async function handleCreateRoom() { const err = $("lobby-error"); err.textContent = ""; @@ -330,7 +351,7 @@ async function handleCreateRoom() { body: JSON.stringify({ user_id: state.userId, media_type: state.mediaType, - platform_filter: $("platform-select").value || null, + platform_filter: [...state.platforms].join(",") || null, // CSV; empty = any solo: $("solo-check").checked, }), }); @@ -444,10 +465,17 @@ async function maybeTopUp() { } } +// The creator (user_a) owns the shared movie/tv choice; solo user is their own creator. +function isRoomCreator() { + return !!state.room && state.userId === state.room.user_a_id; +} + function syncMediaToggle() { document.querySelectorAll("#swipe-media-toggle .toggle-opt").forEach((b) => { b.classList.toggle("active", b.dataset.media === state.mediaType); }); + // Only the creator can switch type (shared pool); hide the control for the joiner. + $("swipe-media-toggle").classList.toggle("hidden", !isRoomCreator()); } async function toggleMedia(media) { @@ -1022,6 +1050,7 @@ function init() { renderAvoidChips(); setupSeedSearch(); setupMediaToggle(); + setupPlatformChips(); setupGestures(); $("onboarding-continue").onclick = handleOnboarding; diff --git a/public/index.html b/public/index.html index 276039c..197caea 100644 --- a/public/index.html +++ b/public/index.html @@ -65,15 +65,15 @@