diff --git a/CHANGELOG.md b/CHANGELOG.md index 248c2eb13f..3f89ebeaab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package pn-frontend + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package pn-frontend diff --git a/lerna.json b/lerna.json index f85c280421..af92cdeea7 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.11.0", + "version": "2.11.1", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/pn-commons/CHANGELOG.md b/packages/pn-commons/CHANGELOG.md index a63aed7bb4..cc79cfc7b8 100644 --- a/packages/pn-commons/CHANGELOG.md +++ b/packages/pn-commons/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package @pagopa-pn/pn-commons + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package @pagopa-pn/pn-commons diff --git a/packages/pn-commons/package.json b/packages/pn-commons/package.json index 4ea0ebf6d6..7a8ccf4a63 100644 --- a/packages/pn-commons/package.json +++ b/packages/pn-commons/package.json @@ -1,6 +1,6 @@ { "name": "@pagopa-pn/pn-commons", - "version": "2.11.0", + "version": "2.11.1", "private": true, "main": "./src/index.ts", "dependencies": { @@ -65,7 +65,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitest/coverage-v8": "^1.6.1", "css-mediaquery": "^0.1.2", "eslint": "7.11.0", "eslint-config-prettier": "^8.10.0", @@ -78,7 +78,7 @@ "prettier": "^2.8.8", "sonarqube-scanner": "^3.3.0", "vite": "^5.1.4", - "vitest": "1.3.1", + "vitest": "^1.6.1", "vitest-sonar-reporter": "^2.0.0" } } diff --git a/packages/pn-commons/src/components/CustomMobileDialog/CustomMobileDialogContent.tsx b/packages/pn-commons/src/components/CustomMobileDialog/CustomMobileDialogContent.tsx index 64f5092992..81880b03d1 100644 --- a/packages/pn-commons/src/components/CustomMobileDialog/CustomMobileDialogContent.tsx +++ b/packages/pn-commons/src/components/CustomMobileDialog/CustomMobileDialogContent.tsx @@ -1,10 +1,11 @@ import { ReactElement, ReactNode, Ref, forwardRef, useImperativeHandle } from 'react'; import CloseIcon from '@mui/icons-material/Close'; -import { Dialog, DialogTitle, Grid, Slide, Typography } from '@mui/material'; +import { Dialog, DialogTitle, Grid, IconButton, Slide, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { TransitionProps } from '@mui/material/transitions'; +import { getLocalizedOrDefaultLabel } from '../../utility/localization.utility'; import { useCustomMobileDialogContext } from './CustomMobileDialog.context'; type Props = { @@ -82,17 +83,19 @@ const CustomMobileDialogContent = forwardRef<{ toggleOpen: () => void }, Props>( - + aria-label={getLocalizedOrDefaultLabel('common', 'button.close')} + > + + diff --git a/packages/pn-commons/src/components/CustomTagGroup/CustomTagGroup.tsx b/packages/pn-commons/src/components/CustomTagGroup/CustomTagGroup.tsx index 188ab61c29..dfce81ee87 100644 --- a/packages/pn-commons/src/components/CustomTagGroup/CustomTagGroup.tsx +++ b/packages/pn-commons/src/components/CustomTagGroup/CustomTagGroup.tsx @@ -21,9 +21,15 @@ const TagIndicator: React.FC<{ arrayChildren: Array; visibleItems: number; dataTestId: string; -}> = ({ boxProps, arrayChildren, visibleItems, dataTestId }) => ( - - + ariaLabel?: string; +}> = ({ boxProps, arrayChildren, visibleItems, dataTestId, ariaLabel }) => ( + + ); @@ -48,14 +54,18 @@ const CustomTagGroup: React.FC = ({ onOpen={onOpen} tooltipContent={<>{arrayChildren.slice(visibleItems).map((c) => c)}} > -
+ c.props?.children?.props?.value) // + .filter(Boolean) + .join(',')} /> -
+
)} {disableTooltip && ( diff --git a/packages/pn-commons/src/components/CustomTagGroup/__test__/CustomTagGroup.test.tsx b/packages/pn-commons/src/components/CustomTagGroup/__test__/CustomTagGroup.test.tsx index 182a14ef9e..e878eecc58 100644 --- a/packages/pn-commons/src/components/CustomTagGroup/__test__/CustomTagGroup.test.tsx +++ b/packages/pn-commons/src/components/CustomTagGroup/__test__/CustomTagGroup.test.tsx @@ -1,13 +1,18 @@ import { vi } from 'vitest'; import { Box } from '@mui/material'; +import { Tag } from '@pagopa/mui-italia'; import { fireEvent, render, screen, waitFor } from '../../../test-utils'; import CustomTagGroup from '../CustomTagGroup'; describe('CustomTagGroup component', () => { const tagsArray = ['mock-tag-1', 'mock-tag-2', 'mock-tag-3', 'mock-tag-4']; - const tags = tagsArray.map((v, i) => {v}); + const tags = tagsArray.map((v, i) => ( + + + + )); const mockCallbackFn = vi.fn(); beforeEach(() => { @@ -57,4 +62,15 @@ describe('CustomTagGroup component', () => { expect(tooltip).not.toBeInTheDocument(); expect(mockCallbackFn).toBeCalledTimes(0); }); + + it('renders component with limited 1 tags with aria-label', () => { + const { getByTestId } = render( + + {tags} + + ); + const tooltipIndicator = getByTestId('custom-tooltip-indicator'); + expect(tooltipIndicator).toBeInTheDocument(); + expect(tooltipIndicator).toHaveAttribute('aria-label', 'mock-tag-3,mock-tag-4'); + }); }); diff --git a/packages/pn-commons/src/components/Data/PnTable/PnTableHeaderCell.tsx b/packages/pn-commons/src/components/Data/PnTable/PnTableHeaderCell.tsx index c9d54df47e..6405ae6215 100644 --- a/packages/pn-commons/src/components/Data/PnTable/PnTableHeaderCell.tsx +++ b/packages/pn-commons/src/components/Data/PnTable/PnTableHeaderCell.tsx @@ -49,6 +49,13 @@ const PnTableHeaderCell = ({ direction={sort.orderBy === columnId ? sort.order : 'asc'} onClick={sortHandler(columnId)} data-testid={testId ? `${testId}.sort.${columnId.toString()}` : null} + sx={{ + '&:focus-visible': { + borderRadius: '2px', + outlineOffset: '4px', + outline: '2px solid currentColor' + } + }} > {children} {sort.orderBy === columnId && ( diff --git a/packages/pn-commons/src/components/InactivityHandler.tsx b/packages/pn-commons/src/components/InactivityHandler.tsx index 61c63dc4f6..f6b8c023a8 100644 --- a/packages/pn-commons/src/components/InactivityHandler.tsx +++ b/packages/pn-commons/src/components/InactivityHandler.tsx @@ -1,4 +1,11 @@ -import { Fragment, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; + +import { Button, DialogContentText, DialogTitle } from '@mui/material'; + +import { getLocalizedOrDefaultLabel } from '../utility/localization.utility'; +import PnDialog from './PnDialog/PnDialog'; +import PnDialogActions from './PnDialog/PnDialogActions'; +import PnDialogContent from './PnDialog/PnDialogContent'; type Props = { /** Inactivity timer (in milliseconds), if 0 the inactivity timer is disabled */ @@ -8,43 +15,66 @@ type Props = { children?: React.ReactNode; }; -const InactivityHandler: React.FC = ({ inactivityTimer, children, onTimerExpired }) => { +const InactivityHandler: React.FC = ({ inactivityTimer, onTimerExpired, children }) => { const [initTimeout, setInitTimeout] = useState(true); - + const [openModal, setOpenModal] = useState(false); const resetTimer = () => setInitTimeout(!initTimeout); - const initListeners = () => { - window.addEventListener('mousemove', resetTimer); - window.addEventListener('scroll', resetTimer); - window.addEventListener('keydown', resetTimer); - }; - - const cleanUpListeners = () => { - window.removeEventListener('mousemove', resetTimer); - window.removeEventListener('scroll', resetTimer); - window.removeEventListener('keydown', resetTimer); - }; - // init timer useEffect(() => { if (inactivityTimer) { - // init listeners - initListeners(); + // this is the timer after wich the inactivity modal is shown + const inactivityWarningTimer = inactivityTimer - 30 * 1000; // init timer const timer = setTimeout(() => { - cleanUpListeners(); onTimerExpired(); }, inactivityTimer); + + const warningTimer = setTimeout(() => { + setOpenModal(true); + }, inactivityWarningTimer); + // cleanup function return () => { + setOpenModal(false); clearTimeout(timer); - cleanUpListeners(); + clearTimeout(warningTimer); }; } return () => {}; }, [initTimeout]); - return {children}; + return ( + <> + + + {getLocalizedOrDefaultLabel('common', 'inactivity.title')} + + + + {getLocalizedOrDefaultLabel('common', 'inactivity.body')} + + + + + + + {children} + + ); }; export default InactivityHandler; diff --git a/packages/pn-commons/src/components/SessionModal.tsx b/packages/pn-commons/src/components/SessionModal.tsx index 2e44d3ffbf..7d7a1b8717 100644 --- a/packages/pn-commons/src/components/SessionModal.tsx +++ b/packages/pn-commons/src/components/SessionModal.tsx @@ -75,7 +75,7 @@ const SessionModal: React.FC = ({ {onConfirm && ( - diff --git a/packages/pn-commons/src/components/__test__/InactivityHandler.test.tsx b/packages/pn-commons/src/components/__test__/InactivityHandler.test.tsx index 5db76205b4..bdec93615d 100644 --- a/packages/pn-commons/src/components/__test__/InactivityHandler.test.tsx +++ b/packages/pn-commons/src/components/__test__/InactivityHandler.test.tsx @@ -1,10 +1,10 @@ import { vi } from 'vitest'; -import { fireEvent, render, waitFor } from '../../test-utils'; +import { act, fireEvent, render, within } from '../../test-utils'; import InactivityHandler from '../InactivityHandler'; const timerExpiredHandler = vi.fn(); -const inactivityTimer = 2000; +const inactivityTimer = 60 * 1000; const Component = () => ( @@ -13,31 +13,35 @@ const Component = () => ( ); describe('InactivityHandler Component', () => { + beforeAll(() => { + vi.useFakeTimers(); + }); + beforeEach(() => { vi.resetAllMocks(); vi.clearAllMocks(); }); + afterAll(() => { + vi.useRealTimers(); + }); + it('test inactivity', async () => { // render component - render(); - await waitFor( - () => { - expect(timerExpiredHandler).toBeCalledTimes(1); - }, - { timeout: inactivityTimer + 1000 } - ); + const { container } = render(); + expect(container).toHaveTextContent('Mocked children'); + await act(() => vi.advanceTimersByTime(inactivityTimer)); + expect(timerExpiredHandler).toHaveBeenCalledTimes(1); }); it('test user interaction', async () => { // render component - const result = render(); - fireEvent.mouseMove(result.container); - await waitFor( - () => { - expect(timerExpiredHandler).toBeCalledTimes(0); - }, - { timeout: inactivityTimer + 1000 } - ); + const { getByTestId } = render(); + await act(() => vi.advanceTimersByTime(inactivityTimer - 30 * 1000)); + const inactivityDialog = getByTestId('inactivity-modal'); + expect(inactivityDialog).toBeInTheDocument(); + const inactivityButton = within(inactivityDialog).getByTestId('inactivity-button'); + fireEvent.click(inactivityButton); + expect(timerExpiredHandler).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/pn-commons/src/utility/costants.tsx b/packages/pn-commons/src/utility/costants.tsx index c38886d7d7..765ed24128 100644 --- a/packages/pn-commons/src/utility/costants.tsx +++ b/packages/pn-commons/src/utility/costants.tsx @@ -15,7 +15,7 @@ export const LANGUAGES: Languages = { export const PRIVACY_LINK_RELATIVE_PATH = '/informativa-privacy'; export const TOS_LINK_RELATIVE_PATH = '/termini-di-servizio'; -const ACCESSIBILITY_LINK = 'https://form.agid.gov.it/view/5bd4c880-772d-11ef-b5af-e9d3099e743c'; +const ACCESSIBILITY_LINK = 'https://form.agid.gov.it/view/978876c0-df2d-11ef-8637-9f856ac3da10'; const getFooterLinkLabels = ( link: string, diff --git a/packages/pn-data-viz/CHANGELOG.md b/packages/pn-data-viz/CHANGELOG.md index 31a97b3454..8a38b5a085 100644 --- a/packages/pn-data-viz/CHANGELOG.md +++ b/packages/pn-data-viz/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package @pagopa-pn/pn-data-viz + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package @pagopa-pn/pn-data-viz diff --git a/packages/pn-data-viz/package.json b/packages/pn-data-viz/package.json index b500f66066..208f38bf96 100644 --- a/packages/pn-data-viz/package.json +++ b/packages/pn-data-viz/package.json @@ -1,6 +1,6 @@ { "name": "@pagopa-pn/pn-data-viz", - "version": "2.11.0", + "version": "2.11.1", "private": true, "description": "SEND component library for data visualization", "keywords": [ @@ -33,7 +33,7 @@ "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-sonarjs": "^0.10.0", "vite": "^5.1.4", - "vitest": "1.3.1", + "vitest": "^1.6.1", "vitest-canvas-mock": "^0.3.3", "vitest-sonar-reporter": "^2.0.0" } diff --git a/packages/pn-pa-webapp/CHANGELOG.md b/packages/pn-pa-webapp/CHANGELOG.md index 5050e28846..91466f126a 100644 --- a/packages/pn-pa-webapp/CHANGELOG.md +++ b/packages/pn-pa-webapp/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package @pagopa-pn/pn-pa-webapp + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package @pagopa-pn/pn-pa-webapp diff --git a/packages/pn-pa-webapp/package.json b/packages/pn-pa-webapp/package.json index 5714ca126f..f392c6ab8d 100644 --- a/packages/pn-pa-webapp/package.json +++ b/packages/pn-pa-webapp/package.json @@ -1,7 +1,7 @@ { "name": "@pagopa-pn/pn-pa-webapp", "description": "Backoffice di SEND per la Pubblica Amministrazione", - "version": "2.11.0", + "version": "2.11.1", "private": true, "dependencies": { "@emotion/react": "^11.11.1", @@ -77,7 +77,7 @@ "@typescript-eslint/parser": "^6.7.3", "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitest/coverage-v8": "^1.6.1", "axios-mock-adapter": "^1.22.0", "eslint": "7.11.0", "eslint-config-prettier": "^8.10.0", @@ -90,7 +90,7 @@ "prettier": "^2.8.8", "sonarqube-scanner": "^3.3.0", "vite": "^5.1.4", - "vitest": "^1.3.1", + "vitest": "^1.6.1", "vitest-canvas-mock": "^0.3.3", "vitest-sonar-reporter": "^2.0.0" } diff --git a/packages/pn-pa-webapp/public/locales/it/apikeys.json b/packages/pn-pa-webapp/public/locales/it/apikeys.json index 5713ef3604..575f73a0e5 100644 --- a/packages/pn-pa-webapp/public/locales/it/apikeys.json +++ b/packages/pn-pa-webapp/public/locales/it/apikeys.json @@ -41,7 +41,7 @@ "status": "Stato" }, "context-menu": { - "title": "Opzioni su API Key", + "title": "Menu azioni su API Key", "enable": "Attiva", "block": "Blocca", "rotate": "Ruota", @@ -86,5 +86,6 @@ "continue-button": "Continua", "required-fields": "Campi obbligatori", "no-groups": "Nessun gruppo disponibile" - } + }, + "api-key-succesfully-deleted": "Chiave pubblica eliminata con successo." } diff --git a/packages/pn-pa-webapp/public/locales/it/common.json b/packages/pn-pa-webapp/public/locales/it/common.json index bbb3fd06bb..8e4271c9f3 100644 --- a/packages/pn-pa-webapp/public/locales/it/common.json +++ b/packages/pn-pa-webapp/public/locales/it/common.json @@ -24,7 +24,8 @@ "logout": "Esci", "pago-pa-link": "Sito di PagoPA S.p.A.", "reserved-area": "Area Riservata", - "notification-platform": "SEND - Servizio Notifiche Digitali" + "notification-platform": "SEND - Servizio Notifiche Digitali", + "logout-message": "Confermi di voler uscire da SEND?" }, "tos": { "title": "SEND - Servizio Notifiche Digitali", @@ -167,5 +168,10 @@ "downtime_language_banner": { "message": "Il testo originario di questi documenti è in lingua italiana. In caso di contrasto tra la versione italiana e le traduzioni in altre lingue, prevarranno il significato e le condizioni della lingua italiana.", "link": "Consulta le traduzioni" + }, + "inactivity": { + "title": "Vuoi davvero uscire da SEND?", + "body": "La disconnessione è in corso. Se hai cambiato idea premi “Mantieni l’accesso” entro pochi secondi.", + "action": "Mantieni l’accesso" } } diff --git a/packages/pn-pa-webapp/src/App.tsx b/packages/pn-pa-webapp/src/App.tsx index 52650e5706..e59f96a376 100644 --- a/packages/pn-pa-webapp/src/App.tsx +++ b/packages/pn-pa-webapp/src/App.tsx @@ -8,13 +8,15 @@ import ErrorIcon from '@mui/icons-material/Error'; import HelpIcon from '@mui/icons-material/Help'; import StatisticsIcon from '@mui/icons-material/ShowChart'; import VpnKey from '@mui/icons-material/VpnKey'; -import { Box } from '@mui/material'; +import { Box, Button, DialogTitle } from '@mui/material'; import { APP_VERSION, AppMessage, AppResponseMessage, Layout, LoadingOverlay, + PnDialog, + PnDialogActions, ResponseEventDispatcher, SideMenu, SideMenuItem, @@ -33,7 +35,6 @@ import { getAdditionalLanguages, getInstitutions, getProductsOfInstitution, - logout, } from './redux/auth/actions'; import { useAppDispatch, useAppSelector } from './redux/hooks'; import { RootState } from './redux/store'; @@ -41,6 +42,7 @@ import { getConfiguration } from './services/configuration.service'; import { PAAppErrorFactory } from './utility/AppError/PAAppErrorFactory'; import './utility/onetrust'; import { getMenuItems } from './utility/role.utility'; +import { goToSelfcareLogin } from './navigation/navigation.utility'; // Cfr. PN-6096 // -------------------- @@ -70,6 +72,7 @@ const ActualApp = () => { // TODO check if it can exist more than one role on user const role = loggedUserOrganizationParty?.roles[0]; const idOrganization = loggedUserOrganizationParty?.id; + const [openModal, setOpenModal] = useState(false); const { tosConsent, privacyConsent } = useAppSelector((state: RootState) => state.userState); const currentStatus = useAppSelector((state: RootState) => state.appStatus.currentStatus); const { SELFCARE_BASE_URL, SELFCARE_SEND_PROD_ID, IS_STATISTICS_ENABLED } = getConfiguration(); @@ -88,21 +91,21 @@ const ActualApp = () => { const productsList = products.length > 0 ? [ - reservedArea, - ...products.map((product) => ({ - ...product, - productUrl: `${product.productUrl}&lang=${i18n.language}`, - })), - ] + reservedArea, + ...products.map((product) => ({ + ...product, + productUrl: `${product.productUrl}&lang=${i18n.language}`, + })), + ] : [ - reservedArea, - { - id: '0', - title: t('header.notification-platform'), - productUrl: '', - linkType: 'internal' as LinkType, - }, - ]; + reservedArea, + { + id: '0', + title: t('header.notification-platform'), + productUrl: '', + linkType: 'internal' as LinkType, + }, + ]; const productId = products.length > 0 ? SELFCARE_SEND_PROD_ID : '0'; const institutionsList = institutions.map((institution) => ({ @@ -200,7 +203,7 @@ const ActualApp = () => { const isPrivacyPage = path[1] === 'privacy-tos'; const handleLogout = () => { - void dispatch(logout()); + setOpenModal(true); }; const handleAssistanceClick = () => { @@ -255,6 +258,31 @@ const ActualApp = () => { onAssistanceClick={handleAssistanceClick} isLogged={!!sessionToken} > + + {t("header.logout-message")} + + + + + + diff --git a/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx b/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx index 9d0599606e..a9201f7ebf 100644 --- a/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx +++ b/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx @@ -45,6 +45,11 @@ const ApiKeyContextMenu = ({ setAnchorEl(null); }; + const handleModalClickAndClose = (view: ModalApiKeyView) => { + handleClose(); // a11y: force close menu for keyboard navigation + handleModalClick(view, apiKeyId); + }; + return ( @@ -66,12 +71,14 @@ const ApiKeyContextMenu = ({ open={open} onClose={handleClose} onClick={handleClose} - PaperProps={{ - elevation: 0, - sx: { - overflow: 'visible', - filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', - mt: 1.5, + slotProps={{ + paper: { + elevation: 0, + sx: { + overflow: 'visible', + filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', + mt: 1.5, + }, }, }} transformOrigin={{ horizontal: 'right', vertical: 'top' }} @@ -81,7 +88,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.VIEW, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.VIEW)} > {t('context-menu.view')} @@ -90,7 +97,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.ROTATE, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.ROTATE)} > {t('context-menu.rotate')} @@ -99,7 +106,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.BLOCK, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.BLOCK)} > {t('context-menu.block')} @@ -108,7 +115,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.DELETE, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.DELETE)} > {t('context-menu.delete')} @@ -117,7 +124,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.ENABLE, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.ENABLE)} > {t('context-menu.enable')} @@ -126,7 +133,7 @@ const ApiKeyContextMenu = ({ handleModalClick(ModalApiKeyView.VIEW_GROUPS_ID, apiKeyId)} + onClick={() => handleModalClickAndClose(ModalApiKeyView.VIEW_GROUPS_ID)} > {t('context-menu.view-groups-id')} diff --git a/packages/pn-pa-webapp/src/navigation/SessionGuard.tsx b/packages/pn-pa-webapp/src/navigation/SessionGuard.tsx index 7a814fe9c7..0d3835afe8 100644 --- a/packages/pn-pa-webapp/src/navigation/SessionGuard.tsx +++ b/packages/pn-pa-webapp/src/navigation/SessionGuard.tsx @@ -54,7 +54,6 @@ const SessionGuardRender = () => { const { isUnauthorizedUser, messageUnauthorizedUser, isClosedSession } = useAppSelector( (state: RootState) => state.userState ); - const dispatch = useAppDispatch(); const { t } = useTranslation(['common']); const { hasApiErrors } = useErrors(); @@ -89,7 +88,10 @@ const SessionGuardRender = () => { ) : ( dispatch(logout())} + onTimerExpired={() => { + sessionStorage.clear(); + goToSelfcareLogin(); + }} > diff --git a/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx b/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx index 7175aee367..a263efb7b5 100644 --- a/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx +++ b/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx @@ -9,6 +9,7 @@ import { CustomPagination, PaginationData, TitleBox, + appStateActions, calculatePages, useIsMobile, } from '@pagopa-pn/pn-commons'; @@ -146,7 +147,16 @@ const ApiKeys = () => { const apiKeyDeleted = (apiKeyId: string) => { handleCloseModal(); - void dispatch(deleteApiKey(apiKeyId)).then(fetchApiKeys); + void dispatch(deleteApiKey(apiKeyId)) + .then(() => + dispatch( + appStateActions.addSuccess({ + title: '', + message: t('api-key-succesfully-deleted'), + }) + ) + ) + .then(fetchApiKeys); }; // Pagination handlers diff --git a/packages/pn-personafisica-login/CHANGELOG.md b/packages/pn-personafisica-login/CHANGELOG.md index de4f53a5d3..e3191288fc 100644 --- a/packages/pn-personafisica-login/CHANGELOG.md +++ b/packages/pn-personafisica-login/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-login + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package @pagopa-pn/pn-personafisica-login diff --git a/packages/pn-personafisica-login/package.json b/packages/pn-personafisica-login/package.json index 32ade8a655..61e0a85d9f 100644 --- a/packages/pn-personafisica-login/package.json +++ b/packages/pn-personafisica-login/package.json @@ -1,7 +1,7 @@ { "name": "@pagopa-pn/pn-personafisica-login", "description": "Pagina di login di SEND per il cittadino", - "version": "2.11.0", + "version": "2.11.1", "private": true, "homepage": "auth", "dependencies": { @@ -67,7 +67,7 @@ "@typescript-eslint/parser": "^6.7.3", "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitest/coverage-v8": "^1.6.1", "eslint": "7.11.0", "eslint-config-prettier": "^8.10.0", "eslint-plugin-functional": "^3.7.2", @@ -79,7 +79,7 @@ "prettier": "^2.8.8", "sonarqube-scanner": "^3.3.0", "vite": "^5.1.4", - "vitest": "^1.3.1", + "vitest": "^1.6.1", "vitest-sonar-reporter": "^2.0.0" } } diff --git a/packages/pn-personafisica-login/src/pages/login/Login.tsx b/packages/pn-personafisica-login/src/pages/login/Login.tsx index acbb2bd839..cf44eed5e0 100644 --- a/packages/pn-personafisica-login/src/pages/login/Login.tsx +++ b/packages/pn-personafisica-login/src/pages/login/Login.tsx @@ -59,10 +59,6 @@ const Login = () => { }); }; - if (showIDPS) { - return setShowIDPS(false)} />; - } - const changeLanguageHandler = async (langCode: string) => { await i18n.changeLanguage(langCode); }; @@ -72,6 +68,10 @@ const Login = () => { window.location.href = `mailto:${PAGOPA_HELP_EMAIL}`; }; + const closeIDPS = () => { + setShowIDPS(false); + }; + return ( { + ); }; diff --git a/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx b/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx index 3e71bc7195..44e63dc30f 100644 --- a/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx +++ b/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx @@ -1,7 +1,7 @@ import { Trans, useTranslation } from 'react-i18next'; import ClearOutlinedIcon from '@mui/icons-material/ClearOutlined'; -import { IconButton } from '@mui/material'; +import { Dialog, DialogContent, IconButton } from '@mui/material'; import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; import Icon from '@mui/material/Icon'; @@ -15,7 +15,7 @@ import { IdentityProvider, getIDPS } from '../../utility/IDPS'; import PFLoginEventStrategyFactory from '../../utility/MixpanelUtils/PFLoginEventStrategyFactory'; import { shuffleList } from '../../utility/utils'; -const SpidSelect = ({ onBack }: { onBack: () => void }) => { +const SpidSelect = ({ show, onClose }: { show: boolean; onClose: () => void }) => { const { URL_API_LOGIN, SPID_TEST_ENV_ENABLED, SPID_VALIDATOR_ENV_ENABLED } = getConfiguration(); const { t } = useTranslation(['login']); const IDPS = getIDPS(SPID_TEST_ENV_ENABLED, SPID_VALIDATOR_ENV_ENABLED); @@ -35,102 +35,108 @@ const SpidSelect = ({ onBack }: { onBack: () => void }) => { }; return ( - - - - - - - - - - - - - - - {t('spidSelect.title')} - - - - - {shuffledIDPS.map((IDP, i) => ( - + + + + + + + - - - ))} + + + ))} + + + + + + + {'spidSelect.hintText'} + + + + - - - - - {'spidSelect.hintText'} - - - - - - - + + ); }; diff --git a/packages/pn-personafisica-login/src/pages/login/__test__/Login.test.tsx b/packages/pn-personafisica-login/src/pages/login/__test__/Login.test.tsx index e1d5f820ad..8e44c0c0c9 100644 --- a/packages/pn-personafisica-login/src/pages/login/__test__/Login.test.tsx +++ b/packages/pn-personafisica-login/src/pages/login/__test__/Login.test.tsx @@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom'; import { vi } from 'vitest'; import { AppRouteParams } from '@pagopa-pn/pn-commons'; -import { getById, queryById } from '@pagopa-pn/pn-commons/src/test-utils'; +import { getById, queryById, waitFor } from '@pagopa-pn/pn-commons/src/test-utils'; import { fireEvent, render } from '../../../__test__/test-utils'; import { getConfiguration } from '../../../services/configuration.service'; @@ -72,7 +72,7 @@ describe('test login page', () => { expect(storageAarOps.read()).toBe('fake-aar-token'); }); - it('select spid login', () => { + it('select spid login', async () => { const { container } = render( @@ -80,7 +80,7 @@ describe('test login page', () => { ); const spidButton = getById(container, 'spidButton'); fireEvent.click(spidButton!); - const spidSelect = getById(container, 'spidSelect'); + const spidSelect = await waitFor(() => document.querySelector('#spidSelect')); expect(spidSelect).toBeInTheDocument(); }); diff --git a/packages/pn-personafisica-login/src/pages/login/__test__/SpidSelect.test.tsx b/packages/pn-personafisica-login/src/pages/login/__test__/SpidSelect.test.tsx index b52dbaf784..7e2ad0ce50 100644 --- a/packages/pn-personafisica-login/src/pages/login/__test__/SpidSelect.test.tsx +++ b/packages/pn-personafisica-login/src/pages/login/__test__/SpidSelect.test.tsx @@ -39,7 +39,8 @@ describe('test spid select page', () => { it('renders page', () => { const { URL_API_LOGIN } = getConfiguration(); - const { container } = render(); + render(); + const container = document.body; expect(container).toHaveTextContent('spidSelect.title'); const backIcon = getById(container, 'backIcon'); expect(backIcon).toBeInTheDocument(); @@ -58,8 +59,9 @@ describe('test spid select page', () => { }); it('clicks on back buttons', () => { - const { container } = render(); - const backIcon = getById(container, 'backIcon'); + render(); + const container = document.body; + const backIcon = getById(document.body, 'backIcon'); fireEvent.click(backIcon); expect(backHandler).toBeCalledTimes(1); const backButton = getById(container, 'backButton'); @@ -68,7 +70,8 @@ describe('test spid select page', () => { }); it('request spid', () => { - const { container } = render(); + render(); + const container = document.body; const requestForSpid = getById(container, 'requestForSpid'); expect(requestForSpid).toHaveAttribute('href', idps.richiediSpid); }); diff --git a/packages/pn-personafisica-webapp/CHANGELOG.md b/packages/pn-personafisica-webapp/CHANGELOG.md index 3374f6deea..696ffb393a 100644 --- a/packages/pn-personafisica-webapp/CHANGELOG.md +++ b/packages/pn-personafisica-webapp/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.1](https://github.com/pagopa/pn-frontend/compare/v2.11.0...v2.11.1) (2025-01-31) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-webapp + + + + + # [2.11.0](https://github.com/pagopa/pn-frontend/compare/v2.11.0-RC.2...v2.11.0) (2025-01-20) **Note:** Version bump only for package @pagopa-pn/pn-personafisica-webapp diff --git a/packages/pn-personafisica-webapp/package.json b/packages/pn-personafisica-webapp/package.json index 368d2fcc11..11d4672aa4 100644 --- a/packages/pn-personafisica-webapp/package.json +++ b/packages/pn-personafisica-webapp/package.json @@ -1,7 +1,7 @@ { "name": "@pagopa-pn/pn-personafisica-webapp", "description": "SEND - Servizio Notifiche Digitali per il cittadino", - "version": "2.11.0", + "version": "2.11.1", "private": true, "dependencies": { "@emotion/react": "^11.11.1", @@ -82,7 +82,7 @@ "@typescript-eslint/parser": "^6.7.3", "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitest/coverage-v8": "^1.6.1", "axios-mock-adapter": "^1.22.0", "eslint": "7.11.0", "eslint-config-prettier": "^8.10.0", @@ -95,7 +95,7 @@ "prettier": "^2.8.8", "sonarqube-scanner": "^3.3.0", "vite": "^5.1.4", - "vitest": "^1.3.1", + "vitest": "^1.6.1", "vitest-sonar-reporter": "^2.0.0" } } diff --git a/packages/pn-personafisica-webapp/public/locales/de/deleghe.json b/packages/pn-personafisica-webapp/public/locales/de/deleghe.json index 18b30d8067..a114e129ee 100644 --- a/packages/pn-personafisica-webapp/public/locales/de/deleghe.json +++ b/packages/pn-personafisica-webapp/public/locales/de/deleghe.json @@ -42,7 +42,8 @@ "notificationsFrom": "Zustellungen von:", "allNotifications": "Alle Zustellungen", "active": "Aktivieren", - "pending": "Wartet auf Bestätigung" + "pending": "Wartet auf Bestätigung", + "menu-aria-label": "Aktionsmenü" } }, "nuovaDelega": { diff --git a/packages/pn-personafisica-webapp/public/locales/en/deleghe.json b/packages/pn-personafisica-webapp/public/locales/en/deleghe.json index 2291612e6c..dbe3a27063 100644 --- a/packages/pn-personafisica-webapp/public/locales/en/deleghe.json +++ b/packages/pn-personafisica-webapp/public/locales/en/deleghe.json @@ -42,7 +42,8 @@ "notificationsFrom": "Notifications from:", "allNotifications": "All notifications", "active": "Active", - "pending": "Awaiting confirmation" + "pending": "Awaiting confirmation", + "menu-aria-label": "Actions menu" } }, "nuovaDelega": { diff --git a/packages/pn-personafisica-webapp/public/locales/fr/deleghe.json b/packages/pn-personafisica-webapp/public/locales/fr/deleghe.json index 6853ce8ece..f782ee7735 100644 --- a/packages/pn-personafisica-webapp/public/locales/fr/deleghe.json +++ b/packages/pn-personafisica-webapp/public/locales/fr/deleghe.json @@ -42,7 +42,8 @@ "notificationsFrom": "Notifications de :", "allNotifications": "Toutes les notifications", "active": "Activer", - "pending": "En attente de confirmation" + "pending": "En attente de confirmation", + "menu-aria-label": "Menu actions" } }, "nuovaDelega": { diff --git a/packages/pn-personafisica-webapp/public/locales/it/common.json b/packages/pn-personafisica-webapp/public/locales/it/common.json index 017e3520af..7f38bd5bd3 100644 --- a/packages/pn-personafisica-webapp/public/locales/it/common.json +++ b/packages/pn-personafisica-webapp/public/locales/it/common.json @@ -31,7 +31,8 @@ }, "header": { "logout": "Esci", - "pago-pa-link": "Sito di PagoPA S.p.A." + "pago-pa-link": "Sito di PagoPA S.p.A.", + "logout-message": "Confermi di voler uscire da SEND?" }, "tos": { "title": "SEND - Servizio Notifiche Digitali", @@ -192,5 +193,10 @@ "message": "Il testo originario di questi documenti è in lingua italiana. In caso di contrasto tra la versione italiana e le traduzioni in altre lingue, prevarranno il significato e le condizioni della lingua italiana.", "link": "Consulta le traduzioni" }, - "no-spaces-at-edges": "Elimina gli spazi all'inizio o alla fine" + "no-spaces-at-edges": "Elimina gli spazi all'inizio o alla fine", + "inactivity": { + "title": "Vuoi davvero uscire da SEND?", + "body": "La disconnessione è in corso. Se hai cambiato idea premi “Mantieni l’accesso” entro pochi secondi.", + "action": "Mantieni l’accesso" + } } diff --git a/packages/pn-personafisica-webapp/public/locales/it/deleghe.json b/packages/pn-personafisica-webapp/public/locales/it/deleghe.json index 1258d3dbc3..fe71397ff4 100644 --- a/packages/pn-personafisica-webapp/public/locales/it/deleghe.json +++ b/packages/pn-personafisica-webapp/public/locales/it/deleghe.json @@ -42,7 +42,8 @@ "notificationsFrom": "Notifiche da:", "allNotifications": "Tutte le notifiche", "active": "Attiva", - "pending": "In attesa di conferma" + "pending": "In attesa di conferma", + "menu-aria-label": "Menù azioni" } }, "nuovaDelega": { diff --git a/packages/pn-personafisica-webapp/public/locales/sl/deleghe.json b/packages/pn-personafisica-webapp/public/locales/sl/deleghe.json index 72c766c143..b1eced0a81 100644 --- a/packages/pn-personafisica-webapp/public/locales/sl/deleghe.json +++ b/packages/pn-personafisica-webapp/public/locales/sl/deleghe.json @@ -42,7 +42,8 @@ "notificationsFrom": "Obvestila od:", "allNotifications": "Vsa obvestila", "active": "Aktiviraj", - "pending": "Čakanje na potrditev" + "pending": "Čakanje na potrditev", + "menu-aria-label": "Meni dejanj" } }, "nuovaDelega": { diff --git a/packages/pn-personafisica-webapp/src/App.tsx b/packages/pn-personafisica-webapp/src/App.tsx index e35ceee655..8f00069cdb 100644 --- a/packages/pn-personafisica-webapp/src/App.tsx +++ b/packages/pn-personafisica-webapp/src/App.tsx @@ -10,7 +10,7 @@ import LogoutRoundedIcon from '@mui/icons-material/LogoutRounded'; import MailOutlineIcon from '@mui/icons-material/MailOutline'; import MarkunreadMailboxIcon from '@mui/icons-material/MarkunreadMailbox'; import SettingsIcon from '@mui/icons-material/Settings'; -import { Box } from '@mui/material'; +import { Box, Button, DialogTitle } from '@mui/material'; import { APP_VERSION, AppMessage, @@ -18,6 +18,8 @@ import { AppResponseError, AppResponseMessage, Layout, + PnDialog, + PnDialogActions, ResponseEventDispatcher, SideMenu, SideMenuItem, @@ -30,11 +32,10 @@ import { import { ProductEntity } from '@pagopa/mui-italia'; import { PFEventsType } from './models/PFEventsType'; -import { getCurrentEventTypePage } from './navigation/navigation.utility'; +import { getCurrentEventTypePage, goToLoginPortal } from './navigation/navigation.utility'; import Router from './navigation/routes'; import * as routes from './navigation/routes.const'; import { getCurrentAppStatus } from './redux/appStatus/actions'; -import { logout } from './redux/auth/actions'; import { useAppDispatch, useAppSelector } from './redux/hooks'; import { getDomicileInfo, getSidemenuInformation } from './redux/sidemenu/actions'; import { RootState } from './redux/store'; @@ -69,6 +70,7 @@ const App = () => { const dispatch = useAppDispatch(); const { t, i18n } = useTranslation(['common', 'notifiche']); const [isInitialized, setIsInitialized] = useState(false); + const [openModal, setOpenModal] = useState(false); const loggedUser = useAppSelector((state: RootState) => state.userState.user); const { tosConsent, fetchedTos, privacyConsent, fetchedPrivacy } = useAppSelector( (state: RootState) => state.userState @@ -226,7 +228,7 @@ const App = () => { }); const handleUserLogout = () => { - void dispatch(logout()); + setOpenModal(true); }; const handleEventTrackingCallbackAppCrash = (e: Error, eInfo: ErrorInfo) => { @@ -302,6 +304,25 @@ const App = () => { eventTrackingCallbackRefreshPage={handleEventTrackingCallbackRefreshPage} enableAssistanceButton={showAssistanceButton} > + + {t('header.logout-message')} + + + + + {/* await dispatch(logout())} /> */} ({ @@ -71,7 +62,6 @@ const reduxInitialState = { describe('App', async () => { let mock: MockAdapter; let result: RenderResult; - const original = window.location; const mockOpenFn = vi.fn(); const originalOpen = window.open; @@ -152,18 +142,19 @@ describe('App', async () => { await waitFor(() => { expect(result.container).toHaveTextContent('Profile Page'); }); - Object.defineProperty(window, 'location', { - writable: true, - value: { href: '', replace: vi.fn() }, - }); + fireEvent.click(userButton!); menu = await waitFor(() => screen.getByRole('presentation')); menuItems = within(menu).getAllByRole('menuitem'); fireEvent.click(menuItems[1]); - await waitFor(() => { - expect(testStore.getState().userState.user.sessionToken).toBe(''); - }); - Object.defineProperty(window, 'location', { writable: true, value: original }); + + const logoutDialog = await waitFor(() => screen.getByTestId('dialog')); + expect(logoutDialog).toBeInTheDocument(); + const confirmLogoutButton = within(logoutDialog).getByTestId('confirm-button'); + fireEvent.click(confirmLogoutButton); + expect(sessionStorage.getItem('user')).toBeNull(); + expect(mockOpenFn).toHaveBeenCalledTimes(1); + expect(mockOpenFn).toHaveBeenCalledWith(`${LOGOUT}`, '_self'); }); it('sidemenu not included if error in API call to fetch TOS and Privacy', async () => { diff --git a/packages/pn-personafisica-webapp/src/components/Contacts/DefaultDigitalContact.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/DefaultDigitalContact.tsx index f1e5c0949f..8e5ecb4a82 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/DefaultDigitalContact.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/DefaultDigitalContact.tsx @@ -134,7 +134,6 @@ const DefaultDigitalContact = forwardRef<{ toggleEdit: () => void }, Props>( + + + {/* await dispatch(logout())} /> */} diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/DefaultDigitalContact.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/DefaultDigitalContact.tsx index 90c6fcb579..3b9637e23a 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/DefaultDigitalContact.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/DefaultDigitalContact.tsx @@ -134,7 +134,6 @@ const DefaultDigitalContact = forwardRef<{ toggleEdit: () => void }, Props>(