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" ;
@@ -42,6 +43,8 @@ interface OnboardingContextValue {
4243 total : number ;
4344 isComplete : boolean ;
4445 dismissed : boolean ;
46+ isReady : boolean ;
47+ isResolved : boolean ;
4548 markDocsRead : ( ) => void ;
4649 dismiss : ( ) => void ;
4750 reopen : ( ) => void ;
@@ -50,11 +53,14 @@ interface OnboardingContextValue {
5053const OnboardingContext =
5154 createRequiredContext < OnboardingContextValue > ( "OnboardingProvider" ) ;
5255
53- function useHasMyRun ( ) : boolean {
56+ function useHasMyRun ( ) : {
57+ hasRun : boolean ;
58+ isLoading : boolean ;
59+ } {
5460 const { available, backendUrl } = useBackend ( ) ;
5561 const filterQuery = filtersToFilterQuery ( parseFilterParam ( "created_by:me" ) ) ;
5662
57- const { data } = useQuery ( {
63+ const { data, isLoading } = useQuery ( {
5864 queryKey : [ ...ONBOARDING_MY_RUN_COUNT_KEY , backendUrl ] ,
5965 enabled : available && Boolean ( backendUrl ) ,
6066 staleTime : STALE_MS ,
@@ -68,19 +74,23 @@ function useHasMyRun(): boolean {
6874 return payload . pipeline_runs ?. length ?? 0 ;
6975 } ,
7076 } ) ;
71- return ( data ?? 0 ) > 0 ;
77+ return { hasRun : ( data ?? 0 ) > 0 , isLoading } ;
7278}
7379
7480export function OnboardingProvider ( { children } : { children : ReactNode } ) {
7581 const { track } = useAnalytics ( ) ;
76- const { data : progress } = useOnboardingProgress ( ) ;
82+ const notify = useToastNotification ( ) ;
83+ const { ready : backendReady , configured } = useBackend ( ) ;
84+ const { data : progress , isLoading : progressLoading } =
85+ useOnboardingProgress ( ) ;
7786 const persist = usePersistOnboardingProgress ( ) ;
7887
79- const { data : tourCompletions } = useTourCompletions ( ) ;
88+ const { data : tourCompletions , isLoading : toursLoading } =
89+ useTourCompletions ( ) ;
8090 const hasCompletedTour = Boolean (
8191 tourCompletions && Object . keys ( tourCompletions ) . length > 0 ,
8292 ) ;
83- const hasMyRun = useHasMyRun ( ) ;
93+ const { hasRun : hasMyRun , isLoading : runsLoading } = useHasMyRun ( ) ;
8494
8595 const stored = progress ?. steps ;
8696 const desiredSteps : OnboardingSteps = {
@@ -91,6 +101,8 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
91101 } ;
92102
93103 const isComplete = ONBOARDING_STEP_IDS . every ( ( id ) => desiredSteps [ id ] ) ;
104+ const isReady = ! progressLoading && ! toursLoading && ! runsLoading ;
105+ const isResolved = ( backendReady || ! configured ) && isReady ;
94106
95107 const [ pipelineWriteCount , setPipelineWriteCount ] = useState ( 0 ) ;
96108
@@ -117,6 +129,32 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
117129 track ( "onboarding.step.completed" , { step_id : "create_pipeline" } ) ;
118130 } , [ pipelineWriteCount , progress , persist , track ] ) ;
119131
132+ const completedRef = useRef < Set < string > | null > ( null ) ;
133+
134+ useEffect ( ( ) => {
135+ if ( ! isResolved ) return ;
136+ const current = new Set (
137+ ONBOARDING_STEP_IDS . filter ( ( id ) => desiredSteps [ id ] ) ,
138+ ) ;
139+ const previous = completedRef . current ;
140+ completedRef . current = current ;
141+ if ( previous === null ) return ;
142+
143+ const newlyCompleted = ONBOARDING_STEP_IDS . filter (
144+ ( id ) => current . has ( id ) && ! previous . has ( id ) ,
145+ ) ;
146+ if ( newlyCompleted . length === 0 ) return ;
147+
148+ if ( isComplete ) {
149+ notify ( "You're all set up - onboarding complete!" , "success" ) ;
150+ return ;
151+ }
152+ for ( const id of newlyCompleted ) {
153+ const label = ONBOARDING_STEPS . find ( ( step ) => step . id === id ) ?. label ;
154+ if ( label ) notify ( `Completed: ${ label } ` , "success" ) ;
155+ }
156+ } , [ isResolved , desiredSteps , isComplete , notify ] ) ;
157+
120158 const markDocsRead = ( ) => {
121159 if ( ! progress || progress . steps . read_docs ) return ;
122160 persist ( { ...progress , steps : { ...progress . steps , read_docs : true } } ) ;
@@ -129,6 +167,7 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
129167 if ( ! progress || progress . dismissed ) return ;
130168 persist ( { ...progress , dismissed : true } ) ;
131169 track ( "onboarding.dismissed" ) ;
170+ notify ( "You can resume onboarding from the Learning Hub" , "info" ) ;
132171 } ;
133172
134173 const reopen = ( ) => {
@@ -148,6 +187,8 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
148187 total : steps . length ,
149188 isComplete,
150189 dismissed : progress ?. dismissed ?? false ,
190+ isReady,
191+ isResolved,
151192 markDocsRead,
152193 dismiss,
153194 reopen,
0 commit comments