diff --git a/app/components/form/fields/NameField.tsx b/app/components/form/fields/NameField.tsx index a8fa6d1e16..b369f4db98 100644 --- a/app/components/form/fields/NameField.tsx +++ b/app/components/form/fields/NameField.tsx @@ -30,6 +30,12 @@ export function NameField< required={required} label={label} name={name} + transform={(value) => + value + .toLowerCase() + .replace(/[\s_]+/g, '-') + .replace(/[^a-z0-9-]/g, '') + } {...textFieldProps} /> ) diff --git a/app/components/form/fields/TextField.tsx b/app/components/form/fields/TextField.tsx index 9163a02e48..46b2f41c22 100644 --- a/app/components/form/fields/TextField.tsx +++ b/app/components/form/fields/TextField.tsx @@ -47,7 +47,7 @@ export interface TextFieldProps< validate?: Validate, TFieldValues> control: Control /** Alters the value of the input during the field's onChange event. */ - transform?: (value: string) => FieldPathValue + transform?: (value: string) => string } export function TextField< diff --git a/app/forms/network-interface-create.tsx b/app/forms/network-interface-create.tsx index 43a93b9414..e224016a8b 100644 --- a/app/forms/network-interface-create.tsx +++ b/app/forms/network-interface-create.tsx @@ -7,6 +7,7 @@ */ import { useMemo } from 'react' import { useForm } from 'react-hook-form' +import type { SetRequired } from 'type-fest' import { useApiQuery, type ApiError, type InstanceNetworkInterfaceCreate } from '@oxide/api' @@ -19,10 +20,10 @@ import { SideModalForm } from '~/components/form/SideModalForm' import { useProjectSelector } from '~/hooks/use-params' import { FormDivider } from '~/ui/lib/Divider' -const defaultValues: InstanceNetworkInterfaceCreate = { +const defaultValues: SetRequired = { name: '', description: '', - ip: undefined, + ip: '', subnetName: '', vpcName: '', } @@ -58,7 +59,7 @@ export function CreateNetworkInterfaceForm({ resourceName="network interface" title="Add network interface" onDismiss={onDismiss} - onSubmit={onSubmit} + onSubmit={({ ip, ...rest }) => onSubmit({ ip: ip.trim() || undefined, ...rest })} loading={loading} submitError={submitError} > @@ -81,12 +82,7 @@ export function CreateNetworkInterfaceForm({ required control={form.control} /> - (ip.trim() === '' ? undefined : ip)} - /> + ) } diff --git a/test/e2e/project-create.e2e.ts b/test/e2e/project-create.e2e.ts index 6db69b6682..944c57a607 100644 --- a/test/e2e/project-create.e2e.ts +++ b/test/e2e/project-create.e2e.ts @@ -30,13 +30,15 @@ test.describe('Project create', () => { }) test('shows field-level validation error and does not POST', async ({ page }) => { - await page.fill('role=textbox[name="Name"]', 'Invalid name') - + const input = page.getByRole('textbox', { name: 'Name' }) + await input.pressSequentially('no sPoNgEbOb_CaSe or spaces') + await expect(input).toHaveValue('no-spongebob-case-or-spaces') + await input.fill('no-ending-dash-') // submit to trigger validation await page.getByRole('button', { name: 'Create project' }).click() await expect( - page.getByText('Can only contain lower-case letters, numbers, and dashes').nth(0) + page.getByText('Must end with a letter or number', { exact: true }).nth(0) ).toBeVisible() })