Skip to content

Commit 19fcf58

Browse files
committed
feat: fetch initial onboarding state + redirect
1 parent 79a933b commit 19fcf58

File tree

6 files changed

+128
-7
lines changed

6 files changed

+128
-7
lines changed

studio/src/components/dashboard/workspace-provider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { WorkspaceNamespace } from '@wundergraph/cosmo-connect/dist/platform/v1/
55
import { useRouter } from 'next/router';
66
import { useApplyParams } from '@/components/analytics/use-apply-params';
77
import { useLocalStorage } from '@/hooks/use-local-storage';
8+
import { useOnboardingNavigation } from '@/hooks/use-onboarding-navigation';
89
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
910

1011
const DEFAULT_NAMESPACE_NAME = 'default';
@@ -103,6 +104,8 @@ export function WorkspaceProvider({ children }: React.PropsWithChildren) {
103104
[namespace, namespaces, setStoredNamespace, applyParams],
104105
);
105106

107+
useOnboardingNavigation();
108+
106109
// Finally, render :)
107110
return (
108111
<WorkspaceContext.Provider
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
createContext,
3+
type Dispatch,
4+
useCallback,
5+
useContext,
6+
useMemo,
7+
useState,
8+
type SetStateAction,
9+
type ReactNode,
10+
} from 'react';
11+
import { PostHogFeatureFlagContext } from '../posthog-feature-flag-provider';
12+
13+
type Onboarding = {
14+
step: number;
15+
finishedAt?: Date;
16+
federatedGraphsCount: number;
17+
};
18+
19+
export interface OnboardingState {
20+
enabled: boolean;
21+
onboarding?: Onboarding;
22+
setOnboarding: Dispatch<SetStateAction<Onboarding | undefined>>;
23+
}
24+
25+
export const OnboardingContext = createContext<OnboardingState>({
26+
onboarding: undefined,
27+
enabled: false,
28+
setOnboarding: () => undefined,
29+
});
30+
31+
export const OnboardingProvider = ({ children }: { children: ReactNode }) => {
32+
const { onboarding: onboardingFlag, status: featureFlagStatus } = useContext(PostHogFeatureFlagContext);
33+
const [onboarding, setOnboarding] = useState<Onboarding | undefined>(undefined);
34+
35+
const value = useMemo(
36+
() => ({
37+
onboarding,
38+
enabled: Boolean(onboardingFlag.enabled && featureFlagStatus === 'success' && onboardingFlag),
39+
setOnboarding,
40+
}),
41+
[onboarding, onboardingFlag, featureFlagStatus],
42+
);
43+
44+
return <OnboardingContext.Provider value={value}>{children}</OnboardingContext.Provider>;
45+
};

studio/src/components/posthog-feature-flag-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const initialState: PostHogFeatureFlagState = {
3030
onboarding: { enabled: false },
3131
};
3232

33-
const PostHogFeatureFlagContext = createContext<PostHogFeatureFlagState>(initialState);
33+
export const PostHogFeatureFlagContext = createContext<PostHogFeatureFlagState>(initialState);
3434

3535
export const usePostHogFeatureFlags = () => useContext(PostHogFeatureFlagContext);
3636

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import Router from 'next/router';
3+
import { useOnboarding } from './use-onboarding';
4+
import { useQuery } from '@connectrpc/connect-query';
5+
import { getOnboarding } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery';
6+
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
7+
8+
/**
9+
* Manages the initial navigation to onboarding wizard and evaluates
10+
* the conditions based on feature flag and onboarding metadata
11+
*/
12+
export const useOnboardingNavigation = () => {
13+
const { enabled, onboarding, setOnboarding } = useOnboarding();
14+
const { data, isError, isPending, error } = useQuery(getOnboarding);
15+
const [initialLoadSuccess, setInitialLoadSuccess] = useState(false);
16+
17+
useEffect(
18+
function initialOnboardingFetch() {
19+
if (isPending) {
20+
return;
21+
}
22+
23+
if (isError || data?.response?.code !== EnumStatusCode.OK) {
24+
setInitialLoadSuccess(false);
25+
return;
26+
}
27+
28+
setInitialLoadSuccess(true);
29+
setOnboarding({
30+
step: Number(data.step ?? 0),
31+
finishedAt: data.finishedAt ? new Date(data.finishedAt) : undefined,
32+
federatedGraphsCount: data.federatedGraphsCount,
33+
});
34+
},
35+
[data, isError, isPending, setOnboarding, error],
36+
);
37+
38+
useEffect(
39+
function handleNavigationToOnboarding() {
40+
// Redirect user back if onboarding metadata failed
41+
if (!initialLoadSuccess && Router.pathname.startsWith('/onboarding')) {
42+
Router.replace('/');
43+
return;
44+
}
45+
46+
// Do not initiate redirect if we fail to fetch onboarding metadata. Fail silently in background.
47+
if (!initialLoadSuccess) {
48+
return;
49+
}
50+
51+
// Do not initiate redirect if the user is not eligible for onboarding
52+
if (!onboarding && !enabled) {
53+
return;
54+
}
55+
56+
// Do not initiate redirect if user has already finished the onboarding
57+
if (onboarding?.finishedAt) {
58+
return;
59+
}
60+
61+
const path = onboarding ? `/onboarding/${onboarding.step}` : '/onboarding';
62+
Router.replace(path);
63+
},
64+
[onboarding, enabled, initialLoadSuccess],
65+
);
66+
};

studio/src/hooks/use-onboarding.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { useContext } from 'react';
2+
import { OnboardingContext } from '@/components/onboarding/onboarding-provider';
3+
4+
export const useOnboarding = () => useContext(OnboardingContext);

studio/src/pages/_app.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import posthog from 'posthog-js';
2323
import { PostHogProvider } from 'posthog-js/react';
2424
import { withErrorBoundary } from '@sentry/nextjs';
2525
import { Footer } from '@/components/layout/footer';
26+
import { OnboardingProvider } from '@/components/onboarding/onboarding-provider';
2627

2728
const queryClient = new QueryClient();
2829

@@ -73,12 +74,14 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
7374
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
7475
<QueryClientProvider client={queryClient}>
7576
<PostHogFeatureFlagProvider>
76-
<AppProvider>
77-
<TooltipProvider>
78-
<Toaster />
79-
{getLayout(<Component {...pageProps} />)}
80-
</TooltipProvider>
81-
</AppProvider>
77+
<OnboardingProvider>
78+
<AppProvider>
79+
<TooltipProvider>
80+
<Toaster />
81+
{getLayout(<Component {...pageProps} />)}
82+
</TooltipProvider>
83+
</AppProvider>
84+
</OnboardingProvider>
8285
</PostHogFeatureFlagProvider>
8386
</QueryClientProvider>
8487
</ThemeProvider>

0 commit comments

Comments
 (0)