1
- import { FormErrorOrDescription } from '@app-builder/components/Form/FormErrorOrDescription' ;
2
- import { FormField } from '@app-builder/components/Form/FormField' ;
3
- import { FormInput } from '@app-builder/components/Form/FormInput' ;
4
- import { FormLabel } from '@app-builder/components/Form/FormLabel' ;
1
+ import { FormErrorOrDescription } from '@app-builder/components/Form/Tanstack/FormErrorOrDescription' ;
2
+ import { FormInput } from '@app-builder/components/Form/Tanstack/FormInput' ;
3
+ import { FormLabel } from '@app-builder/components/Form/Tanstack/FormLabel' ;
5
4
import { setToastMessage } from '@app-builder/components/MarbleToaster' ;
6
5
import { serverServices } from '@app-builder/services/init.server' ;
7
6
import { getRoute } from '@app-builder/utils/routes' ;
8
7
import { fromUUID } from '@app-builder/utils/short-uuid' ;
9
- import { FormProvider , getFormProps , getInputProps , useForm } from '@conform-to/react' ;
10
- import { getZodConstraint , parseWithZod } from '@conform-to/zod' ;
11
8
import { type ActionFunctionArgs , json } from '@remix-run/node' ;
12
9
import { useFetcher , useNavigation } from '@remix-run/react' ;
10
+ import { useForm } from '@tanstack/react-form' ;
13
11
import * as React from 'react' ;
14
12
import { useTranslation } from 'react-i18next' ;
15
13
import { redirectBack } from 'remix-utils/redirect-back' ;
@@ -20,7 +18,7 @@ import { z } from 'zod';
20
18
const updateScenarioFormSchema = z . object ( {
21
19
scenarioId : z . string ( ) . uuid ( ) ,
22
20
name : z . string ( ) . min ( 1 ) ,
23
- description : z . string ( ) . nullable ( ) . default ( null ) ,
21
+ description : z . string ( ) ,
24
22
} ) ;
25
23
26
24
type UpdateScenarioForm = z . infer < typeof updateScenarioFormSchema > ;
@@ -31,39 +29,47 @@ export async function action({ request }: ActionFunctionArgs) {
31
29
i18nextService : { getFixedT } ,
32
30
toastSessionService : { getSession, commitSession } ,
33
31
} = serverServices ;
34
- const { scenario } = await authService . isAuthenticated ( request , {
35
- failureRedirect : getRoute ( '/sign-in' ) ,
36
- } ) ;
37
- const formData = await request . formData ( ) ;
38
- const submission = parseWithZod ( formData , {
39
- schema : updateScenarioFormSchema ,
40
- } ) ;
41
32
42
- if ( submission . status !== 'success' ) {
43
- return json ( submission . reply ( ) ) ;
33
+ const [ t , session , rawData , { scenario } ] = await Promise . all ( [
34
+ getFixedT ( request , [ 'common' ] ) ,
35
+ getSession ( request ) ,
36
+ request . json ( ) ,
37
+ authService . isAuthenticated ( request , {
38
+ failureRedirect : getRoute ( '/sign-in' ) ,
39
+ } ) ,
40
+ ] ) ;
41
+
42
+ const { data, success, error } = updateScenarioFormSchema . safeParse ( rawData ) ;
43
+
44
+ if ( ! success ) {
45
+ return json (
46
+ { status : 'error' , errors : error . flatten ( ) } ,
47
+ {
48
+ headers : { 'Set-Cookie' : await commitSession ( session ) } ,
49
+ } ,
50
+ ) ;
44
51
}
45
52
46
53
try {
47
- await scenario . updateScenario ( submission . value ) ;
54
+ await scenario . updateScenario ( data ) ;
55
+
48
56
return redirectBack ( request , {
49
57
fallback : getRoute ( '/scenarios/:scenarioId' , {
50
- scenarioId : fromUUID ( submission . value . scenarioId ) ,
58
+ scenarioId : fromUUID ( data . scenarioId ) ,
51
59
} ) ,
52
60
} ) ;
53
61
} catch ( error ) {
54
- const session = await getSession ( request ) ;
55
- const t = await getFixedT ( request , [ 'common' ] ) ;
56
-
57
- const formError = t ( 'common:errors.unknown' ) ;
58
-
59
62
setToastMessage ( session , {
60
63
type : 'error' ,
61
- message : formError ,
64
+ message : t ( 'common:errors.unknown' ) ,
62
65
} ) ;
63
66
64
- return json ( submission . reply ( { formErrors : [ formError ] } ) , {
65
- headers : { 'Set-Cookie' : await commitSession ( session ) } ,
66
- } ) ;
67
+ return json (
68
+ { status : 'error' , errors : [ ] } ,
69
+ {
70
+ headers : { 'Set-Cookie' : await commitSession ( session ) } ,
71
+ } ,
72
+ ) ;
67
73
}
68
74
}
69
75
@@ -98,54 +104,77 @@ function UpdateScenarioContent({ defaultValue }: { defaultValue: UpdateScenarioF
98
104
const { t } = useTranslation ( [ 'scenarios' , 'common' ] ) ;
99
105
const fetcher = useFetcher < typeof action > ( ) ;
100
106
101
- const [ form , fields ] = useForm ( {
102
- shouldRevalidate : 'onInput' ,
103
- defaultValue,
104
- lastResult : fetcher . data ,
105
- constraint : getZodConstraint ( updateScenarioFormSchema ) ,
106
- onValidate ( { formData } ) {
107
- return parseWithZod ( formData , {
108
- schema : updateScenarioFormSchema ,
109
- } ) ;
107
+ const form = useForm ( {
108
+ defaultValues : defaultValue ,
109
+ onSubmit : ( { value, formApi } ) => {
110
+ if ( formApi . state . isValid ) {
111
+ fetcher . submit ( value , {
112
+ method : 'PATCH' ,
113
+ action : getRoute ( '/ressources/scenarios/update' ) ,
114
+ encType : 'application/json' ,
115
+ } ) ;
116
+ }
117
+ } ,
118
+ validators : {
119
+ onChangeAsync : updateScenarioFormSchema ,
120
+ onBlurAsync : updateScenarioFormSchema ,
121
+ onSubmitAsync : updateScenarioFormSchema ,
110
122
} ,
111
123
} ) ;
112
124
113
125
return (
114
- < FormProvider context = { form . context } >
115
- < fetcher . Form
116
- method = "PATCH"
117
- action = { getRoute ( '/ressources/scenarios/update' ) }
118
- { ...getFormProps ( form ) }
119
- >
120
- < ModalV2 . Title > { t ( 'scenarios:update_scenario.title' ) } </ ModalV2 . Title >
121
- < div className = "flex flex-col gap-6 p-6" >
122
- < input
123
- { ...getInputProps ( fields . scenarioId , { type : 'hidden' } ) }
124
- key = { fields . scenarioId . key }
125
- />
126
- < FormField name = { fields . name . name } className = "group flex w-full flex-col gap-2" >
127
- < FormLabel > { t ( 'scenarios:create_scenario.name' ) } </ FormLabel >
128
- < FormInput type = "text" placeholder = { t ( 'scenarios:create_scenario.name_placeholder' ) } />
129
- < FormErrorOrDescription />
130
- </ FormField >
131
- < FormField name = { fields . description . name } className = "group flex w-full flex-col gap-2" >
132
- < FormLabel > { t ( 'scenarios:create_scenario.description' ) } </ FormLabel >
133
- < FormInput
134
- type = "text"
135
- placeholder = { t ( 'scenarios:create_scenario.description_placeholder' ) }
136
- />
137
- < FormErrorOrDescription />
138
- </ FormField >
139
- < div className = "flex flex-1 flex-row gap-2" >
140
- < ModalV2 . Close render = { < Button className = "flex-1" variant = "secondary" /> } >
141
- { t ( 'common:cancel' ) }
142
- </ ModalV2 . Close >
143
- < Button className = "flex-1" variant = "primary" type = "submit" >
144
- { t ( 'common:save' ) }
145
- </ Button >
146
- </ div >
126
+ < form
127
+ onSubmit = { ( e ) => {
128
+ e . preventDefault ( ) ;
129
+ e . stopPropagation ( ) ;
130
+ form . handleSubmit ( ) ;
131
+ } }
132
+ >
133
+ < ModalV2 . Title > { t ( 'scenarios:update_scenario.title' ) } </ ModalV2 . Title >
134
+ < div className = "flex flex-col gap-6 p-6" >
135
+ < form . Field name = "name" >
136
+ { ( field ) => (
137
+ < div className = "group flex w-full flex-col gap-2" >
138
+ < FormLabel name = { field . name } > { t ( 'scenarios:create_scenario.name' ) } </ FormLabel >
139
+ < FormInput
140
+ type = "text"
141
+ name = { field . name }
142
+ defaultValue = { field . state . value }
143
+ onChange = { ( e ) => field . handleChange ( e . currentTarget . value ) }
144
+ onBlur = { field . handleBlur }
145
+ valid = { field . state . meta . errors . length === 0 }
146
+ placeholder = { t ( 'scenarios:create_scenario.name_placeholder' ) }
147
+ />
148
+ < FormErrorOrDescription errors = { field . state . meta . errors } />
149
+ </ div >
150
+ ) }
151
+ </ form . Field >
152
+ < form . Field name = "description" >
153
+ { ( field ) => (
154
+ < div className = "group flex w-full flex-col gap-2" >
155
+ < FormLabel name = { field . name } > { t ( 'scenarios:create_scenario.description' ) } </ FormLabel >
156
+ < FormInput
157
+ type = "text"
158
+ name = { field . name }
159
+ defaultValue = { field . state . value }
160
+ onChange = { ( e ) => field . handleChange ( e . currentTarget . value ) }
161
+ onBlur = { field . handleBlur }
162
+ valid = { field . state . meta . errors . length === 0 }
163
+ placeholder = { t ( 'scenarios:create_scenario.description_placeholder' ) }
164
+ />
165
+ < FormErrorOrDescription errors = { field . state . meta . errors } />
166
+ </ div >
167
+ ) }
168
+ </ form . Field >
169
+ < div className = "flex flex-1 flex-row gap-2" >
170
+ < ModalV2 . Close render = { < Button className = "flex-1" variant = "secondary" /> } >
171
+ { t ( 'common:cancel' ) }
172
+ </ ModalV2 . Close >
173
+ < Button className = "flex-1" variant = "primary" type = "submit" >
174
+ { t ( 'common:save' ) }
175
+ </ Button >
147
176
</ div >
148
- </ fetcher . Form >
149
- </ FormProvider >
177
+ </ div >
178
+ </ form >
150
179
) ;
151
180
}
0 commit comments