Skip to content

Commit 8471b75

Browse files
4410-feat(front): Implement Confirmation Prompt for Multiple Record Deletion (twentyhq#4514)
* feat: implement confirmation prompt * feat: remove prop drilling and introduce recoil state * chore: fix eslint issues * feat: set record text according to length of records * chore: fix eslint issues * refactor: made changes according to code review * fix: show delete according to singular and plural records. * fix: eslint issues * feat: show number of selected records * style: fix positioning of actionbar * feat: display ConfirmationModal seperately * chore: remove recoil state and use usestate instead * chore: minor change --------- Co-authored-by: Félix Malfait <[email protected]>
1 parent 0e0f742 commit 8471b75

File tree

5 files changed

+64
-17
lines changed

5 files changed

+64
-17
lines changed

packages/twenty-front/src/modules/object-record/record-action-bar/hooks/useRecordActionBar.tsx

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useMemo } from 'react';
1+
import { useCallback, useMemo, useState } from 'react';
22
import { isNonEmptyString } from '@sniptt/guards';
33
import { useRecoilCallback, useSetRecoilState } from 'recoil';
44

@@ -17,6 +17,7 @@ import {
1717
IconPuzzle,
1818
IconTrash,
1919
} from '@/ui/display/icon';
20+
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
2021
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
2122
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
2223
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
@@ -36,6 +37,8 @@ export const useRecordActionBar = ({
3637
}: useRecordActionBarProps) => {
3738
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
3839
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
40+
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
41+
useState(false);
3942

4043
const { createFavorite, favorites, deleteFavorite } = useFavorites();
4144

@@ -106,10 +109,26 @@ export const useRecordActionBar = ({
106109
const baseActions: ContextMenuEntry[] = useMemo(
107110
() => [
108111
{
109-
label: `Delete (${selectedRecordIds.length})`,
112+
label: 'Delete',
110113
Icon: IconTrash,
111114
accent: 'danger',
112-
onClick: () => handleDeleteClick(),
115+
onClick: () => setIsDeleteRecordsModalOpen(true),
116+
ConfirmationModal: (
117+
<ConfirmationModal
118+
isOpen={isDeleteRecordsModalOpen}
119+
setIsOpen={setIsDeleteRecordsModalOpen}
120+
title={`Delete ${selectedRecordIds.length} ${
121+
selectedRecordIds.length === 1 ? `record` : 'records'
122+
}`}
123+
subtitle={`This action cannot be undone. This will permanently delete ${
124+
selectedRecordIds.length === 1 ? 'this record' : 'these records'
125+
}`}
126+
onConfirmClick={() => handleDeleteClick()}
127+
deleteButtonText={`Delete ${
128+
selectedRecordIds.length > 1 ? 'Records' : 'Record'
129+
}`}
130+
/>
131+
),
113132
},
114133
{
115134
label: `${progress === undefined ? `Export` : `Export (${progress}%)`}`,
@@ -118,7 +137,14 @@ export const useRecordActionBar = ({
118137
onClick: () => download(),
119138
},
120139
],
121-
[handleDeleteClick, download, progress, selectedRecordIds],
140+
[
141+
handleDeleteClick,
142+
download,
143+
progress,
144+
selectedRecordIds,
145+
isDeleteRecordsModalOpen,
146+
setIsDeleteRecordsModalOpen,
147+
],
122148
);
123149

124150
const dataExecuteQuickActionOnmentEnabled = useIsFeatureEnabled(

packages/twenty-front/src/modules/object-record/record-board/action-bar/components/RecordBoardActionBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ export const RecordBoardActionBar = ({
1818
return null;
1919
}
2020

21-
return <ActionBar />;
21+
return <ActionBar selectedIds={selectedRecordIds} />;
2222
};

packages/twenty-front/src/modules/object-record/record-table/action-bar/components/RecordTableActionBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ export const RecordTableActionBar = ({
1616
return null;
1717
}
1818

19-
return <ActionBar />;
19+
return <ActionBar selectedIds={selectedRowIds} />;
2020
};

packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef } from 'react';
1+
import { useRef } from 'react';
22
import styled from '@emotion/styled';
33
import { useRecoilValue } from 'recoil';
44

@@ -7,6 +7,10 @@ import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/cont
77

88
import { ActionBarItem } from './ActionBarItem';
99

10+
type ActionBarProps = {
11+
selectedIds?: string[];
12+
};
13+
1014
const StyledContainerActionBar = styled.div`
1115
align-items: center;
1216
background: ${({ theme }) => theme.background.secondary};
@@ -27,7 +31,15 @@ const StyledContainerActionBar = styled.div`
2731
z-index: 1;
2832
`;
2933

30-
export const ActionBar = () => {
34+
const StyledLabel = styled.div`
35+
color: ${({ theme }) => theme.font.color.tertiary};
36+
font-size: ${({ theme }) => theme.font.size.md};
37+
font-weight: ${({ theme }) => theme.font.weight.medium};
38+
padding-left: ${({ theme }) => theme.spacing(2)};
39+
padding-right: ${({ theme }) => theme.spacing(2)};
40+
`;
41+
42+
export const ActionBar = ({ selectedIds }: ActionBarProps) => {
3143
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
3244
const actionBarEntries = useRecoilValue(actionBarEntriesState);
3345
const wrapperRef = useRef<HTMLDivElement>(null);
@@ -37,14 +49,22 @@ export const ActionBar = () => {
3749
}
3850

3951
return (
40-
<StyledContainerActionBar
41-
data-select-disable
42-
className="action-bar"
43-
ref={wrapperRef}
44-
>
45-
{actionBarEntries.map((item, index) => (
46-
<ActionBarItem key={index} item={item} />
47-
))}
48-
</StyledContainerActionBar>
52+
<>
53+
<StyledContainerActionBar
54+
data-select-disable
55+
className="action-bar"
56+
ref={wrapperRef}
57+
>
58+
{selectedIds && (
59+
<StyledLabel>{selectedIds.length} selected:</StyledLabel>
60+
)}
61+
{actionBarEntries.map((item, index) => (
62+
<ActionBarItem key={index} item={item} />
63+
))}
64+
</StyledContainerActionBar>
65+
<div data-select-disable className="action-bar">
66+
{actionBarEntries[0]?.ConfirmationModal}
67+
</div>
68+
</>
4969
);
5070
};

packages/twenty-front/src/modules/ui/navigation/action-bar/types/ActionBarEntry.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export type ActionBarEntry = {
77
accent?: MenuItemAccent;
88
onClick?: () => void;
99
subActions?: ActionBarEntry[];
10+
ConfirmationModal?: JSX.Element;
1011
};

0 commit comments

Comments
 (0)