Split OnboardingContext into def + provider files

This commit is contained in:
Chad Curtis
2026-05-28 15:42:46 -05:00
parent f390a88f29
commit 239ec43fbd
5 changed files with 51 additions and 41 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import { useNsecPasteGuard } from "@/hooks/useNsecPasteGuard";
import type { AppConfig } from "@/contexts/AppContext";
import { NWCProvider } from "@/contexts/NWCContext";
import { OnboardingProvider } from "@/contexts/OnboardingContext";
import { OnboardingProvider } from "@/contexts/OnboardingProvider";
import { BuildConfigSchema, type BuildConfig } from "@/lib/schemas";
import { secureStorage } from "@/lib/secureStorage";
import AppRouter from "./AppRouter";
+1 -1
View File
@@ -32,7 +32,7 @@ import { Textarea } from '@/components/ui/textarea';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useLoginActions } from '@/hooks/useLoginActions';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useOnboarding, type OnboardingRole } from '@/contexts/OnboardingContext';
import { useOnboarding, type OnboardingRole } from '@/contexts/onboardingContextDef';
import { useToast } from '@/hooks/useToast';
import { useUploadFile } from '@/hooks/useUploadFile';
import { downloadTextFile } from '@/lib/downloadFile';
+1 -1
View File
@@ -26,7 +26,7 @@ import {
type NostrConnectStatus,
} from '@/hooks/useLoginActions';
import { useIsMobile } from '@/hooks/useIsMobile';
import { useOnboarding } from '@/contexts/OnboardingContext';
import { useOnboarding } from '@/contexts/onboardingContextDef';
interface AuthDialogProps {
isOpen: boolean;
+44
View File
@@ -0,0 +1,44 @@
import { useCallback, useState, type ReactNode } from 'react';
import {
OnboardingContext,
type OnboardingRole,
type StartSignupOptions,
} from './onboardingContextDef';
/**
* Provides captive-onboarding state to the whole tree.
*
* Designed for Ditto-style "fullscreen overlay wraps the router" usage: any
* CTA in the app calls `useOnboarding().startSignup()` and a sibling
* `<OnboardingGate>` renders the overlay on top of `<AppRouter />`.
*
* The provider component lives in a separate file from `OnboardingContext` /
* `useOnboarding` so the file containing the component has *only* component
* exports — required for React Fast Refresh to work cleanly on this file.
*/
export function OnboardingProvider({ children }: { children: ReactNode }) {
const [active, setActive] = useState(false);
const [role, setRoleState] = useState<OnboardingRole>(null);
const startSignup = useCallback((options?: StartSignupOptions) => {
setRoleState(options?.role ?? null);
setActive(true);
}, []);
const cancel = useCallback(() => {
setActive(false);
// Don't reset role here — let the consumer keep it through the close
// animation. We re-seed on the next startSignup().
}, []);
const setRole = useCallback((next: 'creator' | 'donor') => {
setRoleState(next);
}, []);
return (
<OnboardingContext.Provider value={{ active, role, startSignup, cancel, setRole }}>
{children}
</OnboardingContext.Provider>
);
}
@@ -1,9 +1,9 @@
import { createContext, useCallback, useContext, useState, type ReactNode } from 'react';
import { createContext, useContext } from 'react';
/**
* The two top-level roles a new user can pick during onboarding. Drives
* downstream copy (creator gets stronger nsec-wallet warnings; donor sees
* lighter copy) and the outro CTA target (creator /campaigns/new, donor /).
* downstream copy (creator vs. donor framing) and the role-pick CTA target
* (creator /campaigns/new, donor /campaigns/all).
*
* `null` before the user has answered the role-picker step.
*/
@@ -13,8 +13,7 @@ export type OnboardingRole = 'creator' | 'donor' | null;
export interface StartSignupOptions {
/**
* Pre-fill the role picker. CTAs that semantically already imply a role
* (e.g. "Start a campaign" in the home hero) can skip the role step by
* passing this. Without a role the flow asks on the second step.
* (e.g. "Start a campaign") can skip the role step by passing this.
*/
role?: 'creator' | 'donor';
}
@@ -35,39 +34,6 @@ export interface OnboardingContextValue {
export const OnboardingContext = createContext<OnboardingContextValue | undefined>(undefined);
/**
* Provides captive-onboarding state to the whole tree.
*
* Designed for Ditto-style "fullscreen overlay wraps the router" usage: any
* CTA in the app calls `useOnboarding().startSignup()` and a sibling
* `<OnboardingGate>` renders the overlay on top of `<AppRouter />`.
*/
export function OnboardingProvider({ children }: { children: ReactNode }) {
const [active, setActive] = useState(false);
const [role, setRoleState] = useState<OnboardingRole>(null);
const startSignup = useCallback((options?: StartSignupOptions) => {
setRoleState(options?.role ?? null);
setActive(true);
}, []);
const cancel = useCallback(() => {
setActive(false);
// Don't reset role here — let the consumer keep it through the close
// animation. We re-seed on the next startSignup().
}, []);
const setRole = useCallback((next: 'creator' | 'donor') => {
setRoleState(next);
}, []);
return (
<OnboardingContext.Provider value={{ active, role, startSignup, cancel, setRole }}>
{children}
</OnboardingContext.Provider>
);
}
/**
* Access the captive onboarding controller. Used by both consumers (CTAs
* that trigger signup) and the gate itself.