Skip to content

Commit dc5f425

Browse files
committed
feat: add confirmation dialog for destructive actions #4743
1 parent 8fe7a97 commit dc5f425

File tree

3 files changed

+461
-69
lines changed

3 files changed

+461
-69
lines changed

frontend/pages/sites/$siteId/storage/index.js

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef } from 'react';
1+
import React, { useState, useRef, useCallback } from 'react';
22
import { useParams, useSearchParams } from 'react-router-dom';
33
import { useSelector } from 'react-redux';
44
import useFileStorage from '@hooks/useFileStorage';
@@ -10,7 +10,7 @@ import NewFileOrFolder from './NewFileOrFolder';
1010
import FileList from './FileList';
1111
import Pagination from '@shared/Pagination';
1212
import QueryPage from '@shared/layouts/QueryPage';
13-
13+
import Dialog from '@shared/Dialog';
1414
import { currentSite } from '@selectors/site';
1515

1616
function FileStoragePage() {
@@ -60,49 +60,69 @@ function FileStoragePage() {
6060
function scrollToTop() {
6161
return scrollTo.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
6262
}
63-
const handleNavigate = (newPath) => {
64-
const decodedPath = decodeURIComponent(newPath);
65-
// Remove trailing slash if it exists
66-
// eslint-disable-next-line sonarjs/slow-regex
67-
let normalizedPath = `${decodedPath.replace(/\/+$/, '')}`;
63+
const handleNavigate = useCallback(
64+
(newPath) => {
65+
const decodedPath = decodeURIComponent(newPath);
66+
// Remove trailing slash if it exists
67+
// eslint-disable-next-line sonarjs/slow-regex
68+
let normalizedPath = `${decodedPath.replace(/\/+$/, '')}`;
6869

69-
if (normalizedPath !== '/') {
70-
normalizedPath += '/'; // Ensure folders always end with "/"
71-
}
72-
searchParams.delete('details');
73-
searchParams.delete('page');
70+
if (normalizedPath !== '/') {
71+
normalizedPath += '/'; // Ensure folders always end with "/"
72+
}
73+
searchParams.delete('details');
74+
searchParams.delete('page');
7475

75-
setSearchParams((prev) => {
76-
const newParams = new URLSearchParams(prev);
77-
newParams.set('path', normalizedPath);
78-
return newParams;
79-
});
80-
scrollToTop();
81-
};
76+
setSearchParams((prev) => {
77+
const newParams = new URLSearchParams(prev);
78+
newParams.set('path', normalizedPath);
79+
return newParams;
80+
});
81+
scrollToTop();
82+
},
83+
[setSearchParams],
84+
);
8285

83-
const handlePageChange = (newPage) => {
84-
if (newPage === currentPage) return;
85-
setSearchParams((prev) => {
86-
const newParams = new URLSearchParams(prev);
87-
newParams.set('page', newPage);
88-
return newParams;
89-
});
90-
scrollToTop();
86+
const INITIAL_DIALOG_PROPS = {
87+
open: false,
9188
};
89+
const resetModal = useCallback(() => {
90+
setDialogProps(INITIAL_DIALOG_PROPS);
91+
}, []);
9292

93-
const handleViewDetails = (file) => {
94-
setHighlightItem(file);
95-
setSavedScrollPos(window.pageYOffset);
96-
setSearchParams({
97-
...Object.fromEntries(searchParams),
98-
details: file,
99-
});
93+
const [dialogProps, setDialogProps] = useState({
94+
closeHandler: resetModal,
95+
});
10096

101-
// scroll all the way up, this view is short
102-
window.scrollTo({ top: 0 });
103-
};
97+
const handlePageChange = useCallback(
98+
(newPage) => {
99+
if (newPage === currentPage) return;
100+
setSearchParams((prev) => {
101+
const newParams = new URLSearchParams(prev);
102+
newParams.set('page', newPage);
103+
return newParams;
104+
});
105+
scrollToTop();
106+
},
107+
[currentPage, setSearchParams],
108+
);
109+
110+
const handleViewDetails = useCallback(
111+
(file) => {
112+
setHighlightItem(file);
113+
setSavedScrollPos(window.pageYOffset);
114+
setSearchParams({
115+
...Object.fromEntries(searchParams),
116+
details: file,
117+
});
118+
119+
// scroll all the way up, this view is short
120+
window.scrollTo({ top: 0 });
121+
},
122+
[setSearchParams, searchParams],
123+
);
104124

105-
const handleCloseDetails = () => {
125+
const handleCloseDetails = useCallback(() => {
106126
setSearchParams((prev) => {
107127
const newParams = new URLSearchParams(prev);
108128
newParams.delete('details');
@@ -112,41 +132,60 @@ function FileStoragePage() {
112132
setTimeout(() => {
113133
window.scrollTo({ top: savedScrollPos, behavior: 'smooth' });
114134
}, 100);
115-
};
135+
}, [setSearchParams, savedScrollPos]);
116136

117-
const handleSort = (sortKey) => {
118-
const currentSortKey = searchParams.get('sortKey') || DEFAULT_SORT_KEY;
119-
const currentSortOrder = searchParams.get('sortOrder') || DEFAULT_SORT_ORDER;
137+
const handleSort = useCallback(
138+
(sortKey) => {
139+
const currentSortKey = searchParams.get('sortKey') || DEFAULT_SORT_KEY;
140+
const currentSortOrder = searchParams.get('sortOrder') || DEFAULT_SORT_ORDER;
120141

121-
let newSortOrder;
122-
if (currentSortKey === sortKey) {
123-
// Toggle the sort order if the same column is clicked
124-
newSortOrder =
125-
currentSortOrder === DEFAULT_SORT_ORDER ? REVERSE_SORT_ORDER : DEFAULT_SORT_ORDER;
126-
} else {
127-
// Alpha sort is better done in ASC, not DESC
128-
newSortOrder = REVERSE_SORT_ORDER;
129-
}
130-
setSearchParams({
131-
...Object.fromEntries(searchParams),
132-
sortKey,
133-
sortOrder: newSortOrder,
134-
page: 1,
135-
});
136-
};
142+
let newSortOrder;
143+
if (currentSortKey === sortKey) {
144+
// Toggle the sort order if the same column is clicked
145+
newSortOrder =
146+
currentSortOrder === DEFAULT_SORT_ORDER
147+
? REVERSE_SORT_ORDER
148+
: DEFAULT_SORT_ORDER;
149+
} else {
150+
// Alpha sort is better done in ASC, not DESC
151+
newSortOrder = REVERSE_SORT_ORDER;
152+
}
153+
setSearchParams({
154+
...Object.fromEntries(searchParams),
155+
sortKey,
156+
sortOrder: newSortOrder,
157+
page: 1,
158+
});
159+
},
160+
[setSearchParams, searchParams],
161+
);
137162

138-
const handleDelete = async (item) => {
139-
const isFolder = item.type === 'directory';
140-
const confirmMessage = isFolder
141-
? // eslint-disable-next-line sonarjs/slow-regex
163+
const handleDelete = useCallback(
164+
async (item) => {
165+
const isFolder = item.type === 'directory';
166+
const confirmMessage = isFolder
167+
? // eslint-disable-next-line sonarjs/slow-regex
142168
`Are you sure you want to delete the folder "${item.name.replace(/\/+$/, '')}"?
143169
Please check that it does not contain any files.`
144-
: `Are you sure you want to delete the file "${item.name}"?`;
145-
146-
if (!window.confirm(confirmMessage)) return;
147-
148-
await deleteItem(item);
149-
};
170+
: `Are you sure you want to delete the file "${item.name}"?`;
171+
const deleteHandler = async () => {
172+
await deleteItem(item);
173+
resetModal();
174+
};
175+
setDialogProps({
176+
...dialogProps,
177+
open: true,
178+
header: 'Are you sure?',
179+
message: confirmMessage,
180+
primaryButton: 'Yes, I want to delete',
181+
primaryHandler: deleteHandler,
182+
secondaryButton: 'Cancel',
183+
secondaryHandler: resetModal,
184+
closeHandler: resetModal,
185+
});
186+
},
187+
[deleteItem, resetModal],
188+
);
150189

151190
const handleUpload = async (files) => {
152191
await Promise.all(files.map((file) => uploadFile(path, file)));
@@ -155,6 +194,7 @@ function FileStoragePage() {
155194
const handleCreateFolder = async (folderName) => {
156195
await createFolder(path, folderName);
157196
};
197+
158198
return (
159199
<QueryPage
160200
data={fetchedPublicFiles}
@@ -203,7 +243,7 @@ function FileStoragePage() {
203243
message={createFolderSuccess}
204244
/>
205245
)}
206-
246+
<Dialog {...dialogProps} />
207247
<div className="grid-col-12" ref={scrollTo}>
208248
<LocationBar
209249
path={path}

0 commit comments

Comments
 (0)