refactor(wizard): move onboarding wizard state into WizardProvider

Lift the onboarding wizard's open state, first-login auto-open effect, and the
wizard render itself out of App into WizardProvider. Feed, Channels, and
SettingsPanel trigger it via useWizard() instead of an onOpenWizard prop; App
and ChannelPage drop the prop plumbing. First step of S6b.3 — removing the
per-module props that block a uniform registry render. (Platform S6b.3)
This commit is contained in:
2026-07-19 00:55:34 +02:00
parent 8434aac78d
commit dd921e3209
7 changed files with 87 additions and 37 deletions
+2 -20
View File
@@ -53,7 +53,6 @@ import ErrorDialog from "./components/ErrorDialog";
import VersionBanner from "./components/VersionBanner";
import DemoBanner from "./components/DemoBanner";
import { focusAccessRequestsTab } from "./lib/adminUsersTab";
import { shouldAutoOpenOnboarding } from "./lib/onboarding";
import { CURRENT_VERSION } from "./lib/releaseNotes";
// Route-level code splitting: each module page (and the heavier modals) is its own lazy chunk,
@@ -74,7 +73,6 @@ const SettingsPanel = lazy(() => import("./components/SettingsPanel"));
const NotificationsPanel = lazy(() => import("./components/NotificationsPanel"));
const Messages = lazy(() => import("./components/Messages"));
const DownloadCenter = lazy(() => import("./components/DownloadCenter"));
const OnboardingWizard = lazy(() => import("./components/OnboardingWizard"));
const About = lazy(() => import("./components/About"));
const ReleaseNotes = lazy(() => import("./components/ReleaseNotes"));
@@ -127,7 +125,6 @@ export default function App() {
plex: loadLayout("plex"),
playlists: loadLayout("playlists"),
}));
const [wizardOpen, setWizardOpen] = useState(false);
const [aboutOpen, setAboutOpen] = useState(false);
const [notesOpen, setNotesOpen] = useState(false);
const [notesHighlight, setNotesHighlight] = useState<string | undefined>(undefined);
@@ -337,13 +334,6 @@ export default function App() {
stripUrlParams();
}, [adminDeepLink, meQuery.data?.id]); // eslint-disable-line react-hooks/exhaustive-deps
// First-login onboarding: prompt the user to connect YouTube (and resume the flow after
// each consent redirect). Derived from granted scopes + storage flags so it's stable
// across the full-page OAuth round-trip; dismissible and reopenable from Settings.
useEffect(() => {
if (meQuery.data && shouldAutoOpenOnboarding(meQuery.data)) setWizardOpen(true);
}, [meQuery.data?.id, meQuery.data?.can_read, meQuery.data?.can_write]);
// On login, adopt server-stored preferences.
useEffect(() => {
const prefs = meQuery.data?.preferences;
@@ -576,7 +566,7 @@ export default function App() {
>
<Suspense fallback={pageFallback}>
{page === "channels" ? (
<Channels me={meQuery.data!} onOpenWizard={() => setWizardOpen(true)} />
<Channels me={meQuery.data!} />
) : page === "stats" ? (
<Stats me={meQuery.data!} />
) : page === "scheduler" && meQuery.data!.role === "admin" ? (
@@ -598,11 +588,7 @@ export default function App() {
) : page === "downloads" && !meQuery.data!.is_demo ? (
<DownloadCenter me={meQuery.data!} />
) : page === "settings" ? (
<SettingsPanel
me={meQuery.data!}
prefs={prefsCtl}
onOpenWizard={() => setWizardOpen(true)}
/>
<SettingsPanel me={meQuery.data!} prefs={prefsCtl} />
) : page === "plex" && meQuery.data!.plex_enabled ? (
<PlexBrowse />
) : (
@@ -613,7 +599,6 @@ export default function App() {
setView={setView}
canRead={meQuery.data!.can_read}
isDemo={meQuery.data!.is_demo}
onOpenWizard={() => setWizardOpen(true)}
ytSearch={ytSearch}
onYtSearch={enterYtSearch}
onExitYtSearch={exitYtSearch}
@@ -629,9 +614,6 @@ export default function App() {
</div>
{meQuery.data && !meQuery.data.is_demo && <ChatDock meId={meQuery.data.id} />}
<Suspense fallback={null}>
{wizardOpen && meQuery.data && !meQuery.data.is_demo && (
<OnboardingWizard me={meQuery.data} onClose={() => setWizardOpen(false)} />
)}
{aboutOpen && (
<About
onClose={() => setAboutOpen(false)}