Skip to content

Commit 4e96756

Browse files
authored
Add realtime typing restrictions on NameField (#2512)
* Add realtime typing restrictions on NameField * update test * Add underscore-to-dash conversion * Add a test to verify 'no sPoNgEbOb CaSe or spaces'
1 parent f7d6daf commit 4e96756

File tree

4 files changed

+17
-13
lines changed

4 files changed

+17
-13
lines changed

app/components/form/fields/NameField.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ export function NameField<
3030
required={required}
3131
label={label}
3232
name={name}
33+
transform={(value) =>
34+
value
35+
.toLowerCase()
36+
.replace(/[\s_]+/g, '-')
37+
.replace(/[^a-z0-9-]/g, '')
38+
}
3339
{...textFieldProps}
3440
/>
3541
)

app/components/form/fields/TextField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export interface TextFieldProps<
4747
validate?: Validate<FieldPathValue<TFieldValues, TName>, TFieldValues>
4848
control: Control<TFieldValues>
4949
/** Alters the value of the input during the field's onChange event. */
50-
transform?: (value: string) => FieldPathValue<TFieldValues, TName>
50+
transform?: (value: string) => string
5151
}
5252

5353
export function TextField<

app/forms/network-interface-create.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import { useMemo } from 'react'
99
import { useForm } from 'react-hook-form'
10+
import type { SetRequired } from 'type-fest'
1011

1112
import { useApiQuery, type ApiError, type InstanceNetworkInterfaceCreate } from '@oxide/api'
1213

@@ -19,10 +20,10 @@ import { SideModalForm } from '~/components/form/SideModalForm'
1920
import { useProjectSelector } from '~/hooks/use-params'
2021
import { FormDivider } from '~/ui/lib/Divider'
2122

22-
const defaultValues: InstanceNetworkInterfaceCreate = {
23+
const defaultValues: SetRequired<InstanceNetworkInterfaceCreate, 'ip'> = {
2324
name: '',
2425
description: '',
25-
ip: undefined,
26+
ip: '',
2627
subnetName: '',
2728
vpcName: '',
2829
}
@@ -58,7 +59,7 @@ export function CreateNetworkInterfaceForm({
5859
resourceName="network interface"
5960
title="Add network interface"
6061
onDismiss={onDismiss}
61-
onSubmit={onSubmit}
62+
onSubmit={({ ip, ...rest }) => onSubmit({ ip: ip.trim() || undefined, ...rest })}
6263
loading={loading}
6364
submitError={submitError}
6465
>
@@ -81,12 +82,7 @@ export function CreateNetworkInterfaceForm({
8182
required
8283
control={form.control}
8384
/>
84-
<TextField
85-
name="ip"
86-
label="IP Address"
87-
control={form.control}
88-
transform={(ip) => (ip.trim() === '' ? undefined : ip)}
89-
/>
85+
<TextField name="ip" label="IP Address" control={form.control} />
9086
</SideModalForm>
9187
)
9288
}

test/e2e/project-create.e2e.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ test.describe('Project create', () => {
3030
})
3131

3232
test('shows field-level validation error and does not POST', async ({ page }) => {
33-
await page.fill('role=textbox[name="Name"]', 'Invalid name')
34-
33+
const input = page.getByRole('textbox', { name: 'Name' })
34+
await input.pressSequentially('no sPoNgEbOb_CaSe or spaces')
35+
await expect(input).toHaveValue('no-spongebob-case-or-spaces')
36+
await input.fill('no-ending-dash-')
3537
// submit to trigger validation
3638
await page.getByRole('button', { name: 'Create project' }).click()
3739

3840
await expect(
39-
page.getByText('Can only contain lower-case letters, numbers, and dashes').nth(0)
41+
page.getByText('Must end with a letter or number', { exact: true }).nth(0)
4042
).toBeVisible()
4143
})
4244

0 commit comments

Comments
 (0)