Skip to content

Commit

Permalink
Revert "Revert all app changes since v6 except two small fixes (#1958)"
Browse files Browse the repository at this point in the history
This reverts commit 1f8ebf2.
  • Loading branch information
david-crespo committed Feb 14, 2024
1 parent 1f8ebf2 commit b4552ee
Show file tree
Hide file tree
Showing 29 changed files with 5,052 additions and 5,727 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"rules": {
"playwright/expect-expect": [
"warn",
{ "additionalAssertFunctionNames": ["expectVisible", "expectRowVisible"] }
{ "assertFunctionNames": ["expectVisible", "expectRowVisible"] }
]
}
}
Expand Down
11 changes: 0 additions & 11 deletions app/components/NoLicense2.tsx

This file was deleted.

46 changes: 0 additions & 46 deletions app/components/ThemeIcons.tsx

This file was deleted.

50 changes: 30 additions & 20 deletions app/components/form/FullPageForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { cloneElement, useEffect, type ReactElement, type ReactNode } from 'react'
import type { FieldValues, UseFormReturn } from 'react-hook-form'
import { useBlocker, type unstable_Blocker as Blocker } from 'react-router-dom'
import { useBlocker, type Blocker } from 'react-router-dom'

import type { ApiError } from '@oxide/api'
import { Modal, PageHeader, PageTitle } from '@oxide/ui'
Expand All @@ -25,7 +25,11 @@ interface FullPageFormProps<TFieldValues extends FieldValues> {
error?: Error
form: UseFormReturn<TFieldValues>
loading?: boolean
onSubmit: (values: TFieldValues) => void
/**
* Use await mutateAsync(), otherwise you'll break the logic below that relies
* on knowing when the submit is done.
*/
onSubmit: (values: TFieldValues) => Promise<void>
/** Error from the API call */
submitError: ApiError | null
/**
Expand Down Expand Up @@ -53,22 +57,25 @@ export function FullPageForm<TFieldValues extends FieldValues>({
onSubmit,
submitError,
}: FullPageFormProps<TFieldValues>) {
const { isSubmitting, isDirty } = form.formState
const { isSubmitting, isDirty, isSubmitSuccessful } = form.formState

/*
Confirms with the user if they want to navigate away
if the form is dirty. Does not intercept everything e.g.
refreshes or closing the tab but serves to reduce
the possibility of a user accidentally losing their progress
*/
const blocker = useBlocker(isDirty)
// Confirms with the user if they want to navigate away if the form is
// dirty. Does not intercept everything e.g. refreshes or closing the tab
// but serves to reduce the possibility of a user accidentally losing their
// progress.
const blocker = useBlocker(isDirty && !isSubmitSuccessful)

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

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

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

{actions && (
<PageActions>
Expand Down
4 changes: 2 additions & 2 deletions app/forms/image-from-snapshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Copyright Oxide Computer Company
*/
import fileSize from 'filesize'
import { filesize } from 'filesize'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

import {
Expand Down Expand Up @@ -83,7 +83,7 @@ export function CreateImageFromSnapshotSideModalForm() {
<PropertiesTable.Row label="Snapshot">{data.name}</PropertiesTable.Row>
<PropertiesTable.Row label="Project">{project}</PropertiesTable.Row>
<PropertiesTable.Row label="Size">
{fileSize(data.size, { base: 2 })}
{filesize(data.size, { base: 2 })}
</PropertiesTable.Row>
</PropertiesTable>

Expand Down
2 changes: 1 addition & 1 deletion app/forms/image-upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Copyright Oxide Computer Company
*/
import cn from 'classnames'
import filesize from 'filesize'
import { filesize } from 'filesize'
import pMap from 'p-map'
import pRetry from 'p-retry'
import { useRef, useState } from 'react'
Expand Down
2 changes: 1 addition & 1 deletion app/forms/instance-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export function CreateInstanceForm() {
? await readBlobAsBase64(values.userData)
: undefined

createInstance.mutate({
await createInstance.mutateAsync({
query: projectSelector,
body: {
name: values.name,
Expand Down
8 changes: 1 addition & 7 deletions app/forms/ip-pool-range-add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom'

import { useApiMutation, useApiQueryClient, type IpRange } from '@oxide/api'
import { Message } from '@oxide/ui'
import { IPV4_REGEX, IPV6_REGEX } from '@oxide/util'
import { validateIp } from '@oxide/util'

import { SideModalForm, TextField } from 'app/components/form'
import { useForm, useIpPoolSelector } from 'app/hooks'
Expand All @@ -21,12 +21,6 @@ const defaultValues: IpRange = {
last: '',
}

function validateIp(s: string) {
const isv4 = IPV4_REGEX.test(s)
const isv6 = !isv4 && IPV6_REGEX.test(s)
return { isv4, isv6, valid: isv4 || isv6 }
}

const invalidAddressError = { type: 'pattern', message: 'Not a valid IP address' }

const diffVersionError = {
Expand Down
5 changes: 3 additions & 2 deletions app/pages/project/access/ProjectAccessPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ export function ProjectAccessPage() {
return groupBy(siloRows.concat(projectRows), (u) => u.id)
.map(([userId, userAssignments]) => {
const siloRole = userAssignments.find((a) => a.roleSource === 'silo')?.roleName
const projectRole = userAssignments.find((a) => a.roleSource === 'project')
?.roleName
const projectRole = userAssignments.find(
(a) => a.roleSource === 'project'
)?.roleName

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

Expand Down
2 changes: 1 addition & 1 deletion app/pages/project/instances/instance/InstancePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Copyright Oxide Computer Company
*/
import { format } from 'date-fns'
import filesize from 'filesize'
import { filesize } from 'filesize'
import { useMemo } from 'react'
import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

Expand Down
4 changes: 2 additions & 2 deletions app/pages/system/inventory/sled/SledPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Copyright Oxide Computer Company
*/
import fileSize from 'filesize'
import { filesize } from 'filesize'
import type { LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api'
Expand All @@ -27,7 +27,7 @@ export function SledPage() {
const { sledId } = useSledParams()
const { data: sled } = usePrefetchedApiQuery('sledView', { path: { sledId } })

const ram = fileSize(sled.usablePhysicalRam, { output: 'object', base: 2 })
const ram = filesize(sled.usablePhysicalRam, { output: 'object', base: 2 })

return (
<>
Expand Down
33 changes: 14 additions & 19 deletions app/util/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Copyright Oxide Computer Company
*/
import { formatDistanceToNowStrict } from 'date-fns'
import { formatDistanceToNowStrict, type FormatDistanceToNowStrictOptions } from 'date-fns'

// locale setup and formatDistance function copied from here and modified
// https://github.com/date-fns/date-fns/blob/56a3856/src/locale/en-US/_lib/formatDistance/index.js
Expand All @@ -29,21 +29,16 @@ const formatDistanceLocale = {
almostXYears: '~ {{count}}y',
}

interface Options {
addSuffix: boolean
}

function formatDistance(
token: keyof typeof formatDistanceLocale,
count: string,
options: Options
) {
const result = formatDistanceLocale[token]?.replace('{{count}}', count)
if (result && options.addSuffix) {
return result + ' ago'
}
return result
}

export const timeAgoAbbr = (d: Date, options: Options = { addSuffix: false }) =>
formatDistanceToNowStrict(d, { ...options, locale: { formatDistance } })
export const timeAgoAbbr = (d: Date, options?: FormatDistanceToNowStrictOptions) =>
formatDistanceToNowStrict(d, {
...options,
locale: {
formatDistance: (token, count, options) => {
const result = formatDistanceLocale[token]?.replace('{{count}}', count.toString())
if (result && options?.addSuffix) {
return result + ' ago'
}
return result
},
},
})
2 changes: 1 addition & 1 deletion libs/api-mocks/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export const handlers = makeHandlers({
const instances = db.instances.filter((i) => i.project_id === project.id)
return paginated(query, instances)
},
instanceCreate({ body, query }) {
async instanceCreate({ body, query }) {
const project = lookup.project(query)

if (body.name === 'no-default-pool') {
Expand Down
9 changes: 5 additions & 4 deletions libs/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ import { navToLogin } from './nav-to-login'
/* eslint-disable @typescript-eslint/no-explicit-any */
export type Params<F> = F extends (p: infer P) => any ? P : never
export type Result<F> = F extends (p: any) => Promise<ApiResult<infer R>> ? R : never
export type ResultItem<F> = Result<F> extends { items: (infer R)[] }
? R extends Record<string, unknown>
? R
export type ResultItem<F> =
Result<F> extends { items: (infer R)[] }
? R extends Record<string, unknown>
? R
: never
: never
: never

type ApiClient = Record<string, (...args: any) => Promise<ApiResult<any>>>
/* eslint-enable @typescript-eslint/no-explicit-any */
Expand Down
4 changes: 2 additions & 2 deletions libs/table/cells/InstanceResourceCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Copyright Oxide Computer Company
*/
import fileSize from 'filesize'
import { filesize } from 'filesize'

import type { Instance } from '@oxide/api'

Expand All @@ -14,7 +14,7 @@ import type { Cell } from './Cell'
export const InstanceResourceCell = ({
value,
}: Cell<Pick<Instance, 'ncpus' | 'memory'>>) => {
const memory = fileSize(value.memory, { output: 'object', base: 2 })
const memory = filesize(value.memory, { output: 'object', base: 2 })
return (
<div className="space-y-0.5">
<div>
Expand Down
4 changes: 2 additions & 2 deletions libs/table/cells/SizeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*
* Copyright Oxide Computer Company
*/
import fileSize from 'filesize'
import { filesize } from 'filesize'

import type { Cell } from './Cell'

/** Human-readable format for size in bytes */
export const SizeCell = ({ value: bytes }: Cell<number>) => {
const size = fileSize(bytes, { base: 2, output: 'object' })
const size = filesize(bytes, { base: 2, output: 'object' })
return (
<span className="text-secondary">
{size.value} <span className="text-quaternary">{size.unit}</span>
Expand Down
1 change: 0 additions & 1 deletion libs/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export { useInterval, useTimeout }

export * from './lib/action-menu/ActionMenu'
export * from './lib/auth-code/AuthCodeInput'
export * from './lib/avatar/Avatar'
export * from './lib/badge/Badge'
export * from './lib/button/Button'
export * from './lib/checkbox/Checkbox'
Expand Down
22 changes: 0 additions & 22 deletions libs/ui/lib/avatar-stack/AvatarStack.stories.tsx

This file was deleted.

Loading

0 comments on commit b4552ee

Please sign in to comment.