From 239ec43fbd613274eb2798a5e34e277707faad41 Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Thu, 28 May 2026 15:42:46 -0500 Subject: [PATCH] Split OnboardingContext into def + provider files --- src/App.tsx | 2 +- src/components/OnboardingGate.tsx | 2 +- src/components/auth/AuthDialog.tsx | 2 +- src/contexts/OnboardingProvider.tsx | 44 +++++++++++++++++++ ...ingContext.tsx => onboardingContextDef.ts} | 42 ++---------------- 5 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 src/contexts/OnboardingProvider.tsx rename src/contexts/{OnboardingContext.tsx => onboardingContextDef.ts} (51%) diff --git a/src/App.tsx b/src/App.tsx index ff9818d8..207f4bff 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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"; diff --git a/src/components/OnboardingGate.tsx b/src/components/OnboardingGate.tsx index 89caa3c1..b9671637 100644 --- a/src/components/OnboardingGate.tsx +++ b/src/components/OnboardingGate.tsx @@ -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'; diff --git a/src/components/auth/AuthDialog.tsx b/src/components/auth/AuthDialog.tsx index 213c2a4c..d4e59146 100644 --- a/src/components/auth/AuthDialog.tsx +++ b/src/components/auth/AuthDialog.tsx @@ -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; diff --git a/src/contexts/OnboardingProvider.tsx b/src/contexts/OnboardingProvider.tsx new file mode 100644 index 00000000..c6060aa1 --- /dev/null +++ b/src/contexts/OnboardingProvider.tsx @@ -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 + * `` renders the overlay on top of ``. + * + * 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(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 ( + + {children} + + ); +} diff --git a/src/contexts/OnboardingContext.tsx b/src/contexts/onboardingContextDef.ts similarity index 51% rename from src/contexts/OnboardingContext.tsx rename to src/contexts/onboardingContextDef.ts index 06fdd1cc..d6d86b12 100644 --- a/src/contexts/OnboardingContext.tsx +++ b/src/contexts/onboardingContextDef.ts @@ -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(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 - * `` renders the overlay on top of ``. - */ -export function OnboardingProvider({ children }: { children: ReactNode }) { - const [active, setActive] = useState(false); - const [role, setRoleState] = useState(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 ( - - {children} - - ); -} - /** * Access the captive onboarding controller. Used by both consumers (CTAs * that trigger signup) and the gate itself.