Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .server-changes/conform-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

Upgrade the dashboard form layer from `@conform-to` 0.9 to 1.x. conform 1.x supports both zod 3 and zod 4, which unblocks the upcoming zod 4 upgrade.
27 changes: 13 additions & 14 deletions apps/webapp/app/components/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { getFormProps, getSelectProps, getInputProps, getTextareaProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid";
import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react";
Expand Down Expand Up @@ -34,11 +34,11 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
const navigation = useNavigation();
const [type, setType] = useState<FeedbackType>(defaultValue);

const [form, { path, feedbackType, message }] = useForm({
const [form, fields] = useForm({
id: "accept-invite",
lastSubmission: lastSubmission as any,
lastResult: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
return parseWithZod(formData, { schema });
},
shouldRevalidate: "onInput",
});
Expand All @@ -47,8 +47,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
form.error === undefined &&
form.errors.length === 0
(form.errors === undefined || form.errors.length === 0)
) {
setOpen(false);
}
Expand Down Expand Up @@ -90,9 +89,9 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
type === "concurrency" ||
type === "hipaa"
) && <hr className="border-grid-dimmed" />}
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Form method="post" action="/resources/feedback" {...getFormProps(form)} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<input value={location.pathname} {...getInputProps(fields.path, { type: "hidden" })} />
<InputGroup className="max-w-full">
Comment thread
carderne marked this conversation as resolved.
{type === "feature" && (
<InfoPanel
Expand Down Expand Up @@ -149,7 +148,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
</InfoPanel>
)}
<Select
{...conform.select(feedbackType)}
{...getSelectProps(fields.feedbackType)}
variant="tertiary/medium"
value={type}
defaultValue={type}
Expand All @@ -164,14 +163,14 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
</SelectItem>
))}
</Select>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
<FormError id={fields.feedbackType.errorId}>{fields.feedbackType.errors}</FormError>
</InputGroup>
<InputGroup className="max-w-full">
<Label>Message</Label>
<TextArea {...conform.textarea(message)} />
<FormError id={message.errorId}>{message.error}</FormError>
<TextArea {...getTextareaProps(fields.message)} />
<FormError id={fields.message.errorId}>{fields.message.errors}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<FormError>{form.errors}</FormError>
<FormButtons
confirmButton={
<Button type="submit" variant="primary/medium">
Expand Down
29 changes: 15 additions & 14 deletions apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { conform, list, requestIntent, useFieldList, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import {
EnvelopeIcon,
GlobeAltIcon,
Expand Down Expand Up @@ -104,20 +104,21 @@ export function ConfigureErrorAlerts({
existingWebhooks.length > 0 ? [...existingWebhooks.map((w) => w.url), ""] : [""]
);

const [form, { emails, webhooks, slackChannel, slackIntegrationId }] = useForm({
const [form, fields] = useForm<z.infer<typeof ErrorAlertsFormSchema>>({
id: "configure-error-alerts",
onValidate({ formData }) {
return parse(formData, { schema: ErrorAlertsFormSchema });
return parseWithZod(formData, { schema: ErrorAlertsFormSchema });
},
shouldRevalidate: "onSubmit",
defaultValue: {
emails: emailFieldValues.current,
webhooks: webhookFieldValues.current,
},
});
const { emails, webhooks, slackChannel, slackIntegrationId } = fields;

const emailFields = useFieldList(form.ref, emails);
const webhookFields = useFieldList(form.ref, webhooks);
const emailFields = emails.getFieldList();
const webhookFields = webhooks.getFieldList();

return (
<div className="grid h-full grid-rows-[auto_1fr_auto] overflow-hidden">
Expand All @@ -138,7 +139,7 @@ export function ConfigureErrorAlerts({
<fetcher.Form
method="post"
action={formAction}
{...form.props}
{...getFormProps(form)}
className="contents"
>
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
Expand All @@ -160,7 +161,7 @@ export function ConfigureErrorAlerts({
{emailFields.map((emailField, index) => (
<Fragment key={emailField.key}>
<Input
{...conform.input(emailField, { type: "email" })}
{...getInputProps(emailField, { type: "email" })}
placeholder={index === 0 ? "Enter an email address" : "Add another email"}
icon={EnvelopeIcon}
onChange={(e) => {
Expand All @@ -169,11 +170,11 @@ export function ConfigureErrorAlerts({
emailFields.length === emailFieldValues.current.length &&
emailFieldValues.current.every((v) => v !== "")
) {
requestIntent(form.ref.current ?? undefined, list.append(emails.name));
form.insert({ name: emails.name });
}
}}
/>
<FormError id={emailField.errorId}>{emailField.error}</FormError>
<FormError id={emailField.errorId}>{emailField.errors}</FormError>
</Fragment>
))}
</InputGroup>
Expand Down Expand Up @@ -320,7 +321,7 @@ export function ConfigureErrorAlerts({
{webhookFields.map((webhookField, index) => (
<Fragment key={webhookField.key}>
<Input
{...conform.input(webhookField, { type: "url" })}
{...getInputProps(webhookField, { type: "url" })}
placeholder={
index === 0 ? "https://example.com/webhook" : "Add another webhook URL"
}
Expand All @@ -331,18 +332,18 @@ export function ConfigureErrorAlerts({
webhookFields.length === webhookFieldValues.current.length &&
webhookFieldValues.current.every((v) => v !== "")
) {
requestIntent(form.ref.current ?? undefined, list.append(webhooks.name));
form.insert({ name: webhooks.name });
}
}}
/>
<FormError id={webhookField.errorId}>{webhookField.error}</FormError>
<FormError id={webhookField.errorId}>{webhookField.errors}</FormError>
</Fragment>
))}
<Hint>We'll issue POST requests to these URLs with a JSON payload.</Hint>
</InputGroup>
</div>

<FormError>{form.error}</FormError>
<FormError>{form.errors}</FormError>
</Fieldset>
</div>

Expand Down
Loading