From f96d78770df8d281a5e34cf818d3c646b61d54f0 Mon Sep 17 00:00:00 2001 From: Connie Liu Date: Tue, 10 Dec 2024 12:06:30 -0500 Subject: [PATCH 1/3] update schema, update vpc edit drawer --- .../VPCs/VPCLanding/VPCEditDrawer.tsx | 121 +++++++++--------- packages/validation/src/vpcs.schema.ts | 12 +- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx index 4254e5d38a5..1e2a61d6149 100644 --- a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx @@ -1,7 +1,8 @@ +import { yupResolver } from '@hookform/resolvers/yup'; import { Notice, TextField } from '@linode/ui'; import { updateVPCSchema } from '@linode/validation/lib/vpcs.schema'; -import { useFormik } from 'formik'; import * as React from 'react'; +import { Controller, useForm } from 'react-hook-form'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; @@ -9,7 +10,6 @@ import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useUpdateVPCMutation } from 'src/queries/vpcs/vpcs'; -import { getErrorMap } from 'src/utilities/errorUtils'; import type { UpdateVPCPayload, VPC } from '@linode/api-v4/lib/vpcs/types'; @@ -36,60 +36,53 @@ export const VPCEditDrawer = (props: Props) => { (vpcPermissions?.permissions === 'read_only' || grants?.vpc.length === 0); const { - error, isPending, mutateAsync: updateVPC, - reset, + reset: resetMutation, } = useUpdateVPCMutation(vpc?.id ?? -1); - interface UpdateVPCPayloadWithNone extends UpdateVPCPayload { - none?: string; - } - - const form = useFormik({ - enableReinitialize: true, - initialValues: { + const { + control, + formState: { errors, isDirty, isSubmitting }, + handleSubmit, + reset: resetForm, + setError, + watch, + } = useForm({ + mode: 'onBlur', + resolver: yupResolver(updateVPCSchema), + values: { description: vpc?.description, label: vpc?.label, }, - async onSubmit(values) { - await updateVPC(values); - onClose(); - }, - validateOnChange: false, - validationSchema: updateVPCSchema, }); - const handleFieldChange = (field: string, value: string) => { - form.setFieldValue(field, value); - if (form.errors[field as keyof UpdateVPCPayloadWithNone]) { - form.setFieldError(field, undefined); - } - }; + const values = watch(); - React.useEffect(() => { - if (open) { - form.resetForm(); - reset(); - } - }, [open]); + const handleDrawerClose = () => { + onClose(); + resetForm(); + resetMutation(); + }; - // If there's an error, sync it with formik - React.useEffect(() => { - if (error) { - const errorMap = getErrorMap(['label', 'description'], error); - for (const [field, reason] of Object.entries(errorMap)) { - form.setFieldError(field, reason); + const onSubmit = async () => { + try { + await updateVPC(values); + handleDrawerClose(); + } catch (errors) { + for (const error of errors) { + setError(error?.field ?? 'root', { message: error.reason }); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [error]); + }; const { data: regionsData, error: regionsError } = useRegionsQuery(); return ( - - {form.errors.none && } + + {errors.root?.message && ( + + )} {readOnly && ( { variant="error" /> )} -
- + ( + + )} + control={control} name="label" - onChange={(e) => handleFieldChange('label', e.target.value)} - value={form.values.label} /> - handleFieldChange('description', e.target.value)} - rows={1} - value={form.values.description} + ( + + )} + control={control} + name="description" /> {regionsData && ( {
diff --git a/packages/validation/src/vpcs.schema.ts b/packages/validation/src/vpcs.schema.ts index be92dd771e3..1196436186c 100644 --- a/packages/validation/src/vpcs.schema.ts +++ b/packages/validation/src/vpcs.schema.ts @@ -2,13 +2,13 @@ import ipaddr from 'ipaddr.js'; import { array, lazy, object, string } from 'yup'; const LABEL_MESSAGE = 'Label must be between 1 and 64 characters.'; -const LABEL_REQUIRED = 'Label is required'; +const LABEL_REQUIRED = 'Label is required.'; const LABEL_REQUIREMENTS = - 'Must include only ASCII letters, numbers, and dashes'; + 'Label must include only ASCII letters, numbers, and dashes.'; const labelTestDetails = { testName: 'no two dashes in a row', - testMessage: 'Must not contain two dashes in a row', + testMessage: 'Label must not contain two dashes in a row.', }; const IP_EITHER_BOTH_NOT_NEITHER = @@ -116,11 +116,11 @@ const labelValidation = string() ) .min(1, LABEL_MESSAGE) .max(64, LABEL_MESSAGE) - .matches(/[a-zA-Z0-9-]+/, LABEL_REQUIREMENTS); + .matches(/^[a-zA-Z0-9-]*$/, LABEL_REQUIREMENTS); export const updateVPCSchema = object({ - label: labelValidation.notRequired(), - description: string().notRequired(), + label: labelValidation, + description: string(), }); export const createSubnetSchema = object().shape( From 70bbe75f8d8b873be8651a5c715691c5444ca2c2 Mon Sep 17 00:00:00 2001 From: Connie Liu Date: Tue, 10 Dec 2024 13:19:29 -0500 Subject: [PATCH 2/3] update SubnetEditDrawer --- .../VPCs/VPCDetail/SubnetEditDrawer.tsx | 80 ++++++++++++------- .../VPCs/VPCLanding/VPCEditDrawer.tsx | 9 +-- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx index d4a58da251d..f07b04602b7 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx @@ -1,12 +1,13 @@ +import { yupResolver } from '@hookform/resolvers/yup'; import { Notice, TextField } from '@linode/ui'; -import { useFormik } from 'formik'; +import { modifySubnetSchema } from '@linode/validation'; import * as React from 'react'; +import { Controller, useForm } from 'react-hook-form'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useUpdateSubnetMutation } from 'src/queries/vpcs/vpcs'; -import { getErrorMap } from 'src/utilities/errorUtils'; import type { ModifySubnetPayload, Subnet } from '@linode/api-v4'; @@ -24,29 +25,41 @@ export const SubnetEditDrawer = (props: Props) => { const { onClose, open, subnet, vpcId } = props; const { - error, isPending, mutateAsync: updateSubnet, - reset, + reset: resetMutation, } = useUpdateSubnetMutation(vpcId, subnet?.id ?? -1); - const form = useFormik({ - enableReinitialize: true, - initialValues: { + const { + control, + formState: { errors, isDirty, isSubmitting }, + handleSubmit, + reset: resetForm, + setError, + } = useForm({ + mode: 'onBlur', + resolver: yupResolver(modifySubnetSchema), + values: { label: subnet?.label ?? '', }, - async onSubmit(values) { - await updateSubnet(values); - onClose(); - }, }); - React.useEffect(() => { - if (open) { - form.resetForm(); - reset(); + const handleDrawerClose = () => { + onClose(); + resetForm(); + resetMutation(); + }; + + const onSubmit = async (values: ModifySubnetPayload) => { + try { + await updateSubnet(values); + handleDrawerClose(); + } catch (errors) { + for (const error of errors) { + setError(error?.field ?? 'root', { message: error.reason }); + } } - }, [open]); + }; const { data: profile } = useProfile(); const { data: grants } = useGrants(); @@ -59,11 +72,11 @@ export const SubnetEditDrawer = (props: Props) => { Boolean(profile?.restricted) && (vpcPermissions?.permissions === 'read_only' || grants?.vpc.length === 0); - const errorMap = getErrorMap(['label'], error); - return ( - - {errorMap.none && } + + {errors.root?.message && ( + + )} {readOnly && ( { variant="error" /> )} -
- + ( + + )} + control={control} name="label" - onChange={form.handleChange} - value={form.values.label} /> {
diff --git a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx index 1e2a61d6149..9a2744b44ba 100644 --- a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx @@ -1,6 +1,6 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Notice, TextField } from '@linode/ui'; -import { updateVPCSchema } from '@linode/validation/lib/vpcs.schema'; +import { updateVPCSchema } from '@linode/validation'; import * as React from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -11,7 +11,7 @@ import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useUpdateVPCMutation } from 'src/queries/vpcs/vpcs'; -import type { UpdateVPCPayload, VPC } from '@linode/api-v4/lib/vpcs/types'; +import type { UpdateVPCPayload, VPC } from '@linode/api-v4'; interface Props { onClose: () => void; @@ -47,7 +47,6 @@ export const VPCEditDrawer = (props: Props) => { handleSubmit, reset: resetForm, setError, - watch, } = useForm({ mode: 'onBlur', resolver: yupResolver(updateVPCSchema), @@ -57,15 +56,13 @@ export const VPCEditDrawer = (props: Props) => { }, }); - const values = watch(); - const handleDrawerClose = () => { onClose(); resetForm(); resetMutation(); }; - const onSubmit = async () => { + const onSubmit = async (values: UpdateVPCPayload) => { try { await updateVPC(values); handleDrawerClose(); From d4f1e72bf8b844e308f58267dcba3ee363b4ac33 Mon Sep 17 00:00:00 2001 From: Connie Liu Date: Tue, 10 Dec 2024 13:29:19 -0500 Subject: [PATCH 3/3] changesets --- .../.changeset/pr-11393-tech-stories-1733855306252.md | 5 +++++ .../validation/.changeset/pr-11393-changed-1733855352751.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/manager/.changeset/pr-11393-tech-stories-1733855306252.md create mode 100644 packages/validation/.changeset/pr-11393-changed-1733855352751.md diff --git a/packages/manager/.changeset/pr-11393-tech-stories-1733855306252.md b/packages/manager/.changeset/pr-11393-tech-stories-1733855306252.md new file mode 100644 index 00000000000..045c531f80c --- /dev/null +++ b/packages/manager/.changeset/pr-11393-tech-stories-1733855306252.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Refactor VPCEditDrawer and SubnetEditDrawer to use `react-hook-form` instead of `formik` ([#11393](https://github.com/linode/manager/pull/11393)) diff --git a/packages/validation/.changeset/pr-11393-changed-1733855352751.md b/packages/validation/.changeset/pr-11393-changed-1733855352751.md new file mode 100644 index 00000000000..afb76254075 --- /dev/null +++ b/packages/validation/.changeset/pr-11393-changed-1733855352751.md @@ -0,0 +1,5 @@ +--- +"@linode/validation": Changed +--- + +Update VPC label validation schema punctuation, fix label validation regex ([#11393](https://github.com/linode/manager/pull/11393))