Skip to content

Commit 25eeaae

Browse files
authored
PLU-361: warning when deleting connections (#794)
## Problem There is no warning/prompt when user is deleting a connection. Other delete actions on Apps/Pipes/Tiles have a warning/prompt when user is attempting to delete. Closes [PLU-361: warning-when-deleting-connections](https://linear.app/ogp/issue/PLU-361/warning-when-deleting-connections) ## Solution Add warning/prompt modal when user is attempting to delete a connection. **Improvements**: - Refactor Tiles to use MenuAlertDialog instead of inline AlertDialog - Add toast on Tile delete success ## Before & After Screenshots **BEFORE**: - No warning/modal, connection is deleted immediately. https://github.com/user-attachments/assets/1d6ccd95-76bf-4e00-ad43-615902f3aa22 - No notification on Tile delete success **AFTER**: - Delete Connection https://github.com/user-attachments/assets/c5b89692-26b1-47b3-80fe-20b64918f622 - Delete Tile https://github.com/user-attachments/assets/c68e63df-9e30-4071-91f0-968f000524fe ## Tests - [X] Modal appears when user attempts to delete a connection. - [X] Connection is deleted successfully upon confirmation on modal. - [X] Able to delete Tiles - [X] Able to duplicate Pipes - [X] Able to delete Pipes **New scripts**: - `packages/frontend/src/components/MenuAlertDialog/index.tsx` : generic MenuAlertDialog for reuse across delete actions (updated from `plumber/packages/frontend/src/components/FlowRow/MenuAlertDialog.tsx`)
1 parent 4c8e637 commit 25eeaae

File tree

4 files changed

+91
-76
lines changed

4 files changed

+91
-76
lines changed

packages/frontend/src/components/AppConnectionRow/index.tsx

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { IConnection } from '@plumber/types'
33
import * as React from 'react'
44
import { useCallback, useRef, useState } from 'react'
55
import { useLazyQuery, useMutation } from '@apollo/client'
6-
import { Card } from '@chakra-ui/react'
6+
import { Card, useDisclosure } from '@chakra-ui/react'
77
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
88
import ErrorIcon from '@mui/icons-material/Error'
99
import MoreHorizIcon from '@mui/icons-material/MoreHoriz'
@@ -15,6 +15,7 @@ import { useToast } from '@opengovsg/design-system-react'
1515
import { DateTime } from 'luxon'
1616

1717
import ConnectionContextMenu from '@/components/AppConnectionContextMenu'
18+
import MenuAlertDialog from '@/components/MenuAlertDialog'
1819
import { DELETE_CONNECTION } from '@/graphql/mutations/delete-connection'
1920
import { TEST_CONNECTION } from '@/graphql/queries/test-connection'
2021
import useFormatMessage from '@/hooks/useFormatMessage'
@@ -46,46 +47,58 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
4647
setTimeout(() => setVerificationVisible(false), 3000)
4748
},
4849
})
49-
const [deleteConnection] = useMutation(DELETE_CONNECTION)
50+
const [deleteConnection, { loading: isDeletingConnection }] =
51+
useMutation(DELETE_CONNECTION)
5052

5153
const formatMessage = useFormatMessage()
5254
const { id, key, formattedData, createdAt, flowCount } = props.connection
5355

5456
const contextButtonRef = useRef<SVGSVGElement | null>(null)
5557
const [anchorEl, setAnchorEl] = useState<SVGSVGElement | null>(null)
58+
const cancelRef = useRef<HTMLButtonElement>(null)
59+
const {
60+
isOpen: isDialogOpen,
61+
onOpen: onDialogOpen,
62+
onClose: onDialogClose,
63+
} = useDisclosure()
5664

5765
const handleClose = () => {
5866
setAnchorEl(null)
5967
}
6068

