Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-opti

We use [Connect](https://connect.build/) to unify the communication between all components of the cosmo platform. Connect is a framework build on top of [gRPC](https://grpc.io/) and simplify code-generation and reuse between `Studio` -> `Controlplane`.

## Source Maps (Firefox)

Firefox does not handle webpack's default `eval-source-map` devtool correctly ([webpack#9267](https://github.com/webpack/webpack/issues/9267)). To get proper source maps in Firefox DevTools, set:

```bash
NEXT_DEVTOOL=source-map pnpm dev
```

or with make:

```bash
NEXT_DEVTOOL=source-map make start-studio
```

This generates separate `.map` files instead of eval-based inline maps. Note: incremental rebuilds will be slower.

## Docker Info

We want runtime envs for docker for each on prem customer. Therefore we have two files to achieve this. One is .env.docker that uses a placeholder env name and an entrypoint.sh script that replaces all placeholder env name with the correct one at runtime in the .next folder. This also requires us to SSR the studio.
17 changes: 16 additions & 1 deletion studio/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,22 @@ const config = {
},
// This is done to reduce the production build size
// see: https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/
webpack: (config, { webpack }) => {
webpack: (config, { webpack, dev, isServer }) => {
// Firefox doesn't handle eval-based source maps well (webpack/webpack#9267).
// Replace Next.js's default EvalSourceMapDevToolPlugin with SourceMapDevToolPlugin
// to generate proper source maps. Opt-in via NEXT_DEVTOOL=source-map.
if (dev && !isServer && process.env.NEXT_DEVTOOL === 'source-map') {
config.plugins = config.plugins.filter((plugin) => plugin.constructor.name !== 'EvalSourceMapDevToolPlugin');
config.devtool = false;
config.plugins.push(
new webpack.SourceMapDevToolPlugin({
filename: '[file].map',
module: true,
columns: true,
}),
);
}

config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_TRACING__: !isSentryTracesEnabled,
Expand Down
24 changes: 17 additions & 7 deletions studio/src/components/app-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useCookieOrganization } from '@/hooks/use-cookie-organization';
import { setUser as setSentryUser } from '@sentry/nextjs';
import { OrganizationRole } from '@/lib/constants';
import { WorkspaceProvider } from '@/components/dashboard/workspace-provider';
import { useFeatureFlags } from '@/components/feature-flag-provider';
import { OnboardingProvider } from '@/components/onboarding/onboarding-provider';

const sessionQueryClient = new QueryClient();

Expand Down Expand Up @@ -107,7 +109,6 @@ const fetchSession = async () => {
export const AppProvider = ({ children }: { children: ReactNode }) => {
const router = useRouter();
const currentOrgSlug = router.query.organizationSlug;

// we store the current org slug in a cookie, so that we can redirect to the correct org after login
// as well as being able to access the cookie on the server.
const [cookieOrgSlug, setOrgSlugCookie] = useCookieOrganization();
Expand All @@ -121,6 +122,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
const queryClient = useQueryClient();

const [user, setUser] = useState<User>();
const { onboarding } = useFeatureFlags();

useEffect(() => {
if (!router.isReady) return;
Expand Down Expand Up @@ -184,16 +186,20 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {

setVerifiedOrganizationSlug(organization.slug);

if (
const shouldRedirectToOrg =
(router.pathname === '/' || router.pathname === '/login' || !currentOrg) &&
router.pathname !== '/account/invitations'
) {
router.pathname !== '/account/invitations';

// If onboarding is enabled, OnboardingProvider handles the redirect instead
if (onboarding.enabled) return;

if (shouldRedirectToOrg) {
const url = new URL(window.location.origin + router.basePath + router.asPath);
const params = new URLSearchParams(url.search);
router.replace(params.size !== 0 ? `/${organization.slug}?${params}` : `/${organization.slug}`);
}
}
}, [router, data, isFetching, error, cookieOrgSlug]);
}, [router, data, isFetching, error, cookieOrgSlug, onboarding.enabled]);

useEffect(() => {
if (!verifiedOrganizationSlug) {
Expand Down Expand Up @@ -235,7 +241,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
return (
<UserContext.Provider value={user}>
<SessionClientContext.Provider value={sessionQueryClient}>
<WorkspaceProvider>{children}</WorkspaceProvider>
<WorkspaceProvider>
<OnboardingProvider>{children}</OnboardingProvider>
</WorkspaceProvider>
</SessionClientContext.Provider>
</UserContext.Provider>
);
Expand All @@ -245,7 +253,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
<TransportProvider transport={transport}>
<UserContext.Provider value={user}>
<SessionClientContext.Provider value={sessionQueryClient}>
<WorkspaceProvider>{children}</WorkspaceProvider>
<WorkspaceProvider>
<OnboardingProvider>{children}</OnboardingProvider>
</WorkspaceProvider>
</SessionClientContext.Provider>
</UserContext.Provider>
</TransportProvider>
Expand Down
55 changes: 55 additions & 0 deletions studio/src/components/feature-flag-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ReactNode, createContext, useContext, useEffect, useReducer, useMemo } from 'react';
import { useFeatureFlagEnabled } from 'posthog-js/react';
import { Loader } from '@/components/ui/loader';

type FeatureFlagStatus = 'idle' | 'pending' | 'success';

interface FeatureFlagState {
status: FeatureFlagStatus;
onboarding: {
enabled: boolean;
};
}

type FeatureFlagAction = { type: 'LOADING' } | { type: 'LOADED'; onboardingEnabled: boolean };

function featureFlagReducer(_state: FeatureFlagState, action: FeatureFlagAction): FeatureFlagState {
switch (action.type) {
case 'LOADING':
return { status: 'pending', onboarding: { enabled: false } };
case 'LOADED':
return { status: 'success', onboarding: { enabled: action.onboardingEnabled } };
}
}

const initialState: FeatureFlagState = {
status: 'idle',
onboarding: { enabled: false },
};

const FeatureFlagContext = createContext<FeatureFlagState>(initialState);

export const useFeatureFlags = () => useContext(FeatureFlagContext);

export const FeatureFlagProvider = ({ children }: { children: ReactNode }) => {
const onboardingFlag = useFeatureFlagEnabled('cosmo-onboarding-v1');
const [state, dispatch] = useReducer(featureFlagReducer, initialState);

useEffect(() => {
if (onboardingFlag === undefined) {
dispatch({ type: 'LOADING' });
} else {
dispatch({ type: 'LOADED', onboardingEnabled: onboardingFlag });
}
}, [onboardingFlag]);

if (state.status !== 'success') {
return (
<div className="fixed inset-0 flex items-center justify-center bg-background">
<Loader />
</div>
);
}

return <FeatureFlagContext.Provider value={state}>{children}</FeatureFlagContext.Provider>;
};
Comment on lines +34 to +55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No timeout or fallback for feature flag loading.

If PostHog fails to initialize (ad blocker, network error, slow connection), onboardingFlag may remain undefined indefinitely, leaving users stuck on the loading screen (lines 46-52).

Consider adding a timeout that falls back to a default state:

🛡️ Proposed fix with timeout fallback
 export const FeatureFlagProvider = ({ children }: { children: ReactNode }) => {
   const onboardingFlag = useFeatureFlagEnabled('cosmo-onboarding-v1');
   const [state, dispatch] = useReducer(featureFlagReducer, initialState);

   useEffect(() => {
     if (onboardingFlag === undefined) {
       dispatch({ type: 'LOADING' });
     } else {
       dispatch({ type: 'LOADED', onboardingEnabled: onboardingFlag });
     }
   }, [onboardingFlag]);

+  // Fallback timeout if PostHog fails to load
+  useEffect(() => {
+    if (state.status === 'success') return;
+    
+    const timeout = setTimeout(() => {
+      if (state.status !== 'success') {
+        dispatch({ type: 'LOADED', onboardingEnabled: false });
+      }
+    }, 5000);
+    
+    return () => clearTimeout(timeout);
+  }, [state.status]);
+
   if (state.status !== 'success') {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/feature-flag-provider.tsx` around lines 34 - 55,
FeatureFlagProvider can hang on the loading screen because useFeatureFlagEnabled
may stay undefined; modify the useEffect that watches onboardingFlag to start a
timeout (e.g., 2–5s) that dispatches a fallback dispatch({ type: 'LOADED',
onboardingEnabled: <defaultBoolean> }) if onboardingFlag remains undefined, and
clear the timeout when onboardingFlag becomes defined or on unmount; keep the
existing dispatch behavior when onboardingFlag resolves, and ensure the timeout
is cleaned up to avoid dispatching after unmount.

2 changes: 1 addition & 1 deletion studio/src/components/layout/title-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const TitleLayout = ({
<div className={cn('flex flex-col justify-between gap-y-4 px-4 lg:flex-row lg:items-center lg:px-8')}>
<div>
<h1 className="text-lg font-semibold">{title}</h1>
<p className="text-sm text-muted-foreground">{subtitle}</p>
<div className="text-sm text-muted-foreground">{subtitle}</div>
</div>
{items}
</div>
Expand Down
143 changes: 143 additions & 0 deletions studio/src/components/onboarding/onboarding-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { useZodForm } from '@/hooks/use-form';
import { emailSchema, organizationNameSchema } from '@/lib/form-schemas';
import { useCurrentOrganization } from '@/hooks/use-current-organization';
import { Cross1Icon, PlusIcon } from '@radix-ui/react-icons';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { Controller, useFieldArray } from 'react-hook-form';
import { z } from 'zod';

const onboardingSchema = z.object({
organizationName: organizationNameSchema,
members: z.array(
z.object({
email: emailSchema,
}),
),
channels: z.object({
slack: z.boolean(),
email: z.boolean(),
}),
});

type OnboardingFormValues = z.infer<typeof onboardingSchema>;

export function OnboardingForm() {
const router = useRouter();
const org = useCurrentOrganization();

const {
register,
control,
handleSubmit,
reset,
formState: { isValid, errors },
} = useZodForm<OnboardingFormValues>({
mode: 'onChange',
schema: onboardingSchema,
defaultValues: {
organizationName: '',
members: [],
channels: { slack: true, email: false },
},
});

useEffect(() => {
if (org?.name) {
reset((prev) => ({ ...prev, organizationName: org.name }));
}
}, [org?.name, reset]);

const { fields, append, remove } = useFieldArray({
control,
name: 'members',
});

const onSubmit = (data: OnboardingFormValues) => {
// TODO: wire up submission
console.log(data);
};
Comment on lines +59 to +62
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove PII from client-side logging before merge.

console.log(data) includes invited member emails and can leak sensitive data in browser logs.

Proposed fix
-  const onSubmit = (data: OnboardingFormValues) => {
+  const onSubmit = (data: OnboardingFormValues): void => {
     // TODO: wire up submission
-    console.log(data);
+    // Avoid logging form payloads containing member emails.
+    if (process.env.NODE_ENV === 'development') {
+      console.debug('Onboarding form submitted');
+    }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onSubmit = (data: OnboardingFormValues) => {
// TODO: wire up submission
console.log(data);
};
const onSubmit = (data: OnboardingFormValues): void => {
// TODO: wire up submission
// Avoid logging form payloads containing member emails.
if (process.env.NODE_ENV === 'development') {
console.debug('Onboarding form submitted');
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/onboarding/onboarding-form.tsx` around lines 59 - 62,
The onSubmit handler currently logs raw OnboardingFormValues (including invited
member emails) via console.log(data); remove or replace this to avoid leaking
PII: either remove the console.log entirely or log a sanitized summary (e.g.,
only counts or masked values) instead. Update the onSubmit function (and any
helper used there) to strip/redact fields like invited members, emails, and
other personally identifying fields from the payload before any client-side
logging, keeping the original data intact for submission but never logging raw
PII.


return (
<form className="space-y-8" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-2">
<label className="text-sm font-medium">Organization Name</label>
<p className="text-sm text-muted-foreground">
This is your organization name. Feel free to keep it or change it.
</p>
<Input placeholder="Acme Inc." className="max-w-md" {...register('organizationName')} />
{errors.organizationName && <span className="text-sm text-destructive">{errors.organizationName.message}</span>}
Comment on lines +67 to +72
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Associate labels with inputs for screen-reader accessibility.

The organization input and dynamic member email inputs are not programmatically labeled, which reduces accessibility.

Proposed fix
-import { useEffect } from 'react';
+import { useEffect } from 'react';
...
-        <label className="text-sm font-medium">Organization Name</label>
+        <label htmlFor="organizationName" className="text-sm font-medium">Organization Name</label>
...
-        <Input placeholder="Acme Inc." className="max-w-md" {...register('organizationName')} />
+        <Input id="organizationName" placeholder="Acme Inc." className="max-w-md" {...register('organizationName')} />
...
-              <Input placeholder="janedoe@example.com" className="max-w-md" {...register(`members.${index}.email`)} />
+              <Input
+                aria-label={`Member email ${index + 1}`}
+                placeholder="janedoe@example.com"
+                className="max-w-md"
+                {...register(`members.${index}.email`)}
+              />

Also applies to: 79-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/onboarding/onboarding-form.tsx` around lines 67 - 72,
The Organization Name label and the dynamic member email inputs lack
programmatic association with their inputs; update the Input component usage for
organizationName (the call using register('organizationName')) to include an
explicit id (e.g., organizationNameId) and set the <label> htmlFor to that id,
add aria-invalid when errors.organizationName exists, and have the error <span>
referenced via aria-describedby; for the dynamic member email inputs (the inputs
created around lines 79-82) generate stable unique ids per member (e.g.,
memberEmail-{index} or using the member.id), set each label htmlFor to that id,
pass the id into the Input, add aria-invalid when the corresponding errors entry
exists, and link the error message with aria-describedby so screen readers
announce validation messages.

</div>

<div className="space-y-2">
<label className="text-sm font-medium">Invite Members</label>
<p className="text-sm text-muted-foreground">Add team members by email. You can always invite more later.</p>
<div className="space-y-2">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<Input placeholder="janedoe@example.com" className="max-w-md" {...register(`members.${index}.email`)} />
<Button type="button" variant="ghost" size="icon-sm" onClick={() => remove(index)}>
<Cross1Icon />
</Button>
</div>
))}
{fields.map((_, index) =>
errors.members?.[index]?.email ? (
<span key={index} className="text-sm text-destructive">
{errors.members[index].email.message}
</span>
) : null,
)}
</div>
<Button type="button" variant="outline" size="sm" onClick={() => append({ email: '' })}>
<PlusIcon className="mr-2" /> Add another
</Button>
</div>

<div className="space-y-2">
<label className="text-sm font-medium">Communication Channels</label>
<p className="text-sm text-muted-foreground">Choose how you&apos;d like to receive notifications.</p>
<div className="space-y-3">
<Controller
control={control}
name="channels.slack"
render={({ field }) => (
<label className="flex items-start gap-3">
<Checkbox checked={field.value} onCheckedChange={(checked) => field.onChange(checked === true)} />
<div className="flex flex-col gap-y-1">
<span className="text-sm font-medium leading-none">Slack</span>
<span className="text-sm text-muted-foreground">Get notified in your Slack workspace</span>
</div>
</label>
)}
/>
<Controller
control={control}
name="channels.email"
render={({ field }) => (
<label className="flex items-start gap-3">
<Checkbox checked={field.value} onCheckedChange={(checked) => field.onChange(checked === true)} />
<div className="flex flex-col gap-y-1">
<span className="text-sm font-medium leading-none">Email</span>
<span className="text-sm text-muted-foreground">Receive updates via email</span>
</div>
</label>
)}
/>
</div>
</div>

<div className="flex items-center gap-2">
<Button type="button" variant="outline" onClick={() => router.push(`/${org?.slug}/graphs`)}>
Skip
</Button>
Comment on lines +134 to +136
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against undefined organization slug before navigation.

If org is undefined, this navigates to /undefined/graphs, resulting in a 404 or unexpected behavior. Add a guard or disable the button when org is unavailable.

🛡️ Proposed fix
-        <Button type="button" variant="outline" onClick={() => router.push(`/${org?.slug}/graphs`)}>
+        <Button type="button" variant="outline" disabled={!org?.slug} onClick={() => router.push(`/${org?.slug}/graphs`)}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button type="button" variant="outline" onClick={() => router.push(`/${org?.slug}/graphs`)}>
Skip
</Button>
<Button type="button" variant="outline" disabled={!org?.slug} onClick={() => router.push(`/${org?.slug}/graphs`)}>
Skip
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/onboarding/onboarding-form.tsx` around lines 134 - 136,
The Skip Button in onboarding-form.tsx calls router.push(`/${org?.slug}/graphs`)
and can navigate to "/undefined/graphs" when org is undefined; change the Button
to be disabled when org?.slug is falsy (e.g., disabled={!org?.slug}) and/or wrap
the onClick handler (the router.push call) with a guard that only calls
router.push when org?.slug exists to prevent navigation to an invalid route.

<Button type="submit" disabled={!isValid}>
Continue
</Button>
</div>
</form>
);
}
17 changes: 17 additions & 0 deletions studio/src/components/onboarding/onboarding-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getDashboardLayout } from '@/components/layout/dashboard-layout';
import { Stepper, type StepperStep } from './stepper';

const ONBOARDING_STEPS: StepperStep[] = [
{ label: 'Settings' },
{ label: 'What is GraphQL Federation?' },
{ label: 'Create your first graph' },
{ label: 'Run your services' },
];

export const getOnboardingLayout = (page: React.ReactNode, currentStep: number) => {
return getDashboardLayout(
page,
'Onboarding',
<Stepper steps={ONBOARDING_STEPS} currentStep={currentStep} className="pt-2" />,
);
};
21 changes: 21 additions & 0 deletions studio/src/components/onboarding/onboarding-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ReactNode, useEffect } from 'react';
import Router from 'next/router';
import { useFeatureFlags } from '@/components/feature-flag-provider';

const ONBOARDING_PATH_PREFIX = '/onboarding';

export const OnboardingProvider = ({ children }: { children: ReactNode }) => {
const { onboarding } = useFeatureFlags();

useEffect(
function handleRedirectInOnboardingProvider() {
if (!onboarding.enabled) return;
if (Router.pathname.startsWith(ONBOARDING_PATH_PREFIX)) return;

Router.replace('/onboarding');
},
[onboarding.enabled],
);
Comment on lines +10 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find publicPaths definition in app-provider to confirm the paths that should be excluded
rg -n "publicPaths" --type=ts -C3

Repository: wundergraph/cosmo

Length of output: 1242


🏁 Script executed:

# Check the actual onboarding-provider.tsx file
cat -n studio/src/components/onboarding/onboarding-provider.tsx

Repository: wundergraph/cosmo

Length of output: 802


🏁 Script executed:

# Find where ONBOARDING_PATH_PREFIX is defined
rg -n "ONBOARDING_PATH_PREFIX" --type=ts -B2 -A2

Repository: wundergraph/cosmo

Length of output: 1054


🏁 Script executed:

# Check how onboarding feature flag is defined and used
rg -n "useFeatureFlags\|onboarding\.enabled" --type=ts -C2 | head -50

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Find where OnboardingProvider is imported/used
rg -n "OnboardingProvider" --type=tsx -B2 -A2

Repository: wundergraph/cosmo

Length of output: 88


🏁 Script executed:

# Check _app.tsx or main layout to see component tree ordering
fd -e tsx -e ts | grep -E "(_app|_document|app\.|layout\.)" | head -20

Repository: wundergraph/cosmo

Length of output: 597


🏁 Script executed:

# Look at feature flag provider to understand when onboarding flag is set
cat -n studio/src/components/feature-flag-provider.tsx | head -50

Repository: wundergraph/cosmo

Length of output: 2043


🏁 Script executed:

# Search for OnboardingProvider usage with correct file type
rg -n "OnboardingProvider" --type ts -B2 -A2

Repository: wundergraph/cosmo

Length of output: 2726


🏁 Script executed:

# Read _app.tsx to see provider ordering
cat -n studio/src/pages/_app.tsx | head -100

Repository: wundergraph/cosmo

Length of output: 4206


Redirect intercepts login/signup paths when onboarding flag is enabled.

This effect redirects all non-onboarding paths to /onboarding when the feature flag is enabled. However, it doesn't exclude public paths like /login and /signup. If the onboarding flag is enabled for unauthenticated users (possible via PostHog feature flags), they'll be redirected away from /login instead of reaching the auth page, breaking the login flow. AppProvider intentionally delegates redirect handling to OnboardingProvider when the flag is enabled (per app-provider.tsx line 193-194), making OnboardingProvider responsible for respecting auth boundaries.

Add checks to exclude public paths:

Proposed fix
+const PUBLIC_PATHS = ['/login', '/signup'];
+
 export const OnboardingProvider = ({ children }: { children: ReactNode }) => {
   const { onboarding } = useFeatureFlags();

   useEffect(
     function handleRedirectInOnboardingProvider() {
       if (!onboarding.enabled) return;
       if (Router.pathname.startsWith(ONBOARDING_PATH_PREFIX)) return;
+      if (PUBLIC_PATHS.includes(Router.pathname)) return;
+      if (Router.pathname.startsWith('/api/')) return;

       Router.replace('/onboarding');
     },
     [onboarding.enabled],
   );

   return children;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/components/onboarding/onboarding-provider.tsx` around lines 10 -
18, The redirect effect in handleRedirectInOnboardingProvider currently sends
all non-onboarding routes to '/onboarding' and must skip public auth routes;
modify the useEffect so it early-returns if Router.pathname matches any public
paths (e.g. '/login', '/signup', and other auth entry points you support) before
calling Router.replace; implement this by adding a small PUBLIC_AUTH_PATHS array
(or set) and checking PUBLIC_AUTH_PATHS.some(p => Router.pathname.startsWith(p))
in the same effect that checks ONBOARDING_PATH_PREFIX and onboarding.enabled so
login/signup are not intercepted.


return children;
};
5 changes: 5 additions & 0 deletions studio/src/components/onboarding/step-1-welcome.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { OnboardingForm } from '@/components/onboarding/onboarding-form';

export function Step1Welcome() {
return <OnboardingForm />;
}
3 changes: 3 additions & 0 deletions studio/src/components/onboarding/step-2-federation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Step2Federation() {
return <p>TODO: What is GraphQL Federation?</p>;
}
3 changes: 3 additions & 0 deletions studio/src/components/onboarding/step-3-create-graph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Step3CreateGraph() {
return <p>TODO: Create your first graph</p>;
}
3 changes: 3 additions & 0 deletions studio/src/components/onboarding/step-4-run-services.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Step4RunServices() {
return <p>TODO: Run your services</p>;
}
Loading
Loading