Skip to content

Commit 2b3d519

Browse files
authored
Add loading state to ok button in confirm delete modal (#1750)
add loading state to ok button in confirm delete modal
1 parent 0895081 commit 2b3d519

File tree

5 files changed

+37
-5
lines changed

5 files changed

+37
-5
lines changed

app/components/ConfirmDeleteModal.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useState } from 'react'
9+
10+
import { type ApiError } from '@oxide/api'
811
import { Message, Modal } from '@oxide/ui'
912
import { classed } from '@oxide/util'
1013

@@ -16,6 +19,12 @@ export const HL = classed.span`text-sans-semi-md text-default`
1619
export function ConfirmDeleteModal() {
1720
const deleteConfig = useConfirmDelete((state) => state.deleteConfig)
1821

22+
// this is a bit sad -- ideally we would be able to use the loading state
23+
// from the mutation directly, but that would require a lot of line changes
24+
// and would require us to hook this up in a way that re-renders whenever the
25+
// loading state changes
26+
const [loading, setLoading] = useState(false)
27+
1928
if (!deleteConfig) return null
2029

2130
const { doDelete, warning, label } = deleteConfig
@@ -31,19 +40,26 @@ export function ConfirmDeleteModal() {
3140
<Modal.Footer
3241
onDismiss={clearConfirmDelete}
3342
onAction={async () => {
34-
await doDelete().catch((error) =>
43+
setLoading(true)
44+
try {
45+
await doDelete()
46+
} catch (error) {
3547
addToast({
3648
variant: 'error',
3749
title: 'Could not delete resource',
38-
content: error.message,
50+
content: (error as ApiError).message,
3951
})
40-
)
52+
}
53+
54+
setLoading(false) // do this regardless of success or error
55+
4156
// TODO: generic success toast?
4257
clearConfirmDelete()
4358
}}
4459
cancelText="Cancel"
4560
actionText="Confirm"
4661
actionType="danger"
62+
actionLoading={loading}
4763
/>
4864
</Modal>
4965
)

app/test/e2e/images.e2e.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,13 @@ test('can delete an image from a project', async ({ page }) => {
125125
await page.goto('/projects/mock-project/images')
126126

127127
await clickRowAction(page, 'image-3', 'Delete')
128+
const spinner = page.getByRole('dialog').getByLabel('Spinner')
129+
await expect(spinner).toBeHidden()
128130
await page.getByRole('button', { name: 'Confirm' }).click()
131+
await expect(spinner).toBeVisible()
129132

130133
// Check deletion was successful
131134
await expectVisible(page, ['text="image-3 has been deleted"'])
132135
await expectNotVisible(page, ['role=cell[name="image-3"]'])
136+
await expect(spinner).toBeHidden()
133137
})

app/test/e2e/snapshots.e2e.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ test('Error on delete snapshot', async ({ page }) => {
6363
const modal = page.getByRole('dialog', { name: 'Confirm delete' })
6464
await expect(modal).toBeVisible()
6565

66+
const spinner = page.getByRole('dialog').getByLabel('Spinner')
67+
await expect(spinner).toBeHidden()
68+
6669
await page.getByRole('button', { name: 'Confirm' }).click()
70+
await expect(spinner).toBeVisible()
6771

6872
// modal closes, but row is not gone and error toast is visible
6973
await expect(modal).toBeHidden()

libs/ui/lib/modal/Modal.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Modal.Footer = ({
118118
onAction,
119119
actionType = 'primary',
120120
actionText,
121+
actionLoading,
121122
cancelText,
122123
disabled = false,
123124
}: {
@@ -126,6 +127,7 @@ Modal.Footer = ({
126127
onAction: () => void
127128
actionType?: 'primary' | 'danger'
128129
actionText: React.ReactNode
130+
actionLoading?: boolean
129131
cancelText?: string
130132
disabled?: boolean
131133
}) => (
@@ -135,7 +137,13 @@ Modal.Footer = ({
135137
<Button variant="secondary" size="sm" onClick={onDismiss}>
136138
{cancelText || 'Cancel'}
137139
</Button>
138-
<Button size="sm" variant={actionType} onClick={onAction} disabled={disabled}>
140+
<Button
141+
size="sm"
142+
variant={actionType}
143+
onClick={onAction}
144+
disabled={disabled}
145+
loading={actionLoading}
146+
>
139147
{actionText}
140148
</Button>
141149
</div>

libs/ui/lib/spinner/Spinner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const Spinner = ({
3838
viewBox={`0 0 ${frameSize + ' ' + frameSize}`}
3939
fill="none"
4040
xmlns="http://www.w3.org/2000/svg"
41-
aria-labelledby="Spinner"
41+
aria-label="Spinner"
4242
className={cn('spinner', `spinner-${variant}`, `spinner-${size}`, className)}
4343
>
4444
<circle

0 commit comments

Comments
 (0)