Skip to content

Commit b4552ee

Browse files
committed
Revert "Revert all app changes since v6 except two small fixes (#1958)"
This reverts commit 1f8ebf2.
1 parent 1f8ebf2 commit b4552ee

File tree

29 files changed

+5052
-5727
lines changed

29 files changed

+5052
-5727
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"rules": {
6767
"playwright/expect-expect": [
6868
"warn",
69-
{ "additionalAssertFunctionNames": ["expectVisible", "expectRowVisible"] }
69+
{ "assertFunctionNames": ["expectVisible", "expectRowVisible"] }
7070
]
7171
}
7272
}

app/components/NoLicense2.tsx

Lines changed: 0 additions & 11 deletions
This file was deleted.

app/components/ThemeIcons.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

app/components/form/FullPageForm.tsx

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
import { cloneElement, useEffect, type ReactElement, type ReactNode } from 'react'
99
import type { FieldValues, UseFormReturn } from 'react-hook-form'
10-
import { useBlocker, type unstable_Blocker as Blocker } from 'react-router-dom'
10+
import { useBlocker, type Blocker } from 'react-router-dom'
1111

1212
import type { ApiError } from '@oxide/api'
1313
import { Modal, PageHeader, PageTitle } from '@oxide/ui'
@@ -25,7 +25,11 @@ interface FullPageFormProps<TFieldValues extends FieldValues> {
2525
error?: Error
2626
form: UseFormReturn<TFieldValues>
2727
loading?: boolean
28-
onSubmit: (values: TFieldValues) => void
28+
/**
29+
* Use await mutateAsync(), otherwise you'll break the logic below that relies
30+
* on knowing when the submit is done.
31+
*/
32+
onSubmit: (values: TFieldValues) => Promise<void>
2933
/** Error from the API call */
3034
submitError: ApiError | null
3135
/**
@@ -53,22 +57,25 @@ export function FullPageForm<TFieldValues extends FieldValues>({
5357
onSubmit,
5458
submitError,
5559
}: FullPageFormProps<TFieldValues>) {
56-
const { isSubmitting, isDirty } = form.formState
60+
const { isSubmitting, isDirty, isSubmitSuccessful } = form.formState
5761

58-
/*
59-
Confirms with the user if they want to navigate away
60-
if the form is dirty. Does not intercept everything e.g.
61-
refreshes or closing the tab but serves to reduce
62-
the possibility of a user accidentally losing their progress
63-
*/
64-
const blocker = useBlocker(isDirty)
62+
// Confirms with the user if they want to navigate away if the form is
63+
// dirty. Does not intercept everything e.g. refreshes or closing the tab
64+
// but serves to reduce the possibility of a user accidentally losing their
65+
// progress.
66+
const blocker = useBlocker(isDirty && !isSubmitSuccessful)
6567

66-
// Reset blocker if form is no longer dirty
68+
// Gating on !isSubmitSuccessful above makes the blocker stop blocking nav
69+
// after a successful submit. However, this can take a little time (there is a
70+
// render in between when isSubmitSuccessful is true but the blocker is still
71+
// ready to block), so we also have this useEffect that lets blocked requests
72+
// through if submit is succesful but the blocker hasn't gotten a chance to
73+
// stop blocking yet.
6774
useEffect(() => {
68-
if (blocker.state === 'blocked' && !isDirty) {
69-
blocker.reset()
75+
if (blocker.state === 'blocked' && isSubmitSuccessful) {
76+
blocker.proceed()
7077
}
71-
}, [blocker, isDirty])
78+
}, [blocker, isSubmitSuccessful])
7279

