From c86cc2234443e8d85c10ca0e8b53baa62d18b733 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 14 Oct 2024 13:24:22 -0400 Subject: [PATCH 01/21] Make toast copy more consistent --- app/components/AttachEphemeralIpModal.tsx | 4 ++-- app/components/AttachFloatingIpModal.tsx | 4 ++-- app/forms/disk-create.tsx | 2 +- app/forms/firewall-rules-create.tsx | 5 +++-- app/forms/floating-ip-create.tsx | 4 ++-- app/forms/floating-ip-edit.tsx | 2 +- app/forms/idp/create.tsx | 4 ++-- app/forms/image-from-snapshot.tsx | 4 ++-- app/forms/instance-create.tsx | 2 +- app/forms/ip-pool-create.tsx | 2 +- app/forms/ip-pool-edit.tsx | 2 +- app/forms/project-create.tsx | 2 +- app/forms/project-edit.tsx | 2 +- app/forms/silo-create.tsx | 2 +- app/forms/snapshot-create.tsx | 4 ++-- app/forms/ssh-key-create.tsx | 4 ++-- app/forms/vpc-create.tsx | 2 +- app/forms/vpc-edit.tsx | 2 +- app/forms/vpc-router-create.tsx | 4 ++-- app/forms/vpc-router-edit.tsx | 4 ++-- app/forms/vpc-router-route-create.tsx | 4 ++-- app/forms/vpc-router-route-edit.tsx | 4 ++-- app/pages/project/disks/DisksPage.tsx | 4 ++-- app/pages/project/floating-ips/FloatingIpsPage.tsx | 12 ++++++------ app/pages/project/images/ImagesPage.tsx | 4 ++-- .../instances/instance/tabs/NetworkingTab.tsx | 10 +++++----- .../project/instances/instance/tabs/StorageTab.tsx | 8 ++++---- app/pages/project/vpcs/RouterPage.tsx | 4 ++-- app/pages/project/vpcs/VpcPage/VpcPage.tsx | 4 ++-- .../project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx | 4 ++-- app/pages/project/vpcs/VpcsPage.tsx | 4 ++-- app/pages/settings/SSHKeysPage.tsx | 4 ++-- app/pages/system/SiloImagesPage.tsx | 6 +++--- app/pages/system/networking/IpPoolPage.tsx | 4 ++-- app/pages/system/networking/IpPoolsPage.tsx | 4 ++-- test/e2e/disks.e2e.ts | 5 +++-- test/e2e/images.e2e.ts | 12 +++++------- test/e2e/instance-disks.e2e.ts | 6 +++--- 38 files changed, 82 insertions(+), 82 deletions(-) diff --git a/app/components/AttachEphemeralIpModal.tsx b/app/components/AttachEphemeralIpModal.tsx index 878021a11f..b8bf866635 100644 --- a/app/components/AttachEphemeralIpModal.tsx +++ b/app/components/AttachEphemeralIpModal.tsx @@ -28,9 +28,9 @@ export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) [siloPools] ) const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', { - onSuccess() { + onSuccess(ephemeralIp) { queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: 'Your ephemeral IP has been attached' }) + addToast({ content: `${ephemeralIp.ip} attached` }) onDismiss() }, onError: (err) => { diff --git a/app/components/AttachFloatingIpModal.tsx b/app/components/AttachFloatingIpModal.tsx index eaedd2dbdc..7598cd52ec 100644 --- a/app/components/AttachFloatingIpModal.tsx +++ b/app/components/AttachFloatingIpModal.tsx @@ -45,10 +45,10 @@ export const AttachFloatingIpModal = ({ }) => { const queryClient = useApiQueryClient() const floatingIpAttach = useApiMutation('floatingIpAttach', { - onSuccess() { + onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: 'Your floating IP has been attached' }) + addToast({ content: `${floatingIp.name} attached` }) onDismiss() }, onError: (err) => { diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index c3671a67f7..bfe0636603 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -76,7 +76,7 @@ export function CreateDiskSideModalForm({ const createDisk = useApiMutation('diskCreate', { onSuccess(data) { queryClient.invalidateQueries('diskList') - addToast({ content: 'Your disk has been created' }) + addToast({ content: `${data.name} created` }) onSuccess?.(data) onDismiss(navigate) }, diff --git a/app/forms/firewall-rules-create.tsx b/app/forms/firewall-rules-create.tsx index 99bebe081f..ed4d961d39 100644 --- a/app/forms/firewall-rules-create.tsx +++ b/app/forms/firewall-rules-create.tsx @@ -74,9 +74,10 @@ export function CreateFirewallRuleForm() { const onDismiss = () => navigate(pb.vpcFirewallRules(vpcSelector)) const updateRules = useApiMutation('vpcFirewallRulesUpdate', { - onSuccess() { + onSuccess(updatedRules) { + const newRule = updatedRules.rules[updatedRules.rules.length - 1] queryClient.invalidateQueries('vpcFirewallRulesView') - addToast({ content: 'Your firewall rule has been created' }) + addToast({ content: `${newRule.name} created` }) navigate(pb.vpcFirewallRules(vpcSelector)) }, }) diff --git a/app/forms/floating-ip-create.tsx b/app/forms/floating-ip-create.tsx index 77424696c7..83a6e2dbe5 100644 --- a/app/forms/floating-ip-create.tsx +++ b/app/forms/floating-ip-create.tsx @@ -65,10 +65,10 @@ export function CreateFloatingIpSideModalForm() { const navigate = useNavigate() const createFloatingIp = useApiMutation('floatingIpCreate', { - onSuccess() { + onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ content: 'Your Floating IP has been created' }) + addToast({ content: `${floatingIp.name} created` }) navigate(pb.floatingIps(projectSelector)) }, }) diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx index 44b19bd538..dffc0662d8 100644 --- a/app/forms/floating-ip-edit.tsx +++ b/app/forms/floating-ip-edit.tsx @@ -47,7 +47,7 @@ export function EditFloatingIpSideModalForm() { const editFloatingIp = useApiMutation('floatingIpUpdate', { onSuccess(_floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: 'Your floating IP has been updated' }) + addToast({ content: `${_floatingIp.name} updated` }) onDismiss() }, }) diff --git a/app/forms/idp/create.tsx b/app/forms/idp/create.tsx index 4fa8e10fa7..401baf3fb7 100644 --- a/app/forms/idp/create.tsx +++ b/app/forms/idp/create.tsx @@ -51,9 +51,9 @@ export function CreateIdpSideModalForm() { const onDismiss = () => navigate(pb.silo({ silo })) const createIdp = useApiMutation('samlIdentityProviderCreate', { - onSuccess() { + onSuccess(idp) { queryClient.invalidateQueries('siloIdentityProviderList') - addToast({ content: 'Your identity provider has been created' }) + addToast({ content: `${idp.name} created` }) onDismiss() }, }) diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index 48c6b9e793..5f441f3f8d 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -54,9 +54,9 @@ export function CreateImageFromSnapshotSideModalForm() { const onDismiss = () => navigate(pb.snapshots({ project })) const createImage = useApiMutation('imageCreate', { - onSuccess() { + onSuccess(image) { queryClient.invalidateQueries('imageList') - addToast({ content: 'Your image has been created' }) + addToast({ content: `${image.name} created` }) onDismiss() }, }) diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 35f91958c5..0398d42096 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -183,7 +183,7 @@ export function CreateInstanceForm() { { path: { instance: instance.name }, query: { project } }, instance ) - addToast({ content: 'Your instance has been created' }) + addToast({ content: `${instance.name} created` }) navigate(pb.instance({ project, instance: instance.name })) }, }) diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index c91e8d8d31..b885b85982 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -30,7 +30,7 @@ export function CreateIpPoolSideModalForm() { const createPool = useApiMutation('ipPoolCreate', { onSuccess(_pool) { queryClient.invalidateQueries('ipPoolList') - addToast({ content: 'Your IP pool has been created' }) + addToast({ content: `${_pool.name} created` }) navigate(pb.ipPools()) }, }) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index 73e2c942c5..d417d5f8fc 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -41,7 +41,7 @@ export function EditIpPoolSideModalForm() { onSuccess(updatedPool) { queryClient.invalidateQueries('ipPoolList') navigate(pb.ipPool({ pool: updatedPool.name })) - addToast({ content: 'Your IP pool has been updated' }) + addToast({ content: `${updatedPool.name} updated` }) // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating ipPoolView causes an error page to flash diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index 020894826c..71f7ae1864 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -33,7 +33,7 @@ export function CreateProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ content: 'Your project has been created' }) + addToast({ content: `${project.name} created` }) navigate(pb.project({ project: project.name })) }, }) diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 284c1de8de..36444a7a0d 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -45,7 +45,7 @@ export function EditProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ content: 'Your project has been updated' }) + addToast({ content: `${project.name} updated` }) onDismiss() }, }) diff --git a/app/forms/silo-create.tsx b/app/forms/silo-create.tsx index 9508386bae..58029da493 100644 --- a/app/forms/silo-create.tsx +++ b/app/forms/silo-create.tsx @@ -57,7 +57,7 @@ export function CreateSiloSideModalForm() { onSuccess(silo) { queryClient.invalidateQueries('siloList') queryClient.setQueryData('siloView', { path: { silo: silo.name } }, silo) - addToast({ content: 'Your silo has been created' }) + addToast({ content: `${silo.name} created` }) onDismiss() }, }) diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 930cec2381..347cc19926 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -52,9 +52,9 @@ export function CreateSnapshotSideModalForm() { const onDismiss = () => navigate(pb.snapshots(projectSelector)) const createSnapshot = useApiMutation('snapshotCreate', { - onSuccess() { + onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ content: 'Your snapshot has been created' }) + addToast({ content: `${snapshot.name} created` }) onDismiss() }, }) diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 14e5b399a3..0a2f373f51 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -35,10 +35,10 @@ export function CreateSSHKeySideModalForm({ onDismiss, message }: Props) { const handleDismiss = onDismiss ? onDismiss : () => navigate(pb.sshKeys()) const createSshKey = useApiMutation('currentUserSshKeyCreate', { - onSuccess() { + onSuccess(sshKey) { queryClient.invalidateQueries('currentUserSshKeyList') handleDismiss() - addToast({ content: 'Your SSH key has been created' }) + addToast({ content: `${sshKey.name} created` }) }, }) const form = useForm({ defaultValues }) diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index f93d040b8c..d8cc642d4e 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -38,7 +38,7 @@ export function CreateVpcSideModalForm() { { path: { vpc: vpc.name }, query: projectSelector }, vpc ) - addToast({ content: 'Your VPC has been created' }) + addToast({ content: `${vpc.name} created` }) navigate(pb.vpc({ vpc: vpc.name, ...projectSelector })) }, }) diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index 9a6380f5fa..2af26e9c49 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -42,7 +42,7 @@ export function EditVpcSideModalForm() { onSuccess(updatedVpc) { queryClient.invalidateQueries('vpcList') navigate(pb.vpc({ project, vpc: updatedVpc.name })) - addToast({ content: 'Your VPC has been updated' }) + addToast({ content: `${updatedVpc.name} updated` }) // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating vpcView causes an error page to flash diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index c808d3a099..e41fad85da 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -30,9 +30,9 @@ export function CreateRouterSideModalForm() { const onDismiss = () => navigate(pb.vpcRouters(vpcSelector)) const createRouter = useApiMutation('vpcRouterCreate', { - onSuccess() { + onSuccess(router) { queryClient.invalidateQueries('vpcRouterList') - addToast({ content: 'Your router has been created' }) + addToast({ content: `${router.name} created` }) onDismiss() }, }) diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 3d8067022d..525fe3baa2 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -51,9 +51,9 @@ export function EditRouterSideModalForm() { } const editRouter = useApiMutation('vpcRouterUpdate', { - onSuccess() { + onSuccess(updatedRouter) { queryClient.invalidateQueries('vpcRouterList') - addToast({ content: 'Your router has been updated' }) + addToast({ content: `${updatedRouter.name} updated` }) navigate(pb.vpcRouters({ project, vpc })) }, }) diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index 4ed2afe6c0..78cafe8ce8 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -44,9 +44,9 @@ export function CreateRouterRouteSideModalForm() { const form = useForm({ defaultValues }) const createRouterRoute = useApiMutation('vpcRouterRouteCreate', { - onSuccess() { + onSuccess(route) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: 'Your route has been created' }) + addToast({ content: `${route.name} created` }) navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index 19ac9934e2..8aa9fa20b8 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -62,9 +62,9 @@ export function EditRouterRouteSideModalForm() { const disabled = route?.kind === 'vpc_subnet' const updateRouterRoute = useApiMutation('vpcRouterRouteUpdate', { - onSuccess() { + onSuccess(updatedRoute) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: 'Your route has been updated' }) + addToast({ content: `${updatedRoute.name} updated` }) navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 11e0c215d6..397c96cee6 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -105,9 +105,9 @@ export function DisksPage() { }) const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('snapshotList') - addToast({ content: 'Snapshot successfully created' }) + addToast({ content: `Snapshot ${variables.body.name} created` }) }, onError(err) { addToast({ diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 08d035c033..2cc410a5e9 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -108,19 +108,19 @@ export function FloatingIpsPage() { const navigate = useNavigate() const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { - onSuccess() { + onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: 'Your floating IP has been detached' }) + addToast({ content: `${floatingIp.name} detached` }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) }, }) const { mutateAsync: deleteFloatingIp } = useApiMutation('floatingIpDelete', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ content: 'Your floating IP has been deleted' }) + addToast({ content: `${variables.path.floatingIp} deleted` }) }, }) @@ -250,9 +250,9 @@ const AttachFloatingIpModal = ({ }) => { const queryClient = useApiQueryClient() const floatingIpAttach = useApiMutation('floatingIpAttach', { - onSuccess() { + onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: 'Your floating IP has been attached' }) + addToast({ content: `${floatingIp.name} attached` }) onDismiss() }, onError: (err) => { diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index 726357a6f8..4637fd3e03 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -58,7 +58,7 @@ export function ImagesPage() { const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ content: `${variables.path.image} has been deleted` }) + addToast({ content: `${variables.path.image} deleted` }) queryClient.invalidateQueries('imageList') }, }) @@ -131,7 +131,7 @@ const PromoteImageModal = ({ onDismiss, imageName }: PromoteModalProps) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { addToast({ - content: `${data.name} has been promoted`, + content: `${data.name} promoted`, cta: { text: 'View silo images', link: '/images', diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 0082f7cfc4..e2ea1bafb1 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -202,9 +202,9 @@ export function NetworkingTab() { }, }) const { mutateAsync: deleteNic } = useApiMutation('instanceNetworkInterfaceDelete', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast({ content: 'Network interface deleted' }) + addToast({ content: `${variables.path.interface} deleted` }) }, }) const { mutate: editNic } = useApiMutation('instanceNetworkInterfaceUpdate', { @@ -297,7 +297,7 @@ export function NetworkingTab() { const { mutateAsync: ephemeralIpDetach } = useApiMutation('instanceEphemeralIpDetach', { onSuccess() { queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: 'Your ephemeral IP has been detached' }) + addToast({ content: 'Ephemeral IP detached' }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) @@ -305,10 +305,10 @@ export function NetworkingTab() { }) const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: 'Your floating IP has been detached' }) + addToast({ content: `${variables.path.floatingIp} detached` }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) diff --git a/app/pages/project/instances/instance/tabs/StorageTab.tsx b/app/pages/project/instances/instance/tabs/StorageTab.tsx index 1083b509e5..7e730bfc2e 100644 --- a/app/pages/project/instances/instance/tabs/StorageTab.tsx +++ b/app/pages/project/instances/instance/tabs/StorageTab.tsx @@ -87,9 +87,9 @@ export function StorageTab() { ) const { mutate: detachDisk } = useApiMutation('instanceDiskDetach', { - onSuccess() { + onSuccess(disk) { queryClient.invalidateQueries('instanceDiskList') - addToast({ content: 'Disk detached' }) + addToast({ content: `${disk.name} detached` }) }, onError(err) { addToast({ @@ -100,9 +100,9 @@ export function StorageTab() { }, }) const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { - onSuccess() { + onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ content: 'Snapshot created' }) + addToast({ content: `${snapshot.name} created` }) }, onError(err) { addToast({ diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index b91ae862a5..275f80c9cd 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -88,9 +88,9 @@ export function RouterPage() { }) const { mutateAsync: deleteRouterRoute } = useApiMutation('vpcRouterRouteDelete', { - onSuccess() { + onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: 'Your route has been deleted' }) + addToast({ content: `${variables.path.route} deleted` }) }, }) diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index 5c5c5d912a..0c19cec4a6 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -46,10 +46,10 @@ export function VpcPage() { }) const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') navigate(pb.vpcs({ project })) - addToast({ content: 'Your VPC has been deleted' }) + addToast({ content: `${variables.path.vpc} deleted` }) }, }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx index 361d7a4921..81204d1915 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx @@ -62,9 +62,9 @@ export function VpcRoutersTab() { ) const { mutateAsync: deleteRouter } = useApiMutation('vpcRouterDelete', { - onSuccess() { + onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterList') - addToast({ content: 'Your router has been deleted' }) + addToast({ content: `${variables.path.router} deleted` }) }, }) diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index e5ce773f28..82e8a51557 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -83,9 +83,9 @@ export function VpcsPage() { const navigate = useNavigate() const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') - addToast({ content: 'Your VPC has been deleted' }) + addToast({ content: `${variables.path.vpc} deleted` }) }, }) diff --git a/app/pages/settings/SSHKeysPage.tsx b/app/pages/settings/SSHKeysPage.tsx index 97ad48f883..dac271f1ea 100644 --- a/app/pages/settings/SSHKeysPage.tsx +++ b/app/pages/settings/SSHKeysPage.tsx @@ -46,9 +46,9 @@ export function SSHKeysPage() { const queryClient = useApiQueryClient() const { mutateAsync: deleteSshKey } = useApiMutation('currentUserSshKeyDelete', { - onSuccess: () => { + onSuccess: (_data, variables) => { queryClient.invalidateQueries('currentUserSshKeyList') - addToast({ content: 'Your SSH key has been deleted' }) + addToast({ content: `${variables.path.sshKey} deleted` }) }, }) diff --git a/app/pages/system/SiloImagesPage.tsx b/app/pages/system/SiloImagesPage.tsx index 6c27a1cf53..94d30a651e 100644 --- a/app/pages/system/SiloImagesPage.tsx +++ b/app/pages/system/SiloImagesPage.tsx @@ -72,7 +72,7 @@ export function SiloImagesPage() { const queryClient = useApiQueryClient() const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ content: `${variables.path.image} has been deleted` }) + addToast({ content: `${variables.path.image} deleted` }) queryClient.invalidateQueries('imageList') }, }) @@ -131,7 +131,7 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { - addToast({ content: `${data.name} has been promoted` }) + addToast({ content: `${data.name} promoted` }) queryClient.invalidateQueries('imageList') }, onError: (err) => { @@ -218,7 +218,7 @@ const DemoteImageModal = ({ const demoteImage = useApiMutation('imageDemote', { onSuccess(data) { addToast({ - content: `${data.name} has been demoted`, + content: `${data.name} demoted`, cta: selectedProject ? { text: `View images in ${selectedProject}`, diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index 7461dc41c1..36dc444b2e 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -82,10 +82,10 @@ export function IpPoolPage() { }) const navigate = useNavigate() const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { - onSuccess() { + onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') navigate(pb.ipPools()) - addToast({ content: 'IP pool deleted' }) + addToast({ content: `${variables.path.pool} deleted` }) }, }) diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index 8084dc77f7..d9b761fd69 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -78,9 +78,9 @@ export function IpPoolsPage() { }) const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { - onSuccess() { + onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') - addToast({ content: 'IP pool deleted' }) + addToast({ content: `${variables.path.pool} deleted` }) }, }) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 03398c4cd6..1287aed51c 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -29,7 +29,8 @@ test('List disks and snapshot', async ({ page }) => { await clickRowAction(page, 'disk-1 db1', 'Snapshot') await expect(page.getByText("Creating snapshot of disk 'disk-1'").nth(0)).toBeVisible() - await expect(page.getByText('Snapshot successfully created').nth(0)).toBeVisible() + // Next line is a little awkward, but we don't actually know what the snapshot name will be + await expect(page.getByText('Successsnapshot disk-1-')).toBeVisible() }) test('Disk snapshot error', async ({ page }) => { @@ -53,7 +54,7 @@ test.describe('Disk create', () => { test.afterEach(async ({ page }) => { await page.getByRole('button', { name: 'Create disk' }).click() - await expectVisible(page, ['text="Your disk has been created"']) + await expectVisible(page, ['text="a-new-disk created"']) await expectVisible(page, ['role=cell[name="a-new-disk"]']) }) diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index c15f91fc80..7362b69a94 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -52,7 +52,7 @@ test('can promote an image from silo', async ({ page }) => { await page.locator('role=button[name="Promote"]').click() // Check it was promoted successfully - await expectVisible(page, ['text="image-1 has been promoted"']) + await expectVisible(page, ['text="image-1 promoted"']) await expectVisible(page, ['role=cell[name="image-1"]']) }) @@ -68,7 +68,7 @@ test('can promote an image from project', async ({ page }) => { // Promote image and check it was successful await page.locator('role=button[name="Promote"]').click() - await expectVisible(page, ['text="image-2 has been promoted"']) + await expectVisible(page, ['text="image-2 promoted"']) await expectNotVisible(page, ['role=cell[name="image-2"]']) await page.click('role=link[name="View silo images"]') @@ -112,7 +112,7 @@ test('can demote an image from silo', async ({ page }) => { await page.getByRole('button', { name: 'Demote' }).click() // Promote image and check it was successful - await expectVisible(page, ['text="arch-2022-06-01 has been demoted"']) + await expectVisible(page, ['text="arch-2022-06-01 demoted"']) await expectNotVisible(page, ['role=cell[name="arch-2022-06-01"]']) await page.click('role=link[name="View images in mock-project"]') @@ -132,7 +132,7 @@ test('can delete an image from a project', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('image-3 has been deleted', { exact: true })).toBeVisible() + await expect(page.getByText('image-3 deleted', { exact: true })).toBeVisible() await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) @@ -150,9 +150,7 @@ test('can delete an image from a silo', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect( - page.getByText('ubuntu-20-04 has been deleted', { exact: true }) - ).toBeVisible() + await expect(page.getByText('ubuntu-20-04 deleted', { exact: true })).toBeVisible() await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) diff --git a/test/e2e/instance-disks.e2e.ts b/test/e2e/instance-disks.e2e.ts index 9e54716196..3e7e01cf23 100644 --- a/test/e2e/instance-disks.e2e.ts +++ b/test/e2e/instance-disks.e2e.ts @@ -130,7 +130,7 @@ test('Detach disk', async ({ page }) => { // Have to stop instance to edit disks await stopInstance(page) - const successMsg = page.getByText('Disk detached').nth(0) + const successMsg = page.getByText('Successdisk-2 detached') const row = page.getByRole('row', { name: 'disk-2' }) await expect(row).toBeVisible() await expect(successMsg).toBeHidden() @@ -143,8 +143,8 @@ test('Detach disk', async ({ page }) => { test('Snapshot disk', async ({ page }) => { await page.goto('/projects/mock-project/instances/db1') - // have to use nth with toasts because the text shows up in multiple spots - const successMsg = page.getByText('Snapshot created').nth(0) + // we don't know the full name of the disk, but this will work to find the toast + const successMsg = page.getByText('Successdisk-1-') await expect(successMsg).toBeHidden() await clickRowAction(page, 'disk-1', 'Snapshot') From a55c4e7812db35be623a6e9f6204aab167ed26a0 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Mon, 14 Oct 2024 11:37:43 -0700 Subject: [PATCH 02/21] Add confirmation for disk deletion --- app/pages/project/disks/DisksPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 397c96cee6..9776731bdd 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -99,8 +99,9 @@ export function DisksPage() { const { Table } = useQueryTable('diskList', { query: { project } }) const { mutateAsync: deleteDisk } = useApiMutation('diskDelete', { - onSuccess() { + onSuccess(_data, variables) { queryClient.invalidateQueries('diskList') + addToast({ content: `${variables.path.disk} deleted` }) }, }) From 29fe8a59139027782bf124f1bbabbd7a062d5830 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 15 Oct 2024 17:40:53 -0700 Subject: [PATCH 03/21] Add highlighting to toast contents --- app/pages/project/disks/DisksPage.tsx | 5 +- app/ui/lib/Toast.tsx | 27 ++++++++-- app/util/str.spec.ts | 78 --------------------------- app/util/str.ts | 18 +++++++ 4 files changed, 45 insertions(+), 83 deletions(-) delete mode 100644 app/util/str.spec.ts diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 9776731bdd..25e851a003 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -32,6 +32,7 @@ import { CreateLink } from '~/ui/lib/CreateButton' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions } from '~/ui/lib/Table' +import { ToastContent } from '~/ui/lib/Toast' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' @@ -101,7 +102,9 @@ export function DisksPage() { const { mutateAsync: deleteDisk } = useApiMutation('diskDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('diskList') - addToast({ content: `${variables.path.disk} deleted` }) + addToast({ + content: , + }) }, }) diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index c11a32d059..f0c5a3a218 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -7,7 +7,7 @@ */ import { announce } from '@react-aria/live-announcer' import cn from 'classnames' -import { useEffect, type ReactElement } from 'react' +import { useEffect, type ReactElement, type ReactNode } from 'react' import { Link, type To } from 'react-router-dom' import { @@ -17,6 +17,9 @@ import { Warning12Icon, } from '@oxide/design-system/icons/react' +import { HL } from '~/components/HL' +import { extractText } from '~/util/str' + import { TimeoutIndicator } from './TimeoutIndicator' import { Truncate } from './Truncate' @@ -24,7 +27,7 @@ type Variant = 'success' | 'error' | 'info' export interface ToastProps { title?: string - content?: string + content?: string | ReactNode onClose: () => void variant?: Variant timeout?: number | null @@ -82,7 +85,7 @@ export const Toast = ({ const timeout = timeoutArg === undefined ? defaultTimeout : timeoutArg // TODO: consider assertive announce for error toasts useEffect( - () => announce((title || defaultTitle[variant]) + ' ' + content, 'polite'), + () => announce((title || defaultTitle[variant]) + ' ' + extractText(content), 'polite'), [title, content, variant] ) return ( @@ -95,7 +98,9 @@ export const Toast = ({ >
{icon[variant]}
-
{title || defaultTitle[variant]}
+ {(title || variant !== 'success') && ( +
{title || defaultTitle[variant]}
+ )}
{content}
{cta && ( @@ -126,3 +131,17 @@ export const Toast = ({
) } + +type kind = 'Disk' // more to come, or we can just make this `string` +type ToastVerb = 'created' | 'updated' | 'deleted' | 'promoted' | 'demoted' +type ToastContentProps = { kind: kind; name: string; verb: ToastVerb; variant?: Variant } +export const ToastContent = ({ + kind, + name, + verb, + variant = 'success', +}: ToastContentProps) => ( + <> + {kind} {name} {verb} + +) diff --git a/app/util/str.spec.ts b/app/util/str.spec.ts deleted file mode 100644 index 3a4a8a28e2..0000000000 --- a/app/util/str.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright Oxide Computer Company - */ -import { describe, expect, it } from 'vitest' - -import { camelCase, capitalize, commaSeries, kebabCase, titleCase } from './str' - -describe('capitalize', () => { - it('capitalizes the first letter', () => { - expect(capitalize('this is a sentence')).toEqual('This is a sentence') - }) -}) - -describe('camelCase', () => { - it('basic formats to camel case', () => { - expect(camelCase('camelCase')).toBe('camelCase') - expect(camelCase('separate words')).toBe('separateWords') - expect(camelCase('snake_case')).toBe('snakeCase') - expect(camelCase('kebab-case')).toBe('kebabCase') - expect(camelCase('PascalCase')).toBe('pascalCase') - expect(camelCase('SCREAMING_CASE')).toBe('screamingCase') - expect(camelCase('whatEVerTHIS_iS')).toBe('whatEverThisIs') - }) -}) - -describe('kebabCase', () => { - it('basic formats to kebab case', () => { - expect(kebabCase('kebab-case')).toBe('kebab-case') - expect(kebabCase('separate words')).toBe('separate-words') - expect(kebabCase('snake_case')).toBe('snake-case') - expect(kebabCase('kebab-case')).toBe('kebab-case') - expect(kebabCase('PascalCase')).toBe('pascal-case') - expect(kebabCase('SCREAMING_CASE')).toBe('screaming-case') - expect(kebabCase('whatEVerTHIS_iS')).toBe('what-ever-this-is') - }) -}) - -it('commaSeries', () => { - expect(commaSeries([], 'or')).toBe('') - expect(commaSeries(['a'], 'or')).toBe('a') - expect(commaSeries(['a', 'b'], 'or')).toBe('a or b') - expect(commaSeries(['a', 'b'], 'or')).toBe('a or b') - expect(commaSeries(['a', 'b', 'c'], 'or')).toBe('a, b, or c') -}) - -describe('titleCase', () => { - it('converts single words to title case', () => { - expect(titleCase('hello')).toBe('Hello') - }) - - it('converts multiple words to title case', () => { - expect(titleCase('hello world')).toBe('Hello World') - }) - - it('handles mixed case input correctly', () => { - expect(titleCase('hElLo WoRlD')).toBe('Hello World') - }) - - it('works correctly with strings containing punctuation', () => { - expect(titleCase('hello, world!')).toBe('Hello, World!') - }) - - it('works correctly with empty strings', () => { - expect(titleCase('')).toBe('') - }) - - it('handles strings with only one character', () => { - expect(titleCase('a')).toBe('A') - }) - - it('doesn’t modify non-letter characters', () => { - expect(titleCase('123 abc')).toBe('123 Abc') - }) -}) diff --git a/app/util/str.ts b/app/util/str.ts index 934530917c..a7620050c9 100644 --- a/app/util/str.ts +++ b/app/util/str.ts @@ -6,6 +6,8 @@ * Copyright Oxide Computer Company */ +import React from 'react' + export const capitalize = (s: string) => s && s.charAt(0).toUpperCase() + s.slice(1) export const pluralize = (s: string, n: number) => `${n} ${s}${n === 1 ? '' : 's'}` @@ -55,3 +57,19 @@ export const titleCase = (text: string): string => { * it look like `AAAAAAAAAAAAAAAA==`? */ export const isAllZeros = (base64Data: string) => /^A*=*$/.test(base64Data) + +/** + * Extract the string contents of a ReactNode, so <>This highlighted text becomes "This highlighted text" + */ +export const extractText = (children: React.ReactNode): string => + React.Children.toArray(children) + .map((child) => + typeof child === 'string' + ? child + : React.isValidElement(child) + ? extractText(child.props.children) + : '' + ) + .join(' ') + .trim() + .replace(/\s+/g, ' ') From d8ea07a289f8605b8e9e085a23b7ddbfb6fa92c1 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 15 Oct 2024 17:41:19 -0700 Subject: [PATCH 04/21] keep str test --- app/util/str.spec.tsx | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 app/util/str.spec.tsx diff --git a/app/util/str.spec.tsx b/app/util/str.spec.tsx new file mode 100644 index 0000000000..1be63c62d8 --- /dev/null +++ b/app/util/str.spec.tsx @@ -0,0 +1,112 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { describe, expect, it } from 'vitest' + +import { + camelCase, + capitalize, + commaSeries, + extractText, + kebabCase, + titleCase, +} from './str' + +describe('capitalize', () => { + it('capitalizes the first letter', () => { + expect(capitalize('this is a sentence')).toEqual('This is a sentence') + }) +}) + +describe('camelCase', () => { + it('basic formats to camel case', () => { + expect(camelCase('camelCase')).toBe('camelCase') + expect(camelCase('separate words')).toBe('separateWords') + expect(camelCase('snake_case')).toBe('snakeCase') + expect(camelCase('kebab-case')).toBe('kebabCase') + expect(camelCase('PascalCase')).toBe('pascalCase') + expect(camelCase('SCREAMING_CASE')).toBe('screamingCase') + expect(camelCase('whatEVerTHIS_iS')).toBe('whatEverThisIs') + }) +}) + +describe('kebabCase', () => { + it('basic formats to kebab case', () => { + expect(kebabCase('kebab-case')).toBe('kebab-case') + expect(kebabCase('separate words')).toBe('separate-words') + expect(kebabCase('snake_case')).toBe('snake-case') + expect(kebabCase('kebab-case')).toBe('kebab-case') + expect(kebabCase('PascalCase')).toBe('pascal-case') + expect(kebabCase('SCREAMING_CASE')).toBe('screaming-case') + expect(kebabCase('whatEVerTHIS_iS')).toBe('what-ever-this-is') + }) +}) + +it('commaSeries', () => { + expect(commaSeries([], 'or')).toBe('') + expect(commaSeries(['a'], 'or')).toBe('a') + expect(commaSeries(['a', 'b'], 'or')).toBe('a or b') + expect(commaSeries(['a', 'b'], 'or')).toBe('a or b') + expect(commaSeries(['a', 'b', 'c'], 'or')).toBe('a, b, or c') +}) + +describe('titleCase', () => { + it('converts single words to title case', () => { + expect(titleCase('hello')).toBe('Hello') + }) + + it('converts multiple words to title case', () => { + expect(titleCase('hello world')).toBe('Hello World') + }) + + it('handles mixed case input correctly', () => { + expect(titleCase('hElLo WoRlD')).toBe('Hello World') + }) + + it('works correctly with strings containing punctuation', () => { + expect(titleCase('hello, world!')).toBe('Hello, World!') + }) + + it('works correctly with empty strings', () => { + expect(titleCase('')).toBe('') + }) + + it('handles strings with only one character', () => { + expect(titleCase('a')).toBe('A') + }) + + it('doesn’t modify non-letter characters', () => { + expect(titleCase('123 abc')).toBe('123 Abc') + }) +}) + +describe('extractText', () => { + it('extracts strings from React components', () => { + expect( + extractText( + <> + This is my text + + ) + ).toBe('This is my text') + }) + it('extracts strings from nested elements', () => { + expect( + extractText( +

+ This is my{' '} + + nested text + +

+ ) + ).toBe('This is my nested text') + }) + it('can handle regular strings', () => { + expect(extractText('Some more text')).toBe('Some more text') + }) +}) From 72f754067f8a4e88cab1ba29bbd68154cb4f8efe Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 15 Oct 2024 18:37:14 -0700 Subject: [PATCH 05/21] Fix tests; a few more ToastContents, but will hold here --- app/forms/disk-create.tsx | 3 ++- app/forms/ip-pool-create.tsx | 3 ++- app/pages/project/disks/DisksPage.tsx | 4 +++- .../project/instances/instance/tabs/StorageTab.tsx | 7 +++++-- app/ui/lib/Toast.tsx | 13 ++++++++++--- test/e2e/disks.e2e.ts | 4 ++-- test/e2e/instance-disks.e2e.ts | 4 ++-- 7 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index bfe0636603..f184900880 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -35,6 +35,7 @@ import { FieldLabel } from '~/ui/lib/FieldLabel' import { Radio } from '~/ui/lib/Radio' import { RadioGroup } from '~/ui/lib/RadioGroup' import { Slash } from '~/ui/lib/Slash' +import { ToastContent } from '~/ui/lib/Toast' import { toLocaleDateString } from '~/util/date' import { bytesToGiB, GiB } from '~/util/units' @@ -76,7 +77,7 @@ export function CreateDiskSideModalForm({ const createDisk = useApiMutation('diskCreate', { onSuccess(data) { queryClient.invalidateQueries('diskList') - addToast({ content: `${data.name} created` }) + addToast({ content: }) onSuccess?.(data) onDismiss(navigate) }, diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index b885b85982..4a3dc64f6f 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -14,6 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' import { addToast } from '~/stores/toast' +import { ToastContent } from '~/ui/lib/Toast' import { pb } from '~/util/path-builder' const defaultValues: IpPoolCreate = { @@ -30,7 +31,7 @@ export function CreateIpPoolSideModalForm() { const createPool = useApiMutation('ipPoolCreate', { onSuccess(_pool) { queryClient.invalidateQueries('ipPoolList') - addToast({ content: `${_pool.name} created` }) + addToast({ content: }) navigate(pb.ipPools()) }, }) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 25e851a003..52b5c7e94d 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -111,7 +111,9 @@ export function DisksPage() { const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(_data, variables) { queryClient.invalidateQueries('snapshotList') - addToast({ content: `Snapshot ${variables.body.name} created` }) + addToast({ + content: , + }) }, onError(err) { addToast({ diff --git a/app/pages/project/instances/instance/tabs/StorageTab.tsx b/app/pages/project/instances/instance/tabs/StorageTab.tsx index 7e730bfc2e..56b434b5db 100644 --- a/app/pages/project/instances/instance/tabs/StorageTab.tsx +++ b/app/pages/project/instances/instance/tabs/StorageTab.tsx @@ -37,6 +37,7 @@ import { Button } from '~/ui/lib/Button' import { CreateButton } from '~/ui/lib/CreateButton' import { EMBody, EmptyMessage } from '~/ui/lib/EmptyMessage' import { TableControls, TableEmptyBox, TableTitle } from '~/ui/lib/Table' +import { ToastContent } from '~/ui/lib/Toast' import { links } from '~/util/links' import { fancifyStates } from './common' @@ -89,7 +90,7 @@ export function StorageTab() { const { mutate: detachDisk } = useApiMutation('instanceDiskDetach', { onSuccess(disk) { queryClient.invalidateQueries('instanceDiskList') - addToast({ content: `${disk.name} detached` }) + addToast({ content: }) }, onError(err) { addToast({ @@ -102,7 +103,9 @@ export function StorageTab() { const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ content: `${snapshot.name} created` }) + addToast({ + content: , + }) }, onError(err) { addToast({ diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index f0c5a3a218..13f56b4b69 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -27,7 +27,7 @@ type Variant = 'success' | 'error' | 'info' export interface ToastProps { title?: string - content?: string | ReactNode + content?: ReactNode onClose: () => void variant?: Variant timeout?: number | null @@ -132,8 +132,15 @@ export const Toast = ({ ) } -type kind = 'Disk' // more to come, or we can just make this `string` -type ToastVerb = 'created' | 'updated' | 'deleted' | 'promoted' | 'demoted' +type kind = 'Disk' | 'Pool' | 'Snapshot' // more to come, or we can just make this `string` +type ToastVerb = + | 'created' + | 'updated' + | 'deleted' + | 'promoted' + | 'demoted' + | 'attached' + | 'detached' type ToastContentProps = { kind: kind; name: string; verb: ToastVerb; variant?: Variant } export const ToastContent = ({ kind, diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 1287aed51c..5b05cfee46 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -30,7 +30,7 @@ test('List disks and snapshot', async ({ page }) => { await clickRowAction(page, 'disk-1 db1', 'Snapshot') await expect(page.getByText("Creating snapshot of disk 'disk-1'").nth(0)).toBeVisible() // Next line is a little awkward, but we don't actually know what the snapshot name will be - await expect(page.getByText('Successsnapshot disk-1-')).toBeVisible() + await expect(page.getByText('Snapshot disk-1-')).toBeVisible() }) test('Disk snapshot error', async ({ page }) => { @@ -54,7 +54,7 @@ test.describe('Disk create', () => { test.afterEach(async ({ page }) => { await page.getByRole('button', { name: 'Create disk' }).click() - await expectVisible(page, ['text="a-new-disk created"']) + await expect(page.getByText('Disk a-new-disk created')).toBeVisible() await expectVisible(page, ['role=cell[name="a-new-disk"]']) }) diff --git a/test/e2e/instance-disks.e2e.ts b/test/e2e/instance-disks.e2e.ts index 3e7e01cf23..19f3835d9b 100644 --- a/test/e2e/instance-disks.e2e.ts +++ b/test/e2e/instance-disks.e2e.ts @@ -130,7 +130,7 @@ test('Detach disk', async ({ page }) => { // Have to stop instance to edit disks await stopInstance(page) - const successMsg = page.getByText('Successdisk-2 detached') + const successMsg = page.getByText('Disk disk-2 detached') const row = page.getByRole('row', { name: 'disk-2' }) await expect(row).toBeVisible() await expect(successMsg).toBeHidden() @@ -144,7 +144,7 @@ test('Snapshot disk', async ({ page }) => { await page.goto('/projects/mock-project/instances/db1') // we don't know the full name of the disk, but this will work to find the toast - const successMsg = page.getByText('Successdisk-1-') + const successMsg = page.getByText('Snapshot disk-1-') await expect(successMsg).toBeHidden() await clickRowAction(page, 'disk-1', 'Snapshot') From d284ad5c38e682313d823880efd5f5e1e36f3885 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 10:23:44 -0700 Subject: [PATCH 06/21] Move away from ToastContent --- app/components/AttachEphemeralIpModal.tsx | 10 ++++++++- app/components/AttachFloatingIpModal.tsx | 10 ++++++++- app/components/HL.tsx | 9 ++++++++ app/forms/disk-create.tsx | 10 +++++++-- app/forms/firewall-rules-create.tsx | 9 +++++++- app/forms/floating-ip-create.tsx | 9 +++++++- app/forms/floating-ip-edit.tsx | 9 +++++++- app/forms/image-from-snapshot.tsx | 9 +++++++- app/forms/ip-pool-create.tsx | 10 +++++++-- app/pages/project/disks/DisksPage.tsx | 14 +++++++++--- .../instances/instance/tabs/StorageTab.tsx | 17 ++++++++++---- app/ui/lib/Toast.tsx | 22 ------------------- 12 files changed, 99 insertions(+), 39 deletions(-) diff --git a/app/components/AttachEphemeralIpModal.tsx b/app/components/AttachEphemeralIpModal.tsx index b8bf866635..86cf7ac1de 100644 --- a/app/components/AttachEphemeralIpModal.tsx +++ b/app/components/AttachEphemeralIpModal.tsx @@ -17,6 +17,8 @@ import { Badge } from '~/ui/lib/Badge' import { Modal } from '~/ui/lib/Modal' import { ALL_ISH } from '~/util/consts' +import { HLs } from './HL' + export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) => { const queryClient = useApiQueryClient() const { project, instance } = useInstanceSelector() @@ -30,7 +32,13 @@ export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', { onSuccess(ephemeralIp) { queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: `${ephemeralIp.ip} attached` }) + addToast({ + content: ( + <> + IP {ephemeralIp.ip} attached + + ), + }) onDismiss() }, onError: (err) => { diff --git a/app/components/AttachFloatingIpModal.tsx b/app/components/AttachFloatingIpModal.tsx index 7598cd52ec..aa7a770ab9 100644 --- a/app/components/AttachFloatingIpModal.tsx +++ b/app/components/AttachFloatingIpModal.tsx @@ -15,6 +15,8 @@ import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' import { Slash } from '~/ui/lib/Slash' +import { HLs } from './HL' + function FloatingIpLabel({ fip }: { fip: FloatingIp }) { return (
@@ -48,7 +50,13 @@ export const AttachFloatingIpModal = ({ onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: `${floatingIp.name} attached` }) + addToast({ + content: ( + <> + IP {floatingIp.name} attached + + ), + }) onDismiss() }, onError: (err) => { diff --git a/app/components/HL.tsx b/app/components/HL.tsx index 234dab7d30..35198df202 100644 --- a/app/components/HL.tsx +++ b/app/components/HL.tsx @@ -8,3 +8,12 @@ import { classed } from '~/util/classed' export const HL = classed.span`text-sans-semi-md text-default` + +/** HL with "success"-colored text */ +export const HLs = classed.span`text-sans-semi-md text-accent children:text-accent` + +// HL with "error"-colored text +export const HLe = classed.span`text-sans-semi-md text-error children:text-error` + +// HL with "info"-colored text +export const HLi = classed.span`text-sans-semi-md text-notice children:text-notice` diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index f184900880..8f659504f0 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -28,6 +28,7 @@ import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' import { RadioField } from '~/components/form/fields/RadioField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' @@ -35,7 +36,6 @@ import { FieldLabel } from '~/ui/lib/FieldLabel' import { Radio } from '~/ui/lib/Radio' import { RadioGroup } from '~/ui/lib/RadioGroup' import { Slash } from '~/ui/lib/Slash' -import { ToastContent } from '~/ui/lib/Toast' import { toLocaleDateString } from '~/util/date' import { bytesToGiB, GiB } from '~/util/units' @@ -77,7 +77,13 @@ export function CreateDiskSideModalForm({ const createDisk = useApiMutation('diskCreate', { onSuccess(data) { queryClient.invalidateQueries('diskList') - addToast({ content: }) + addToast({ + content: ( + <> + Disk {data.name}created + + ), + }) onSuccess?.(data) onDismiss(navigate) }, diff --git a/app/forms/firewall-rules-create.tsx b/app/forms/firewall-rules-create.tsx index ed4d961d39..61629f4099 100644 --- a/app/forms/firewall-rules-create.tsx +++ b/app/forms/firewall-rules-create.tsx @@ -18,6 +18,7 @@ import { } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { ALL_ISH } from '~/util/consts' @@ -77,7 +78,13 @@ export function CreateFirewallRuleForm() { onSuccess(updatedRules) { const newRule = updatedRules.rules[updatedRules.rules.length - 1] queryClient.invalidateQueries('vpcFirewallRulesView') - addToast({ content: `${newRule.name} created` }) + addToast({ + content: ( + <> + Rule {newRule.name} created + + ), + }) navigate(pb.vpcFirewallRules(vpcSelector)) }, }) diff --git a/app/forms/floating-ip-create.tsx b/app/forms/floating-ip-create.tsx index 83a6e2dbe5..d8b12e338a 100644 --- a/app/forms/floating-ip-create.tsx +++ b/app/forms/floating-ip-create.tsx @@ -23,6 +23,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Badge } from '~/ui/lib/Badge' @@ -68,7 +69,13 @@ export function CreateFloatingIpSideModalForm() { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ content: `${floatingIp.name} created` }) + addToast({ + content: ( + <> + Floating IP {floatingIp.name} created + + ), + }) navigate(pb.floatingIps(projectSelector)) }, }) diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx index dffc0662d8..8896196e3f 100644 --- a/app/forms/floating-ip-edit.tsx +++ b/app/forms/floating-ip-edit.tsx @@ -18,6 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getFloatingIpSelector, useFloatingIpSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from 'app/util/path-builder' @@ -47,7 +48,13 @@ export function EditFloatingIpSideModalForm() { const editFloatingIp = useApiMutation('floatingIpUpdate', { onSuccess(_floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: `${_floatingIp.name} updated` }) + addToast({ + content: ( + <> + Floating IP {_floatingIp.name} updated + + ), + }) onDismiss() }, }) diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index 5f441f3f8d..acb7aa0ce5 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -21,6 +21,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { PropertiesTable } from '~/ui/lib/PropertiesTable' @@ -56,7 +57,13 @@ export function CreateImageFromSnapshotSideModalForm() { const createImage = useApiMutation('imageCreate', { onSuccess(image) { queryClient.invalidateQueries('imageList') - addToast({ content: `${image.name} created` }) + addToast({ + content: ( + <> + Image {image.name} created + + ), + }) onDismiss() }, }) diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index 4a3dc64f6f..132b76e429 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -13,8 +13,8 @@ import { useApiMutation, useApiQueryClient, type IpPoolCreate } from '@oxide/api import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { addToast } from '~/stores/toast' -import { ToastContent } from '~/ui/lib/Toast' import { pb } from '~/util/path-builder' const defaultValues: IpPoolCreate = { @@ -31,7 +31,13 @@ export function CreateIpPoolSideModalForm() { const createPool = useApiMutation('ipPoolCreate', { onSuccess(_pool) { queryClient.invalidateQueries('ipPoolList') - addToast({ content: }) + addToast({ + content: ( + <> + Pool {_pool.name} created + + ), + }) navigate(pb.ipPools()) }, }) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 52b5c7e94d..3d90665d3d 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -20,6 +20,7 @@ import { import { Storage16Icon, Storage24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { DiskStateBadge } from '~/components/StateBadge' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' @@ -32,7 +33,6 @@ import { CreateLink } from '~/ui/lib/CreateButton' import { EmptyMessage } from '~/ui/lib/EmptyMessage' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { TableActions } from '~/ui/lib/Table' -import { ToastContent } from '~/ui/lib/Toast' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' @@ -103,7 +103,11 @@ export function DisksPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('diskList') addToast({ - content: , + content: ( + <> + Disk {variables.path.disk} deleted + + ), }) }, }) @@ -112,7 +116,11 @@ export function DisksPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('snapshotList') addToast({ - content: , + content: ( + <> + Snapshot {variables.body.name} created + + ), }) }, onError(err) { diff --git a/app/pages/project/instances/instance/tabs/StorageTab.tsx b/app/pages/project/instances/instance/tabs/StorageTab.tsx index 56b434b5db..f966a38ff7 100644 --- a/app/pages/project/instances/instance/tabs/StorageTab.tsx +++ b/app/pages/project/instances/instance/tabs/StorageTab.tsx @@ -23,7 +23,7 @@ import { } from '@oxide/api' import { Storage24Icon } from '@oxide/design-system/icons/react' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { DiskStateBadge } from '~/components/StateBadge' import { AttachDiskSideModalForm } from '~/forms/disk-attach' import { CreateDiskSideModalForm } from '~/forms/disk-create' @@ -37,7 +37,6 @@ import { Button } from '~/ui/lib/Button' import { CreateButton } from '~/ui/lib/CreateButton' import { EMBody, EmptyMessage } from '~/ui/lib/EmptyMessage' import { TableControls, TableEmptyBox, TableTitle } from '~/ui/lib/Table' -import { ToastContent } from '~/ui/lib/Toast' import { links } from '~/util/links' import { fancifyStates } from './common' @@ -90,7 +89,13 @@ export function StorageTab() { const { mutate: detachDisk } = useApiMutation('instanceDiskDetach', { onSuccess(disk) { queryClient.invalidateQueries('instanceDiskList') - addToast({ content: }) + addToast({ + content: ( + <> + Disk {disk.name} detached + + ), + }) }, onError(err) { addToast({ @@ -104,7 +109,11 @@ export function StorageTab() { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') addToast({ - content: , + content: ( + <> + Snapshot {snapshot.name} created + + ), }) }, onError(err) { diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index 13f56b4b69..a190ceded9 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -17,7 +17,6 @@ import { Warning12Icon, } from '@oxide/design-system/icons/react' -import { HL } from '~/components/HL' import { extractText } from '~/util/str' import { TimeoutIndicator } from './TimeoutIndicator' @@ -131,24 +130,3 @@ export const Toast = ({
) } - -type kind = 'Disk' | 'Pool' | 'Snapshot' // more to come, or we can just make this `string` -type ToastVerb = - | 'created' - | 'updated' - | 'deleted' - | 'promoted' - | 'demoted' - | 'attached' - | 'detached' -type ToastContentProps = { kind: kind; name: string; verb: ToastVerb; variant?: Variant } -export const ToastContent = ({ - kind, - name, - verb, - variant = 'success', -}: ToastContentProps) => ( - <> - {kind} {name} {verb} - -) From bc37a3d9cd47def83dd95679f7a586c1b19bd6eb Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 10:53:02 -0700 Subject: [PATCH 07/21] Migrate other addToasts to new approach --- app/forms/idp/create.tsx | 9 ++++++- app/forms/instance-create.tsx | 10 +++++-- app/forms/ip-pool-edit.tsx | 9 ++++++- app/forms/project-create.tsx | 9 ++++++- app/forms/project-edit.tsx | 9 ++++++- app/forms/silo-create.tsx | 9 ++++++- app/forms/snapshot-create.tsx | 9 ++++++- app/forms/ssh-key-create.tsx | 9 ++++++- app/forms/vpc-create.tsx | 9 ++++++- app/forms/vpc-edit.tsx | 9 ++++++- app/forms/vpc-router-create.tsx | 9 ++++++- app/forms/vpc-router-edit.tsx | 9 ++++++- app/forms/vpc-router-route-create.tsx | 9 ++++++- app/forms/vpc-router-route-edit.tsx | 9 ++++++- .../project/floating-ips/FloatingIpsPage.tsx | 26 ++++++++++++++++--- app/pages/project/images/ImagesPage.tsx | 15 +++++++++-- .../instances/instance/tabs/NetworkingTab.tsx | 18 ++++++++++--- app/pages/project/vpcs/RouterPage.tsx | 10 +++++-- app/pages/project/vpcs/VpcPage/VpcPage.tsx | 9 ++++++- .../vpcs/VpcPage/tabs/VpcRoutersTab.tsx | 9 ++++++- app/pages/project/vpcs/VpcsPage.tsx | 9 ++++++- app/pages/settings/SSHKeysPage.tsx | 9 ++++++- app/pages/system/SiloImagesPage.tsx | 23 +++++++++++++--- app/pages/system/networking/IpPoolPage.tsx | 10 +++++-- app/pages/system/networking/IpPoolsPage.tsx | 9 ++++++- 25 files changed, 238 insertions(+), 36 deletions(-) diff --git a/app/forms/idp/create.tsx b/app/forms/idp/create.tsx index 401baf3fb7..995e78bc37 100644 --- a/app/forms/idp/create.tsx +++ b/app/forms/idp/create.tsx @@ -15,6 +15,7 @@ import { FileField } from '~/components/form/fields/FileField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useSiloSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { readBlobAsBase64 } from '~/util/file' @@ -53,7 +54,13 @@ export function CreateIdpSideModalForm() { const createIdp = useApiMutation('samlIdentityProviderCreate', { onSuccess(idp) { queryClient.invalidateQueries('siloIdentityProviderList') - addToast({ content: `${idp.name} created` }) + addToast({ + content: ( + <> + IDP {idp.name} created + + ), + }) onDismiss() }, }) diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 0398d42096..02ec8e64a9 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -54,7 +54,7 @@ import { SshKeysField } from '~/components/form/fields/SshKeysField' import { TextField } from '~/components/form/fields/TextField' import { Form } from '~/components/form/Form' import { FullPageForm } from '~/components/form/FullPageForm' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Badge } from '~/ui/lib/Badge' @@ -183,7 +183,13 @@ export function CreateInstanceForm() { { path: { instance: instance.name }, query: { project } }, instance ) - addToast({ content: `${instance.name} created` }) + addToast({ + content: ( + <> + Instance {instance.name} created + + ), + }) navigate(pb.instance({ project, instance: instance.name })) }, }) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index d417d5f8fc..fb0524d79a 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -18,6 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -41,7 +42,13 @@ export function EditIpPoolSideModalForm() { onSuccess(updatedPool) { queryClient.invalidateQueries('ipPoolList') navigate(pb.ipPool({ pool: updatedPool.name })) - addToast({ content: `${updatedPool.name} updated` }) + addToast({ + content: ( + <> + IP Pool {updatedPool.name} updated + + ), + }) // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating ipPoolView causes an error page to flash diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index 71f7ae1864..3c75457a8a 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -13,6 +13,7 @@ import { useApiMutation, useApiQueryClient, type ProjectCreate } from '@oxide/ap import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -33,7 +34,13 @@ export function CreateProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ content: `${project.name} created` }) + addToast({ + content: ( + <> + Project {project.name} created + + ), + }) navigate(pb.project({ project: project.name })) }, }) diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 36444a7a0d..4ba627475b 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -18,6 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -45,7 +46,13 @@ export function EditProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ content: `${project.name} updated` }) + addToast({ + content: ( + <> + Project {project.name} updated + + ), + }) onDismiss() }, }) diff --git a/app/forms/silo-create.tsx b/app/forms/silo-create.tsx index 58029da493..1c2bf4179f 100644 --- a/app/forms/silo-create.tsx +++ b/app/forms/silo-create.tsx @@ -19,6 +19,7 @@ import { RadioField } from '~/components/form/fields/RadioField' import { TextField } from '~/components/form/fields/TextField' import { TlsCertsField } from '~/components/form/fields/TlsCertsField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { FieldLabel } from '~/ui/lib/FieldLabel' @@ -57,7 +58,13 @@ export function CreateSiloSideModalForm() { onSuccess(silo) { queryClient.invalidateQueries('siloList') queryClient.setQueryData('siloView', { path: { silo: silo.name } }, silo) - addToast({ content: `${silo.name} created` }) + addToast({ + content: ( + <> + Silo {silo.name} created + + ), + }) onDismiss() }, }) diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 347cc19926..f28f1d8617 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -22,6 +22,7 @@ import { ComboboxField } from '~/components/form/fields/ComboboxField' import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { toComboboxItems } from '~/ui/lib/Combobox' @@ -54,7 +55,13 @@ export function CreateSnapshotSideModalForm() { const createSnapshot = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ content: `${snapshot.name} created` }) + addToast({ + content: ( + <> + Snapshot {snapshot.name} created + + ), + }) onDismiss() }, }) diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 0a2f373f51..0fbd52297d 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -14,6 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -38,7 +39,13 @@ export function CreateSSHKeySideModalForm({ onDismiss, message }: Props) { onSuccess(sshKey) { queryClient.invalidateQueries('currentUserSshKeyList') handleDismiss() - addToast({ content: `${sshKey.name} created` }) + addToast({ + content: ( + <> + SSH key {sshKey.name} created + + ), + }) }, }) const form = useForm({ defaultValues }) diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index d8cc642d4e..cd2ce82e09 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -14,6 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -38,7 +39,13 @@ export function CreateVpcSideModalForm() { { path: { vpc: vpc.name }, query: projectSelector }, vpc ) - addToast({ content: `${vpc.name} created` }) + addToast({ + content: ( + <> + VPC {vpc.name} created + + ), + }) navigate(pb.vpc({ vpc: vpc.name, ...projectSelector })) }, }) diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index 2af26e9c49..fb5c574727 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -18,6 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -42,7 +43,13 @@ export function EditVpcSideModalForm() { onSuccess(updatedVpc) { queryClient.invalidateQueries('vpcList') navigate(pb.vpc({ project, vpc: updatedVpc.name })) - addToast({ content: `${updatedVpc.name} updated` }) + addToast({ + content: ( + <> + VPC {updatedVpc.name} updated + + ), + }) // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating vpcView causes an error page to flash diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index e41fad85da..261e96a466 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -13,6 +13,7 @@ import { useApiMutation, useApiQueryClient, type VpcRouterCreate } from '@oxide/ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -32,7 +33,13 @@ export function CreateRouterSideModalForm() { const createRouter = useApiMutation('vpcRouterCreate', { onSuccess(router) { queryClient.invalidateQueries('vpcRouterList') - addToast({ content: `${router.name} created` }) + addToast({ + content: ( + <> + Router {router.name} created + + ), + }) onDismiss() }, }) diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 525fe3baa2..053f66d787 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -23,6 +23,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -53,7 +54,13 @@ export function EditRouterSideModalForm() { const editRouter = useApiMutation('vpcRouterUpdate', { onSuccess(updatedRouter) { queryClient.invalidateQueries('vpcRouterList') - addToast({ content: `${updatedRouter.name} updated` }) + addToast({ + content: ( + <> + Router {updatedRouter.name} updated + + ), + }) navigate(pb.vpcRouters({ project, vpc })) }, }) diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index 78cafe8ce8..48696b8f6d 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -11,6 +11,7 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, useApiMutation, useApiQueryClient } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { RouteFormFields, type RouteFormValues } from '~/forms/vpc-router-route-common' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' @@ -46,7 +47,13 @@ export function CreateRouterRouteSideModalForm() { const createRouterRoute = useApiMutation('vpcRouterRouteCreate', { onSuccess(route) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: `${route.name} created` }) + addToast({ + content: ( + <> + Route {route.name} created + + ), + }) navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index 8aa9fa20b8..8ef6311311 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -17,6 +17,7 @@ import { } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { RouteFormFields, routeFormMessage, @@ -64,7 +65,13 @@ export function EditRouterRouteSideModalForm() { const updateRouterRoute = useApiMutation('vpcRouterRouteUpdate', { onSuccess(updatedRoute) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: `${updatedRoute.name} updated` }) + addToast({ + content: ( + <> + Route {updatedRoute.name} updated + + ), + }) navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 2cc410a5e9..50df6188ed 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -22,7 +22,7 @@ import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react import { DocsPopover } from '~/components/DocsPopover' import { ListboxField } from '~/components/form/fields/ListboxField' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' @@ -110,7 +110,13 @@ export function FloatingIpsPage() { const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: `${floatingIp.name} detached` }) + addToast({ + content: ( + <> + Floating IP {floatingIp.name} detached + + ), + }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) @@ -120,7 +126,13 @@ export function FloatingIpsPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ content: `${variables.path.floatingIp} deleted` }) + addToast({ + content: ( + <> + Floating IP {variables.path.floatingIp} deleted + + ), + }) }, }) @@ -252,7 +264,13 @@ const AttachFloatingIpModal = ({ const floatingIpAttach = useApiMutation('floatingIpAttach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ content: `${floatingIp.name} attached` }) + addToast({ + content: ( + <> + Floating IP {floatingIp.name} attached + + ), + }) onDismiss() }, onError: (err) => { diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index 4637fd3e03..a3ccc20539 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -13,6 +13,7 @@ import { apiQueryClient, useApiMutation, useApiQueryClient, type Image } from '@ import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -58,7 +59,13 @@ export function ImagesPage() { const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ content: `${variables.path.image} deleted` }) + addToast({ + content: ( + <> + Image {variables.path.image} deleted + + ), + }) queryClient.invalidateQueries('imageList') }, }) @@ -131,7 +138,11 @@ const PromoteImageModal = ({ onDismiss, imageName }: PromoteModalProps) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { addToast({ - content: `${data.name} promoted`, + content: ( + <> + Image {data.name} promoted + + ), cta: { text: 'View silo images', link: '/images', diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index e2ea1bafb1..edac2058c4 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -24,7 +24,7 @@ import { IpGlobal24Icon, Networking24Icon } from '@oxide/design-system/icons/rea import { AttachEphemeralIpModal } from '~/components/AttachEphemeralIpModal' import { AttachFloatingIpModal } from '~/components/AttachFloatingIpModal' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { ListPlusCell } from '~/components/ListPlusCell' import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create' import { EditNetworkInterfaceForm } from '~/forms/network-interface-edit' @@ -204,7 +204,13 @@ export function NetworkingTab() { const { mutateAsync: deleteNic } = useApiMutation('instanceNetworkInterfaceDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast({ content: `${variables.path.interface} deleted` }) + addToast({ + content: ( + <> + Network interface {variables.path.interface} deleted + + ), + }) }, }) const { mutate: editNic } = useApiMutation('instanceNetworkInterfaceUpdate', { @@ -308,7 +314,13 @@ export function NetworkingTab() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ content: `${variables.path.floatingIp} detached` }) + addToast({ + content: ( + <> + Floating IP {variables.path.floatingIp} detached + + ), + }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index 275f80c9cd..43083be5ba 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -21,7 +21,7 @@ import { type RouteTarget, } from '~/api' import { DocsPopover } from '~/components/DocsPopover' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { routeFormMessage } from '~/forms/vpc-router-route-common' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' @@ -90,7 +90,13 @@ export function RouterPage() { const { mutateAsync: deleteRouterRoute } = useApiMutation('vpcRouterRouteDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterRouteList') - addToast({ content: `${variables.path.route} deleted` }) + addToast({ + content: ( + <> + Route {variables.path.route} deleted + + ), + }) }, }) diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index 0c19cec4a6..a34dea12d8 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -16,6 +16,7 @@ import { } from '@oxide/api' import { Networking24Icon } from '@oxide/design-system/icons/react' +import { HLs } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RouteTabs, Tab } from '~/components/RouteTabs' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' @@ -49,7 +50,13 @@ export function VpcPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') navigate(pb.vpcs({ project })) - addToast({ content: `${variables.path.vpc} deleted` }) + addToast({ + content: ( + <> + VPC {variables.path.vpc} deleted + + ), + }) }, }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx index 81204d1915..6ff2e8a07f 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx @@ -11,6 +11,7 @@ import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, useApiMutation, type VpcRouter } from '@oxide/api' +import { HLs } from '~/components/HL' import { routeFormMessage } from '~/forms/vpc-router-route-common' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' @@ -64,7 +65,13 @@ export function VpcRoutersTab() { const { mutateAsync: deleteRouter } = useApiMutation('vpcRouterDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterList') - addToast({ content: `${variables.path.router} deleted` }) + addToast({ + content: ( + <> + Router {variables.path.router} deleted + + ), + }) }, }) diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index 82e8a51557..3482b79ffd 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -20,6 +20,7 @@ import { import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' @@ -85,7 +86,13 @@ export function VpcsPage() { const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') - addToast({ content: `${variables.path.vpc} deleted` }) + addToast({ + content: ( + <> + VPC {variables.path.vpc} deleted + + ), + }) }, }) diff --git a/app/pages/settings/SSHKeysPage.tsx b/app/pages/settings/SSHKeysPage.tsx index dac271f1ea..899a14eea6 100644 --- a/app/pages/settings/SSHKeysPage.tsx +++ b/app/pages/settings/SSHKeysPage.tsx @@ -13,6 +13,7 @@ import { apiQueryClient, useApiMutation, useApiQueryClient, type SshKey } from ' import { Key16Icon, Key24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' @@ -48,7 +49,13 @@ export function SSHKeysPage() { const { mutateAsync: deleteSshKey } = useApiMutation('currentUserSshKeyDelete', { onSuccess: (_data, variables) => { queryClient.invalidateQueries('currentUserSshKeyList') - addToast({ content: `${variables.path.sshKey} deleted` }) + addToast({ + content: ( + <> + SSH key {variables.path.sshKey} deleted + + ), + }) }, }) diff --git a/app/pages/system/SiloImagesPage.tsx b/app/pages/system/SiloImagesPage.tsx index 94d30a651e..63e9b83f68 100644 --- a/app/pages/system/SiloImagesPage.tsx +++ b/app/pages/system/SiloImagesPage.tsx @@ -23,6 +23,7 @@ import { DocsPopover } from '~/components/DocsPopover' import { ComboboxField } from '~/components/form/fields/ComboboxField' import { toImageComboboxItem } from '~/components/form/fields/ImageSelectField' import { ListboxField } from '~/components/form/fields/ListboxField' +import { HLs } from '~/components/HL' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { makeLinkCell } from '~/table/cells/LinkCell' @@ -72,7 +73,13 @@ export function SiloImagesPage() { const queryClient = useApiQueryClient() const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ content: `${variables.path.image} deleted` }) + addToast({ + content: ( + <> + Image {variables.path.image} deleted + + ), + }) queryClient.invalidateQueries('imageList') }, }) @@ -131,7 +138,13 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { - addToast({ content: `${data.name} promoted` }) + addToast({ + content: ( + <> + Image {data.name} promoted + + ), + }) queryClient.invalidateQueries('imageList') }, onError: (err) => { @@ -218,7 +231,11 @@ const DemoteImageModal = ({ const demoteImage = useApiMutation('imageDemote', { onSuccess(data) { addToast({ - content: `${data.name} demoted`, + content: ( + <> + Image {data.name} demoted + + ), cta: selectedProject ? { text: `View images in ${selectedProject}`, diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index 36dc444b2e..6be095c31d 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -26,7 +26,7 @@ import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react import { CapacityBar } from '~/components/CapacityBar' import { DocsPopover } from '~/components/DocsPopover' import { ComboboxField } from '~/components/form/fields/ComboboxField' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { QueryParamTabs } from '~/components/QueryParamTabs' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' @@ -85,7 +85,13 @@ export function IpPoolPage() { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') navigate(pb.ipPools()) - addToast({ content: `${variables.path.pool} deleted` }) + addToast({ + content: ( + <> + Pool {variables.path.pool} deleted + + ), + }) }, }) diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index d9b761fd69..c415e908e6 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -20,6 +20,7 @@ import { import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { IpUtilCell } from '~/components/IpPoolUtilization' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' @@ -80,7 +81,13 @@ export function IpPoolsPage() { const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') - addToast({ content: `${variables.path.pool} deleted` }) + addToast({ + content: ( + <> + Pool {variables.path.pool} deleted + + ), + }) }, }) From 6f0cfa645068a30f23c6437e9ae1202685007597 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 13:44:41 -0700 Subject: [PATCH 08/21] Add toast on a couple of onSuccess functions missing them --- app/components/AttachEphemeralIpModal.tsx | 3 +-- app/components/AttachFloatingIpModal.tsx | 3 +-- app/forms/firewall-rules-create.tsx | 2 +- app/forms/firewall-rules-edit.tsx | 12 +++++++++++- app/forms/ip-pool-create.tsx | 2 +- app/forms/ip-pool-edit.tsx | 2 +- app/forms/network-interface-edit.tsx | 11 ++++++++++- app/forms/project-access.tsx | 4 ++++ app/forms/subnet-edit.tsx | 11 ++++++++++- app/pages/project/access/ProjectAccessPage.tsx | 6 +++++- .../project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx | 2 ++ app/pages/system/silos/SiloQuotasTab.tsx | 2 ++ app/pages/system/silos/SilosPage.tsx | 11 ++++++++++- 13 files changed, 59 insertions(+), 12 deletions(-) diff --git a/app/components/AttachEphemeralIpModal.tsx b/app/components/AttachEphemeralIpModal.tsx index 86cf7ac1de..61a1156568 100644 --- a/app/components/AttachEphemeralIpModal.tsx +++ b/app/components/AttachEphemeralIpModal.tsx @@ -11,14 +11,13 @@ import { useForm } from 'react-hook-form' import { useApiMutation, useApiQueryClient, usePrefetchedApiQuery } from '~/api' import { ListboxField } from '~/components/form/fields/ListboxField' +import { HLs } from '~/components/HL' import { useInstanceSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Badge } from '~/ui/lib/Badge' import { Modal } from '~/ui/lib/Modal' import { ALL_ISH } from '~/util/consts' -import { HLs } from './HL' - export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) => { const queryClient = useApiQueryClient() const { project, instance } = useInstanceSelector() diff --git a/app/components/AttachFloatingIpModal.tsx b/app/components/AttachFloatingIpModal.tsx index aa7a770ab9..903e43f3d4 100644 --- a/app/components/AttachFloatingIpModal.tsx +++ b/app/components/AttachFloatingIpModal.tsx @@ -10,13 +10,12 @@ import { useForm } from 'react-hook-form' import { useApiMutation, useApiQueryClient, type FloatingIp, type Instance } from '~/api' import { ListboxField } from '~/components/form/fields/ListboxField' +import { HLs } from '~/components/HL' import { addToast } from '~/stores/toast' import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' import { Slash } from '~/ui/lib/Slash' -import { HLs } from './HL' - function FloatingIpLabel({ fip }: { fip: FloatingIp }) { return (
diff --git a/app/forms/firewall-rules-create.tsx b/app/forms/firewall-rules-create.tsx index 61629f4099..57d73a2930 100644 --- a/app/forms/firewall-rules-create.tsx +++ b/app/forms/firewall-rules-create.tsx @@ -81,7 +81,7 @@ export function CreateFirewallRuleForm() { addToast({ content: ( <> - Rule {newRule.name} created + Firewall rule {newRule.name} created ), }) diff --git a/app/forms/firewall-rules-edit.tsx b/app/forms/firewall-rules-edit.tsx index 50957bff10..e7a374e530 100644 --- a/app/forms/firewall-rules-edit.tsx +++ b/app/forms/firewall-rules-edit.tsx @@ -18,11 +18,13 @@ import { import { trigger404 } from '~/components/ErrorBoundary' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getFirewallRuleSelector, useFirewallRuleSelector, useVpcSelector, } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { ALL_ISH } from '~/util/consts' import { invariant } from '~/util/invariant' import { pb } from '~/util/path-builder' @@ -64,13 +66,21 @@ export function EditFirewallRuleForm() { const onDismiss = () => navigate(pb.vpcFirewallRules(vpcSelector)) const updateRules = useApiMutation('vpcFirewallRulesUpdate', { - onSuccess() { + onSuccess(updatedRules, { body }) { // Nav before the invalidate because I once saw the above invariant fail // briefly after successful edit (error page flashed but then we land // on the rules list ok) and I think it was a race condition where the // invalidate managed to complete while the modal was still open. onDismiss() queryClient.invalidateQueries('vpcFirewallRulesView') + const updatedRule = body.rules[body.rules.length - 1] + addToast({ + content: ( + <> + Firewall rule {updatedRule.name} updated + + ), + }) }, }) diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index 132b76e429..58bc43df76 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -34,7 +34,7 @@ export function CreateIpPoolSideModalForm() { addToast({ content: ( <> - Pool {_pool.name} created + IP pool {_pool.name} created ), }) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index fb0524d79a..dabf012de8 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -45,7 +45,7 @@ export function EditIpPoolSideModalForm() { addToast({ content: ( <> - IP Pool {updatedPool.name} updated + IP pool {updatedPool.name} updated ), }) diff --git a/app/forms/network-interface-edit.tsx b/app/forms/network-interface-edit.tsx index c57bde7899..b9d771ecb3 100644 --- a/app/forms/network-interface-edit.tsx +++ b/app/forms/network-interface-edit.tsx @@ -20,7 +20,9 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextFieldInner } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useInstanceSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { FieldLabel } from '~/ui/lib/FieldLabel' import * as MiniTable from '~/ui/lib/MiniTable' @@ -42,8 +44,15 @@ export function EditNetworkInterfaceForm({ const instanceSelector = useInstanceSelector() const editNetworkInterface = useApiMutation('instanceNetworkInterfaceUpdate', { - onSuccess() { + onSuccess(nic) { queryClient.invalidateQueries('instanceNetworkInterfaceList') + addToast({ + content: ( + <> + Network interface {nic.name} updated + + ), + }) onDismiss() }, }) diff --git a/app/forms/project-access.tsx b/app/forms/project-access.tsx index 826b587744..ae9551cd37 100644 --- a/app/forms/project-access.tsx +++ b/app/forms/project-access.tsx @@ -17,6 +17,7 @@ import { import { ListboxField } from '~/components/form/fields/ListboxField' import { SideModalForm } from '~/components/form/SideModalForm' import { useProjectSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { actorToItem, @@ -35,6 +36,8 @@ export function ProjectAccessAddUserSideModal({ onDismiss, policy }: AddRoleModa const updatePolicy = useApiMutation('projectPolicyUpdate', { onSuccess: () => { queryClient.invalidateQueries('projectPolicyView') + // We don't have the name of the user or group, so we'll just have a generic message + addToast({ content: 'Role assigned' }) onDismiss() }, }) @@ -97,6 +100,7 @@ export function ProjectAccessEditUserSideModal({ const updatePolicy = useApiMutation('projectPolicyUpdate', { onSuccess: () => { queryClient.invalidateQueries('projectPolicyView') + addToast({ content: 'Role updated' }) onDismiss() }, }) diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 6bfd7e18c1..fbc3bc20e5 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -25,7 +25,9 @@ import { useCustomRouterItems, } from '~/components/form/fields/useItemsList' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { getVpcSubnetSelector, useVpcSubnetSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { pb } from '~/util/path-builder' @@ -51,8 +53,15 @@ export function EditSubnetForm() { }) const updateSubnet = useApiMutation('vpcSubnetUpdate', { - onSuccess() { + onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') + addToast({ + content: ( + <> + Subnet {subnet.name} updated + + ), + }) onDismiss() }, }) diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 832749910e..05173294af 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -34,6 +34,7 @@ import { } from '~/forms/project-access' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' +import { addToast } from '~/stores/toast' import { getActionsCol } from '~/table/columns/action-col' import { Table } from '~/table/Table' import { Badge } from '~/ui/lib/Badge' @@ -119,7 +120,10 @@ export function ProjectAccessPage() { const queryClient = useApiQueryClient() const { mutateAsync: updatePolicy } = useApiMutation('projectPolicyUpdate', { - onSuccess: () => queryClient.invalidateQueries('projectPolicyView'), + onSuccess: () => { + queryClient.invalidateQueries('projectPolicyView') + addToast({ content: 'Access removed' }) + }, // TODO: handle 403 }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx index 285bb2b82c..dda1540f6d 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx @@ -18,6 +18,7 @@ import { import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' +import { addToast } from '~/stores/toast' import { makeLinkCell } from '~/table/cells/LinkCell' import { RouterLinkCell } from '~/table/cells/RouterLinkCell' import { TwoLineCell } from '~/table/cells/TwoLineCell' @@ -47,6 +48,7 @@ export function VpcSubnetsTab() { const { mutateAsync: deleteSubnet } = useApiMutation('vpcSubnetDelete', { onSuccess() { queryClient.invalidateQueries('vpcSubnetList') + addToast({ content: 'Subnet deleted' }) }, }) diff --git a/app/pages/system/silos/SiloQuotasTab.tsx b/app/pages/system/silos/SiloQuotasTab.tsx index 14df8fbb74..8037974cd1 100644 --- a/app/pages/system/silos/SiloQuotasTab.tsx +++ b/app/pages/system/silos/SiloQuotasTab.tsx @@ -18,6 +18,7 @@ import { import { NumberField } from '~/components/form/fields/NumberField' import { SideModalForm } from '~/components/form/SideModalForm' import { useSiloSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { Button } from '~/ui/lib/Button' import { Message } from '~/ui/lib/Message' import { Table } from '~/ui/lib/Table' @@ -106,6 +107,7 @@ function EditQuotasForm({ onDismiss }: { onDismiss: () => void }) { const updateQuotas = useApiMutation('siloQuotasUpdate', { onSuccess() { apiQueryClient.invalidateQueries('siloUtilizationView') + addToast({ content: 'Quotas updated' }) onDismiss() }, }) diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index 6fbec47227..99d994f397 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -19,8 +19,10 @@ import { import { Cloud16Icon, Cloud24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { HLs } from '~/components/HL' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' +import { addToast } from '~/stores/toast' import { BooleanCell } from '~/table/cells/BooleanCell' import { makeLinkCell } from '~/table/cells/LinkCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' @@ -76,8 +78,15 @@ export function SilosPage() { }) const { mutateAsync: deleteSilo } = useApiMutation('siloDelete', { - onSuccess() { + onSuccess(silo, { path }) { queryClient.invalidateQueries('siloList') + addToast({ + content: ( + <> + Silo {path.silo} deleted + + ), + }) }, }) From eacc7925f210458d029f1177d24fc8a256f43426 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 13:58:31 -0700 Subject: [PATCH 09/21] Fix a few tests --- test/e2e/disks.e2e.ts | 4 +++- test/e2e/images.e2e.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 5b05cfee46..fce4c81ae1 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -30,7 +30,9 @@ test('List disks and snapshot', async ({ page }) => { await clickRowAction(page, 'disk-1 db1', 'Snapshot') await expect(page.getByText("Creating snapshot of disk 'disk-1'").nth(0)).toBeVisible() // Next line is a little awkward, but we don't actually know what the snapshot name will be - await expect(page.getByText('Snapshot disk-1-')).toBeVisible() + await expect( + page.getByText(/Snapshot disk-1-.*created/, { exact: false }).nth(0) + ).toBeVisible() }) test('Disk snapshot error', async ({ page }) => { diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index 7362b69a94..6924b4408a 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -150,7 +150,7 @@ test('can delete an image from a silo', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('ubuntu-20-04 deleted', { exact: true })).toBeVisible() + await expect(page.getByText('Image ubuntu-20-04 deleted', { exact: true })).toBeVisible() await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) From 0beea3db15b887db4efa6fb35a7f1e54f00d01bd Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 14:11:38 -0700 Subject: [PATCH 10/21] Missed a couple --- test/e2e/instance-disks.e2e.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/instance-disks.e2e.ts b/test/e2e/instance-disks.e2e.ts index 19f3835d9b..5fae1cff98 100644 --- a/test/e2e/instance-disks.e2e.ts +++ b/test/e2e/instance-disks.e2e.ts @@ -130,7 +130,7 @@ test('Detach disk', async ({ page }) => { // Have to stop instance to edit disks await stopInstance(page) - const successMsg = page.getByText('Disk disk-2 detached') + const successMsg = page.getByText('Disk disk-2 detached').first() const row = page.getByRole('row', { name: 'disk-2' }) await expect(row).toBeVisible() await expect(successMsg).toBeHidden() @@ -144,7 +144,7 @@ test('Snapshot disk', async ({ page }) => { await page.goto('/projects/mock-project/instances/db1') // we don't know the full name of the disk, but this will work to find the toast - const successMsg = page.getByText('Snapshot disk-1-') + const successMsg = page.getByText('Snapshot disk-1-').first() await expect(successMsg).toBeHidden() await clickRowAction(page, 'disk-1', 'Snapshot') From 874c49b10eecc7e2a07639bca797d7bf5ca5de96 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 14:31:28 -0700 Subject: [PATCH 11/21] Formatting --- app/forms/idp/create.tsx | 2 +- app/forms/subnet-create.tsx | 11 ++++++++++- app/pages/project/vpcs/RouterPage.tsx | 13 ++++--------- .../project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx | 4 +++- app/pages/system/silos/SiloIpPoolsTab.tsx | 2 ++ 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/forms/idp/create.tsx b/app/forms/idp/create.tsx index 995e78bc37..d02243efbf 100644 --- a/app/forms/idp/create.tsx +++ b/app/forms/idp/create.tsx @@ -57,7 +57,7 @@ export function CreateIdpSideModalForm() { addToast({ content: ( <> - IDP {idp.name} created + IdP {idp.name} created ), }) diff --git a/app/forms/subnet-create.tsx b/app/forms/subnet-create.tsx index 5ed229999c..de0592aebf 100644 --- a/app/forms/subnet-create.tsx +++ b/app/forms/subnet-create.tsx @@ -20,7 +20,9 @@ import { useCustomRouterItems, } from '~/components/form/fields/useItemsList' import { SideModalForm } from '~/components/form/SideModalForm' +import { HLs } from '~/components/HL' import { useVpcSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { pb } from '~/util/path-builder' @@ -42,9 +44,16 @@ export function CreateSubnetForm() { const onDismiss = () => navigate(pb.vpcSubnets(vpcSelector)) const createSubnet = useApiMutation('vpcSubnetCreate', { - onSuccess() { + onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') onDismiss() + addToast({ + content: ( + <> + Subnet {subnet.name} created + + ), + }) }, }) diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index 43083be5ba..1374602b31 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -21,7 +21,7 @@ import { type RouteTarget, } from '~/api' import { DocsPopover } from '~/components/DocsPopover' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { routeFormMessage } from '~/forms/vpc-router-route-common' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' @@ -88,15 +88,10 @@ export function RouterPage() { }) const { mutateAsync: deleteRouterRoute } = useApiMutation('vpcRouterRouteDelete', { - onSuccess(_data, variables) { + onSuccess() { apiQueryClient.invalidateQueries('vpcRouterRouteList') - addToast({ - content: ( - <> - Route {variables.path.route} deleted - - ), - }) + // We only have the ID, so will show a generic confirmation message + addToast({ content: 'Route deleted' }) }, }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx index dda1540f6d..93e848a90f 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx @@ -46,8 +46,10 @@ export function VpcSubnetsTab() { const { Table } = useQueryTable('vpcSubnetList', { query: vpcSelector }) const { mutateAsync: deleteSubnet } = useApiMutation('vpcSubnetDelete', { - onSuccess() { + onSuccess(a, b) { + console.log(a, b) queryClient.invalidateQueries('vpcSubnetList') + // We only have the ID, so will show a generic confirmation message addToast({ content: 'Subnet deleted' }) }, }) diff --git a/app/pages/system/silos/SiloIpPoolsTab.tsx b/app/pages/system/silos/SiloIpPoolsTab.tsx index 81aef8d4c9..b19a51ba88 100644 --- a/app/pages/system/silos/SiloIpPoolsTab.tsx +++ b/app/pages/system/silos/SiloIpPoolsTab.tsx @@ -81,6 +81,8 @@ export function SiloIpPoolsTab() { const { mutateAsync: unlinkPool } = useApiMutation('ipPoolSiloUnlink', { onSuccess() { queryClient.invalidateQueries('siloIpPoolList') + // We only have the ID, so will show a generic confirmation message + addToast({ content: 'IP pool unlinked' }) }, }) From 8852827007464abe52dea6272cb947d7b21e07e2 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 15:15:21 -0700 Subject: [PATCH 12/21] surely saying this was the last test to fix won't jinx it --- app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx | 3 +-- test/e2e/disks.e2e.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx index 93e848a90f..0dcb974a19 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx @@ -46,8 +46,7 @@ export function VpcSubnetsTab() { const { Table } = useQueryTable('vpcSubnetList', { query: vpcSelector }) const { mutateAsync: deleteSubnet } = useApiMutation('vpcSubnetDelete', { - onSuccess(a, b) { - console.log(a, b) + onSuccess() { queryClient.invalidateQueries('vpcSubnetList') // We only have the ID, so will show a generic confirmation message addToast({ content: 'Subnet deleted' }) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index fce4c81ae1..95ec95f10b 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -56,7 +56,7 @@ test.describe('Disk create', () => { test.afterEach(async ({ page }) => { await page.getByRole('button', { name: 'Create disk' }).click() - await expect(page.getByText('Disk a-new-disk created')).toBeVisible() + await expect(page.getByText('Disk a-new-disk created').first()).toBeVisible() await expectVisible(page, ['role=cell[name="a-new-disk"]']) }) From 92d024b39fa4883093d36fad311eaa92a13172cb Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Wed, 16 Oct 2024 15:39:43 -0700 Subject: [PATCH 13/21] nope; it was not fully reading the previous CI report --- test/e2e/disks.e2e.ts | 10 ++++++---- test/e2e/images.e2e.ts | 12 +++++++----- test/e2e/instance-disks.e2e.ts | 4 +++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 95ec95f10b..f3554d2bb3 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -31,7 +31,7 @@ test('List disks and snapshot', async ({ page }) => { await expect(page.getByText("Creating snapshot of disk 'disk-1'").nth(0)).toBeVisible() // Next line is a little awkward, but we don't actually know what the snapshot name will be await expect( - page.getByText(/Snapshot disk-1-.*created/, { exact: false }).nth(0) + page.getByText(/Snapshot disk-1-[a-z0-9]{6} created/, { exact: true }).first() ).toBeVisible() }) @@ -41,10 +41,12 @@ test('Disk snapshot error', async ({ page }) => { // special disk that triggers snapshot error await clickRowAction(page, 'disk-snapshot-error', 'Snapshot') await expect( - page.getByText("Creating snapshot of disk 'disk-snapshot-error'").nth(0) + page + .getByText("Creating snapshot of disk 'disk-snapshot-error'", { exact: true }) + .first() ).toBeVisible() - await expect(page.getByText('Failed to create snapshot').nth(0)).toBeVisible() - await expect(page.getByText('Cannot snapshot disk').nth(0)).toBeVisible() + await expect(page.getByText('Failed to create snapshot', { exact: true })).toBeVisible() + await expect(page.getByText('Cannot snapshot disk', { exact: true })).toBeVisible() }) test.describe('Disk create', () => { diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index 6924b4408a..fb2a2192ca 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -52,7 +52,7 @@ test('can promote an image from silo', async ({ page }) => { await page.locator('role=button[name="Promote"]').click() // Check it was promoted successfully - await expectVisible(page, ['text="image-1 promoted"']) + await expect(page.getByText('Image image-1 promoted', { exact: true })).toBeVisible() await expectVisible(page, ['role=cell[name="image-1"]']) }) @@ -68,7 +68,7 @@ test('can promote an image from project', async ({ page }) => { // Promote image and check it was successful await page.locator('role=button[name="Promote"]').click() - await expectVisible(page, ['text="image-2 promoted"']) + await expect(page.getByText('Image image-2 promoted', { exact: true })).toBeVisible() await expectNotVisible(page, ['role=cell[name="image-2"]']) await page.click('role=link[name="View silo images"]') @@ -111,8 +111,10 @@ test('can demote an image from silo', async ({ page }) => { await selectOption(page, 'Project', 'mock-project') await page.getByRole('button', { name: 'Demote' }).click() - // Promote image and check it was successful - await expectVisible(page, ['text="arch-2022-06-01 demoted"']) + // Demote image and check it was successful + await expect( + page.getByText('Image arch-2022-06-01 demoted', { exact: true }) + ).toBeVisible() await expectNotVisible(page, ['role=cell[name="arch-2022-06-01"]']) await page.click('role=link[name="View images in mock-project"]') @@ -132,7 +134,7 @@ test('can delete an image from a project', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('image-3 deleted', { exact: true })).toBeVisible() + await expect(page.getByText('Image image-3 deleted', { exact: true })).toBeVisible() await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) diff --git a/test/e2e/instance-disks.e2e.ts b/test/e2e/instance-disks.e2e.ts index 5fae1cff98..cbe23599ea 100644 --- a/test/e2e/instance-disks.e2e.ts +++ b/test/e2e/instance-disks.e2e.ts @@ -144,7 +144,9 @@ test('Snapshot disk', async ({ page }) => { await page.goto('/projects/mock-project/instances/db1') // we don't know the full name of the disk, but this will work to find the toast - const successMsg = page.getByText('Snapshot disk-1-').first() + const successMsg = page + .getByText(/Snapshot disk-1-[a-z0-9]{6} created/, { exact: true }) + .first() await expect(successMsg).toBeHidden() await clickRowAction(page, 'disk-1', 'Snapshot') From 983b791167d04dfa4ae0a90db54409be19db6bd5 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 17 Oct 2024 01:20:24 -0700 Subject: [PATCH 14/21] Add expectToast helper --- app/components/ToastStack.tsx | 5 ++++- app/forms/disk-create.tsx | 2 +- test/e2e/disks.e2e.ts | 32 ++++++++++++++++++------------ test/e2e/floating-ip-update.e2e.ts | 11 +++++++++- test/e2e/images.e2e.ts | 5 +++-- test/e2e/instance-disks.e2e.ts | 10 +++++----- test/e2e/ip-pools.e2e.ts | 10 +++++----- test/e2e/utils.ts | 16 +++++++++++++++ 8 files changed, 63 insertions(+), 28 deletions(-) diff --git a/app/components/ToastStack.tsx b/app/components/ToastStack.tsx index 56d5c9a4b5..78fe655e69 100644 --- a/app/components/ToastStack.tsx +++ b/app/components/ToastStack.tsx @@ -22,7 +22,10 @@ export function ToastStack() { }) return ( -
+
{transition((style, item) => ( - Disk {data.name}created + Disk {data.name} created ), }) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index f3554d2bb3..d8c67b91fc 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -5,7 +5,15 @@ * * Copyright Oxide Computer Company */ -import { clickRowAction, expect, expectRowVisible, expectVisible, test } from './utils' +import { + clickRowAction, + expect, + expectNoToast, + expectRowVisible, + expectToast, + expectVisible, + test, +} from './utils' test('List disks and snapshot', async ({ page }) => { await page.goto('/projects/mock-project/disks') @@ -28,11 +36,9 @@ test('List disks and snapshot', async ({ page }) => { }) await clickRowAction(page, 'disk-1 db1', 'Snapshot') - await expect(page.getByText("Creating snapshot of disk 'disk-1'").nth(0)).toBeVisible() + await expectToast(page, "Creating snapshot of disk 'disk-1'") // Next line is a little awkward, but we don't actually know what the snapshot name will be - await expect( - page.getByText(/Snapshot disk-1-[a-z0-9]{6} created/, { exact: true }).first() - ).toBeVisible() + await expectToast(page, /Snapshot disk-1-[a-z0-9]{6} created/) }) test('Disk snapshot error', async ({ page }) => { @@ -40,13 +46,13 @@ test('Disk snapshot error', async ({ page }) => { // special disk that triggers snapshot error await clickRowAction(page, 'disk-snapshot-error', 'Snapshot') - await expect( - page - .getByText("Creating snapshot of disk 'disk-snapshot-error'", { exact: true }) - .first() - ).toBeVisible() - await expect(page.getByText('Failed to create snapshot', { exact: true })).toBeVisible() - await expect(page.getByText('Cannot snapshot disk', { exact: true })).toBeVisible() + await expectToast(page, "Creating snapshot of disk 'disk-snapshot-error'") + // we have to force it to wait until the toast is gone … + await expectNoToast(page, "Creating snapshot of disk 'disk-snapshot-error'") + // … before we can check for the error message + await expectToast(page, 'Failed to create snapshotCannot snapshot disk') + // just including an actual expect to satisfy the linter + await expect(page.getByRole('cell', { name: 'disk-snapshot-error' })).toBeVisible() }) test.describe('Disk create', () => { @@ -58,7 +64,7 @@ test.describe('Disk create', () => { test.afterEach(async ({ page }) => { await page.getByRole('button', { name: 'Create disk' }).click() - await expect(page.getByText('Disk a-new-disk created').first()).toBeVisible() + await expectToast(page, 'Disk a-new-disk created') await expectVisible(page, ['role=cell[name="a-new-disk"]']) }) diff --git a/test/e2e/floating-ip-update.e2e.ts b/test/e2e/floating-ip-update.e2e.ts index 68bcf0d05d..4ce1179c90 100644 --- a/test/e2e/floating-ip-update.e2e.ts +++ b/test/e2e/floating-ip-update.e2e.ts @@ -6,7 +6,14 @@ * Copyright Oxide Computer Company */ -import { clickRowAction, expect, expectRowVisible, expectVisible, test } from './utils' +import { + clickRowAction, + expect, + expectRowVisible, + expectToast, + expectVisible, + test, +} from './utils' const floatingIpsPage = '/projects/mock-project/floating-ips' const originalName = 'cola-float' @@ -32,6 +39,7 @@ test('can update a floating IP', async ({ page }) => { name: updatedName, description: updatedDescription, }) + await expectToast(page, `Floating IP ${updatedName} updated`) }) // Make sure that it still works even if the name doesn't change @@ -47,4 +55,5 @@ test('can update *just* the floating IP description', async ({ page }) => { name: originalName, description: updatedDescription, }) + await expectToast(page, `Floating IP ${originalName} updated`) }) diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index fb2a2192ca..d78f83ca0a 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -12,6 +12,7 @@ import { clipboardText, expect, expectNotVisible, + expectToast, expectVisible, getPageAsUser, selectOption, @@ -134,7 +135,7 @@ test('can delete an image from a project', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('Image image-3 deleted', { exact: true })).toBeVisible() + await expectToast(page, 'Image image-3 deleted') await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) @@ -152,7 +153,7 @@ test('can delete an image from a silo', async ({ page }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('Image ubuntu-20-04 deleted', { exact: true })).toBeVisible() + await expectToast(page, 'Image ubuntu-20-04 deleted') await expect(cell).toBeHidden() await expect(spinner).toBeHidden() }) diff --git a/test/e2e/instance-disks.e2e.ts b/test/e2e/instance-disks.e2e.ts index cbe23599ea..b53e872e4d 100644 --- a/test/e2e/instance-disks.e2e.ts +++ b/test/e2e/instance-disks.e2e.ts @@ -8,8 +8,10 @@ import { clickRowAction, expect, + expectNoToast, expectNotVisible, expectRowVisible, + expectToast, expectVisible, stopInstance, test, @@ -144,14 +146,12 @@ test('Snapshot disk', async ({ page }) => { await page.goto('/projects/mock-project/instances/db1') // we don't know the full name of the disk, but this will work to find the toast - const successMsg = page - .getByText(/Snapshot disk-1-[a-z0-9]{6} created/, { exact: true }) - .first() - await expect(successMsg).toBeHidden() + const toastMessage = /Snapshot disk-1-[a-z0-9]{6} created/ + await expectNoToast(page, toastMessage) await clickRowAction(page, 'disk-1', 'Snapshot') - await expect(successMsg).toBeVisible() // we see the toast! + await expectToast(page, toastMessage) // we see the toast! // now go see the snapshot on the snapshots page await page.getByRole('link', { name: 'Snapshots' }).click() diff --git a/test/e2e/ip-pools.e2e.ts b/test/e2e/ip-pools.e2e.ts index df0de16b04..ea17e815dc 100644 --- a/test/e2e/ip-pools.e2e.ts +++ b/test/e2e/ip-pools.e2e.ts @@ -8,7 +8,7 @@ import { expect, test } from '@playwright/test' -import { clickRowAction, expectRowVisible } from './utils' +import { clickRowAction, expectRowVisible, expectToast } from './utils' test('IP pool list', async ({ page }) => { await page.goto('/system/networking/ip-pools') @@ -118,10 +118,10 @@ test('IP pool delete from IP Pools list page', async ({ page }) => { await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() - await expect(page.getByText('Could not delete resource').first()).toBeVisible() - await expect( - page.getByText('IP pool cannot be deleted while it contains IP ranges').first() - ).toBeVisible() + await expectToast( + page, + 'Could not delete resourceIP pool cannot be deleted while it contains IP ranges' + ) await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeVisible() diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index e74c39e0ae..89b5837633 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -115,6 +115,22 @@ export async function stopInstance(page: Page) { await expect(page.getByText('statestopped')).toBeVisible() } +/** + * Assert that a toast with text matching `expectedText` is visible. + * If testing multiple toasts, interleave the `expectToast` with `expectNoToast` + * so the toasts don't overlap. + */ +export async function expectToast(page: Page, expectedText: string | RegExp) { + await expect(page.getByTestId('Toasts')).toHaveText(expectedText) +} + +/** + * Assert that a toast with text matching `expectedText` is not visible. + */ +export async function expectNoToast(page: Page, expectedText: string | RegExp) { + await expect(page.getByTestId('Toasts')).not.toHaveText(expectedText) +} + /** * Close toast and wait for it to fade out. For some reason it prevents things * from working, but only in tests as far as we can tell. From 749a32b862af0885dd12633dc13fbb43092af3e5 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 17 Oct 2024 06:33:40 -0700 Subject: [PATCH 15/21] use a lighter weight font --- app/components/HL.tsx | 8 ++++---- app/pages/project/instances/actions.tsx | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/components/HL.tsx b/app/components/HL.tsx index 35198df202..9af02404dc 100644 --- a/app/components/HL.tsx +++ b/app/components/HL.tsx @@ -7,13 +7,13 @@ */ import { classed } from '~/util/classed' -export const HL = classed.span`text-sans-semi-md text-default` +export const HL = classed.span`text-sans-md text-default` /** HL with "success"-colored text */ -export const HLs = classed.span`text-sans-semi-md text-accent children:text-accent` +export const HLs = classed.span`text-sans-md text-accent children:text-accent` // HL with "error"-colored text -export const HLe = classed.span`text-sans-semi-md text-error children:text-error` +export const HLe = classed.span`text-sans-md text-error children:text-error` // HL with "info"-colored text -export const HLi = classed.span`text-sans-semi-md text-notice children:text-notice` +export const HLi = classed.span`text-sans-md text-notice children:text-notice` diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index df251f3ab5..f8e271eda3 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -10,7 +10,7 @@ import { useNavigate } from 'react-router-dom' import { instanceCan, useApiMutation, type Instance } from '@oxide/api' -import { HL } from '~/components/HL' +import { HL, HLs } from '~/components/HL' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -79,7 +79,13 @@ export const useMakeInstanceActions = ( doAction: () => stopInstanceAsync(instanceParams, { onSuccess: () => - addToast({ title: `Stopping instance '${instance.name}'` }), + addToast({ + content: ( + <> + Stopping instance {instance.name} + + ), + }), }), modalTitle: 'Confirm stop instance', modalContent: ( From 868aa24ac52552c4c5fcf965225af1c8dc1401ec Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Thu, 17 Oct 2024 07:12:54 -0700 Subject: [PATCH 16/21] Close toasts at end of expectToast --- test/e2e/disks.e2e.ts | 10 ++++++---- test/e2e/utils.ts | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index d8c67b91fc..4793b373ce 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -37,6 +37,8 @@ test('List disks and snapshot', async ({ page }) => { await clickRowAction(page, 'disk-1 db1', 'Snapshot') await expectToast(page, "Creating snapshot of disk 'disk-1'") + // expectToast should have closed the toast already, but verify + await expectNoToast(page, "Creating snapshot of disk 'disk-1'") // Next line is a little awkward, but we don't actually know what the snapshot name will be await expectToast(page, /Snapshot disk-1-[a-z0-9]{6} created/) }) @@ -47,12 +49,12 @@ test('Disk snapshot error', async ({ page }) => { // special disk that triggers snapshot error await clickRowAction(page, 'disk-snapshot-error', 'Snapshot') await expectToast(page, "Creating snapshot of disk 'disk-snapshot-error'") - // we have to force it to wait until the toast is gone … - await expectNoToast(page, "Creating snapshot of disk 'disk-snapshot-error'") - // … before we can check for the error message - await expectToast(page, 'Failed to create snapshotCannot snapshot disk') // just including an actual expect to satisfy the linter await expect(page.getByRole('cell', { name: 'disk-snapshot-error' })).toBeVisible() + // expectToast should have closed the toast already, but let's just verify … + await expectNoToast(page, "Creating snapshot of disk 'disk-snapshot-error'") + // … before we can check for the error toast + await expectToast(page, 'Failed to create snapshotCannot snapshot disk') }) test.describe('Disk create', () => { diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index 89b5837633..8545adde8c 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -117,11 +117,10 @@ export async function stopInstance(page: Page) { /** * Assert that a toast with text matching `expectedText` is visible. - * If testing multiple toasts, interleave the `expectToast` with `expectNoToast` - * so the toasts don't overlap. */ export async function expectToast(page: Page, expectedText: string | RegExp) { await expect(page.getByTestId('Toasts')).toHaveText(expectedText) + await closeToast(page) } /** From dbe02c4f073d0b1592cd5aba66e8945b57df5bba Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 29 Oct 2024 14:59:07 -0500 Subject: [PATCH 17/21] let addToast take the content directly, fix type errors, change one case --- app/components/AttachEphemeralIpModal.tsx | 8 +------- app/pages/LoginPage.tsx | 2 +- app/pages/project/disks/DisksPage.tsx | 2 +- app/pages/project/instances/actions.tsx | 7 ++++--- app/stores/toast.ts | 12 +++++++++++- app/ui/lib/Toast.tsx | 2 +- test/e2e/disks.e2e.ts | 8 ++++---- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/app/components/AttachEphemeralIpModal.tsx b/app/components/AttachEphemeralIpModal.tsx index 52add32ca3..73b671f7cb 100644 --- a/app/components/AttachEphemeralIpModal.tsx +++ b/app/components/AttachEphemeralIpModal.tsx @@ -32,13 +32,7 @@ export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', { onSuccess(ephemeralIp) { queryClient.invalidateQueries('instanceExternalIpList') - addToast({ - content: ( - <> - IP {ephemeralIp.ip} attached - - ), - }) + addToast(<>IP {ephemeralIp.ip} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/pages/LoginPage.tsx b/app/pages/LoginPage.tsx index 92b14cc7f2..4ff7a48d2d 100644 --- a/app/pages/LoginPage.tsx +++ b/app/pages/LoginPage.tsx @@ -35,7 +35,7 @@ export function LoginPage() { useEffect(() => { if (loginPost.isSuccess) { - addToast({ title: 'Logged in' }) + addToast('Logged in') navigate(searchParams.get('redirect_uri') || pb.projects()) } }, [loginPost.isSuccess, navigate, searchParams]) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 3d90665d3d..d80931ed80 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -137,7 +137,7 @@ export function DisksPage() { { label: 'Snapshot', onActivate() { - addToast({ title: `Creating snapshot of disk '${disk.name}'` }) + addToast(<>Creating snapshot of disk {disk.name}) // prettier-ignore createSnapshot({ query: { project }, body: { diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index f8e271eda3..ba70edd239 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -58,7 +58,7 @@ export const useMakeInstanceActions = ( label: 'Start', onActivate() { startInstance(instanceParams, { - onSuccess: () => addToast({ title: `Starting instance '${instance.name}'` }), + onSuccess: () => addToast(<>Starting instance {instance.name}), // prettier-ignore onError: (error) => addToast({ variant: 'error', @@ -110,7 +110,8 @@ export const useMakeInstanceActions = ( label: 'Reboot', onActivate() { rebootInstance(instanceParams, { - onSuccess: () => addToast({ title: `Rebooting instance '${instance.name}'` }), + onSuccess: () => + addToast(<>Rebooting instance {instance.name}), // prettier-ignore onError: (error) => addToast({ variant: 'error', @@ -135,7 +136,7 @@ export const useMakeInstanceActions = ( doDelete: () => deleteInstanceAsync(instanceParams, { onSuccess: () => - addToast({ title: `Deleting instance '${instance.name}'` }), + addToast(<>Deleting instance {instance.name}), // prettier-ignore }), label: instance.name, resourceKind: 'instance', diff --git a/app/stores/toast.ts b/app/stores/toast.ts index ea06db7213..6bf3c4f5e8 100644 --- a/app/stores/toast.ts +++ b/app/stores/toast.ts @@ -5,6 +5,7 @@ * * Copyright Oxide Computer Company */ +import { type ReactElement } from 'react' import { v4 as uuid } from 'uuid' import { create } from 'zustand' @@ -17,9 +18,18 @@ type Toast = { export const useToastStore = create<{ toasts: Toast[] }>(() => ({ toasts: [] })) -export function addToast(options: Toast['options']) { +/** + * If argument is `ReactElement | string`, use it directly as `{ content }`. + * Otherwise it's a config object. + */ +export function addToast(optionsOrContent: Toast['options'] | ReactElement | string) { + const options = + typeof optionsOrContent === 'object' && 'content' in optionsOrContent + ? optionsOrContent + : { content: optionsOrContent } useToastStore.setState(({ toasts }) => ({ toasts: [...toasts, { id: uuid(), options }] })) } + export function removeToast(id: Toast['id']) { useToastStore.setState(({ toasts }) => ({ toasts: toasts.filter((t) => t.id !== id) })) } diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index a190ceded9..4b55cd4eab 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -26,7 +26,7 @@ type Variant = 'success' | 'error' | 'info' export interface ToastProps { title?: string - content?: ReactNode + content: ReactNode onClose: () => void variant?: Variant timeout?: number | null diff --git a/test/e2e/disks.e2e.ts b/test/e2e/disks.e2e.ts index 4793b373ce..e735f7dad6 100644 --- a/test/e2e/disks.e2e.ts +++ b/test/e2e/disks.e2e.ts @@ -36,9 +36,9 @@ test('List disks and snapshot', async ({ page }) => { }) await clickRowAction(page, 'disk-1 db1', 'Snapshot') - await expectToast(page, "Creating snapshot of disk 'disk-1'") + await expectToast(page, 'Creating snapshot of disk disk-1') // expectToast should have closed the toast already, but verify - await expectNoToast(page, "Creating snapshot of disk 'disk-1'") + await expectNoToast(page, 'Creating snapshot of disk disk-1') // Next line is a little awkward, but we don't actually know what the snapshot name will be await expectToast(page, /Snapshot disk-1-[a-z0-9]{6} created/) }) @@ -48,11 +48,11 @@ test('Disk snapshot error', async ({ page }) => { // special disk that triggers snapshot error await clickRowAction(page, 'disk-snapshot-error', 'Snapshot') - await expectToast(page, "Creating snapshot of disk 'disk-snapshot-error'") + await expectToast(page, 'Creating snapshot of disk disk-snapshot-error') // just including an actual expect to satisfy the linter await expect(page.getByRole('cell', { name: 'disk-snapshot-error' })).toBeVisible() // expectToast should have closed the toast already, but let's just verify … - await expectNoToast(page, "Creating snapshot of disk 'disk-snapshot-error'") + await expectNoToast(page, 'Creating snapshot of disk disk-snapshot-error') // … before we can check for the error toast await expectToast(page, 'Failed to create snapshotCannot snapshot disk') }) From d90b1c7994cb3c125111d2ce359ae8d07deede20 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 29 Oct 2024 15:26:30 -0500 Subject: [PATCH 18/21] a billion one-liner toasts with the magic of prettier-ignore --- app/components/AttachFloatingIpModal.tsx | 8 +------ app/forms/disk-create.tsx | 8 +------ app/forms/firewall-rules-create.tsx | 8 +------ app/forms/firewall-rules-edit.tsx | 8 +------ app/forms/floating-ip-create.tsx | 8 +------ app/forms/floating-ip-edit.tsx | 8 +------ app/forms/idp/create.tsx | 8 +------ app/forms/image-from-snapshot.tsx | 8 +------ app/forms/instance-create.tsx | 8 +------ app/forms/ip-pool-create.tsx | 8 +------ app/forms/ip-pool-edit.tsx | 8 +------ app/forms/network-interface-edit.tsx | 8 +------ app/forms/project-create.tsx | 8 +------ app/forms/project-edit.tsx | 8 +------ app/forms/silo-create.tsx | 8 +------ app/forms/snapshot-create.tsx | 8 +------ app/forms/ssh-key-create.tsx | 8 +------ app/forms/subnet-create.tsx | 8 +------ app/forms/subnet-edit.tsx | 8 +------ app/forms/vpc-create.tsx | 8 +------ app/forms/vpc-edit.tsx | 8 +------ app/forms/vpc-router-create.tsx | 8 +------ app/forms/vpc-router-edit.tsx | 8 +------ app/forms/vpc-router-route-create.tsx | 8 +------ app/forms/vpc-router-route-edit.tsx | 8 +------ app/pages/project/disks/DisksPage.tsx | 16 ++----------- .../project/floating-ips/FloatingIpsPage.tsx | 24 +++---------------- app/pages/project/images/ImagesPage.tsx | 8 +------ app/pages/project/instances/actions.tsx | 8 +------ .../instances/instance/tabs/NetworkingTab.tsx | 16 ++----------- .../instances/instance/tabs/StorageTab.tsx | 16 ++----------- app/pages/project/vpcs/VpcPage/VpcPage.tsx | 8 +------ .../vpcs/VpcPage/tabs/VpcRoutersTab.tsx | 8 +------ app/pages/project/vpcs/VpcsPage.tsx | 8 +------ app/pages/settings/SSHKeysPage.tsx | 8 +------ app/pages/system/SiloImagesPage.tsx | 16 ++----------- app/pages/system/networking/IpPoolPage.tsx | 8 +------ app/pages/system/networking/IpPoolsPage.tsx | 8 +------ app/pages/system/silos/SilosPage.tsx | 8 +------ 39 files changed, 45 insertions(+), 315 deletions(-) diff --git a/app/components/AttachFloatingIpModal.tsx b/app/components/AttachFloatingIpModal.tsx index 903e43f3d4..e8baa92b7c 100644 --- a/app/components/AttachFloatingIpModal.tsx +++ b/app/components/AttachFloatingIpModal.tsx @@ -49,13 +49,7 @@ export const AttachFloatingIpModal = ({ onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ - content: ( - <> - IP {floatingIp.name} attached - - ), - }) + addToast(<>IP {floatingIp.name} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index cb511e12c6..12990cfb50 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -77,13 +77,7 @@ export function CreateDiskSideModalForm({ const createDisk = useApiMutation('diskCreate', { onSuccess(data) { queryClient.invalidateQueries('diskList') - addToast({ - content: ( - <> - Disk {data.name} created - - ), - }) + addToast(<>Disk {data.name} created) // prettier-ignore onSuccess?.(data) onDismiss(navigate) }, diff --git a/app/forms/firewall-rules-create.tsx b/app/forms/firewall-rules-create.tsx index 57d73a2930..255645f2be 100644 --- a/app/forms/firewall-rules-create.tsx +++ b/app/forms/firewall-rules-create.tsx @@ -78,13 +78,7 @@ export function CreateFirewallRuleForm() { onSuccess(updatedRules) { const newRule = updatedRules.rules[updatedRules.rules.length - 1] queryClient.invalidateQueries('vpcFirewallRulesView') - addToast({ - content: ( - <> - Firewall rule {newRule.name} created - - ), - }) + addToast(<>Firewall rule {newRule.name} created) // prettier-ignore navigate(pb.vpcFirewallRules(vpcSelector)) }, }) diff --git a/app/forms/firewall-rules-edit.tsx b/app/forms/firewall-rules-edit.tsx index e7a374e530..afc7ae2a9b 100644 --- a/app/forms/firewall-rules-edit.tsx +++ b/app/forms/firewall-rules-edit.tsx @@ -74,13 +74,7 @@ export function EditFirewallRuleForm() { onDismiss() queryClient.invalidateQueries('vpcFirewallRulesView') const updatedRule = body.rules[body.rules.length - 1] - addToast({ - content: ( - <> - Firewall rule {updatedRule.name} updated - - ), - }) + addToast(<>Firewall rule {updatedRule.name} updated) // prettier-ignore }, }) diff --git a/app/forms/floating-ip-create.tsx b/app/forms/floating-ip-create.tsx index 1fd061743e..880189c8ba 100644 --- a/app/forms/floating-ip-create.tsx +++ b/app/forms/floating-ip-create.tsx @@ -49,13 +49,7 @@ export function CreateFloatingIpSideModalForm() { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ - content: ( - <> - Floating IP {floatingIp.name} created - - ), - }) + addToast(<>Floating IP {floatingIp.name} created) // prettier-ignore navigate(pb.floatingIps(projectSelector)) }, }) diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx index 8896196e3f..1e03e79721 100644 --- a/app/forms/floating-ip-edit.tsx +++ b/app/forms/floating-ip-edit.tsx @@ -48,13 +48,7 @@ export function EditFloatingIpSideModalForm() { const editFloatingIp = useApiMutation('floatingIpUpdate', { onSuccess(_floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ - content: ( - <> - Floating IP {_floatingIp.name} updated - - ), - }) + addToast(<>Floating IP {_floatingIp.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/idp/create.tsx b/app/forms/idp/create.tsx index a31ef7458c..7eeada64be 100644 --- a/app/forms/idp/create.tsx +++ b/app/forms/idp/create.tsx @@ -54,13 +54,7 @@ export function CreateIdpSideModalForm() { const createIdp = useApiMutation('samlIdentityProviderCreate', { onSuccess(idp) { queryClient.invalidateQueries('siloIdentityProviderList') - addToast({ - content: ( - <> - IdP {idp.name} created - - ), - }) + addToast(<>IdP {idp.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index acb7aa0ce5..adfd2d3f5c 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -57,13 +57,7 @@ export function CreateImageFromSnapshotSideModalForm() { const createImage = useApiMutation('imageCreate', { onSuccess(image) { queryClient.invalidateQueries('imageList') - addToast({ - content: ( - <> - Image {image.name} created - - ), - }) + addToast(<>Image {image.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 8fe1e210ef..7e3568492b 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -184,13 +184,7 @@ export function CreateInstanceForm() { { path: { instance: instance.name }, query: { project } }, instance ) - addToast({ - content: ( - <> - Instance {instance.name} created - - ), - }) + addToast(<>Instance {instance.name} created) // prettier-ignore navigate(pb.instance({ project, instance: instance.name })) }, }) diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index d8aa5678ce..cc443ce406 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -32,13 +32,7 @@ export function CreateIpPoolSideModalForm() { const createPool = useApiMutation('ipPoolCreate', { onSuccess(_pool) { queryClient.invalidateQueries('ipPoolList') - addToast({ - content: ( - <> - IP pool {_pool.name} created - - ), - }) + addToast(<>IP pool {_pool.name} created) // prettier-ignore navigate(pb.ipPools()) }, }) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index 3ff2e5f95f..d14d5db125 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -44,13 +44,7 @@ export function EditIpPoolSideModalForm() { onSuccess(updatedPool) { queryClient.invalidateQueries('ipPoolList') navigate(pb.ipPool({ pool: updatedPool.name })) - addToast({ - content: ( - <> - IP pool {updatedPool.name} updated - - ), - }) + addToast(<>IP pool {updatedPool.name} updated) // prettier-ignore // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating ipPoolView causes an error page to flash diff --git a/app/forms/network-interface-edit.tsx b/app/forms/network-interface-edit.tsx index b9d771ecb3..d975888439 100644 --- a/app/forms/network-interface-edit.tsx +++ b/app/forms/network-interface-edit.tsx @@ -46,13 +46,7 @@ export function EditNetworkInterfaceForm({ const editNetworkInterface = useApiMutation('instanceNetworkInterfaceUpdate', { onSuccess(nic) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast({ - content: ( - <> - Network interface {nic.name} updated - - ), - }) + addToast(<>Network interface {nic.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index 3c75457a8a..2a1c5cd79f 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -34,13 +34,7 @@ export function CreateProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ - content: ( - <> - Project {project.name} created - - ), - }) + addToast(<>Project {project.name} created) // prettier-ignore navigate(pb.project({ project: project.name })) }, }) diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 4ba627475b..f962b10e4b 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -46,13 +46,7 @@ export function EditProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast({ - content: ( - <> - Project {project.name} updated - - ), - }) + addToast(<>Project {project.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/silo-create.tsx b/app/forms/silo-create.tsx index 1c2bf4179f..e83d2639b1 100644 --- a/app/forms/silo-create.tsx +++ b/app/forms/silo-create.tsx @@ -58,13 +58,7 @@ export function CreateSiloSideModalForm() { onSuccess(silo) { queryClient.invalidateQueries('siloList') queryClient.setQueryData('siloView', { path: { silo: silo.name } }, silo) - addToast({ - content: ( - <> - Silo {silo.name} created - - ), - }) + addToast(<>Silo {silo.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index f28f1d8617..5707de2ad4 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -55,13 +55,7 @@ export function CreateSnapshotSideModalForm() { const createSnapshot = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ - content: ( - <> - Snapshot {snapshot.name} created - - ), - }) + addToast(<>Snapshot {snapshot.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 0fbd52297d..20b3f13896 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -39,13 +39,7 @@ export function CreateSSHKeySideModalForm({ onDismiss, message }: Props) { onSuccess(sshKey) { queryClient.invalidateQueries('currentUserSshKeyList') handleDismiss() - addToast({ - content: ( - <> - SSH key {sshKey.name} created - - ), - }) + addToast(<>SSH key {sshKey.name} created) // prettier-ignore }, }) const form = useForm({ defaultValues }) diff --git a/app/forms/subnet-create.tsx b/app/forms/subnet-create.tsx index de0592aebf..c619557474 100644 --- a/app/forms/subnet-create.tsx +++ b/app/forms/subnet-create.tsx @@ -47,13 +47,7 @@ export function CreateSubnetForm() { onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') onDismiss() - addToast({ - content: ( - <> - Subnet {subnet.name} created - - ), - }) + addToast(<>Subnet {subnet.name} created) // prettier-ignore }, }) diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index fbc3bc20e5..94d2ac4940 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -55,13 +55,7 @@ export function EditSubnetForm() { const updateSubnet = useApiMutation('vpcSubnetUpdate', { onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') - addToast({ - content: ( - <> - Subnet {subnet.name} updated - - ), - }) + addToast(<>Subnet {subnet.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index cd2ce82e09..04a510d622 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -39,13 +39,7 @@ export function CreateVpcSideModalForm() { { path: { vpc: vpc.name }, query: projectSelector }, vpc ) - addToast({ - content: ( - <> - VPC {vpc.name} created - - ), - }) + addToast(<>VPC {vpc.name} created) // prettier-ignore navigate(pb.vpc({ vpc: vpc.name, ...projectSelector })) }, }) diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index fb5c574727..dee65bb37b 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -43,13 +43,7 @@ export function EditVpcSideModalForm() { onSuccess(updatedVpc) { queryClient.invalidateQueries('vpcList') navigate(pb.vpc({ project, vpc: updatedVpc.name })) - addToast({ - content: ( - <> - VPC {updatedVpc.name} updated - - ), - }) + addToast(<>VPC {updatedVpc.name} updated) // prettier-ignore // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating vpcView causes an error page to flash diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index 261e96a466..05eeda7ba7 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -33,13 +33,7 @@ export function CreateRouterSideModalForm() { const createRouter = useApiMutation('vpcRouterCreate', { onSuccess(router) { queryClient.invalidateQueries('vpcRouterList') - addToast({ - content: ( - <> - Router {router.name} created - - ), - }) + addToast(<>Router {router.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 053f66d787..1ee4153c03 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -54,13 +54,7 @@ export function EditRouterSideModalForm() { const editRouter = useApiMutation('vpcRouterUpdate', { onSuccess(updatedRouter) { queryClient.invalidateQueries('vpcRouterList') - addToast({ - content: ( - <> - Router {updatedRouter.name} updated - - ), - }) + addToast(<>Router {updatedRouter.name} updated) // prettier-ignore navigate(pb.vpcRouters({ project, vpc })) }, }) diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index 48696b8f6d..d4c4d158ac 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -47,13 +47,7 @@ export function CreateRouterRouteSideModalForm() { const createRouterRoute = useApiMutation('vpcRouterRouteCreate', { onSuccess(route) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ - content: ( - <> - Route {route.name} created - - ), - }) + addToast(<>Route {route.name} created) // prettier-ignore navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index 8ef6311311..5c45b5a56e 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -65,13 +65,7 @@ export function EditRouterRouteSideModalForm() { const updateRouterRoute = useApiMutation('vpcRouterRouteUpdate', { onSuccess(updatedRoute) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast({ - content: ( - <> - Route {updatedRoute.name} updated - - ), - }) + addToast(<>Route {updatedRoute.name} updated) // prettier-ignore navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index d80931ed80..255e5113ef 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -102,26 +102,14 @@ export function DisksPage() { const { mutateAsync: deleteDisk } = useApiMutation('diskDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('diskList') - addToast({ - content: ( - <> - Disk {variables.path.disk} deleted - - ), - }) + addToast(<>Disk {variables.path.disk} deleted) // prettier-ignore }, }) const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(_data, variables) { queryClient.invalidateQueries('snapshotList') - addToast({ - content: ( - <> - Snapshot {variables.body.name} created - - ), - }) + addToast(<>Snapshot {variables.body.name} created) // prettier-ignore }, onError(err) { addToast({ diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 50df6188ed..11d54f6607 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -110,13 +110,7 @@ export function FloatingIpsPage() { const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ - content: ( - <> - Floating IP {floatingIp.name} detached - - ), - }) + addToast(<>Floating IP {floatingIp.name} detached) // prettier-ignore }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) @@ -126,13 +120,7 @@ export function FloatingIpsPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast({ - content: ( - <> - Floating IP {variables.path.floatingIp} deleted - - ), - }) + addToast(<>Floating IP {variables.path.floatingIp} deleted) // prettier-ignore }, }) @@ -264,13 +252,7 @@ const AttachFloatingIpModal = ({ const floatingIpAttach = useApiMutation('floatingIpAttach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast({ - content: ( - <> - Floating IP {floatingIp.name} attached - - ), - }) + addToast(<>Floating IP {floatingIp.name} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index a3ccc20539..c0e2792a09 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -59,13 +59,7 @@ export function ImagesPage() { const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ - content: ( - <> - Image {variables.path.image} deleted - - ), - }) + addToast(<>Image {variables.path.image} deleted) // prettier-ignore queryClient.invalidateQueries('imageList') }, }) diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index ba70edd239..1599db22bf 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -79,13 +79,7 @@ export const useMakeInstanceActions = ( doAction: () => stopInstanceAsync(instanceParams, { onSuccess: () => - addToast({ - content: ( - <> - Stopping instance {instance.name} - - ), - }), + addToast(<>Stopping instance {instance.name}), // prettier-ignore }), modalTitle: 'Confirm stop instance', modalContent: ( diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index edac2058c4..e8da450771 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -204,13 +204,7 @@ export function NetworkingTab() { const { mutateAsync: deleteNic } = useApiMutation('instanceNetworkInterfaceDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast({ - content: ( - <> - Network interface {variables.path.interface} deleted - - ), - }) + addToast(<>Network interface {variables.path.interface} deleted) // prettier-ignore }, }) const { mutate: editNic } = useApiMutation('instanceNetworkInterfaceUpdate', { @@ -314,13 +308,7 @@ export function NetworkingTab() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast({ - content: ( - <> - Floating IP {variables.path.floatingIp} detached - - ), - }) + addToast(<>Floating IP {variables.path.floatingIp} detached) // prettier-ignore }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) diff --git a/app/pages/project/instances/instance/tabs/StorageTab.tsx b/app/pages/project/instances/instance/tabs/StorageTab.tsx index f966a38ff7..32bd1c84c7 100644 --- a/app/pages/project/instances/instance/tabs/StorageTab.tsx +++ b/app/pages/project/instances/instance/tabs/StorageTab.tsx @@ -89,13 +89,7 @@ export function StorageTab() { const { mutate: detachDisk } = useApiMutation('instanceDiskDetach', { onSuccess(disk) { queryClient.invalidateQueries('instanceDiskList') - addToast({ - content: ( - <> - Disk {disk.name} detached - - ), - }) + addToast(<>Disk {disk.name} detached) // prettier-ignore }, onError(err) { addToast({ @@ -108,13 +102,7 @@ export function StorageTab() { const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast({ - content: ( - <> - Snapshot {snapshot.name} created - - ), - }) + addToast(<>Snapshot {snapshot.name} created) // prettier-ignore }, onError(err) { addToast({ diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index a34dea12d8..cbf7b1f0d5 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -50,13 +50,7 @@ export function VpcPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') navigate(pb.vpcs({ project })) - addToast({ - content: ( - <> - VPC {variables.path.vpc} deleted - - ), - }) + addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore }, }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx index 6ff2e8a07f..26f26e5ac6 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx @@ -65,13 +65,7 @@ export function VpcRoutersTab() { const { mutateAsync: deleteRouter } = useApiMutation('vpcRouterDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterList') - addToast({ - content: ( - <> - Router {variables.path.router} deleted - - ), - }) + addToast(<>Router {variables.path.router} deleted) // prettier-ignore }, }) diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index 3482b79ffd..b9785ccb29 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -86,13 +86,7 @@ export function VpcsPage() { const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') - addToast({ - content: ( - <> - VPC {variables.path.vpc} deleted - - ), - }) + addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore }, }) diff --git a/app/pages/settings/SSHKeysPage.tsx b/app/pages/settings/SSHKeysPage.tsx index 899a14eea6..4d1d3f421d 100644 --- a/app/pages/settings/SSHKeysPage.tsx +++ b/app/pages/settings/SSHKeysPage.tsx @@ -49,13 +49,7 @@ export function SSHKeysPage() { const { mutateAsync: deleteSshKey } = useApiMutation('currentUserSshKeyDelete', { onSuccess: (_data, variables) => { queryClient.invalidateQueries('currentUserSshKeyList') - addToast({ - content: ( - <> - SSH key {variables.path.sshKey} deleted - - ), - }) + addToast(<>SSH key {variables.path.sshKey} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/SiloImagesPage.tsx b/app/pages/system/SiloImagesPage.tsx index 63e9b83f68..7a02c28b72 100644 --- a/app/pages/system/SiloImagesPage.tsx +++ b/app/pages/system/SiloImagesPage.tsx @@ -73,13 +73,7 @@ export function SiloImagesPage() { const queryClient = useApiQueryClient() const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast({ - content: ( - <> - Image {variables.path.image} deleted - - ), - }) + addToast(<>Image {variables.path.image} deleted) // prettier-ignore queryClient.invalidateQueries('imageList') }, }) @@ -138,13 +132,7 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { - addToast({ - content: ( - <> - Image {data.name} promoted - - ), - }) + addToast(<>Image {data.name} promoted) // prettier-ignore queryClient.invalidateQueries('imageList') }, onError: (err) => { diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index 6be095c31d..70c19aa81c 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -85,13 +85,7 @@ export function IpPoolPage() { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') navigate(pb.ipPools()) - addToast({ - content: ( - <> - Pool {variables.path.pool} deleted - - ), - }) + addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index c415e908e6..1cbb91ac79 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -81,13 +81,7 @@ export function IpPoolsPage() { const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') - addToast({ - content: ( - <> - Pool {variables.path.pool} deleted - - ), - }) + addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index 99d994f397..ec7f0e473b 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -80,13 +80,7 @@ export function SilosPage() { const { mutateAsync: deleteSilo } = useApiMutation('siloDelete', { onSuccess(silo, { path }) { queryClient.invalidateQueries('siloList') - addToast({ - content: ( - <> - Silo {path.silo} deleted - - ), - }) + addToast(<>Silo {path.silo} deleted) // prettier-ignore }, }) From a3d736b04977c43a6d673e3ae4977e6a606cff71 Mon Sep 17 00:00:00 2001 From: Charlie Park Date: Tue, 29 Oct 2024 15:58:47 -0500 Subject: [PATCH 19/21] eliminate HLs by using CSS instead --- app/components/AttachEphemeralIpModal.tsx | 4 ++-- app/components/AttachFloatingIpModal.tsx | 4 ++-- app/components/HL.tsx | 12 ++---------- app/forms/disk-create.tsx | 4 ++-- app/forms/firewall-rules-create.tsx | 4 ++-- app/forms/firewall-rules-edit.tsx | 4 ++-- app/forms/floating-ip-create.tsx | 4 ++-- app/forms/floating-ip-edit.tsx | 4 ++-- app/forms/idp/create.tsx | 4 ++-- app/forms/image-from-snapshot.tsx | 4 ++-- app/forms/instance-create.tsx | 4 ++-- app/forms/ip-pool-create.tsx | 4 ++-- app/forms/ip-pool-edit.tsx | 4 ++-- app/forms/network-interface-edit.tsx | 4 ++-- app/forms/project-create.tsx | 4 ++-- app/forms/project-edit.tsx | 4 ++-- app/forms/silo-create.tsx | 4 ++-- app/forms/snapshot-create.tsx | 4 ++-- app/forms/ssh-key-create.tsx | 4 ++-- app/forms/subnet-create.tsx | 4 ++-- app/forms/subnet-edit.tsx | 4 ++-- app/forms/vpc-create.tsx | 4 ++-- app/forms/vpc-edit.tsx | 4 ++-- app/forms/vpc-router-create.tsx | 4 ++-- app/forms/vpc-router-edit.tsx | 4 ++-- app/forms/vpc-router-route-create.tsx | 4 ++-- app/forms/vpc-router-route-edit.tsx | 4 ++-- app/pages/project/disks/DisksPage.tsx | 8 ++++---- .../project/floating-ips/FloatingIpsPage.tsx | 8 ++++---- app/pages/project/images/ImagesPage.tsx | 6 +++--- app/pages/project/instances/actions.tsx | 10 +++++----- .../instances/instance/tabs/NetworkingTab.tsx | 6 +++--- .../instances/instance/tabs/StorageTab.tsx | 6 +++--- app/pages/project/vpcs/VpcPage/VpcPage.tsx | 4 ++-- .../project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx | 4 ++-- app/pages/project/vpcs/VpcsPage.tsx | 4 ++-- app/pages/settings/SSHKeysPage.tsx | 4 ++-- app/pages/system/SiloImagesPage.tsx | 8 ++++---- app/pages/system/networking/IpPoolPage.tsx | 4 ++-- app/pages/system/networking/IpPoolsPage.tsx | 4 ++-- app/pages/system/silos/SilosPage.tsx | 4 ++-- app/ui/styles/components/highlight.css | 17 +++++++++++++++++ app/ui/styles/index.css | 1 + package-lock.json | 10 +++++----- 44 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 app/ui/styles/components/highlight.css diff --git a/app/components/AttachEphemeralIpModal.tsx b/app/components/AttachEphemeralIpModal.tsx index 73b671f7cb..25177c0dde 100644 --- a/app/components/AttachEphemeralIpModal.tsx +++ b/app/components/AttachEphemeralIpModal.tsx @@ -11,7 +11,7 @@ import { useForm } from 'react-hook-form' import { useApiMutation, useApiQueryClient, usePrefetchedApiQuery } from '~/api' import { ListboxField } from '~/components/form/fields/ListboxField' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useInstanceSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Modal } from '~/ui/lib/Modal' @@ -32,7 +32,7 @@ export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', { onSuccess(ephemeralIp) { queryClient.invalidateQueries('instanceExternalIpList') - addToast(<>IP {ephemeralIp.ip} attached) // prettier-ignore + addToast(<>IP {ephemeralIp.ip} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/components/AttachFloatingIpModal.tsx b/app/components/AttachFloatingIpModal.tsx index e8baa92b7c..cc351bde18 100644 --- a/app/components/AttachFloatingIpModal.tsx +++ b/app/components/AttachFloatingIpModal.tsx @@ -10,7 +10,7 @@ import { useForm } from 'react-hook-form' import { useApiMutation, useApiQueryClient, type FloatingIp, type Instance } from '~/api' import { ListboxField } from '~/components/form/fields/ListboxField' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { addToast } from '~/stores/toast' import { Message } from '~/ui/lib/Message' import { Modal } from '~/ui/lib/Modal' @@ -49,7 +49,7 @@ export const AttachFloatingIpModal = ({ onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast(<>IP {floatingIp.name} attached) // prettier-ignore + addToast(<>IP {floatingIp.name} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/components/HL.tsx b/app/components/HL.tsx index 9af02404dc..da6659356d 100644 --- a/app/components/HL.tsx +++ b/app/components/HL.tsx @@ -7,13 +7,5 @@ */ import { classed } from '~/util/classed' -export const HL = classed.span`text-sans-md text-default` - -/** HL with "success"-colored text */ -export const HLs = classed.span`text-sans-md text-accent children:text-accent` - -// HL with "error"-colored text -export const HLe = classed.span`text-sans-md text-error children:text-error` - -// HL with "info"-colored text -export const HLi = classed.span`text-sans-md text-notice children:text-notice` +// ox-highlight needed for CSS ensuring the HL color matches the container +export const HL = classed.span`ox-highlight text-sans-md text-default` diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index 12990cfb50..593056f4df 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -28,7 +28,7 @@ import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' import { RadioField } from '~/components/form/fields/RadioField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' @@ -77,7 +77,7 @@ export function CreateDiskSideModalForm({ const createDisk = useApiMutation('diskCreate', { onSuccess(data) { queryClient.invalidateQueries('diskList') - addToast(<>Disk {data.name} created) // prettier-ignore + addToast(<>Disk {data.name} created) // prettier-ignore onSuccess?.(data) onDismiss(navigate) }, diff --git a/app/forms/firewall-rules-create.tsx b/app/forms/firewall-rules-create.tsx index 255645f2be..35aee97230 100644 --- a/app/forms/firewall-rules-create.tsx +++ b/app/forms/firewall-rules-create.tsx @@ -18,7 +18,7 @@ import { } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { ALL_ISH } from '~/util/consts' @@ -78,7 +78,7 @@ export function CreateFirewallRuleForm() { onSuccess(updatedRules) { const newRule = updatedRules.rules[updatedRules.rules.length - 1] queryClient.invalidateQueries('vpcFirewallRulesView') - addToast(<>Firewall rule {newRule.name} created) // prettier-ignore + addToast(<>Firewall rule {newRule.name} created) // prettier-ignore navigate(pb.vpcFirewallRules(vpcSelector)) }, }) diff --git a/app/forms/firewall-rules-edit.tsx b/app/forms/firewall-rules-edit.tsx index afc7ae2a9b..bbea4f975e 100644 --- a/app/forms/firewall-rules-edit.tsx +++ b/app/forms/firewall-rules-edit.tsx @@ -18,7 +18,7 @@ import { import { trigger404 } from '~/components/ErrorBoundary' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getFirewallRuleSelector, useFirewallRuleSelector, @@ -74,7 +74,7 @@ export function EditFirewallRuleForm() { onDismiss() queryClient.invalidateQueries('vpcFirewallRulesView') const updatedRule = body.rules[body.rules.length - 1] - addToast(<>Firewall rule {updatedRule.name} updated) // prettier-ignore + addToast(<>Firewall rule {updatedRule.name} updated) // prettier-ignore }, }) diff --git a/app/forms/floating-ip-create.tsx b/app/forms/floating-ip-create.tsx index 880189c8ba..cab5b694e6 100644 --- a/app/forms/floating-ip-create.tsx +++ b/app/forms/floating-ip-create.tsx @@ -23,7 +23,7 @@ import { toIpPoolItem } from '~/components/form/fields/ip-pool-item' import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Message } from '~/ui/lib/Message' @@ -49,7 +49,7 @@ export function CreateFloatingIpSideModalForm() { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast(<>Floating IP {floatingIp.name} created) // prettier-ignore + addToast(<>Floating IP {floatingIp.name} created) // prettier-ignore navigate(pb.floatingIps(projectSelector)) }, }) diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx index 1e03e79721..26fe356f92 100644 --- a/app/forms/floating-ip-edit.tsx +++ b/app/forms/floating-ip-edit.tsx @@ -18,7 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getFloatingIpSelector, useFloatingIpSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from 'app/util/path-builder' @@ -48,7 +48,7 @@ export function EditFloatingIpSideModalForm() { const editFloatingIp = useApiMutation('floatingIpUpdate', { onSuccess(_floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast(<>Floating IP {_floatingIp.name} updated) // prettier-ignore + addToast(<>Floating IP {_floatingIp.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/idp/create.tsx b/app/forms/idp/create.tsx index 7eeada64be..42004e88e1 100644 --- a/app/forms/idp/create.tsx +++ b/app/forms/idp/create.tsx @@ -15,7 +15,7 @@ import { FileField } from '~/components/form/fields/FileField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useSiloSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { readBlobAsBase64 } from '~/util/file' @@ -54,7 +54,7 @@ export function CreateIdpSideModalForm() { const createIdp = useApiMutation('samlIdentityProviderCreate', { onSuccess(idp) { queryClient.invalidateQueries('siloIdentityProviderList') - addToast(<>IdP {idp.name} created) // prettier-ignore + addToast(<>IdP {idp.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index adfd2d3f5c..c6a9ac1e20 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -21,7 +21,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { PropertiesTable } from '~/ui/lib/PropertiesTable' @@ -57,7 +57,7 @@ export function CreateImageFromSnapshotSideModalForm() { const createImage = useApiMutation('imageCreate', { onSuccess(image) { queryClient.invalidateQueries('imageList') - addToast(<>Image {image.name} created) // prettier-ignore + addToast(<>Image {image.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 7e3568492b..8350ccf20a 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -56,7 +56,7 @@ import { SshKeysField } from '~/components/form/fields/SshKeysField' import { TextField } from '~/components/form/fields/TextField' import { Form } from '~/components/form/Form' import { FullPageForm } from '~/components/form/FullPageForm' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { Button } from '~/ui/lib/Button' @@ -184,7 +184,7 @@ export function CreateInstanceForm() { { path: { instance: instance.name }, query: { project } }, instance ) - addToast(<>Instance {instance.name} created) // prettier-ignore + addToast(<>Instance {instance.name} created) // prettier-ignore navigate(pb.instance({ project, instance: instance.name })) }, }) diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index cc443ce406..8afa803e9e 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -13,7 +13,7 @@ import { useApiMutation, useApiQueryClient, type IpPoolCreate } from '@oxide/api import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { addToast } from '~/stores/toast' import { Message } from '~/ui/lib/Message' import { pb } from '~/util/path-builder' @@ -32,7 +32,7 @@ export function CreateIpPoolSideModalForm() { const createPool = useApiMutation('ipPoolCreate', { onSuccess(_pool) { queryClient.invalidateQueries('ipPoolList') - addToast(<>IP pool {_pool.name} created) // prettier-ignore + addToast(<>IP pool {_pool.name} created) // prettier-ignore navigate(pb.ipPools()) }, }) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index d14d5db125..cbd0b7db7d 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -18,7 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -44,7 +44,7 @@ export function EditIpPoolSideModalForm() { onSuccess(updatedPool) { queryClient.invalidateQueries('ipPoolList') navigate(pb.ipPool({ pool: updatedPool.name })) - addToast(<>IP pool {updatedPool.name} updated) // prettier-ignore + addToast(<>IP pool {updatedPool.name} updated) // prettier-ignore // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating ipPoolView causes an error page to flash diff --git a/app/forms/network-interface-edit.tsx b/app/forms/network-interface-edit.tsx index d975888439..401403f900 100644 --- a/app/forms/network-interface-edit.tsx +++ b/app/forms/network-interface-edit.tsx @@ -20,7 +20,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextFieldInner } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useInstanceSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' @@ -46,7 +46,7 @@ export function EditNetworkInterfaceForm({ const editNetworkInterface = useApiMutation('instanceNetworkInterfaceUpdate', { onSuccess(nic) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast(<>Network interface {nic.name} updated) // prettier-ignore + addToast(<>Network interface {nic.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index 2a1c5cd79f..faaee13df7 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -13,7 +13,7 @@ import { useApiMutation, useApiQueryClient, type ProjectCreate } from '@oxide/ap import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -34,7 +34,7 @@ export function CreateProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast(<>Project {project.name} created) // prettier-ignore + addToast(<>Project {project.name} created) // prettier-ignore navigate(pb.project({ project: project.name })) }, }) diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index f962b10e4b..7af23a1723 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -18,7 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -46,7 +46,7 @@ export function EditProjectSideModalForm() { queryClient.invalidateQueries('projectList') // avoid the project fetch when the project page loads since we have the data queryClient.setQueryData('projectView', { path: { project: project.name } }, project) - addToast(<>Project {project.name} updated) // prettier-ignore + addToast(<>Project {project.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/silo-create.tsx b/app/forms/silo-create.tsx index e83d2639b1..ea6b82651e 100644 --- a/app/forms/silo-create.tsx +++ b/app/forms/silo-create.tsx @@ -19,7 +19,7 @@ import { RadioField } from '~/components/form/fields/RadioField' import { TextField } from '~/components/form/fields/TextField' import { TlsCertsField } from '~/components/form/fields/TlsCertsField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { FieldLabel } from '~/ui/lib/FieldLabel' @@ -58,7 +58,7 @@ export function CreateSiloSideModalForm() { onSuccess(silo) { queryClient.invalidateQueries('siloList') queryClient.setQueryData('siloView', { path: { silo: silo.name } }, silo) - addToast(<>Silo {silo.name} created) // prettier-ignore + addToast(<>Silo {silo.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 5707de2ad4..25c7f90db8 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -22,7 +22,7 @@ import { ComboboxField } from '~/components/form/fields/ComboboxField' import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { toComboboxItems } from '~/ui/lib/Combobox' @@ -55,7 +55,7 @@ export function CreateSnapshotSideModalForm() { const createSnapshot = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast(<>Snapshot {snapshot.name} created) // prettier-ignore + addToast(<>Snapshot {snapshot.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 20b3f13896..82ba183e23 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -14,7 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -39,7 +39,7 @@ export function CreateSSHKeySideModalForm({ onDismiss, message }: Props) { onSuccess(sshKey) { queryClient.invalidateQueries('currentUserSshKeyList') handleDismiss() - addToast(<>SSH key {sshKey.name} created) // prettier-ignore + addToast(<>SSH key {sshKey.name} created) // prettier-ignore }, }) const form = useForm({ defaultValues }) diff --git a/app/forms/subnet-create.tsx b/app/forms/subnet-create.tsx index c619557474..e2bbb2666a 100644 --- a/app/forms/subnet-create.tsx +++ b/app/forms/subnet-create.tsx @@ -20,7 +20,7 @@ import { useCustomRouterItems, } from '~/components/form/fields/useItemsList' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' @@ -47,7 +47,7 @@ export function CreateSubnetForm() { onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') onDismiss() - addToast(<>Subnet {subnet.name} created) // prettier-ignore + addToast(<>Subnet {subnet.name} created) // prettier-ignore }, }) diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 94d2ac4940..49ab973fbc 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -25,7 +25,7 @@ import { useCustomRouterItems, } from '~/components/form/fields/useItemsList' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getVpcSubnetSelector, useVpcSubnetSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' @@ -55,7 +55,7 @@ export function EditSubnetForm() { const updateSubnet = useApiMutation('vpcSubnetUpdate', { onSuccess(subnet) { queryClient.invalidateQueries('vpcSubnetList') - addToast(<>Subnet {subnet.name} updated) // prettier-ignore + addToast(<>Subnet {subnet.name} updated) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index 04a510d622..43f8fa15a1 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -14,7 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { TextField } from '~/components/form/fields/TextField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -39,7 +39,7 @@ export function CreateVpcSideModalForm() { { path: { vpc: vpc.name }, query: projectSelector }, vpc ) - addToast(<>VPC {vpc.name} created) // prettier-ignore + addToast(<>VPC {vpc.name} created) // prettier-ignore navigate(pb.vpc({ vpc: vpc.name, ...projectSelector })) }, }) diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index dee65bb37b..0982d17f10 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -18,7 +18,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -43,7 +43,7 @@ export function EditVpcSideModalForm() { onSuccess(updatedVpc) { queryClient.invalidateQueries('vpcList') navigate(pb.vpc({ project, vpc: updatedVpc.name })) - addToast(<>VPC {updatedVpc.name} updated) // prettier-ignore + addToast(<>VPC {updatedVpc.name} updated) // prettier-ignore // Only invalidate if we're staying on the same page. If the name // _has_ changed, invalidating vpcView causes an error page to flash diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index 05eeda7ba7..3d08d456cc 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -13,7 +13,7 @@ import { useApiMutation, useApiQueryClient, type VpcRouterCreate } from '@oxide/ import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -33,7 +33,7 @@ export function CreateRouterSideModalForm() { const createRouter = useApiMutation('vpcRouterCreate', { onSuccess(router) { queryClient.invalidateQueries('vpcRouterList') - addToast(<>Router {router.name} created) // prettier-ignore + addToast(<>Router {router.name} created) // prettier-ignore onDismiss() }, }) diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 1ee4153c03..134aadcf26 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -23,7 +23,7 @@ import { import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -54,7 +54,7 @@ export function EditRouterSideModalForm() { const editRouter = useApiMutation('vpcRouterUpdate', { onSuccess(updatedRouter) { queryClient.invalidateQueries('vpcRouterList') - addToast(<>Router {updatedRouter.name} updated) // prettier-ignore + addToast(<>Router {updatedRouter.name} updated) // prettier-ignore navigate(pb.vpcRouters({ project, vpc })) }, }) diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index d4c4d158ac..8030b55dcd 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -11,7 +11,7 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, useApiMutation, useApiQueryClient } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { RouteFormFields, type RouteFormValues } from '~/forms/vpc-router-route-common' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' @@ -47,7 +47,7 @@ export function CreateRouterRouteSideModalForm() { const createRouterRoute = useApiMutation('vpcRouterRouteCreate', { onSuccess(route) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast(<>Route {route.name} created) // prettier-ignore + addToast(<>Route {route.name} created) // prettier-ignore navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index 5c45b5a56e..da1c06338e 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -17,7 +17,7 @@ import { } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { RouteFormFields, routeFormMessage, @@ -65,7 +65,7 @@ export function EditRouterRouteSideModalForm() { const updateRouterRoute = useApiMutation('vpcRouterRouteUpdate', { onSuccess(updatedRoute) { queryClient.invalidateQueries('vpcRouterRouteList') - addToast(<>Route {updatedRoute.name} updated) // prettier-ignore + addToast(<>Route {updatedRoute.name} updated) // prettier-ignore navigate(pb.vpcRouter(routerSelector)) }, }) diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 255e5113ef..298e4af5f9 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -20,7 +20,7 @@ import { import { Storage16Icon, Storage24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { DiskStateBadge } from '~/components/StateBadge' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' @@ -102,14 +102,14 @@ export function DisksPage() { const { mutateAsync: deleteDisk } = useApiMutation('diskDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('diskList') - addToast(<>Disk {variables.path.disk} deleted) // prettier-ignore + addToast(<>Disk {variables.path.disk} deleted) // prettier-ignore }, }) const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(_data, variables) { queryClient.invalidateQueries('snapshotList') - addToast(<>Snapshot {variables.body.name} created) // prettier-ignore + addToast(<>Snapshot {variables.body.name} created) // prettier-ignore }, onError(err) { addToast({ @@ -125,7 +125,7 @@ export function DisksPage() { { label: 'Snapshot', onActivate() { - addToast(<>Creating snapshot of disk {disk.name}) // prettier-ignore + addToast(<>Creating snapshot of disk {disk.name}) // prettier-ignore createSnapshot({ query: { project }, body: { diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 11d54f6607..ae5b95b57c 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -22,7 +22,7 @@ import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react import { DocsPopover } from '~/components/DocsPopover' import { ListboxField } from '~/components/form/fields/ListboxField' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' @@ -110,7 +110,7 @@ export function FloatingIpsPage() { const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast(<>Floating IP {floatingIp.name} detached) // prettier-ignore + addToast(<>Floating IP {floatingIp.name} detached) // prettier-ignore }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) @@ -120,7 +120,7 @@ export function FloatingIpsPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('ipPoolUtilizationView') - addToast(<>Floating IP {variables.path.floatingIp} deleted) // prettier-ignore + addToast(<>Floating IP {variables.path.floatingIp} deleted) // prettier-ignore }, }) @@ -252,7 +252,7 @@ const AttachFloatingIpModal = ({ const floatingIpAttach = useApiMutation('floatingIpAttach', { onSuccess(floatingIp) { queryClient.invalidateQueries('floatingIpList') - addToast(<>Floating IP {floatingIp.name} attached) // prettier-ignore + addToast(<>Floating IP {floatingIp.name} attached) // prettier-ignore onDismiss() }, onError: (err) => { diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index c0e2792a09..cf5c43dee2 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -13,7 +13,7 @@ import { apiQueryClient, useApiMutation, useApiQueryClient, type Image } from '@ import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -59,7 +59,7 @@ export function ImagesPage() { const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast(<>Image {variables.path.image} deleted) // prettier-ignore + addToast(<>Image {variables.path.image} deleted) // prettier-ignore queryClient.invalidateQueries('imageList') }, }) @@ -134,7 +134,7 @@ const PromoteImageModal = ({ onDismiss, imageName }: PromoteModalProps) => { addToast({ content: ( <> - Image {data.name} promoted + Image {data.name} promoted ), cta: { diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index 1599db22bf..b18886dd9a 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -10,7 +10,7 @@ import { useNavigate } from 'react-router-dom' import { instanceCan, useApiMutation, type Instance } from '@oxide/api' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -58,7 +58,7 @@ export const useMakeInstanceActions = ( label: 'Start', onActivate() { startInstance(instanceParams, { - onSuccess: () => addToast(<>Starting instance {instance.name}), // prettier-ignore + onSuccess: () => addToast(<>Starting instance {instance.name}), // prettier-ignore onError: (error) => addToast({ variant: 'error', @@ -79,7 +79,7 @@ export const useMakeInstanceActions = ( doAction: () => stopInstanceAsync(instanceParams, { onSuccess: () => - addToast(<>Stopping instance {instance.name}), // prettier-ignore + addToast(<>Stopping instance {instance.name}), // prettier-ignore }), modalTitle: 'Confirm stop instance', modalContent: ( @@ -105,7 +105,7 @@ export const useMakeInstanceActions = ( onActivate() { rebootInstance(instanceParams, { onSuccess: () => - addToast(<>Rebooting instance {instance.name}), // prettier-ignore + addToast(<>Rebooting instance {instance.name}), // prettier-ignore onError: (error) => addToast({ variant: 'error', @@ -130,7 +130,7 @@ export const useMakeInstanceActions = ( doDelete: () => deleteInstanceAsync(instanceParams, { onSuccess: () => - addToast(<>Deleting instance {instance.name}), // prettier-ignore + addToast(<>Deleting instance {instance.name}), // prettier-ignore }), label: instance.name, resourceKind: 'instance', diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index e8da450771..d1d8c11431 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -24,7 +24,7 @@ import { IpGlobal24Icon, Networking24Icon } from '@oxide/design-system/icons/rea import { AttachEphemeralIpModal } from '~/components/AttachEphemeralIpModal' import { AttachFloatingIpModal } from '~/components/AttachFloatingIpModal' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { ListPlusCell } from '~/components/ListPlusCell' import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create' import { EditNetworkInterfaceForm } from '~/forms/network-interface-edit' @@ -204,7 +204,7 @@ export function NetworkingTab() { const { mutateAsync: deleteNic } = useApiMutation('instanceNetworkInterfaceDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('instanceNetworkInterfaceList') - addToast(<>Network interface {variables.path.interface} deleted) // prettier-ignore + addToast(<>Network interface {variables.path.interface} deleted) // prettier-ignore }, }) const { mutate: editNic } = useApiMutation('instanceNetworkInterfaceUpdate', { @@ -308,7 +308,7 @@ export function NetworkingTab() { onSuccess(_data, variables) { queryClient.invalidateQueries('floatingIpList') queryClient.invalidateQueries('instanceExternalIpList') - addToast(<>Floating IP {variables.path.floatingIp} detached) // prettier-ignore + addToast(<>Floating IP {variables.path.floatingIp} detached) // prettier-ignore }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) diff --git a/app/pages/project/instances/instance/tabs/StorageTab.tsx b/app/pages/project/instances/instance/tabs/StorageTab.tsx index 32bd1c84c7..2b7c24d126 100644 --- a/app/pages/project/instances/instance/tabs/StorageTab.tsx +++ b/app/pages/project/instances/instance/tabs/StorageTab.tsx @@ -23,7 +23,7 @@ import { } from '@oxide/api' import { Storage24Icon } from '@oxide/design-system/icons/react' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { DiskStateBadge } from '~/components/StateBadge' import { AttachDiskSideModalForm } from '~/forms/disk-attach' import { CreateDiskSideModalForm } from '~/forms/disk-create' @@ -89,7 +89,7 @@ export function StorageTab() { const { mutate: detachDisk } = useApiMutation('instanceDiskDetach', { onSuccess(disk) { queryClient.invalidateQueries('instanceDiskList') - addToast(<>Disk {disk.name} detached) // prettier-ignore + addToast(<>Disk {disk.name} detached) // prettier-ignore }, onError(err) { addToast({ @@ -102,7 +102,7 @@ export function StorageTab() { const { mutate: createSnapshot } = useApiMutation('snapshotCreate', { onSuccess(snapshot) { queryClient.invalidateQueries('snapshotList') - addToast(<>Snapshot {snapshot.name} created) // prettier-ignore + addToast(<>Snapshot {snapshot.name} created) // prettier-ignore }, onError(err) { addToast({ diff --git a/app/pages/project/vpcs/VpcPage/VpcPage.tsx b/app/pages/project/vpcs/VpcPage/VpcPage.tsx index cbf7b1f0d5..97adda95eb 100644 --- a/app/pages/project/vpcs/VpcPage/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage/VpcPage.tsx @@ -16,7 +16,7 @@ import { } from '@oxide/api' import { Networking24Icon } from '@oxide/design-system/icons/react' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RouteTabs, Tab } from '~/components/RouteTabs' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' @@ -50,7 +50,7 @@ export function VpcPage() { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') navigate(pb.vpcs({ project })) - addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore + addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore }, }) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx index 26f26e5ac6..cd411c4c90 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcRoutersTab.tsx @@ -11,7 +11,7 @@ import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, useApiMutation, type VpcRouter } from '@oxide/api' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { routeFormMessage } from '~/forms/vpc-router-route-common' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' @@ -65,7 +65,7 @@ export function VpcRoutersTab() { const { mutateAsync: deleteRouter } = useApiMutation('vpcRouterDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('vpcRouterList') - addToast(<>Router {variables.path.router} deleted) // prettier-ignore + addToast(<>Router {variables.path.router} deleted) // prettier-ignore }, }) diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index b9785ccb29..69df4371e3 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -20,7 +20,7 @@ import { import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' @@ -86,7 +86,7 @@ export function VpcsPage() { const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', { onSuccess(_data, variables) { queryClient.invalidateQueries('vpcList') - addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore + addToast(<>VPC {variables.path.vpc} deleted) // prettier-ignore }, }) diff --git a/app/pages/settings/SSHKeysPage.tsx b/app/pages/settings/SSHKeysPage.tsx index 4d1d3f421d..3b2fd881c8 100644 --- a/app/pages/settings/SSHKeysPage.tsx +++ b/app/pages/settings/SSHKeysPage.tsx @@ -13,7 +13,7 @@ import { apiQueryClient, useApiMutation, useApiQueryClient, type SshKey } from ' import { Key16Icon, Key24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' @@ -49,7 +49,7 @@ export function SSHKeysPage() { const { mutateAsync: deleteSshKey } = useApiMutation('currentUserSshKeyDelete', { onSuccess: (_data, variables) => { queryClient.invalidateQueries('currentUserSshKeyList') - addToast(<>SSH key {variables.path.sshKey} deleted) // prettier-ignore + addToast(<>SSH key {variables.path.sshKey} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/SiloImagesPage.tsx b/app/pages/system/SiloImagesPage.tsx index 7a02c28b72..2346153936 100644 --- a/app/pages/system/SiloImagesPage.tsx +++ b/app/pages/system/SiloImagesPage.tsx @@ -23,7 +23,7 @@ import { DocsPopover } from '~/components/DocsPopover' import { ComboboxField } from '~/components/form/fields/ComboboxField' import { toImageComboboxItem } from '~/components/form/fields/ImageSelectField' import { ListboxField } from '~/components/form/fields/ListboxField' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { makeLinkCell } from '~/table/cells/LinkCell' @@ -73,7 +73,7 @@ export function SiloImagesPage() { const queryClient = useApiQueryClient() const { mutateAsync: deleteImage } = useApiMutation('imageDelete', { onSuccess(_data, variables) { - addToast(<>Image {variables.path.image} deleted) // prettier-ignore + addToast(<>Image {variables.path.image} deleted) // prettier-ignore queryClient.invalidateQueries('imageList') }, }) @@ -132,7 +132,7 @@ const PromoteImageModal = ({ onDismiss }: { onDismiss: () => void }) => { const promoteImage = useApiMutation('imagePromote', { onSuccess(data) { - addToast(<>Image {data.name} promoted) // prettier-ignore + addToast(<>Image {data.name} promoted) // prettier-ignore queryClient.invalidateQueries('imageList') }, onError: (err) => { @@ -221,7 +221,7 @@ const DemoteImageModal = ({ addToast({ content: ( <> - Image {data.name} demoted + Image {data.name} demoted ), cta: selectedProject diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index 70c19aa81c..a5c44487e7 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -26,7 +26,7 @@ import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react import { CapacityBar } from '~/components/CapacityBar' import { DocsPopover } from '~/components/DocsPopover' import { ComboboxField } from '~/components/form/fields/ComboboxField' -import { HL, HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { QueryParamTabs } from '~/components/QueryParamTabs' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' @@ -85,7 +85,7 @@ export function IpPoolPage() { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') navigate(pb.ipPools()) - addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore + addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index 1cbb91ac79..eaa8fcf386 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -20,7 +20,7 @@ import { import { IpGlobal16Icon, IpGlobal24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { IpUtilCell } from '~/components/IpPoolUtilization' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' @@ -81,7 +81,7 @@ export function IpPoolsPage() { const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { onSuccess(_data, variables) { apiQueryClient.invalidateQueries('ipPoolList') - addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore + addToast(<>Pool {variables.path.pool} deleted) // prettier-ignore }, }) diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index ec7f0e473b..7f10b98449 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -19,7 +19,7 @@ import { import { Cloud16Icon, Cloud24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' -import { HLs } from '~/components/HL' +import { HL } from '~/components/HL' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -80,7 +80,7 @@ export function SilosPage() { const { mutateAsync: deleteSilo } = useApiMutation('siloDelete', { onSuccess(silo, { path }) { queryClient.invalidateQueries('siloList') - addToast(<>Silo {path.silo} deleted) // prettier-ignore + addToast(<>Silo {path.silo} deleted) // prettier-ignore }, }) diff --git a/app/ui/styles/components/highlight.css b/app/ui/styles/components/highlight.css new file mode 100644 index 0000000000..8991c03d60 --- /dev/null +++ b/app/ui/styles/components/highlight.css @@ -0,0 +1,17 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ + +.text-accent-secondary .ox-highlight { + @apply text-accent; +} +.text-error-secondary .ox-highlight { + @apply text-error; +} +.text-info-secondary .ox-highlight { + @apply text-info; +} diff --git a/app/ui/styles/index.css b/app/ui/styles/index.css index 5746fa8af6..83cf57071b 100644 --- a/app/ui/styles/index.css +++ b/app/ui/styles/index.css @@ -30,6 +30,7 @@ @import './components/loading-bar.css'; @import './components/Tabs.css'; @import './components/form.css'; +@import './components/highlight.css'; @import './components/login-page.css'; @import './components/mini-table.css'; @import './components/properties-table.css'; diff --git a/package-lock.json b/package-lock.json index 240f30f43f..c5490f12b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6687,7 +6687,7 @@ "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHLV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "dev": true, "license": "MIT", "bin": { @@ -11856,7 +11856,7 @@ "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxHLnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -15382,7 +15382,7 @@ "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIHLLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -16001,7 +16001,7 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "integrity": "sha512-E/ZsdU4HL/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "devOptional": true, "license": "MIT" }, @@ -17064,7 +17064,7 @@ "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLaSSajf35bcYnA==", "devOptional": true, "license": "ISC", "dependencies": { From 4ecef0696583396bac7e5b3c94de37c3d2ffa258 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 29 Oct 2024 16:07:42 -0500 Subject: [PATCH 20/21] actually..... do it in tailwind with group. yowza --- app/components/HL.tsx | 10 ++++++++-- app/ui/lib/Toast.tsx | 5 ++++- app/ui/styles/components/highlight.css | 17 ----------------- app/ui/styles/index.css | 1 - 4 files changed, 12 insertions(+), 21 deletions(-) delete mode 100644 app/ui/styles/components/highlight.css diff --git a/app/components/HL.tsx b/app/components/HL.tsx index da6659356d..9a5ca1e1b1 100644 --- a/app/components/HL.tsx +++ b/app/components/HL.tsx @@ -7,5 +7,11 @@ */ import { classed } from '~/util/classed' -// ox-highlight needed for CSS ensuring the HL color matches the container -export const HL = classed.span`ox-highlight text-sans-md text-default` +// note parent with secondary text color must have 'group' on it for +// this to work. see Toast for an example +export const HL = classed.span` + text-sans-md text-default + group-[.text-accent-secondary]:text-accent + group-[.text-error-secondary]:text-error + group-[.text-info-secondary]:text-info +` diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index 4b55cd4eab..05d3cd8dbd 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -100,7 +100,10 @@ export const Toast = ({ {(title || variant !== 'success') && (
{title || defaultTitle[variant]}
)} -
{content}
+ {/* 'group' is necessary for HL color trick to work. see HL.tsx */} +
+ {content} +
{cta && ( Date: Tue, 29 Oct 2024 21:17:51 -0500 Subject: [PATCH 21/21] undo spurious changes to checksums in package-lock.json --- .github/workflows/lintBuildTest.yml | 2 +- package-lock.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 55af5bc78c..b7cedaa85f 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'npm' - name: Cache node_modules uses: actions/cache@v4 diff --git a/package-lock.json b/package-lock.json index c5490f12b8..240f30f43f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6687,7 +6687,7 @@ "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHLV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "dev": true, "license": "MIT", "bin": { @@ -11856,7 +11856,7 @@ "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxHLnrwRr4elSSg==", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -15382,7 +15382,7 @@ "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIHLLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -16001,7 +16001,7 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HL/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "devOptional": true, "license": "MIT" }, @@ -17064,7 +17064,7 @@ "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLaSSajf35bcYnA==", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "devOptional": true, "license": "ISC", "dependencies": {