61-
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current)
62-
const onContextMenuAction = useCallback(
63-
async (
64-
_event: React.MouseEvent<Element, MouseEvent>,
65-
action: { [key: string]: string },
66-
) => {
67-
if (action.type === 'delete') {
68-
await deleteConnection({
69-
variables: { input: { id } },
70-
update: (cache) => {
71-
const connectionCacheId = cache.identify({
72-
__typename: 'Connection',
73-
id,
74-
})
75-
76-
cache.evict({
77-
id: connectionCacheId,
78-
})
79-
},
69+
const onConnectionDelete = useCallback(async () => {
70+
await deleteConnection({
71+
variables: { input: { id } },
72+
update: (cache) => {
73+
const connectionCacheId = cache.identify({
74+
__typename: 'Connection',
75+
id,
8076
})
81-
77+
cache.evict({
78+
id: connectionCacheId,
79+
})
80+
},
81+
onCompleted: () => {
82+
onDialogClose()
8283
toast({
8384
title: 'The connection has been deleted.',
8485
status: 'success',
8586
duration: 3000,
8687
isClosable: true,
8788
position: 'bottom-right',
8889
})
90+
},
91+
})
92+
}, [deleteConnection, id, toast, onDialogClose])
93+
94+
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current)
95+
const onContextMenuAction = useCallback(
96+
async (
97+
_event: React.MouseEvent<Element, MouseEvent>,
98+
action: { [key: string]: string },
99+
) => {
100+
if (action.type === 'delete') {
101+
onDialogOpen()
89102
} else if (action.type === 'test') {
90103
setVerificationVisible(true)
91104
const testResults = await testConnection({
@@ -101,7 +114,7 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
101114
}
102115
}
103116
},
104-
[deleteConnection, id, testConnection, toast],
117+
[id, onDialogOpen, testConnection],
105118
)
106119

107120
const relativeCreatedAt = DateTime.fromMillis(
@@ -195,6 +208,15 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
195208
anchorEl={anchorEl}
196209
/>
197210
)}
211+
<MenuAlertDialog
212+
isDialogOpen={isDialogOpen}
213+
cancelRef={cancelRef}
214+
onDialogClose={onDialogClose}
215+
dialogHeader="Connection"
216+
dialogType="delete"
217+
onClick={onConnectionDelete}
218+
isLoading={isDeletingConnection}
219+
/>
198220
</>
199221
)
200222
}

packages/frontend/src/components/FlowRow/FlowContextMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@ import {
2323
useToast,
2424
} from '@opengovsg/design-system-react'
2525

26+
import MenuAlertDialog, { AlertDialogType } from '@/components/MenuAlertDialog'
2627
import * as URLS from '@/config/urls'
2728
import { DELETE_FLOW } from '@/graphql/mutations/delete-flow'
2829
import { DUPLICATE_FLOW } from '@/graphql/mutations/duplicate-flow'
2930

30-
import MenuAlertDialog, { AlertDialogType } from './MenuAlertDialog'
31-
3231
interface FlowContextMenuProps {
3332
flow: IFlow
3433
}
@@ -184,7 +183,8 @@ export default function FlowContextMenu(props: FlowContextMenuProps) {
184183
isDialogOpen={isDialogOpen}
185184
cancelRef={cancelRef}
186185
onDialogClose={onDialogClose}
187-
type={dialogType}
186+
dialogHeader="Pipe"
187+
dialogType={dialogType}
188188
onClick={dialogType === 'delete' ? onFlowDelete : onFlowDuplicate}
189189
isLoading={dialogType === 'delete' ? isDeletingFlow : isDuplicatingFlow}
190190
/>

packages/frontend/src/components/FlowRow/MenuAlertDialog.tsx renamed to packages/frontend/src/components/MenuAlertDialog/index.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import {
99
import { Button } from '@opengovsg/design-system-react'
1010

1111
export type AlertDialogType = 'delete' | 'duplicate'
12+
export type AlertHeaderType = 'Connection' | 'Pipe' | 'Tile'
1213

1314
interface MenuAlertDialogProps {
1415
isDialogOpen: boolean
1516
cancelRef: React.RefObject<HTMLButtonElement>
1617
onDialogClose: () => void
17-
type: AlertDialogType
18+
dialogType: AlertDialogType
19+
dialogHeader: AlertHeaderType
1820
onClick: () => void
1921
isLoading: boolean
2022
}
@@ -25,12 +27,15 @@ interface AlertDialogContent {
2527
buttonText: string
2628
}
2729

28-
function getAlertDialogContent(type: AlertDialogType): AlertDialogContent {
29-
switch (type) {
30+
function getAlertDialogContent(
31+
dialogHeader: AlertHeaderType,
32+
dialogType: AlertDialogType,
33+
): AlertDialogContent {
34+
switch (dialogType) {
3035
case 'delete':
3136
return {
32-
header: 'Delete Pipe',
33-
body: "Are you sure you want to delete this pipe? You can't undo this action afterwards.",
37+
header: `Delete ${dialogHeader}`,
38+
body: `Are you sure you want to delete this ${dialogHeader?.toLowerCase()}? You can't undo this action afterwards.`,
3439
buttonText: 'Delete',
3540
}
3641
case 'duplicate':
@@ -43,9 +48,19 @@ function getAlertDialogContent(type: AlertDialogType): AlertDialogContent {
4348
}
4449

4550
export default function MenuAlertDialog(props: MenuAlertDialogProps) {
46-
const { isDialogOpen, cancelRef, onDialogClose, type, onClick, isLoading } =
47-
props
48-
const { header, body, buttonText } = getAlertDialogContent(type)
51+
const {
52+
isDialogOpen,
53+
cancelRef,
54+
onDialogClose,
55+
dialogHeader,
56+
dialogType,
57+
onClick,
58+
isLoading,
59+
} = props
60+
const { header, body, buttonText } = getAlertDialogContent(
61+
dialogHeader,
62+
dialogType,
63+
)
4964

5065
return (
5166
<AlertDialog

packages/frontend/src/pages/Tiles/components/TileList.tsx

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ import { MdOutlineRemoveRedEye } from 'react-icons/md'
44
import { Link, useNavigate } from 'react-router-dom'
55
import { useMutation } from '@apollo/client'
66
import {
7-
AlertDialog,
8-
AlertDialogBody,
9-
AlertDialogContent,
10-
AlertDialogFooter,
11-
AlertDialogHeader,
12-
AlertDialogOverlay,
137
Box,
148
Divider,
159
Flex,
@@ -23,13 +17,14 @@ import {
2317
VStack,
2418
} from '@chakra-ui/react'
2519
import {
26-
Button,
2720
IconButton,
2821
Tag,
2922
TagLabel,
3023
TagLeftIcon,
24+
useToast,
3125
} from '@opengovsg/design-system-react'
3226

27+
import MenuAlertDialog from '@/components/MenuAlertDialog'
3328
import * as URLS from '@/config/urls'
3429
import type { TableMetadata } from '@/graphql/__generated__/graphql'
3530
import { DELETE_TABLE } from '@/graphql/mutations/tiles/delete-table'
@@ -38,6 +33,7 @@ import { toPrettyDateString } from '@/helpers/dateTime'
3833

3934
const TileListItem = ({ table }: { table: TableMetadata }): JSX.Element => {
4035
const navigate = useNavigate()
36+
const toast = useToast()
4137
const [deleteTable, { loading: isDeletingTable }] = useMutation(
4238
DELETE_TABLE,
4339
{
@@ -73,7 +69,14 @@ const TileListItem = ({ table }: { table: TableMetadata }): JSX.Element => {
7369
const deleteTile = useCallback(async () => {
7470
await deleteTable()
7571
onDialogClose()
76-
}, [deleteTable, onDialogClose])
72+
toast({
73+
title: 'The tile has been deleted.',
74+
status: 'success',
75+
duration: 3000,
76+
isClosable: true,
77+
position: 'top',
78+
})
79+
}, [deleteTable, onDialogClose, toast])
7780

7881
return (
7982
<Link to={URLS.TILE(table.id)}>
@@ -145,40 +148,15 @@ const TileListItem = ({ table }: { table: TableMetadata }): JSX.Element => {
145148
</Menu>
146149
</Flex>
147150
</Flex>
148-
<AlertDialog
149-
isOpen={isDialogOpen}
150-
leastDestructiveRef={cancelRef}
151-
onClose={onDialogClose}
152-
>
153-
<AlertDialogOverlay>
154-
<AlertDialogContent>
155-
<AlertDialogHeader>Delete Tile</AlertDialogHeader>
156-
157-
<AlertDialogBody>
158-
{"Are you sure? You can't undo this action afterwards."}
159-
</AlertDialogBody>
160-
161-
<AlertDialogFooter>
162-
<Button
163-
ref={cancelRef}
164-
onClick={onDialogClose}
165-
variant="clear"
166-
colorScheme="secondary"
167-
>
168-
Cancel
169-
</Button>
170-
<Button
171-
colorScheme="critical"
172-
onClick={deleteTile}
173-
ml={3}
174-
isLoading={isDeletingTable}
175-
>
176-
Delete
177-
</Button>
178-
</AlertDialogFooter>
179-
</AlertDialogContent>
180-
</AlertDialogOverlay>
181-
</AlertDialog>
151+
<MenuAlertDialog
152+
isDialogOpen={isDialogOpen}
153+
cancelRef={cancelRef}
154+
onDialogClose={onDialogClose}
155+
dialogHeader="Tile"
156+
dialogType="delete"
157+
onClick={deleteTile}
158+
isLoading={isDeletingTable}
159+
/>
182160
</Link>
183161
)
184162
}

0 commit comments

Comments
 (0)