@@ -7,11 +7,44 @@ import { createOnboarding } from '@wundergraph/cosmo-connect/dist/platform/v1/pl
77import { useRouter } from 'next/router' ;
88import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb' ;
99import { useToast } from '../ui/use-toast' ;
10+ import { SubmitHandler , useZodForm } from '@/hooks/use-form' ;
11+ import { Controller } from 'react-hook-form' ;
12+ import { z } from 'zod' ;
13+ import { Form } from '../ui/form' ;
14+ import { Checkbox } from '../ui/checkbox' ;
15+ import { TrafficAnimation } from './traffic-animation' ;
16+
17+ const onboardingSchema = z . object ( {
18+ channels : z . object ( {
19+ slack : z . boolean ( ) ,
20+ email : z . boolean ( ) ,
21+ } ) ,
22+ } ) ;
23+
24+ type OnboardingFormValues = z . infer < typeof onboardingSchema > ;
25+
26+ const WhyListItem = ( { title, text } : { title : string ; text : string } ) => (
27+ < li className = "flex gap-2" >
28+ < span className = "mt-2 size-1.5 shrink-0 rounded-full bg-muted-foreground/60" />
29+ < div className = "flex flex-col" >
30+ < span className = "text-sm font-medium" > { title } </ span >
31+ < span className = "text-sm text-muted-foreground" > { text } </ span >
32+ </ div >
33+ </ li >
34+ ) ;
1035
1136export const Step1 = ( ) => {
1237 const router = useRouter ( ) ;
1338 const { toast } = useToast ( ) ;
14- const { setStep, setSkipped, setOnboarding } = useOnboarding ( ) ;
39+ const { setStep, setSkipped, setOnboarding, onboarding } = useOnboarding ( ) ;
40+
41+ const form = useZodForm < OnboardingFormValues > ( {
42+ mode : 'onChange' ,
43+ schema : onboardingSchema ,
44+ defaultValues : {
45+ channels : { slack : onboarding ?. slack ?? false , email : onboarding ?. email ?? false } ,
46+ } ,
47+ } ) ;
1548
1649 const { mutate, isPending } = useMutation ( createOnboarding , {
1750 onSuccess : ( d ) => {
@@ -23,9 +56,12 @@ export const Step1 = () => {
2356 return ;
2457 }
2558
59+ const formValues = form . getValues ( ) ;
2660 setOnboarding ( {
2761 federatedGraphsCount : d . federatedGraphsCount ,
2862 finishedAt : d . finishedAt ? new Date ( d . finishedAt ) : undefined ,
63+ slack : formValues . channels . slack ,
64+ email : formValues . channels . email ,
2965 } ) ;
3066 router . push ( '/onboarding/2' ) ;
3167 } ,
@@ -37,23 +73,88 @@ export const Step1 = () => {
3773 } ,
3874 } ) ;
3975
76+ const onSubmit : SubmitHandler < OnboardingFormValues > = ( data ) => {
77+ mutate ( {
78+ slack : data . channels . slack ,
79+ email : data . channels . email ,
80+ } ) ;
81+ } ;
82+
4083 useEffect ( ( ) => {
4184 setStep ( 1 ) ;
4285 } , [ setStep ] ) ;
4386
4487 return (
4588 < OnboardingContainer >
46- < h2 className = "text-2xl font-semibold tracking-tight" > Step 1</ h2 >
89+ < div className = "flex w-full flex-col gap-8 text-left" >
90+ < div className = "space-y-2" >
91+ < p className = "text-sm text-muted-foreground" >
92+ In ~< span className = "font-medium text-foreground" > 3 minutes</ span > you will have a federated GraphQL graph
93+ running locally and serving live traffic into Cosmo Cloud platform.
94+ </ p >
95+ </ div >
96+
97+ < TrafficAnimation />
98+
99+ < div className = "space-y-3" >
100+ < p className = "text-sm font-semibold" > What you will do</ p >
101+ < ul className = "flex flex-col gap-3" >
102+ < WhyListItem
103+ title = "Create your first graph"
104+ text = "See how the products and reviews subgraphs compose into one supergraph, giving your client a single endpoint to resolve the data it needs."
105+ />
106+ < WhyListItem
107+ title = "Run your services"
108+ text = "Run the same router stack you would run in production, locally."
109+ />
110+ < WhyListItem title = "Send a query" text = "Watch real request metrics flow through the router." />
111+ </ ul >
112+ </ div >
113+
114+ < Form { ...form } >
115+ < form onSubmit = { form . handleSubmit ( onSubmit ) } className = "w-full" >
116+ < div className = "rounded-md border border-dashed p-4" >
117+ < p className = "text-sm font-medium" > If you get stuck, how can we reach you?</ p >
118+ < div className = "mt-3 flex flex-col gap-3" >
119+ < Controller
120+ control = { form . control }
121+ name = "channels.slack"
122+ render = { ( { field } ) => (
123+ < label className = "flex items-start gap-3" >
124+ < Checkbox checked = { field . value } onCheckedChange = { ( checked ) => field . onChange ( checked === true ) } />
125+ < div className = "flex flex-col gap-y-1" >
126+ < span className = "text-sm font-medium leading-none" > Slack</ span >
127+ < span className = "text-[0.8rem] text-muted-foreground" >
128+ We automatically create a Slack channel for you.
129+ </ span >
130+ </ div >
131+ </ label >
132+ ) }
133+ />
134+ < Controller
135+ control = { form . control }
136+ name = "channels.email"
137+ render = { ( { field } ) => (
138+ < label className = "flex items-start gap-3" >
139+ < Checkbox checked = { field . value } onCheckedChange = { ( checked ) => field . onChange ( checked === true ) } />
140+ < div className = "flex flex-col gap-y-1" >
141+ < span className = "text-sm font-medium leading-none" > Email</ span >
142+ < span className = "text-[0.8rem] text-muted-foreground" > Receive updates via email.</ span >
143+ </ div >
144+ </ label >
145+ ) }
146+ />
147+ </ div >
148+ </ div >
149+ </ form >
150+ </ Form >
151+ </ div >
152+
47153 < OnboardingNavigation
48154 onSkip = { setSkipped }
155+ forwardLabel = "Start the tour"
49156 forward = { {
50- onClick : ( ) => {
51- // TODO: replace with real values in form
52- mutate ( {
53- slack : true ,
54- email : false ,
55- } ) ;
56- } ,
157+ onClick : form . handleSubmit ( onSubmit ) ,
57158 isLoading : isPending ,
58159 } }
59160 />
0 commit comments