Skip to content

Commit eb7f856

Browse files
Make toast copy more consistent (#2504)
* Make toast copy more consistent * Add confirmation for disk deletion * Add highlighting to toast contents * keep str test * Fix tests; a few more ToastContents, but will hold here * Move away from ToastContent * Migrate other addToasts to new approach * Add toast on a couple of onSuccess functions missing them * Fix a few tests * Missed a couple * Formatting * surely saying this was the last test to fix won't jinx it * nope; it was not fully reading the previous CI report * Add expectToast helper * use a lighter weight font * Close toasts at end of expectToast * let addToast take the content directly, fix type errors, change one case * a billion one-liner toasts with the magic of prettier-ignore * eliminate HLs by using CSS instead * actually..... do it in tailwind with group. yowza * undo spurious changes to checksums in package-lock.json --------- Co-authored-by: David Crespo <[email protected]>
1 parent df0dea4 commit eb7f856

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+311
-120
lines changed

.github/workflows/lintBuildTest.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- uses: actions/checkout@v4
1414
- uses: actions/setup-node@v4
1515
with:
16-
node-version: 20
16+
node-version: 22
1717
cache: 'npm'
1818
- name: Cache node_modules
1919
uses: actions/cache@v4

app/components/AttachEphemeralIpModal.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useForm } from 'react-hook-form'
1111

