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:
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
createContext,
|
||||
lazy,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "../lib/api";
|
||||
import { shouldAutoOpenOnboarding } from "../lib/onboarding";
|
||||
|
||||
const OnboardingWizard = lazy(() => import("./OnboardingWizard"));
|
||||
|
||||
// Owns the onboarding wizard's open state (and the wizard render itself), lifted out of App so any
|
||||
// module can trigger "connect YouTube" via a hook instead of an onOpenWizard prop threaded through
|
||||
// the tree. Mirrors the other split-context providers; composed in main.tsx around <App/>.
|
||||
//
|
||||
// The wizard auto-opens on first login for accounts that still need to connect YouTube (derived from
|
||||
// granted scopes + storage flags, so it's stable across the OAuth round-trip) and is reopenable from
|
||||
// Settings / the notify "reconnect" action. It reads `me` as a passive ["me"] observer (App owns the
|
||||
// fetching query), so it re-runs the auto-open check when the account resolves or its grants change.
|
||||
|
||||
interface WizardActions {
|
||||
openWizard: () => void;
|
||||
closeWizard: () => void;
|
||||
}
|
||||
|
||||
// Only the actions are shared — the open flag stays local to the provider (it renders the wizard
|
||||
// itself), so no consumer needs to subscribe to it.
|
||||
const ActionsContext = createContext<WizardActions>({ openWizard: () => {}, closeWizard: () => {} });
|
||||
|
||||
export function useWizard(): WizardActions {
|
||||
return useContext(ActionsContext);
|
||||
}
|
||||
|
||||
export function WizardProvider({ children }: { children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
// Passive observer of the account App fetches (enabled:false → reads cache, re-renders on update).
|
||||
const { data: me } = useQuery({ queryKey: ["me"], queryFn: api.me, enabled: false });
|
||||
|
||||
const openWizard = useCallback(() => setOpen(true), []);
|
||||
const closeWizard = useCallback(() => setOpen(false), []);
|
||||
|
||||
// First-login onboarding: prompt the user to connect YouTube (and resume the flow after each
|
||||
// consent redirect). Re-checked when the account or its grants change.
|
||||
useEffect(() => {
|
||||
if (me && shouldAutoOpenOnboarding(me)) setOpen(true);
|
||||
}, [me?.id, me?.can_read, me?.can_write]);
|
||||
|
||||
const actions = useMemo<WizardActions>(() => ({ openWizard, closeWizard }), [openWizard, closeWizard]);
|
||||
|
||||
return (
|
||||
<ActionsContext.Provider value={actions}>
|
||||
{children}
|
||||
{/* The wizard is a full-screen overlay; the demo account can't connect YouTube, so it never
|
||||
opens there. Lazy — its chunk loads only when first opened. */}
|
||||
<Suspense fallback={null}>
|
||||
{open && me && !me.is_demo && <OnboardingWizard me={me} onClose={closeWizard} />}
|
||||
</Suspense>
|
||||
</ActionsContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user