7380
const childArray = flattenChildren(children)
7481
const actions = pluckFirstOfType(childArray, Form.Actions)
@@ -81,24 +88,27 @@ export function FullPageForm<TFieldValues extends FieldValues>({
8188
<form
8289
className="ox-form pb-20"
8390
id={id}
84-
onSubmit={(e) => {
91+
onSubmit={async (e) => {
8592
// This modal being in a portal doesn't prevent the submit event
8693
// from bubbling up out of the portal. Normally that's not a
8794
// problem, but sometimes (e.g., instance create) we render the
8895
// SideModalForm from inside another form, in which case submitting
8996
// the inner form submits the outer form unless we stop propagation
9097
e.stopPropagation()
91-
// This resets `isDirty` whilst keeping the values meaning
92-
// we are not prevented from navigating away by the blocker
93-
form.reset({} as TFieldValues, { keepValues: true })
94-
form.handleSubmit(onSubmit)(e)
98+
// Important to await here so isSubmitSuccessful doesn't become true
99+
// until the submit is actually successful. Note you must use await
100+
// mutateAsync() inside onSubmit in order to make this wait
101+
await form.handleSubmit(onSubmit)(e)
95102
}}
96103
autoComplete="off"
97104
>
98105
{childArray}
99106
</form>
100107

101-
{blocker ? <ConfirmNavigation blocker={blocker} /> : null}
108+
{/* rendering of the modal must be gated on isSubmitSuccessful because
109+
there is a brief moment where isSubmitSuccessful is true but the proceed()
110+
hasn't fired yet, which means we get a brief flash of this modal */}
111+
{!isSubmitSuccessful && <ConfirmNavigation blocker={blocker} />}
102112

103113
{actions && (
104114
<PageActions>

app/forms/image-from-snapshot.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import fileSize from 'filesize'
8+
import { filesize } from 'filesize'
99
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
1010

1111
import {
@@ -83,7 +83,7 @@ export function CreateImageFromSnapshotSideModalForm() {
8383
<PropertiesTable.Row label="Snapshot">{data.name}</PropertiesTable.Row>
8484
<PropertiesTable.Row label="Project">{project}</PropertiesTable.Row>
8585
<PropertiesTable.Row label="Size">
86-
{fileSize(data.size, { base: 2 })}
86+
{filesize(data.size, { base: 2 })}
8787
</PropertiesTable.Row>
8888
</PropertiesTable>
8989

app/forms/image-upload.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88
import cn from 'classnames'
9-
import filesize from 'filesize'
9+
import { filesize } from 'filesize'
1010
import pMap from 'p-map'
1111
import pRetry from 'p-retry'
1212
import { useRef, useState } from 'react'

app/forms/instance-create.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export function CreateInstanceForm() {
188188
? await readBlobAsBase64(values.userData)
189189
: undefined
190190

191-
createInstance.mutate({
191+
await createInstance.mutateAsync({
192192
query: projectSelector,
193193
body: {
194194
name: values.name,

app/forms/ip-pool-range-add.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom'
99

1010
import { useApiMutation, useApiQueryClient, type IpRange } from '@oxide/api'
1111
import { Message } from '@oxide/ui'
12-
import { IPV4_REGEX, IPV6_REGEX } from '@oxide/util'
12+
import { validateIp } from '@oxide/util'
1313

1414
import { SideModalForm, TextField } from 'app/components/form'
1515
import { useForm, useIpPoolSelector } from 'app/hooks'
@@ -21,12 +21,6 @@ const defaultValues: IpRange = {
2121
last: '',
2222
}
2323

24-
function validateIp(s: string) {
25-
const isv4 = IPV4_REGEX.test(s)
26-
const isv6 = !isv4 && IPV6_REGEX.test(s)
27-
return { isv4, isv6, valid: isv4 || isv6 }
28-
}
29-
3024
const invalidAddressError = { type: 'pattern', message: 'Not a valid IP address' }
3125

3226
const diffVersionError = {

app/pages/project/access/ProjectAccessPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ export function ProjectAccessPage() {
9696
return groupBy(siloRows.concat(projectRows), (u) => u.id)
9797
.map(([userId, userAssignments]) => {
9898
const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName
99-
const projectRole = userAssignments.find((a) => a.roleSource === 'project')
100-
?.roleName
99+
const projectRole = userAssignments.find(
100+
(a) => a.roleSource === 'project'
101+
)?.roleName
101102

102103
const roles = [siloRole, projectRole].filter(isTruthy)
103104

app/pages/project/instances/instance/InstancePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88
import { format } from 'date-fns'
9-
import filesize from 'filesize'
9+
import { filesize } from 'filesize'
1010
import { useMemo } from 'react'
1111
import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
1212

0 commit comments

Comments
 (0)