From b0624c9cb78b0a71c58a41a20e471379e8308d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Wed, 1 Apr 2026 12:38:01 +0200 Subject: [PATCH 1/7] feat: restrict onboarding content to max width --- studio/src/components/layout/onboarding-layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/src/components/layout/onboarding-layout.tsx b/studio/src/components/layout/onboarding-layout.tsx index 59932b0147..f99e5df3e8 100644 --- a/studio/src/components/layout/onboarding-layout.tsx +++ b/studio/src/components/layout/onboarding-layout.tsx @@ -5,7 +5,7 @@ export interface OnboardingLayoutProps { export const OnboardingLayout = ({ children }: OnboardingLayoutProps) => { return (
-
{children}
+
{children}
); }; From e205f5d6e9ee64de4c001bf3b09cf46375b59a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Wed, 1 Apr 2026 14:18:08 +0200 Subject: [PATCH 2/7] feat: improve onboarding layout --- .../components/layout/onboarding-layout.tsx | 15 +++++++++++--- studio/src/pages/onboarding/[step].tsx | 20 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/studio/src/components/layout/onboarding-layout.tsx b/studio/src/components/layout/onboarding-layout.tsx index f99e5df3e8..567ffa9375 100644 --- a/studio/src/components/layout/onboarding-layout.tsx +++ b/studio/src/components/layout/onboarding-layout.tsx @@ -1,11 +1,20 @@ +import { Logo } from '../logo'; + export interface OnboardingLayoutProps { children?: React.ReactNode; + title?: string; } -export const OnboardingLayout = ({ children }: OnboardingLayoutProps) => { +export const OnboardingLayout = ({ children, title }: OnboardingLayoutProps) => { return ( -
-
{children}
+
+
+ + {title &&

{title}

} +
+
+
{children}
+
); }; diff --git a/studio/src/pages/onboarding/[step].tsx b/studio/src/pages/onboarding/[step].tsx index 362d096401..9820802195 100644 --- a/studio/src/pages/onboarding/[step].tsx +++ b/studio/src/pages/onboarding/[step].tsx @@ -12,16 +12,28 @@ const OnboardingStep: NextPageWithLayout = () => { switch (step) { case '0': case '1': - return ; + return ( + + + + ); case '2': - return ; + return ( + + + + ); case '3': - return ; + return ( + + + + ) default: return null; } }; -OnboardingStep.getLayout = (page) => {page}; +OnboardingStep.getLayout = (page) => page; export default OnboardingStep; From 4461953a93358862e97ac42b21bd956cb8085d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Wed, 1 Apr 2026 14:29:59 +0200 Subject: [PATCH 3/7] feat: add stepper --- .../components/layout/onboarding-layout.tsx | 20 +++--- .../components/onboarding/onboarding-steps.ts | 10 +++ studio/src/components/onboarding/stepper.tsx | 63 +++++++++++++++++++ studio/src/pages/onboarding/[step].tsx | 32 +++++++--- 4 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 studio/src/components/onboarding/onboarding-steps.ts create mode 100644 studio/src/components/onboarding/stepper.tsx diff --git a/studio/src/components/layout/onboarding-layout.tsx b/studio/src/components/layout/onboarding-layout.tsx index 567ffa9375..dde21fab83 100644 --- a/studio/src/components/layout/onboarding-layout.tsx +++ b/studio/src/components/layout/onboarding-layout.tsx @@ -1,19 +1,23 @@ import { Logo } from '../logo'; +import { Card, CardContent } from '../ui/card'; +import { Stepper } from '../onboarding/stepper'; +import { ONBOARDING_STEPS } from '../onboarding/onboarding-steps'; +import { useOnboarding } from '@/hooks/use-onboarding'; -export interface OnboardingLayoutProps { - children?: React.ReactNode; - title?: string; -} +export const OnboardingLayout = ({ children, title }: { children?: React.ReactNode; title?: string }) => { + const { currentStep } = useOnboarding(); -export const OnboardingLayout = ({ children, title }: OnboardingLayoutProps) => { return (
-
+
{title &&

{title}

} +
-
-
{children}
+
+ + {children} +
); diff --git a/studio/src/components/onboarding/onboarding-steps.ts b/studio/src/components/onboarding/onboarding-steps.ts new file mode 100644 index 0000000000..f8ab26d64c --- /dev/null +++ b/studio/src/components/onboarding/onboarding-steps.ts @@ -0,0 +1,10 @@ +export interface StepperStep { + number: number; + label: string; +} + +export const ONBOARDING_STEPS: StepperStep[] = [ + { number: 1, label: 'Get started with WunderGraph' }, + { number: 2, label: 'Create your first graph' }, + { number: 3, label: 'Run your services' }, +]; diff --git a/studio/src/components/onboarding/stepper.tsx b/studio/src/components/onboarding/stepper.tsx new file mode 100644 index 0000000000..cfddd29c3c --- /dev/null +++ b/studio/src/components/onboarding/stepper.tsx @@ -0,0 +1,63 @@ +import { cn } from '@/lib/utils'; +import { CheckIcon } from '@radix-ui/react-icons'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useRouter } from 'next/router'; +import type { StepperStep } from './onboarding-steps'; + +export function Stepper({ + steps, + currentStep, + className, +}: { + steps: StepperStep[]; + currentStep: number; + className?: string; +}) { + const router = useRouter(); + + return ( + +
+ {steps.map((step, index) => { + const isCompleted = index < currentStep; + const isCurrent = index === currentStep; + const canNavigate = isCompleted && !isCurrent; + + return ( +
+ {index > 0 && ( +
+
+
+ )} + + + + + {step.label} + +
+ ); + })} +
+ + ); +} diff --git a/studio/src/pages/onboarding/[step].tsx b/studio/src/pages/onboarding/[step].tsx index 9820802195..8beec4d58c 100644 --- a/studio/src/pages/onboarding/[step].tsx +++ b/studio/src/pages/onboarding/[step].tsx @@ -1,34 +1,46 @@ import { OnboardingLayout } from '@/components/layout/onboarding-layout'; +import { ONBOARDING_STEPS } from '@/components/onboarding/onboarding-steps'; import { Step1 } from '@/components/onboarding/step-1'; import { Step2 } from '@/components/onboarding/step-2'; import { Step3 } from '@/components/onboarding/step-3'; import { NextPageWithLayout } from '@/lib/page'; import { useRouter } from 'next/router'; +const normalizeOnboardingStep = (step: string | string[] | undefined) => { + const value = Array.isArray(step) ? step[0] : step; + const parsedStep = Number.parseInt(value ?? '', 10); + + if (!Number.isInteger(parsedStep)) { + return 1; + } + + return Math.min(Math.max(parsedStep, 1), ONBOARDING_STEPS.length); +}; + const OnboardingStep: NextPageWithLayout = () => { const router = useRouter(); - const { step } = router.query; + const stepNumber = normalizeOnboardingStep(router.query.step); + const title = ONBOARDING_STEPS[stepNumber - 1]?.label; - switch (step) { - case '0': - case '1': + switch (stepNumber) { + case 1: return ( - + ); - case '2': + case 2: return ( - + ); - case '3': + case 3: return ( - + - ) + ); default: return null; } From b4c1442450ae8cdc6e23e76f8f880d7263499e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Wed, 1 Apr 2026 14:48:38 +0200 Subject: [PATCH 4/7] feat: onboarding navigation buttons --- .../onboarding/onboarding-navigation.tsx | 71 +++++++++++++++++++ studio/src/components/onboarding/step-1.tsx | 39 ++++------ studio/src/components/onboarding/step-2.tsx | 19 +---- studio/src/components/onboarding/step-3.tsx | 47 +++++------- 4 files changed, 106 insertions(+), 70 deletions(-) create mode 100644 studio/src/components/onboarding/onboarding-navigation.tsx diff --git a/studio/src/components/onboarding/onboarding-navigation.tsx b/studio/src/components/onboarding/onboarding-navigation.tsx new file mode 100644 index 0000000000..08cfd700ef --- /dev/null +++ b/studio/src/components/onboarding/onboarding-navigation.tsx @@ -0,0 +1,71 @@ +import { ArrowLeftIcon, ArrowRightIcon, InfoCircledIcon } from '@radix-ui/react-icons'; +import { Link } from '../ui/link'; +import { Button } from '../ui/button'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; + +export const OnboardingNavigation = ({ + backHref, + forward, + forwardLabel = 'Next', + onSkip, +}: { + backHref?: string; + forward: { href: string } | { onClick: () => void; isLoading?: boolean }; + forwardLabel?: string; + onSkip: () => void; +}) => { + return ( +
+
+ + + + + + You can always get back to this wizard from the application. Safe to skip. + +
+
+ {backHref ? ( + + ) : ( + + )} + {'href' in forward ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/studio/src/components/onboarding/step-1.tsx b/studio/src/components/onboarding/step-1.tsx index d7a5765738..743724102e 100644 --- a/studio/src/components/onboarding/step-1.tsx +++ b/studio/src/components/onboarding/step-1.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react'; -import { Link } from '../ui/link'; -import { Button } from '../ui/button'; import { useOnboarding } from '@/hooks/use-onboarding'; +import { OnboardingNavigation } from './onboarding-navigation'; import { useMutation } from '@connectrpc/connect-query'; import { createOnboarding } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; import { useRouter } from 'next/router'; @@ -44,29 +43,19 @@ export const Step1 = () => { return (

Step 1

-
- -
- - -
-
+ { + // TODO: replace with real values in form + mutate({ + slack: true, + email: false, + }); + }, + isLoading: isPending, + }} + />
); }; diff --git a/studio/src/components/onboarding/step-2.tsx b/studio/src/components/onboarding/step-2.tsx index a8e559873f..62021a8745 100644 --- a/studio/src/components/onboarding/step-2.tsx +++ b/studio/src/components/onboarding/step-2.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react'; -import { Link } from '../ui/link'; -import { Button } from '../ui/button'; import { useOnboarding } from '@/hooks/use-onboarding'; +import { OnboardingNavigation } from './onboarding-navigation'; export const Step2 = () => { const { setStep, setSkipped } = useOnboarding(); @@ -12,20 +11,8 @@ export const Step2 = () => { return (
-

Step 2

-
- -
- - -
-
+

Step 3

+
); }; diff --git a/studio/src/components/onboarding/step-3.tsx b/studio/src/components/onboarding/step-3.tsx index 0aff769f79..61e1dc0212 100644 --- a/studio/src/components/onboarding/step-3.tsx +++ b/studio/src/components/onboarding/step-3.tsx @@ -1,18 +1,21 @@ import { useEffect } from 'react'; -import { useRouter } from 'next/router'; -import { useMutation } from '@connectrpc/connect-query'; import { useOnboarding } from '@/hooks/use-onboarding'; +import { OnboardingNavigation } from './onboarding-navigation'; +import { useMutation } from '@connectrpc/connect-query'; import { finishOnboarding } from '@wundergraph/cosmo-connect/dist/platform/v1/platform-PlatformService_connectquery'; -import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; import { useToast } from '../ui/use-toast'; -import { Link } from '../ui/link'; -import { Button } from '../ui/button'; +import { useRouter } from 'next/router'; +import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; export const Step3 = () => { const router = useRouter(); const { toast } = useToast(); const { setStep, setSkipped, setOnboarding } = useOnboarding(); + useEffect(() => { + setStep(3); + }, [setStep]); + const { mutate, isPending } = useMutation(finishOnboarding, { onSuccess: (d) => { if (d.response?.code !== EnumStatusCode.OK) { @@ -40,32 +43,18 @@ export const Step3 = () => { }, }); - useEffect(() => { - setStep(3); - }, [setStep]); - return (
-

Step 3

-
- -
- - -
-
+

Step 4

+ mutate({}), + isLoading: isPending, + }} + forwardLabel="Finish" + />
); }; From 54e9d292214804ba4b0fd04b146f1697bc215ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Syn=C3=A1=C4=8Dek?= Date: Wed, 1 Apr 2026 14:56:24 +0200 Subject: [PATCH 5/7] feat: common layout for the step content --- studio/src/components/layout/onboarding-layout.tsx | 4 ++-- .../components/onboarding/onboarding-container.tsx | 3 +++ .../onboarding/onboarding-navigation.tsx | 2 +- studio/src/components/onboarding/step-1.tsx | 5 +++-- studio/src/components/onboarding/step-2.tsx | 9 +++++---- studio/src/components/onboarding/step-3.tsx | 14 ++++++-------- studio/tailwind.config.js | 3 +++ 7 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 studio/src/components/onboarding/onboarding-container.tsx diff --git a/studio/src/components/layout/onboarding-layout.tsx b/studio/src/components/layout/onboarding-layout.tsx index dde21fab83..4d7742232c 100644 --- a/studio/src/components/layout/onboarding-layout.tsx +++ b/studio/src/components/layout/onboarding-layout.tsx @@ -9,14 +9,14 @@ export const OnboardingLayout = ({ children, title }: { children?: React.ReactNo return (
-
+
{title &&

{title}

}
- {children} + {children}
diff --git a/studio/src/components/onboarding/onboarding-container.tsx b/studio/src/components/onboarding/onboarding-container.tsx new file mode 100644 index 0000000000..acdf36cc10 --- /dev/null +++ b/studio/src/components/onboarding/onboarding-container.tsx @@ -0,0 +1,3 @@ +export const OnboardingContainer = ({ children }: { children: React.ReactNode }) => { + return
{children}
; +}; diff --git a/studio/src/components/onboarding/onboarding-navigation.tsx b/studio/src/components/onboarding/onboarding-navigation.tsx index 08cfd700ef..6be0af3c0d 100644 --- a/studio/src/components/onboarding/onboarding-navigation.tsx +++ b/studio/src/components/onboarding/onboarding-navigation.tsx @@ -15,7 +15,7 @@ export const OnboardingNavigation = ({ onSkip: () => void; }) => { return ( -
+