1212
import { useApiMutation, useApiQueryClient, usePrefetchedApiQuery } from '~/api'
1313
import { ListboxField } from '~/components/form/fields/ListboxField'
14+
import { HL } from '~/components/HL'
1415
import { useInstanceSelector } from '~/hooks/use-params'
1516
import { addToast } from '~/stores/toast'
1617
import { Modal } from '~/ui/lib/Modal'
@@ -29,9 +30,9 @@ export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void })
2930
[siloPools]
3031
)
3132
const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', {
32-
onSuccess() {
33+
onSuccess(ephemeralIp) {
3334
queryClient.invalidateQueries('instanceExternalIpList')
34-
addToast({ content: 'Your ephemeral IP has been attached' })
35+
addToast(<>IP <HL>{ephemeralIp.ip}</HL> attached</>) // prettier-ignore
3536
onDismiss()
3637
},
3738
onError: (err) => {

app/components/AttachFloatingIpModal.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useForm } from 'react-hook-form'
1010

1111
import { useApiMutation, useApiQueryClient, type FloatingIp, type Instance } from '~/api'
1212
import { ListboxField } from '~/components/form/fields/ListboxField'
13+
import { HL } from '~/components/HL'
1314
import { addToast } from '~/stores/toast'
1415
import { Message } from '~/ui/lib/Message'
1516
import { Modal } from '~/ui/lib/Modal'
@@ -45,10 +46,10 @@ export const AttachFloatingIpModal = ({
4546
}) => {
4647
const queryClient = useApiQueryClient()
4748
const floatingIpAttach = useApiMutation('floatingIpAttach', {
48-
onSuccess() {
49+
onSuccess(floatingIp) {
4950
queryClient.invalidateQueries('floatingIpList')
5051
queryClient.invalidateQueries('instanceExternalIpList')
51-
addToast({ content: 'Your floating IP has been attached' })
52+
addToast(<>IP <HL>{floatingIp.name}</HL> attached</>) // prettier-ignore
5253
onDismiss()
5354
},
5455
onError: (err) => {

app/components/HL.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,11 @@
77
*/
88
import { classed } from '~/util/classed'
99

10-
export const HL = classed.span`text-sans-semi-md text-default`
10+
// note parent with secondary text color must have 'group' on it for
11+
// this to work. see Toast for an example
12+
export const HL = classed.span`
13+
text-sans-md text-default
14+
group-[.text-accent-secondary]:text-accent
15+
group-[.text-error-secondary]:text-error
16+
group-[.text-info-secondary]:text-info
17+
`

app/components/ToastStack.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export function ToastStack() {
2222
})
2323

2424
return (
25-
<div className="pointer-events-auto fixed bottom-4 left-4 z-toast flex flex-col items-end space-y-2">
25+
<div
26+
className="pointer-events-auto fixed bottom-4 left-4 z-toast flex flex-col items-end space-y-2"
27+
data-testid="Toasts"
28+
>
2629
{transition((style, item) => (
2730
<animated.div
2831
style={{

app/forms/disk-create.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ListboxField } from '~/components/form/fields/ListboxField'
2828
import { NameField } from '~/components/form/fields/NameField'
2929
import { RadioField } from '~/components/form/fields/RadioField'
3030
import { SideModalForm } from '~/components/form/SideModalForm'
31+
import { HL } from '~/components/HL'
3132
import { useProjectSelector } from '~/hooks/use-params'
3233
import { addToast } from '~/stores/toast'
3334
import { FormDivider } from '~/ui/lib/Divider'
@@ -76,7 +77,7 @@ export function CreateDiskSideModalForm({
7677
const createDisk = useApiMutation('diskCreate', {
7778
onSuccess(data) {
7879
queryClient.invalidateQueries('diskList')
79-
addToast({ content: 'Your disk has been created' })
80+
addToast(<>Disk <HL>{data.name}</HL> created</>) // prettier-ignore
8081
onSuccess?.(data)
8182
onDismiss(navigate)
8283
},

app/forms/firewall-rules-create.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '@oxide/api'
1919

2020
import { SideModalForm } from '~/components/form/SideModalForm'
21+
import { HL } from '~/components/HL'
2122
import { getVpcSelector, useVpcSelector } from '~/hooks/use-params'
2223
import { addToast } from '~/stores/toast'
2324
import { ALL_ISH } from '~/util/consts'
@@ -74,9 +75,10 @@ export function CreateFirewallRuleForm() {
7475
const onDismiss = () => navigate(pb.vpcFirewallRules(vpcSelector))
7576

7677
const updateRules = useApiMutation('vpcFirewallRulesUpdate', {
77-
onSuccess() {
78+
onSuccess(updatedRules) {
79+
const newRule = updatedRules.rules[updatedRules.rules.length - 1]
7880
queryClient.invalidateQueries('vpcFirewallRulesView')
79-
addToast({ content: 'Your firewall rule has been created' })
81+
addToast(<>Firewall rule <HL>{newRule.name}</HL> created</>) // prettier-ignore
8082
navigate(pb.vpcFirewallRules(vpcSelector))
8183
},
8284
})

app/forms/firewall-rules-edit.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import {
1818

1919
import { trigger404 } from '~/components/ErrorBoundary'
2020
import { SideModalForm } from '~/components/form/SideModalForm'
21+
import { HL } from '~/components/HL'
2122
import {
2223
getFirewallRuleSelector,
2324
useFirewallRuleSelector,
2425
useVpcSelector,
2526
} from '~/hooks/use-params'
27+
import { addToast } from '~/stores/toast'
2628
import { ALL_ISH } from '~/util/consts'
2729
import { invariant } from '~/util/invariant'
2830
import { pb } from '~/util/path-builder'
@@ -64,13 +66,15 @@ export function EditFirewallRuleForm() {
6466
const onDismiss = () => navigate(pb.vpcFirewallRules(vpcSelector))
6567

6668
const updateRules = useApiMutation('vpcFirewallRulesUpdate', {
67-
onSuccess() {
69+
onSuccess(updatedRules, { body }) {
6870
// Nav before the invalidate because I once saw the above invariant fail
6971
// briefly after successful edit (error page flashed but then we land
7072
// on the rules list ok) and I think it was a race condition where the
7173
// invalidate managed to complete while the modal was still open.
7274
onDismiss()
7375
queryClient.invalidateQueries('vpcFirewallRulesView')
76+
const updatedRule = body.rules[body.rules.length - 1]
77+
addToast(<>Firewall rule <HL>{updatedRule.name}</HL> updated</>) // prettier-ignore
7478
},
7579
})
7680

app/forms/floating-ip-create.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { toIpPoolItem } from '~/components/form/fields/ip-pool-item'
2323
import { ListboxField } from '~/components/form/fields/ListboxField'
2424
import { NameField } from '~/components/form/fields/NameField'
2525
import { SideModalForm } from '~/components/form/SideModalForm'
26+
import { HL } from '~/components/HL'
2627
import { useProjectSelector } from '~/hooks/use-params'
2728
import { addToast } from '~/stores/toast'
2829
import { Message } from '~/ui/lib/Message'
@@ -45,10 +46,10 @@ export function CreateFloatingIpSideModalForm() {
4546
const navigate = useNavigate()
4647

4748
const createFloatingIp = useApiMutation('floatingIpCreate', {
48-
onSuccess() {
49+
onSuccess(floatingIp) {
4950
queryClient.invalidateQueries('floatingIpList')
5051
queryClient.invalidateQueries('ipPoolUtilizationView')
51-
addToast({ content: 'Your Floating IP has been created' })
52+
addToast(<>Floating IP <HL>{floatingIp.name}</HL> created</>) // prettier-ignore
5253
navigate(pb.floatingIps(projectSelector))
5354
},
5455
})

app/forms/floating-ip-edit.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { DescriptionField } from '~/components/form/fields/DescriptionField'
1919
import { NameField } from '~/components/form/fields/NameField'
2020
import { SideModalForm } from '~/components/form/SideModalForm'
21+
import { HL } from '~/components/HL'
2122
import { getFloatingIpSelector, useFloatingIpSelector } from '~/hooks/use-params'
2223
import { addToast } from '~/stores/toast'
2324
import { pb } from 'app/util/path-builder'
@@ -47,7 +48,7 @@ export function EditFloatingIpSideModalForm() {
4748
const editFloatingIp = useApiMutation('floatingIpUpdate', {
4849
onSuccess(_floatingIp) {
4950
queryClient.invalidateQueries('floatingIpList')
50-
addToast({ content: 'Your floating IP has been updated' })
51+
addToast(<>Floating IP <HL>{_floatingIp.name}</HL> updated</>) // prettier-ignore
5152
onDismiss()
5253
},
5354
})

app/forms/idp/create.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { FileField } from '~/components/form/fields/FileField'
1515
import { NameField } from '~/components/form/fields/NameField'
1616
import { TextField } from '~/components/form/fields/TextField'
1717
import { SideModalForm } from '~/components/form/SideModalForm'
18+
import { HL } from '~/components/HL'
1819
import { useSiloSelector } from '~/hooks/use-params'
1920
import { addToast } from '~/stores/toast'
2021
import { readBlobAsBase64 } from '~/util/file'
@@ -51,9 +52,9 @@ export function CreateIdpSideModalForm() {
5152
const onDismiss = () => navigate(pb.silo({ silo }))
5253

5354
const createIdp = useApiMutation('samlIdentityProviderCreate', {
54-
onSuccess() {
55+
onSuccess(idp) {
5556
queryClient.invalidateQueries('siloIdentityProviderList')
56-
addToast({ content: 'Your identity provider has been created' })
57+
addToast(<>IdP <HL>{idp.name}</HL> created</>) // prettier-ignore
5758
onDismiss()
5859
},
5960
})

app/forms/image-from-snapshot.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
2121
import { NameField } from '~/components/form/fields/NameField'
2222
import { TextField } from '~/components/form/fields/TextField'
2323
import { SideModalForm } from '~/components/form/SideModalForm'
24+
import { HL } from '~/components/HL'
2425
import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params'
2526
import { addToast } from '~/stores/toast'
2627
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
@@ -54,9 +55,9 @@ export function CreateImageFromSnapshotSideModalForm() {
5455
const onDismiss = () => navigate(pb.snapshots({ project }))
5556

5657
const createImage = useApiMutation('imageCreate', {
57-
onSuccess() {
58+
onSuccess(image) {
5859
queryClient.invalidateQueries('imageList')
59-
addToast({ content: 'Your image has been created' })
60+
addToast(<>Image <HL>{image.name}</HL> created</>) // prettier-ignore
6061
onDismiss()
6162
},
6263
})

app/forms/instance-create.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export function CreateInstanceForm() {
184184
{ path: { instance: instance.name }, query: { project } },
185185
instance
186186
)
187-
addToast({ content: 'Your instance has been created' })
187+
addToast(<>Instance <HL>{instance.name}</HL> created</>) // prettier-ignore
188188
navigate(pb.instance({ project, instance: instance.name }))
189189
},
190190
})

app/forms/ip-pool-create.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useApiMutation, useApiQueryClient, type IpPoolCreate } from '@oxide/api
1313
import { DescriptionField } from '~/components/form/fields/DescriptionField'
1414
import { NameField } from '~/components/form/fields/NameField'
1515
import { SideModalForm } from '~/components/form/SideModalForm'
16+
import { HL } from '~/components/HL'
1617
import { addToast } from '~/stores/toast'
1718
import { Message } from '~/ui/lib/Message'
1819
import { pb } from '~/util/path-builder'
@@ -31,7 +32,7 @@ export function CreateIpPoolSideModalForm() {
3132
const createPool = useApiMutation('ipPoolCreate', {
3233
onSuccess(_pool) {
3334
queryClient.invalidateQueries('ipPoolList')
34-
addToast({ content: 'Your IP pool has been created' })
35+
addToast(<>IP pool <HL>{_pool.name}</HL> created</>) // prettier-ignore
3536
navigate(pb.ipPools())
3637
},
3738
})

app/forms/ip-pool-edit.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { DescriptionField } from '~/components/form/fields/DescriptionField'
1919
import { NameField } from '~/components/form/fields/NameField'
2020
import { SideModalForm } from '~/components/form/SideModalForm'
21+
import { HL } from '~/components/HL'
2122
import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params'
2223
import { addToast } from '~/stores/toast'
2324
import { pb } from '~/util/path-builder'
@@ -43,7 +44,7 @@ export function EditIpPoolSideModalForm() {
4344
onSuccess(updatedPool) {
4445
queryClient.invalidateQueries('ipPoolList')
4546
navigate(pb.ipPool({ pool: updatedPool.name }))
46-
addToast({ content: 'Your IP pool has been updated' })
47+
addToast(<>IP pool <HL>{updatedPool.name}</HL> updated</>) // prettier-ignore
4748

4849
// Only invalidate if we're staying on the same page. If the name
4950
// _has_ changed, invalidating ipPoolView causes an error page to flash

app/forms/network-interface-edit.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
2020
import { NameField } from '~/components/form/fields/NameField'
2121
import { TextFieldInner } from '~/components/form/fields/TextField'
2222
import { SideModalForm } from '~/components/form/SideModalForm'
23+
import { HL } from '~/components/HL'
2324
import { useInstanceSelector } from '~/hooks/use-params'
25+
import { addToast } from '~/stores/toast'
2426
import { FormDivider } from '~/ui/lib/Divider'
2527
import { FieldLabel } from '~/ui/lib/FieldLabel'
2628
import * as MiniTable from '~/ui/lib/MiniTable'
@@ -42,8 +44,9 @@ export function EditNetworkInterfaceForm({
4244
const instanceSelector = useInstanceSelector()
4345

4446
const editNetworkInterface = useApiMutation('instanceNetworkInterfaceUpdate', {
45-
onSuccess() {
47+
onSuccess(nic) {
4648
queryClient.invalidateQueries('instanceNetworkInterfaceList')
49+
addToast(<>Network interface <HL>{nic.name}</HL> updated</>) // prettier-ignore
4750
onDismiss()
4851
},
4952
})

app/forms/project-access.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { ListboxField } from '~/components/form/fields/ListboxField'
1818
import { SideModalForm } from '~/components/form/SideModalForm'
1919
import { useProjectSelector } from '~/hooks/use-params'
20+
import { addToast } from '~/stores/toast'
2021

2122
import {
2223
actorToItem,
@@ -35,6 +36,8 @@ export function ProjectAccessAddUserSideModal({ onDismiss, policy }: AddRoleModa
3536
const updatePolicy = useApiMutation('projectPolicyUpdate', {
3637
onSuccess: () => {
3738
queryClient.invalidateQueries('projectPolicyView')
39+
// We don't have the name of the user or group, so we'll just have a generic message
40+
addToast({ content: 'Role assigned' })
3841
onDismiss()
3942
},
4043
})
@@ -97,6 +100,7 @@ export function ProjectAccessEditUserSideModal({
97100
const updatePolicy = useApiMutation('projectPolicyUpdate', {
98101
onSuccess: () => {
99102
queryClient.invalidateQueries('projectPolicyView')
103+
addToast({ content: 'Role updated' })
100104
onDismiss()
101105
},
102106
})

app/forms/project-create.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useApiMutation, useApiQueryClient, type ProjectCreate } from '@oxide/ap
1313
import { DescriptionField } from '~/components/form/fields/DescriptionField'
1414
import { NameField } from '~/components/form/fields/NameField'
1515
import { SideModalForm } from '~/components/form/SideModalForm'
16+
import { HL } from '~/components/HL'
1617
import { addToast } from '~/stores/toast'
1718
import { pb } from '~/util/path-builder'
1819

@@ -33,7 +34,7 @@ export function CreateProjectSideModalForm() {
3334
queryClient.invalidateQueries('projectList')
3435
// avoid the project fetch when the project page loads since we have the data
3536
queryClient.setQueryData('projectView', { path: { project: project.name } }, project)
36-
addToast({ content: 'Your project has been created' })
37+
addToast(<>Project <HL>{project.name}</HL> created</>) // prettier-ignore
3738
navigate(pb.project({ project: project.name }))
3839
},
3940
})

app/forms/project-edit.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { DescriptionField } from '~/components/form/fields/DescriptionField'
1919
import { NameField } from '~/components/form/fields/NameField'
2020
import { SideModalForm } from '~/components/form/SideModalForm'
21+
import { HL } from '~/components/HL'
2122
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
2223
import { addToast } from '~/stores/toast'
2324
import { pb } from '~/util/path-builder'
@@ -45,7 +46,7 @@ export function EditProjectSideModalForm() {
4546
queryClient.invalidateQueries('projectList')
4647
// avoid the project fetch when the project page loads since we have the data
4748
queryClient.setQueryData('projectView', { path: { project: project.name } }, project)
48-
addToast({ content: 'Your project has been updated' })
49+
addToast(<>Project <HL>{project.name}</HL> updated</>) // prettier-ignore
4950
onDismiss()
5051
},
5152
})

app/forms/silo-create.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RadioField } from '~/components/form/fields/RadioField'
1919
import { TextField } from '~/components/form/fields/TextField'
2020
import { TlsCertsField } from '~/components/form/fields/TlsCertsField'
2121
import { SideModalForm } from '~/components/form/SideModalForm'
22+
import { HL } from '~/components/HL'
2223
import { addToast } from '~/stores/toast'
2324
import { FormDivider } from '~/ui/lib/Divider'
2425
import { FieldLabel } from '~/ui/lib/FieldLabel'
@@ -57,7 +58,7 @@ export function CreateSiloSideModalForm() {
5758
onSuccess(silo) {
5859
queryClient.invalidateQueries('siloList')
5960
queryClient.setQueryData('siloView', { path: { silo: silo.name } }, silo)
60-
addToast({ content: 'Your silo has been created' })
61+
addToast(<>Silo <HL>{silo.name}</HL> created</>) // prettier-ignore
6162
onDismiss()
6263
},
6364
})

app/forms/snapshot-create.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ComboboxField } from '~/components/form/fields/ComboboxField'
2222
import { DescriptionField } from '~/components/form/fields/DescriptionField'
2323
import { NameField } from '~/components/form/fields/NameField'
2424
import { SideModalForm } from '~/components/form/SideModalForm'
25+
import { HL } from '~/components/HL'
2526
import { useProjectSelector } from '~/hooks/use-params'
2627
import { addToast } from '~/stores/toast'
2728
import { toComboboxItems } from '~/ui/lib/Combobox'
@@ -52,9 +53,9 @@ export function CreateSnapshotSideModalForm() {
5253
const onDismiss = () => navigate(pb.snapshots(projectSelector))
5354

5455
const createSnapshot = useApiMutation('snapshotCreate', {
55-
onSuccess() {
56+
onSuccess(snapshot) {
5657
queryClient.invalidateQueries('snapshotList')
57-
addToast({ content: 'Your snapshot has been created' })
58+
addToast(<>Snapshot <HL>{snapshot.name}</HL> created</>) // prettier-ignore
5859
onDismiss()
5960
},
6061
})

0 commit comments

Comments
 (0)