@@ -8,12 +8,51 @@ import { useRouter } from 'next/router';
88import { useCurrentOrganization } from '@/hooks/use-current-organization' ;
99import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb' ;
1010import { useToast } from '../ui/use-toast' ;
11+ import { SubmitHandler , useZodForm } from '@/hooks/use-form' ;
12+ import { Controller , useFieldArray } from 'react-hook-form' ;
13+ import { z } from 'zod' ;
14+ import { emailSchema , organizationNameSchema } from '@/lib/schemas' ;
15+ import { Form , FormControl , FormDescription , FormField , FormItem , FormLabel , FormMessage } from '../ui/form' ;
16+ import { Input } from '../ui/input' ;
17+ import { Button } from '../ui/button' ;
18+ import { Checkbox } from '../ui/checkbox' ;
19+ import { Cross1Icon , PlusIcon } from '@radix-ui/react-icons' ;
20+
21+ const onboardingSchema = z . object ( {
22+ organizationName : organizationNameSchema ,
23+ members : z . array (
24+ z . object ( {
25+ email : emailSchema . or ( z . literal ( '' ) ) ,
26+ } ) ,
27+ ) ,
28+ channels : z . object ( {
29+ slack : z . boolean ( ) ,
30+ email : z . boolean ( ) ,
31+ } ) ,
32+ } ) ;
33+
34+ type OnboardingFormValues = z . infer < typeof onboardingSchema > ;
1135
1236export const Step1 = ( ) => {
1337 const router = useRouter ( ) ;
1438 const { toast } = useToast ( ) ;
1539 const organization = useCurrentOrganization ( ) ;
16- const { setStep, setSkipped, setOnboarding } = useOnboarding ( ) ;
40+ const { setStep, setSkipped, setOnboarding, onboarding } = useOnboarding ( ) ;
41+
42+ const form = useZodForm < OnboardingFormValues > ( {
43+ mode : 'onChange' ,
44+ schema : onboardingSchema ,
45+ defaultValues : {
46+ organizationName : organization ?. name ?? '' ,
47+ members : [ { email : '' } ] ,
48+ channels : { slack : onboarding ?. slack ?? false , email : onboarding ?. email ?? false } ,
49+ } ,
50+ } ) ;
51+
52+ const { fields, append, remove } = useFieldArray ( {
53+ control : form . control ,
54+ name : 'members' ,
55+ } ) ;
1756
1857 const { mutate, isPending } = useMutation ( createOnboarding , {
1958 onSuccess : ( d ) => {
@@ -25,9 +64,13 @@ export const Step1 = () => {
2564 return ;
2665 }
2766
67+ // TODO: read slack + email from CreateOnboarding response once proto is updated
68+ const formValues = form . getValues ( ) ;
2869 setOnboarding ( {
2970 federatedGraphsCount : d . federatedGraphsCount ,
3071 finishedAt : d . finishedAt ? new Date ( d . finishedAt ) : undefined ,
72+ slack : formValues . channels . slack ,
73+ email : formValues . channels . email ,
3174 } ) ;
3275 router . push ( '/onboarding/2' ) ;
3376 } ,
@@ -39,26 +82,110 @@ export const Step1 = () => {
3982 } ,
4083 } ) ;
4184
85+ const onSubmit : SubmitHandler < OnboardingFormValues > = ( data ) => {
86+ const emails = data . members . map ( ( m ) => m . email ) . filter ( ( e ) => e . length > 0 ) ;
87+
88+ mutate ( {
89+ organizationName : data . organizationName ,
90+ slack : data . channels . slack ,
91+ email : data . channels . email ,
92+ invititationEmails : emails ,
93+ } ) ;
94+ } ;
95+
4296 useEffect ( ( ) => {
4397 setStep ( 1 ) ;
4498 } , [ setStep ] ) ;
4599
46100 return (
47101 < OnboardingContainer >
48- < h2 className = "text-2xl font-semibold tracking-tight" > Step 1</ h2 >
102+ < Form { ...form } >
103+ < form onSubmit = { form . handleSubmit ( onSubmit ) } className = "w-full space-y-8 text-left" >
104+ < FormField
105+ control = { form . control }
106+ name = "organizationName"
107+ render = { ( { field } ) => (
108+ < FormItem >
109+ < FormLabel > Organization Name</ FormLabel >
110+ < FormDescription > This is your organization name. You can always change it later.</ FormDescription >
111+ < FormControl >
112+ < Input { ...field } />
113+ </ FormControl >
114+ < FormMessage />
115+ </ FormItem >
116+ ) }
117+ />
118+
119+ < div className = "space-y-3 pt-4" >
120+ < FormLabel > Invite Members</ FormLabel >
121+ < FormDescription > Add team members by email.</ FormDescription >
122+ < div className = "space-y-2" >
123+ { fields . map ( ( field , index ) => (
124+ < div key = { field . id } >
125+ < div className = "flex items-center gap-2" >
126+ < Input placeholder = "janedoe@example.com" { ...form . register ( `members.${ index } .email` ) } />
127+ { fields . length > 1 && (
128+ < Button type = "button" variant = "ghost" size = "icon-sm" onClick = { ( ) => remove ( index ) } >
129+ < Cross1Icon />
130+ </ Button >
131+ ) }
132+ </ div >
133+ { form . formState . errors . members ?. [ index ] ?. email && (
134+ < p className = "mt-1 text-sm text-destructive" >
135+ { form . formState . errors . members [ index ] . email . message }
136+ </ p >
137+ ) }
138+ </ div >
139+ ) ) }
140+ </ div >
141+ < Button type = "button" variant = "outline" size = "sm" onClick = { ( ) => append ( { email : '' } ) } >
142+ < PlusIcon className = "mr-2" /> Add another
143+ </ Button >
144+ </ div >
145+
146+ < div className = "space-y-3 pt-4" >
147+ < FormLabel > Preferred way for us to reach you?</ FormLabel >
148+ < FormDescription > If you get stuck with your Cosmo setup, we want to be able to help you.</ FormDescription >
149+ < div className = "space-y-4" >
150+ < Controller
151+ control = { form . control }
152+ name = "channels.slack"
153+ render = { ( { field } ) => (
154+ < label className = "flex items-start gap-3" >
155+ < Checkbox checked = { field . value } onCheckedChange = { ( checked ) => field . onChange ( checked === true ) } />
156+ < div className = "flex flex-col gap-y-1" >
157+ < span className = "text-sm font-medium leading-none" > Slack</ span >
158+ < span className = "text-[0.8rem] text-muted-foreground" >
159+ We automatically create a Slack channel for you
160+ </ span >
161+ </ div >
162+ </ label >
163+ ) }
164+ />
165+ < Controller
166+ control = { form . control }
167+ name = "channels.email"
168+ render = { ( { field } ) => (
169+ < label className = "flex items-start gap-3" >
170+ < Checkbox checked = { field . value } onCheckedChange = { ( checked ) => field . onChange ( checked === true ) } />
171+ < div className = "flex flex-col gap-y-1" >
172+ < span className = "text-sm font-medium leading-none" > Email</ span >
173+ < span className = "text-[0.8rem] text-muted-foreground" > Receive updates via email</ span >
174+ </ div >
175+ </ label >
176+ ) }
177+ />
178+ </ div >
179+ </ div >
180+ </ form >
181+ </ Form >
182+
49183 < OnboardingNavigation
50184 onSkip = { setSkipped }
51185 forward = { {
52- onClick : ( ) => {
53- // TODO: replace with real values in form
54- mutate ( {
55- organizationName : organization ?. name ?? '' ,
56- slack : true ,
57- email : false ,
58- invititationEmails : [ ] ,
59- } ) ;
60- } ,
186+ onClick : form . handleSubmit ( onSubmit ) ,
61187 isLoading : isPending ,
188+ disabled : ! form . formState . isValid ,
62189 } }
63190 />
64191 </ OnboardingContainer >
0 commit comments