refactor(playlists): scope module state in PlaylistsProvider

Lift the Playlists module's shared local state (rail name filter + selected
playlist) out of App into a split-context PlaylistsProvider, mirroring
FeedFiltersProvider. Header, PlaylistsRail, and the Playlists detail pane read
it via usePlaylists()/usePlaylistsActions() instead of props drilled through
App; the modules take only a uniform `me` prop now. App loses two useStates and
the selected-playlist persistence helper. (Platform S6b.2)
This commit is contained in:
2026-07-18 23:43:19 +02:00
parent f4a68575c5
commit d0fc061795
6 changed files with 95 additions and 48 deletions
+2 -23
View File
@@ -134,7 +134,6 @@ export default function App() {
// Client-side name filters for the Channels + Playlists lists — lifted here so the shared top
// SearchBar can drive them (the modules read/seed them via props).
const [channelsSearch, setChannelsSearch] = useState("");
const [playlistsSearch, setPlaylistsSearch] = useState("");
const [wizardOpen, setWizardOpen] = useState(false);
// Channel status chip, persisted so a reload (F5) keeps it; clamp a stale value at render.
const [channelFilterRaw, setChannelFilter] = useAccountPersistedState(LS.channelFilter, "all");
@@ -246,17 +245,6 @@ export default function App() {
writeAccount(LS.playlistsCollapsed, next);
api.savePrefs({ playlistsCollapsed: next }).catch(() => {});
}
// The selected playlist — App state so the App-level PlaylistsRail and the module's detail pane
// (Playlists) stay in sync. Persisted so a reload keeps it (it's not in the URL).
const [selectedPlaylistId, setSelectedPlaylistIdState] = useState<number | null>(() => {
const s = getAccountRaw(LS.playlist);
return s ? Number(s) : null;
});
const setSelectedPlaylistId = (id: number | null) => {
setSelectedPlaylistIdState(id);
if (id != null) setAccountRaw(LS.playlist, String(id));
};
useEffect(() => applyTheme(theme), [theme]);
// Apply the draft prefs locally for instant preview (their localStorage mirrors update via
@@ -611,10 +599,7 @@ export default function App() {
)}
{page === "playlists" && !channelView && (
<PlaylistsRail
canWrite={meQuery.data!.can_write}
search={playlistsSearch}
selectedId={selectedPlaylistId}
setSelectedId={setSelectedPlaylistId}
me={meQuery.data!}
layout={panelLayouts.playlists}
setLayout={(l) => setPanelLayout("playlists", l)}
collapsed={playlistsCollapsed}
@@ -663,8 +648,6 @@ export default function App() {
setPlexQ={setPlexQ}
channelsSearch={channelsSearch}
setChannelsSearch={setChannelsSearch}
playlistsSearch={playlistsSearch}
setPlaylistsSearch={setPlaylistsSearch}
onYtSearch={enterYtSearch}
onGoToFullHistory={() => {
setChannelFilter("needs_full");
@@ -715,11 +698,7 @@ export default function App() {
) : page === "audit" && meQuery.data!.role === "admin" ? (
<AuditLog />
) : page === "playlists" ? (
<Playlists
canWrite={meQuery.data!.can_write}
selectedId={selectedPlaylistId}
setSelectedId={setSelectedPlaylistId}
/>
<Playlists me={meQuery.data!} />
) : page === "notifications" ? (
<NotificationsPanel onFocusChannel={focusChannel} />
) : page === "messages" && !meQuery.data!.is_demo ? (