Skip to content

Commit ed57ece

Browse files
committed
refactor: update Scenario Create & Update form
1 parent 88bd016 commit ed57ece

File tree

2 files changed

+110
-77
lines changed

2 files changed

+110
-77
lines changed

packages/app-builder/src/routes/ressources+/scenarios+/create.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ function CreateScenarioContent() {
133133
}
134134
},
135135
validators: {
136-
onChangeAsync: createScenarioFormSchema,
137-
onBlurAsync: createScenarioFormSchema,
138-
onSubmitAsync: createScenarioFormSchema,
136+
onChange: createScenarioFormSchema,
137+
onBlur: createScenarioFormSchema,
138+
onSubmit: createScenarioFormSchema,
139139
},
140140
});
141141

@@ -225,7 +225,11 @@ function CreateScenarioContent() {
225225
<Select.Default
226226
placeholder={t('scenarios:create_scenario.trigger_object_placeholder')}
227227
defaultValue={field.state.value}
228-
onValueChange={field.handleChange}
228+
onValueChange={(value) => {
229+
console.log('Value', value);
230+
field.handleChange(value);
231+
field.handleBlur();
232+
}}
229233
>
230234
{dataModelFetcher.state === 'loading' ? <p>{t('common:loading')}</p> : null}
231235
{dataModel.map((tableName) => {
@@ -239,13 +243,13 @@ function CreateScenarioContent() {
239243
<p>{t('scenarios:create_scenario.no_trigger_object')}</p>
240244
) : null}
241245
</Select.Default>
242-
<FormErrorOrDescription />
246+
<FormErrorOrDescription errors={field.state.meta.errors} />
243247
</div>
244248
)}
245249
</form.Field>
246250
</div>
247251
<div className="flex flex-1 flex-row gap-2">
248-
<ModalV2.Close render={<Button className="flex-1" variant="secondary" />}>
252+
<ModalV2.Close render={<Button className="flex-1" type="button" variant="secondary" />}>
249253
{t('common:cancel')}
250254
</ModalV2.Close>
251255
<Button className="flex-1" variant="primary" type="submit">
Lines changed: 100 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
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';
54
import { setToastMessage } from '@app-builder/components/MarbleToaster';
65
import { serverServices } from '@app-builder/services/init.server';
76
import { getRoute } from '@app-builder/utils/routes';
87
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';
118
import { type ActionFunctionArgs, json } from '@remix-run/node';
129
import { useFetcher, useNavigation } from '@remix-run/react';
10+
import { useForm } from '@tanstack/react-form';
1311
import * as React from 'react';
1412
import { useTranslation } from 'react-i18next';
1513
import { redirectBack } from 'remix-utils/redirect-back';
@@ -20,7 +18,7 @@ import { z } from 'zod';
2018
const updateScenarioFormSchema = z.object({
2119
scenarioId: z.string().uuid(),
2220
name: z.string().min(1),
23-
description: z.string().nullable().default(null),
21+
description: z.string(),
2422
});
2523

2624
type UpdateScenarioForm = z.infer<typeof updateScenarioFormSchema>;
@@ -31,39 +29,47 @@ export async function action({ request }: ActionFunctionArgs) {
3129
i18nextService: { getFixedT },
3230
toastSessionService: { getSession, commitSession },
3331
} = 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-
});
4132

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+
);
4451
}
4552

4653
try {
47-
await scenario.updateScenario(submission.value);
54+
await scenario.updateScenario(data);
55+
4856
return redirectBack(request, {
4957
fallback: getRoute('/scenarios/:scenarioId', {
50-
scenarioId: fromUUID(submission.value.scenarioId),
58+
scenarioId: fromUUID(data.scenarioId),
5159
}),
5260
});
5361
} catch (error) {
54-
const session = await getSession(request);
55-
const t = await getFixedT(request, ['common']);
56-
57-
const formError = t('common:errors.unknown');
58-
5962
setToastMessage(session, {
6063
type: 'error',
61-
message: formError,
64+
message: t('common:errors.unknown'),
6265
});
6366

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+
);
6773
}
6874
}
6975

@@ -98,54 +104,77 @@ function UpdateScenarioContent({ defaultValue }: { defaultValue: UpdateScenarioF
98104
const { t } = useTranslation(['scenarios', 'common']);
99105
const fetcher = useFetcher<typeof action>();
100106

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,
110122
},
111123
});
112124

113125
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>
147176
</div>
148-
</fetcher.Form>
149-
</FormProvider>
177+
</div>
178+
</form>
150179
);
151180
}

0 commit comments

Comments
 (0)