Skip to content

Commit 33fc669

Browse files
authored
feat(admin panel): alert for unsaved changes on customize view (#866)
* feat(admin panel): add unsaved changes alert reminder when a user adds or removes a theme/language * feat(admin panel): new icon for customize view * test(admin panel): customize - unsaved changes reminder tests
1 parent d5d9010 commit 33fc669

File tree

3 files changed

+63
-12
lines changed

3 files changed

+63
-12
lines changed

src/oneid/oneid-control-panel/src/components/DrawerNav.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@mui/material';
99
import { Link, useLocation } from 'react-router-dom';
1010
import { uniqueId } from 'lodash';
11-
import { MoveToInbox, Mail, People } from '@mui/icons-material';
11+
import { MoveToInbox, People, Palette } from '@mui/icons-material';
1212
import { ROUTE_PATH, USER_ROOT_PATH } from '../utils/constants';
1313
import { ENV } from '../utils/env';
1414
import logoPagoPa from '../assets/logo_pagopa.png';
@@ -25,7 +25,7 @@ const navData = (clientId?: string, isAuthenticated?: boolean) => [
2525
},
2626
{
2727
name: 'Customize UI',
28-
icon: <Mail fontSize="inherit" />,
28+
icon: <Palette fontSize="inherit" />,
2929
to: ROUTE_PATH.CUSTOMIZE,
3030
isVisible: !!clientId && isAuthenticated,
3131
matchPath: (pathname: string) => pathname.startsWith(ROUTE_PATH.CUSTOMIZE),

src/oneid/oneid-control-panel/src/pages/Customize/Customize.test.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable sonarjs/no-duplicate-string */
22
import React from 'react';
3-
import { render, screen, within } from '@testing-library/react';
3+
import { render, screen, within, waitFor } from '@testing-library/react';
44
import userEvent from '@testing-library/user-event';
55
import { describe, it, expect, vi } from 'vitest';
66
import { Customize } from './Customize';
@@ -282,4 +282,39 @@ describe('Refactored ReservedAreaForm Component', () => {
282282
expect(screen.queryByText('enterprise')).not.toBeInTheDocument();
283283
expect(screen.getByTestId('theme-select')).toHaveTextContent('default');
284284
});
285+
286+
it('should show and hide the unsaved changes reminder', async () => {
287+
const user = userEvent.setup();
288+
render(<Customize />, { wrapper: createWrapper() });
289+
290+
expect(
291+
screen.queryByText(/You have unsaved changes/i)
292+
).not.toBeInTheDocument();
293+
294+
await user.click(screen.getByRole('button', { name: /Add Language/i }));
295+
296+
// select a new language
297+
const languageSelect = screen.getByTestId('language-selector');
298+
const button = within(languageSelect).getByRole('combobox', {
299+
hidden: true,
300+
});
301+
await user.click(button);
302+
await user.click(screen.getByRole('option', { name: /deutsch/i }));
303+
304+
await user.click(screen.getByRole('button', { name: 'Add' }));
305+
306+
// the reminder is now visible
307+
expect(screen.getByText(/You have unsaved changes/i)).toBeInTheDocument();
308+
309+
const saveButton = screen.getByRole('button', { name: /Save Changes/i });
310+
expect(saveButton).not.toBeDisabled();
311+
await user.click(saveButton);
312+
313+
// reminder is hidden again
314+
await waitFor(() => {
315+
expect(
316+
screen.queryByText(/You have unsaved changes/i)
317+
).not.toBeInTheDocument();
318+
});
319+
});
285320
});

src/oneid/oneid-control-panel/src/pages/Customize/Customize.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
DialogContentText,
1717
DialogTitle,
1818
Divider,
19+
Alert,
20+
Fade,
1921
} from '@mui/material';
2022
import SaveIcon from '@mui/icons-material/Save';
2123
import {
@@ -67,6 +69,9 @@ function CustomizeDashboard() {
6769
const [errorUi, setErrorUi] = useState<ClientErrors | null>(null);
6870
const [notify, setNotify] = useState<Notify>({ open: false });
6971

72+
const [unsavedChangesReminder, setUnsavedChangesReminder] =
73+
useState<boolean>(false);
74+
7075
useEffect(() => {
7176
if (isFetched) {
7277
setClientData(
@@ -142,6 +147,7 @@ function CustomizeDashboard() {
142147
data: clientData as ClientWithoutSensitiveData,
143148
clientId,
144149
});
150+
setUnsavedChangesReminder(false);
145151
};
146152

147153
const isFormValid = () => {
@@ -252,6 +258,7 @@ function CustomizeDashboard() {
252258

253259
const handleAddLanguage = () => {
254260
if (!languageToAdd) return;
261+
setUnsavedChangesReminder(true);
255262
setClientData((prev) => {
256263
if (!prev || !prev.localizedContentMap) return prev;
257264
const newThemeContent = {
@@ -272,6 +279,7 @@ function CustomizeDashboard() {
272279

273280
const handleRemoveLanguage = (langToRemove: string) => {
274281
if (!activeTheme || Object.keys(activeTheme).length <= 1) return;
282+
setUnsavedChangesReminder(true);
275283
setClientData((prev) => {
276284
if (!prev || !prev.localizedContentMap) return prev;
277285
const themeContent = prev.localizedContentMap[activeThemeKey] as Record<
@@ -319,6 +327,7 @@ function CustomizeDashboard() {
319327
return;
320328
}
321329

330+
setUnsavedChangesReminder(true);
322331
setClientData((prev) => {
323332
if (!prev) prev = {} as ClientWithoutSensitiveData; // Ensure prev is always defined
324333
// Always set backButtonEnabled explicitly and ensure it's always boolean
@@ -350,6 +359,7 @@ function CustomizeDashboard() {
350359
setConfirmModalOpen(false);
351360
return;
352361
}
362+
setUnsavedChangesReminder(true);
353363
setClientData((prev) => {
354364
if (!prev?.localizedContentMap) return prev;
355365
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -410,15 +420,21 @@ function CustomizeDashboard() {
410420
)}
411421
</ContentBox>
412422

413-
<Button
414-
sx={{ mt: 3, mb: 4 }}
415-
variant="contained"
416-
startIcon={<SaveIcon />}
417-
onClick={handleSubmit}
418-
disabled={isUpdating || !isFormValid()}
419-
>
420-
{isUpdating ? 'Saving...' : 'Save Changes'}
421-
</Button>
423+
<Box sx={{ mt: 3, mb: 4 }}>
424+
<Fade in={unsavedChangesReminder} timeout={500} unmountOnExit>
425+
<Alert severity="info" sx={{ mb: 2 }}>
426+
You have unsaved changes. Click ‘Save Changes’ to apply them.
427+
</Alert>
428+
</Fade>
429+
<Button
430+
variant="contained"
431+
startIcon={<SaveIcon />}
432+
onClick={handleSubmit}
433+
disabled={isUpdating || !isFormValid()}
434+
>
435+
{isUpdating ? 'Saving...' : 'Save Changes'}
436+
</Button>
437+
</Box>
422438

423439
{/* TODO swith to useModal hook */}
424440
{/* Modals are unchanged but their handlers are updated */}

0 commit comments

Comments
 (0)