11import { useQuery } from "@tanstack/react-query" ;
2- import { type ReactNode , useEffect , useState } from "react" ;
2+ import { type ReactNode , useEffect , useRef , useState } from "react" ;
33
44import type { ListPipelineJobsResponse } from "@/api/types.gen" ;
55import { useDocsVisitTracking } from "@/hooks/useDocsVisitTracking" ;
66import {
77 createRequiredContext ,
88 useRequiredContext ,
99} from "@/hooks/useRequiredContext" ;
10+ import useToastNotification from "@/hooks/useToastNotification" ;
1011import { useAnalytics } from "@/providers/AnalyticsProvider" ;
1112import { useBackend } from "@/providers/BackendProvider" ;
1213import { useTourCompletions } from "@/providers/TourProvider/tourCompletion" ;
@@ -41,6 +42,8 @@ interface OnboardingContextValue {
4142 total : number ;
4243 isComplete : boolean ;
4344 dismissed : boolean ;
45+ isReady : boolean ;
46+ isResolved : boolean ;
4447 markDocsRead : ( ) => void ;
4548 dismiss : ( ) => void ;
4649 reopen : ( ) => void ;
@@ -49,11 +52,14 @@ interface OnboardingContextValue {
4952const OnboardingContext =
5053 createRequiredContext < OnboardingContextValue > ( "OnboardingProvider" ) ;
5154
52- function useHasMyRun ( ) : boolean {
55+ function useHasMyRun ( ) : {
56+ hasRun : boolean ;
57+ isLoading : boolean ;
58+ } {
5359 const { available, backendUrl } = useBackend ( ) ;
5460 const filterQuery = filtersToFilterQuery ( parseFilterParam ( "created_by:me" ) ) ;
5561
56- const { data } = useQuery ( {
62+ const { data, isLoading } = useQuery ( {
5763 queryKey : [ "onboarding" , "myRunCount" , backendUrl ] ,
5864 enabled : available && Boolean ( backendUrl ) ,
5965 staleTime : STALE_MS ,
@@ -67,19 +73,23 @@ function useHasMyRun(): boolean {
6773 return payload . pipeline_runs ?. length ?? 0 ;
6874 } ,
6975 } ) ;
70- return ( data ?? 0 ) > 0 ;
76+ return { hasRun : ( data ?? 0 ) > 0 , isLoading } ;
7177}
7278
7379export function OnboardingProvider ( { children } : { children : ReactNode } ) {
7480 const { track } = useAnalytics ( ) ;
75- const { data : progress } = useOnboardingProgress ( ) ;
81+ const notify = useToastNotification ( ) ;
82+ const { ready : backendReady , configured } = useBackend ( ) ;
83+ const { data : progress , isLoading : progressLoading } =
84+ useOnboardingProgress ( ) ;
7685 const persist = usePersistOnboardingProgress ( ) ;
7786
78- const { data : tourCompletions } = useTourCompletions ( ) ;
87+ const { data : tourCompletions , isLoading : toursLoading } =
88+ useTourCompletions ( ) ;
7989 const hasCompletedTour = Boolean (
8090 tourCompletions && Object . keys ( tourCompletions ) . length > 0 ,
8191 ) ;
82- const hasMyRun = useHasMyRun ( ) ;
92+ const { hasRun : hasMyRun , isLoading : runsLoading } = useHasMyRun ( ) ;
8393
8494 const stored = progress ?. steps ;
8595 const desiredSteps : OnboardingSteps = {
@@ -90,6 +100,8 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
90100 } ;
91101
92102 const isComplete = ONBOARDING_STEP_IDS . every ( ( id ) => desiredSteps [ id ] ) ;
103+ const isReady = ! progressLoading && ! toursLoading && ! runsLoading ;
104+ const isResolved = ( backendReady || ! configured ) && isReady ;
93105
94106 const [ pipelineWriteCount , setPipelineWriteCount ] = useState ( 0 ) ;
95107
@@ -116,6 +128,32 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
116128 track ( "onboarding.step.completed" , { step_id : "create_pipeline" } ) ;
117129 } , [ pipelineWriteCount , progress , persist , track ] ) ;
118130
131+ const completedRef = useRef < Set < string > | null > ( null ) ;
132+
133+ useEffect ( ( ) => {
134+ if ( ! isResolved ) return ;
135+ const current = new Set (
136+ ONBOARDING_STEP_IDS . filter ( ( id ) => desiredSteps [ id ] ) ,
137+ ) ;
138+ const previous = completedRef . current ;
139+ completedRef . current = current ;
140+ if ( previous === null ) return ;
141+
142+ const newlyCompleted = ONBOARDING_STEP_IDS . filter (
143+ ( id ) => current . has ( id ) && ! previous . has ( id ) ,
144+ ) ;
145+ if ( newlyCompleted . length === 0 ) return ;
146+
147+ if ( isComplete ) {
148+ notify ( "You're all set up - onboarding complete!" , "success" ) ;
149+ return ;
150+ }
151+ for ( const id of newlyCompleted ) {
152+ const label = ONBOARDING_STEPS . find ( ( step ) => step . id === id ) ?. label ;
153+ if ( label ) notify ( `Completed: ${ label } ` , "success" ) ;
154+ }
155+ } , [ isResolved , desiredSteps , isComplete , notify ] ) ;
156+
119157 const markDocsRead = ( ) => {
120158 if ( ! progress || progress . steps . read_docs ) return ;
121159 persist ( { ...progress , steps : { ...progress . steps , read_docs : true } } ) ;
@@ -128,6 +166,7 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
128166 if ( ! progress || progress . dismissed ) return ;
129167 persist ( { ...progress , dismissed : true } ) ;
130168 track ( "onboarding.dismissed" ) ;
169+ notify ( "You can resume onboarding from the Learning Hub" , "info" ) ;
131170 } ;
132171
133172 const reopen = ( ) => {
@@ -147,6 +186,8 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
147186 total : steps . length ,
148187 isComplete,
149188 dismissed : progress ?. dismissed ?? false ,
189+ isReady,
190+ isResolved,
150191 markDocsRead,
151192 dismiss,
152193 reopen,
0 commit comments