diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f89ebeaab..ecb848b7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,48 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package pn-frontend + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package pn-frontend + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13210:** manage accessibility when closing modal ([#1451](https://github.com/pagopa/pn-frontend/issues/1451)) ([2b4981b](https://github.com/pagopa/pn-frontend/commit/2b4981bddc8f6651ac47bb2cf371f63e33962959)) +* **pn-13211:** Removed redirect when login fails ([#1445](https://github.com/pagopa/pn-frontend/issues/1445)) ([6ff0d2a](https://github.com/pagopa/pn-frontend/commit/6ff0d2aec98f161d5f23e7af49e41c31c7a7dcb7)) +* **pn-13213:** aria label for payments and statistics checkboxes ([#1450](https://github.com/pagopa/pn-frontend/issues/1450)) ([0d4d250](https://github.com/pagopa/pn-frontend/commit/0d4d25007aa3c2012c6bd3cba235cc32e8d65be1)) +* **pn-13214:** inactivity handler accessibility ([#1454](https://github.com/pagopa/pn-frontend/issues/1454)) ([7af07bd](https://github.com/pagopa/pn-frontend/commit/7af07bdc5861815ae484e0856d17f80ec3ac6096)) +* **pn-13216:** Added descriptions to radio buttons ([#1444](https://github.com/pagopa/pn-frontend/issues/1444)) ([9fb7b79](https://github.com/pagopa/pn-frontend/commit/9fb7b79a755c6850d9ec2ffbb62aff60ec88e7a1)) +* **pn-13255:** fixed aria attributes on the modal for revoking the delegation ([#1453](https://github.com/pagopa/pn-frontend/issues/1453)) ([cf2bfd9](https://github.com/pagopa/pn-frontend/commit/cf2bfd9844c412aa7f881a833c2fcaae01650355)) +* **pn-13591:** Handle Calendar for a11y ([#1448](https://github.com/pagopa/pn-frontend/issues/1448)) ([7be12df](https://github.com/pagopa/pn-frontend/commit/7be12dfb572f2eb50e3f857d78174410b1a181ac)) +* **pn-13593:** screen reader doesn't read api key value ([#1449](https://github.com/pagopa/pn-frontend/issues/1449)) ([7b11fb7](https://github.com/pagopa/pn-frontend/commit/7b11fb7ceb22d46bfd4dcea6bcbb6e1a2c3f85ae)) +* **pn-13596:** screen reader doesn't read notification status tooltip on mobile ([#1439](https://github.com/pagopa/pn-frontend/issues/1439)) ([b28f472](https://github.com/pagopa/pn-frontend/commit/b28f47258e5bd3cf4d9a2a3c95b86bfb42114ce8)) +* **pn-13598:** accessibility for the delegation tag group ([#1452](https://github.com/pagopa/pn-frontend/issues/1452)) ([a8626a2](https://github.com/pagopa/pn-frontend/commit/a8626a27ca23f9de030bc0b51d825d685bcee737)) + + +### Features + +* **pn-13215:** Screen reader doesn't read all the attachments in timeline ([#1442](https://github.com/pagopa/pn-frontend/issues/1442)) ([877bf49](https://github.com/pagopa/pn-frontend/commit/877bf49c486485f9e0ce6b04b6423a78824180e8)) +* **pn-13595:** removed automatic redirect before logout ([#1456](https://github.com/pagopa/pn-frontend/issues/1456)) ([c7d823f](https://github.com/pagopa/pn-frontend/commit/c7d823f72e9e64eeeccfcafebb0b05f4b188959c)) + + + + + ## [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 diff --git a/lerna.json b/lerna.json index af92cdeea7..d3336a0e5d 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.11.1", + "version": "2.12.0", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/pn-commons/CHANGELOG.md b/packages/pn-commons/CHANGELOG.md index cc79cfc7b8..d2a0273333 100644 --- a/packages/pn-commons/CHANGELOG.md +++ b/packages/pn-commons/CHANGELOG.md @@ -3,6 +3,43 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package @pagopa-pn/pn-commons + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package @pagopa-pn/pn-commons + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13210:** manage accessibility when closing modal ([#1451](https://github.com/pagopa/pn-frontend/issues/1451)) ([2b4981b](https://github.com/pagopa/pn-frontend/commit/2b4981bddc8f6651ac47bb2cf371f63e33962959)) +* **pn-13213:** aria label for payments and statistics checkboxes ([#1450](https://github.com/pagopa/pn-frontend/issues/1450)) ([0d4d250](https://github.com/pagopa/pn-frontend/commit/0d4d25007aa3c2012c6bd3cba235cc32e8d65be1)) +* **pn-13214:** inactivity handler accessibility ([#1454](https://github.com/pagopa/pn-frontend/issues/1454)) ([7af07bd](https://github.com/pagopa/pn-frontend/commit/7af07bdc5861815ae484e0856d17f80ec3ac6096)) +* **pn-13591:** Handle Calendar for a11y ([#1448](https://github.com/pagopa/pn-frontend/issues/1448)) ([7be12df](https://github.com/pagopa/pn-frontend/commit/7be12dfb572f2eb50e3f857d78174410b1a181ac)) +* **pn-13596:** screen reader doesn't read notification status tooltip on mobile ([#1439](https://github.com/pagopa/pn-frontend/issues/1439)) ([b28f472](https://github.com/pagopa/pn-frontend/commit/b28f47258e5bd3cf4d9a2a3c95b86bfb42114ce8)) +* **pn-13598:** accessibility for the delegation tag group ([#1452](https://github.com/pagopa/pn-frontend/issues/1452)) ([a8626a2](https://github.com/pagopa/pn-frontend/commit/a8626a27ca23f9de030bc0b51d825d685bcee737)) + + +### Features + +* **pn-13215:** Screen reader doesn't read all the attachments in timeline ([#1442](https://github.com/pagopa/pn-frontend/issues/1442)) ([877bf49](https://github.com/pagopa/pn-frontend/commit/877bf49c486485f9e0ce6b04b6423a78824180e8)) + + + + + ## [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 diff --git a/packages/pn-commons/package.json b/packages/pn-commons/package.json index 66a0476ba6..c028d32e09 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.1", + "version": "2.12.0", "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/CustomDatePicker.tsx b/packages/pn-commons/src/components/CustomDatePicker.tsx index 56cf8c4acc..2023f06326 100644 --- a/packages/pn-commons/src/components/CustomDatePicker.tsx +++ b/packages/pn-commons/src/components/CustomDatePicker.tsx @@ -26,11 +26,12 @@ const CustomDatePicker = ( slotProps={{ ...props.slotProps, toolbar: { hidden: true }, - actionBar: { actions: [] }, + actionBar: { actions: ['cancel'] }, layout: { className: 'PnDatePicker' }, // don't remove it! it is used for tests }} closeOnSelect localeText={{ + cancelButtonLabel: getLocalizedOrDefaultLabel('common', 'button.close'), openPreviousView: getLocalizedOrDefaultLabel('common', 'date-picker.left-arrow'), openNextView: getLocalizedOrDefaultLabel('common', 'date-picker.right-arrow'), calendarViewSwitchingButtonAriaLabel: (view) => @@ -47,7 +48,6 @@ const CustomDatePicker = ( return getLocalizedOrDefaultLabel('common', 'date-picker.select-date'); }, }} - data-testid="ciao" /> ); 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 2768e5c66f..89e45c02bf 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 }) => ( + + ); @@ -50,14 +56,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/CustomTooltip.tsx b/packages/pn-commons/src/components/CustomTooltip.tsx index 656149e1da..c5d2ab5844 100644 --- a/packages/pn-commons/src/components/CustomTooltip.tsx +++ b/packages/pn-commons/src/components/CustomTooltip.tsx @@ -1,14 +1,10 @@ import { cloneElement, useState } from 'react'; -import { Box, SxProps } from '@mui/material'; -import ClickAwayListener from '@mui/material/ClickAwayListener'; -// ReactNode, cloneElement import Tooltip, { TooltipProps } from '@mui/material/Tooltip'; type Props = { tooltipContent: any; openOnClick: boolean; - sx?: SxProps; onOpen?: () => void; children: JSX.Element; tooltipProps?: Partial; @@ -18,7 +14,6 @@ const CustomTooltip: React.FC = ({ openOnClick, tooltipContent, children, - sx, onOpen, tooltipProps, }) => { @@ -37,27 +32,23 @@ const CustomTooltip: React.FC = ({ }; return ( - - - - {openOnClick ? cloneElement(children, { + + {openOnClick + ? cloneElement(children, { onClick: handleTooltipOpen, - }) : children} - - - + }) + : children} + ); }; 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/NotificationDetail/NotificationDetailTimelineStep.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx index 810ed683b4..2aa0ddf51c 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx @@ -281,24 +281,22 @@ const NotificationDetailTimelineStep = ({ - {timelineStatusInfos.description}  - {s.legalFactsIds && - s.legalFactsIds.length > 0 && - s.legalFactsIds.map((lf) => ( - clickHandler(lf)} - key={lf.key} - data-testid="download-legalfact-micro" - > - {getLegalFactLabel(s, lf.category, lf.key || '')} - - ))} + {timelineStatusInfos.description} + {s.legalFactsIds && + s.legalFactsIds.length > 0 && + s.legalFactsIds.map((lf) => ( + clickHandler(lf)} + disabled={disableDownloads} + key={lf.key} + data-testid="download-legalfact-micro" + > + {getLegalFactLabel(s, lf.category, lf.key || '')} + + ))} } diff --git a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentPagoPAItem.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentPagoPAItem.tsx index e66900025f..7baf350b35 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentPagoPAItem.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentPagoPAItem.tsx @@ -157,6 +157,7 @@ const NotificationPaymentPagoPAStatusElem: React.FC<{ !isSinglePayment && ( { expect(microLegalFacts[counter]).toHaveTextContent( getLegalFactLabel(step, lf.category, lf.key || '') ); - expect(microLegalFacts[counter]).toHaveStyle({ - color: theme.palette.primary.main, - cursor: 'pointer', - }); } + expect(microLegalFacts[counter]).toBeEnabled() counter++; } }); @@ -140,7 +137,7 @@ describe('NotificationDetailTimelineStep', () => { moreLessButton = getByTestId('more-less-timeline-step'); expect(moreLessButton).toHaveTextContent('Show Less'); fireEvent.click(moreLessButton); - expect(eventTrackingCallbackShowMore).toBeCalledTimes(2); + expect(eventTrackingCallbackShowMore).toHaveBeenCalledTimes(2); // After clicking "Show Less", additional steps should be hidden dateItemsMicro = queryAllByTestId('dateItemMicro'); expect(dateItemsMicro).toHaveLength(0); @@ -165,7 +162,7 @@ describe('NotificationDetailTimelineStep', () => { fireEvent.click(btn); // Verify that the clickHandler function is called with the expected arguments expect(mockClickHandler).toHaveBeenCalledTimes(index + 1); - expect(mockClickHandler).toBeCalledWith(legalFacts[index].file); + expect(mockClickHandler).toHaveBeenCalledWith(legalFacts[index].file); }); // expand step const moreLessButton = getByTestId('more-less-timeline-step'); @@ -179,14 +176,14 @@ describe('NotificationDetailTimelineStep', () => { fireEvent.click(microLegalFacts[counter]); // Verify that the clickHandler function is called with the expected arguments expect(mockClickHandler).toHaveBeenCalledTimes(legalFacts.length + counter + 1); - expect(mockClickHandler).toBeCalledWith(lf); + expect(mockClickHandler).toHaveBeenCalledWith(lf); } counter++; } }); }); - it('renders component with disabled donwloads', () => { + it('renders component with disabled downloads', () => { const { getAllByTestId, getByTestId } = render( { fireEvent.click(moreLessButton); const microLegalFacts = getAllByTestId('download-legalfact-micro'); microLegalFacts.forEach((btn) => { - expect(btn).toHaveStyle({ - color: theme.palette.text.disabled, - cursor: 'default', - }); + expect(btn).toBeDisabled(); }); }); diff --git a/packages/pn-commons/src/components/Notifications/StatusTooltip.tsx b/packages/pn-commons/src/components/Notifications/StatusTooltip.tsx index b4804f4308..00193b53b3 100644 --- a/packages/pn-commons/src/components/Notifications/StatusTooltip.tsx +++ b/packages/pn-commons/src/components/Notifications/StatusTooltip.tsx @@ -3,6 +3,7 @@ import { Fragment, ReactNode } from 'react'; import { SxProps, TooltipProps } from '@mui/material'; import Chip from '@mui/material/Chip'; +import { useIsMobile } from '../../hooks'; import CustomTooltip from '../CustomTooltip'; const StatusTooltip = ({ @@ -19,15 +20,20 @@ const StatusTooltip = ({ chipProps?: SxProps; }) => { const tooltipContent = {tooltip}; + const isMobile = useIsMobile(); return ( ); 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-data-viz/CHANGELOG.md b/packages/pn-data-viz/CHANGELOG.md index 8a38b5a085..405740a1d2 100644 --- a/packages/pn-data-viz/CHANGELOG.md +++ b/packages/pn-data-viz/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package @pagopa-pn/pn-data-viz + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package @pagopa-pn/pn-data-viz + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13213:** aria label for payments and statistics checkboxes ([#1450](https://github.com/pagopa/pn-frontend/issues/1450)) ([0d4d250](https://github.com/pagopa/pn-frontend/commit/0d4d25007aa3c2012c6bd3cba235cc32e8d65be1)) + + + + + ## [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 diff --git a/packages/pn-data-viz/package.json b/packages/pn-data-viz/package.json index c4ce13cc40..4847c4a06e 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.1", + "version": "2.12.0", "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-data-viz/src/PnEcharts.tsx b/packages/pn-data-viz/src/PnEcharts.tsx index d919cfea49..e6f63e09b1 100644 --- a/packages/pn-data-viz/src/PnEcharts.tsx +++ b/packages/pn-data-viz/src/PnEcharts.tsx @@ -4,15 +4,8 @@ import type { EChartOption, SetOptionOpts } from 'echarts'; import { useEffect, useMemo, useRef } from 'react'; import type { CSSProperties } from 'react'; -import { - Avatar, - Box, - Checkbox, - ListItem, - ListItemAvatar, - ListItemText, - Stack, -} from '@mui/material'; +import CircleIcon from '@mui/icons-material/Circle'; +import { Box, Checkbox, FormControl, FormControlLabel, Stack, Typography } from '@mui/material'; import senderDashboard from './theme/senderDashboard'; @@ -106,28 +99,41 @@ export function PnECharts({ () => legend?.map((item, index) => { const color = option.color?.[index] ?? ''; - const avatarSx = { - bgcolor: color, + const circleSx = { + color, width: 10, height: 10, + mr: 1, }; return ( - - toggleSerie(item)} - defaultChecked - sx={{ - color, - '&.Mui-checked': { - color, - }, - }} + + toggleSerie(item)} + defaultChecked + sx={{ + color, + '&.Mui-checked': { + color, + }, + }} + /> + } + label={ + <> + + + {item} + + + } /> - -   - - - + ); }), [theme] diff --git a/packages/pn-pa-webapp/CHANGELOG.md b/packages/pn-pa-webapp/CHANGELOG.md index 91466f126a..bcc0e4aaa9 100644 --- a/packages/pn-pa-webapp/CHANGELOG.md +++ b/packages/pn-pa-webapp/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package @pagopa-pn/pn-pa-webapp + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package @pagopa-pn/pn-pa-webapp + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13210:** manage accessibility when closing modal ([#1451](https://github.com/pagopa/pn-frontend/issues/1451)) ([2b4981b](https://github.com/pagopa/pn-frontend/commit/2b4981bddc8f6651ac47bb2cf371f63e33962959)) +* **pn-13214:** inactivity handler accessibility ([#1454](https://github.com/pagopa/pn-frontend/issues/1454)) ([7af07bd](https://github.com/pagopa/pn-frontend/commit/7af07bdc5861815ae484e0856d17f80ec3ac6096)) +* **pn-13593:** screen reader doesn't read api key value ([#1449](https://github.com/pagopa/pn-frontend/issues/1449)) ([7b11fb7](https://github.com/pagopa/pn-frontend/commit/7b11fb7ceb22d46bfd4dcea6bcbb6e1a2c3f85ae)) +* **pn-13596:** screen reader doesn't read notification status tooltip on mobile ([#1439](https://github.com/pagopa/pn-frontend/issues/1439)) ([b28f472](https://github.com/pagopa/pn-frontend/commit/b28f47258e5bd3cf4d9a2a3c95b86bfb42114ce8)) + + +### Features + +* **pn-13595:** removed automatic redirect before logout ([#1456](https://github.com/pagopa/pn-frontend/issues/1456)) ([c7d823f](https://github.com/pagopa/pn-frontend/commit/c7d823f72e9e64eeeccfcafebb0b05f4b188959c)) + + + + + ## [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 diff --git a/packages/pn-pa-webapp/openapitools.json b/packages/pn-pa-webapp/openapitools.json index 7090bbdd30..b1bc3635d2 100644 --- a/packages/pn-pa-webapp/openapitools.json +++ b/packages/pn-pa-webapp/openapitools.json @@ -6,7 +6,7 @@ "generators": { "bff-notifications": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-pa-notifications.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-pa-notifications.yaml", "output": "./src/generated-client/notifications", "additionalProperties": { "supportsES6": true, @@ -17,7 +17,7 @@ }, "bff-api-keys": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-pa-apikeys.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-pa-apikeys.yaml", "output": "./src/generated-client/api-keys", "additionalProperties": { "supportsES6": true, @@ -28,7 +28,7 @@ }, "bff-tos-privacy": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-tos-privacy.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-tos-privacy.yaml", "output": "./src/generated-client/tos-privacy", "additionalProperties": { "supportsES6": true, @@ -39,7 +39,7 @@ }, "bff-pa-info": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-pa-info.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-pa-info.yaml", "output": "./src/generated-client/info-pa", "additionalProperties": { "supportsES6": true, @@ -50,7 +50,7 @@ }, "bff-downtime-logs": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-downtime-logs.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-downtime-logs.yaml", "output": "./src/generated-client/downtime-logs", "additionalProperties": { "supportsES6": true, diff --git a/packages/pn-pa-webapp/package.json b/packages/pn-pa-webapp/package.json index a88300b386..06d6d2c76d 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.1", + "version": "2.12.0", "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 9b61db645e..a9201f7ebf 100644 --- a/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx +++ b/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx @@ -2,7 +2,7 @@ import { MouseEvent, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MoreVert } from '@mui/icons-material'; -import { Box, IconButton, Menu, MenuItem, Typography } from '@mui/material'; +import { Box, IconButton, Menu, MenuItem, Stack, Typography } from '@mui/material'; import { CustomTagGroup, CustomTooltip, @@ -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')} @@ -156,22 +163,31 @@ const ApiKeyDataSwitch: React.FC<{ } if (type === 'value') { return ( - - {`${data.value.substring(0, 10)}...`} + + {data.value} + data.value || ''} /> - + ); } if (type === 'lastUpdate') { diff --git a/packages/pn-pa-webapp/src/components/ApiKeys/DesktopApiKeys.tsx b/packages/pn-pa-webapp/src/components/ApiKeys/DesktopApiKeys.tsx index ede10be398..0bae9f9535 100644 --- a/packages/pn-pa-webapp/src/components/ApiKeys/DesktopApiKeys.tsx +++ b/packages/pn-pa-webapp/src/components/ApiKeys/DesktopApiKeys.tsx @@ -52,13 +52,13 @@ const DesktopApiKeys = ({ apiKeys, handleModalClick }: Props) => { { id: 'name', label: t('table.name'), - cellProps: { width: '30%' }, + cellProps: { width: '20%' }, sortable: false, }, { id: 'value', label: t('table.api-key'), - cellProps: { width: '25%' }, + cellProps: { width: '30%' }, sortable: false, }, { @@ -74,13 +74,13 @@ const DesktopApiKeys = ({ apiKeys, handleModalClick }: Props) => { { id: 'status', label: t('table.status'), - cellProps: { width: '10%', align: 'left' }, + cellProps: { width: '15%' }, sortable: false, // TODO: will be re-enabled in PN-1124 }, { id: 'contextMenu', label: '', - cellProps: { width: '5%', align: 'left' }, + cellProps: { width: '5%' }, sortable: false, }, ]; @@ -88,10 +88,10 @@ const DesktopApiKeys = ({ apiKeys, handleModalClick }: Props) => { return ( <> {apiKeys && apiKeys.length > 0 ? ( - + {columns.map((column) => ( - + {column.label} ))} diff --git a/packages/pn-pa-webapp/src/components/ApiKeys/__test__/ApiKeyDataSwitch.test.tsx b/packages/pn-pa-webapp/src/components/ApiKeys/__test__/ApiKeyDataSwitch.test.tsx index 113da96edd..6da7ef1ed0 100644 --- a/packages/pn-pa-webapp/src/components/ApiKeys/__test__/ApiKeyDataSwitch.test.tsx +++ b/packages/pn-pa-webapp/src/components/ApiKeys/__test__/ApiKeyDataSwitch.test.tsx @@ -31,7 +31,7 @@ describe('ApiKeyDataSwitch Component', () => { const { container, getByTestId } = render( ); - const regexp = new RegExp(`^${data.value.substring(0, 10)}...$`, 'ig'); + const regexp = new RegExp(`^${data.value}`, 'ig'); expect(container).toHaveTextContent(regexp); const clipboard = getByTestId('copyToClipboard'); expect(clipboard).toBeInTheDocument(); @@ -52,11 +52,11 @@ describe('ApiKeyDataSwitch Component', () => { const groupsString = data.groups.length > 3 ? data.groups - .map((group) => group.name) - .splice(0, 3) - .join('') + - '\\+' + - (data.groups.length - 3).toString() + .map((group) => group.name) + .splice(0, 3) + .join('') + + '\\+' + + (data.groups.length - 3).toString() : data.groups.map((group) => group.name).join(''); const regexp = new RegExp(`^${groupsString}$`, 'ig'); expect(container).toHaveTextContent(regexp); 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-pa-webapp/src/pages/Statistics.page.tsx b/packages/pn-pa-webapp/src/pages/Statistics.page.tsx index 6599162384..142753f7a5 100644 --- a/packages/pn-pa-webapp/src/pages/Statistics.page.tsx +++ b/packages/pn-pa-webapp/src/pages/Statistics.page.tsx @@ -138,8 +138,11 @@ const Statistics = () => { diff --git a/packages/pn-pa-webapp/src/pages/__test__/ApiKeys.page.test.tsx b/packages/pn-pa-webapp/src/pages/__test__/ApiKeys.page.test.tsx index 72de5048dc..ca8f071d25 100644 --- a/packages/pn-pa-webapp/src/pages/__test__/ApiKeys.page.test.tsx +++ b/packages/pn-pa-webapp/src/pages/__test__/ApiKeys.page.test.tsx @@ -129,8 +129,7 @@ describe('ApiKeys Page', async () => { .reply(200, { ...mockApiKeysDTO, items: mockApiKeysDTO.items.slice(0, 2) }); mock .onGet( - `/bff/v1/api-keys?limit=10&lastKey=${ - mockApiKeysDTO.lastKey + `/bff/v1/api-keys?limit=10&lastKey=${mockApiKeysDTO.lastKey }&lastUpdate=${encodeURIComponent(mockApiKeysDTO.lastUpdate!)}&showVirtualKey=true` ) .reply(200, { ...mockApiKeysDTO, items: mockApiKeysDTO.items.slice(2) }); @@ -143,7 +142,7 @@ describe('ApiKeys Page', async () => { let rows = result.getAllByTestId('tableApiKeys.body.row'); expect(rows).toHaveLength(2); rows.forEach((row, index) => { - expect(row).toHaveTextContent(`${mockApiKeysDTO.items[index].value.substring(0, 10)}...`); + expect(row).toHaveTextContent(`${mockApiKeysDTO.items[index].value}`); }); expect(mock.history.get).toHaveLength(1); // change page @@ -157,7 +156,7 @@ describe('ApiKeys Page', async () => { await waitFor(() => { rows = result.getAllByTestId('tableApiKeys.body.row'); expect(rows).toHaveLength(1); - expect(rows[0]).toHaveTextContent(`${mockApiKeysDTO.items[2].value.substring(0, 10)}...`); + expect(rows[0]).toHaveTextContent(`${mockApiKeysDTO.items[2].value}`); }); // change size const itemsPerPageSelector = result.getByTestId('itemsPerPageSelector'); @@ -172,7 +171,7 @@ describe('ApiKeys Page', async () => { rows = result.getAllByTestId('tableApiKeys.body.row'); expect(rows).toHaveLength(3); rows.forEach((row, index) => { - expect(row).toHaveTextContent(`${mockApiKeysDTO.items[index].value.substring(0, 10)}...`); + expect(row).toHaveTextContent(`${mockApiKeysDTO.items[index].value.substring(0, 10)}`); }); }); }); diff --git a/packages/pn-personafisica-login/CHANGELOG.md b/packages/pn-personafisica-login/CHANGELOG.md index e3191288fc..118fbe071c 100644 --- a/packages/pn-personafisica-login/CHANGELOG.md +++ b/packages/pn-personafisica-login/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-login + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-login + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13210:** manage accessibility when closing modal ([#1451](https://github.com/pagopa/pn-frontend/issues/1451)) ([2b4981b](https://github.com/pagopa/pn-frontend/commit/2b4981bddc8f6651ac47bb2cf371f63e33962959)) +* **pn-13211:** Removed redirect when login fails ([#1445](https://github.com/pagopa/pn-frontend/issues/1445)) ([6ff0d2a](https://github.com/pagopa/pn-frontend/commit/6ff0d2aec98f161d5f23e7af49e41c31c7a7dcb7)) + + + + + ## [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 diff --git a/packages/pn-personafisica-login/package.json b/packages/pn-personafisica-login/package.json index 1bf7f3b27d..b360778ccc 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.1", + "version": "2.12.0", "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-login/src/pages/loginError/LoginError.tsx b/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx index 9f8b83728a..582074a52e 100644 --- a/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx +++ b/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx @@ -26,7 +26,6 @@ const LoginError = () => { const navigate = useNavigate(); const [urlSearchParams] = useSearchParams(); const errorCode = urlSearchParams.has('errorCode') ? urlSearchParams.get('errorCode') : null; - const navigationTimeout = process.env.NODE_ENV !== 'test' ? 15000 : 2000; // PN-1989 - per alcune causali di errore, si evita il passaggio transitorio per la pagina di errore // e si fa il redirect verso la pagina di login immediatamente @@ -64,23 +63,12 @@ const LoginError = () => { }; const goToLogin = () => navigate(ROUTE_LOGIN); + // log error useEffect(() => { handleError(window.location.search, errorCode!); }, []); - useEffect(() => { - const timeout = setTimeout(() => { - navigate(ROUTE_LOGIN); - }, navigationTimeout); - - return () => { - if (timeout) { - clearTimeout(timeout); - } - }; - }, []); - return ( diff --git a/packages/pn-personafisica-login/src/pages/loginError/__test__/LoginError.test.tsx b/packages/pn-personafisica-login/src/pages/loginError/__test__/LoginError.test.tsx index 203d03ab97..3ddebaba1f 100644 --- a/packages/pn-personafisica-login/src/pages/loginError/__test__/LoginError.test.tsx +++ b/packages/pn-personafisica-login/src/pages/loginError/__test__/LoginError.test.tsx @@ -1,7 +1,7 @@ import { BrowserRouter } from 'react-router-dom'; import { vi } from 'vitest'; -import { act, getById } from '@pagopa-pn/pn-commons/src/test-utils'; +import { fireEvent, getById, waitFor } from '@pagopa-pn/pn-commons/src/test-utils'; import { render } from '../../../__test__/test-utils'; import { ROUTE_LOGIN } from '../../../navigation/routes.const'; @@ -32,13 +32,14 @@ function mockCreateMockedSearchParams() { return mockedSearchParams; } + describe('LoginError component', () => { afterEach(() => { vi.clearAllMocks(); }); it('login technical error - code generic', async () => { - vi.useFakeTimers(); + spidErrorCode = '2'; render( @@ -49,17 +50,15 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.message'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + const buttonRedirect = getById(document.body, 'login-button'); + fireEvent.click(buttonRedirect) + await waitFor(()=>{ + expect(mockNavigateFn).toHaveBeenCalledTimes(1); + expect(mockNavigateFn).toHaveBeenCalledWith(ROUTE_LOGIN); + }) }); it('login too many retry error - code 19', async () => { - vi.useFakeTimers(); spidErrorCode = '19'; render( @@ -71,17 +70,9 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_19'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); }); it('login two authentication factor error - code 20', async () => { - vi.useFakeTimers(); spidErrorCode = '20'; render( @@ -93,17 +84,9 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_20'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); }); it('login waiting for too long error - code 21', async () => { - vi.useFakeTimers(); spidErrorCode = '21'; render( @@ -115,17 +98,9 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_21'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); }); it('login consent necessary error - code 22', async () => { - vi.useFakeTimers(); spidErrorCode = '22'; render( @@ -137,17 +112,10 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_22'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + }); it('login spid identity rewoked or suspended error - code 23', async () => { - vi.useFakeTimers(); spidErrorCode = '23'; render( @@ -159,17 +127,10 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_23'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + }); it('user cancelled the login - code 25', async () => { - vi.useFakeTimers(); spidErrorCode = '25'; render( @@ -181,17 +142,10 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_25'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + }); it('user used a different spid type - code 30', async () => { - vi.useFakeTimers(); spidErrorCode = '30'; render( @@ -203,17 +157,10 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_30'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + }); it("user doesn't have the minimum required age - code 1001", async () => { - vi.useFakeTimers(); spidErrorCode = '1001'; render( @@ -225,12 +172,6 @@ describe('LoginError component', () => { expect(errorDialog).toHaveTextContent('loginError.title'); const message = getById(errorDialog, 'message'); expect(message).toHaveTextContent('loginError.code.error_1001'); - // Wait login redirect - act(() => { - vi.advanceTimersByTime(2000); - }); - expect(mockNavigateFn).toBeCalledTimes(1); - expect(mockNavigateFn).toBeCalledWith(ROUTE_LOGIN); - vi.useRealTimers(); + }); }); diff --git a/packages/pn-personafisica-webapp/CHANGELOG.md b/packages/pn-personafisica-webapp/CHANGELOG.md index 696ffb393a..b7a23b39db 100644 --- a/packages/pn-personafisica-webapp/CHANGELOG.md +++ b/packages/pn-personafisica-webapp/CHANGELOG.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.12.0](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.1...v2.12.0) (2025-02-18) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-webapp + + + + + +# [2.12.0-RC.1](https://github.com/pagopa/pn-frontend/compare/v2.12.0-RC.0...v2.12.0-RC.1) (2025-02-05) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-webapp + + + + + +# [2.12.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.11.1...v2.12.0-RC.0) (2025-02-04) + + +### Bug Fixes + +* **pn-13210:** manage accessibility when closing modal ([#1451](https://github.com/pagopa/pn-frontend/issues/1451)) ([2b4981b](https://github.com/pagopa/pn-frontend/commit/2b4981bddc8f6651ac47bb2cf371f63e33962959)) +* **pn-13214:** inactivity handler accessibility ([#1454](https://github.com/pagopa/pn-frontend/issues/1454)) ([7af07bd](https://github.com/pagopa/pn-frontend/commit/7af07bdc5861815ae484e0856d17f80ec3ac6096)) +* **pn-13216:** Added descriptions to radio buttons ([#1444](https://github.com/pagopa/pn-frontend/issues/1444)) ([9fb7b79](https://github.com/pagopa/pn-frontend/commit/9fb7b79a755c6850d9ec2ffbb62aff60ec88e7a1)) +* **pn-13255:** fixed aria attributes on the modal for revoking the delegation ([#1453](https://github.com/pagopa/pn-frontend/issues/1453)) ([cf2bfd9](https://github.com/pagopa/pn-frontend/commit/cf2bfd9844c412aa7f881a833c2fcaae01650355)) +* **pn-13598:** accessibility for the delegation tag group ([#1452](https://github.com/pagopa/pn-frontend/issues/1452)) ([a8626a2](https://github.com/pagopa/pn-frontend/commit/a8626a27ca23f9de030bc0b51d825d685bcee737)) + + +### Features + +* **pn-13595:** removed automatic redirect before logout ([#1456](https://github.com/pagopa/pn-frontend/issues/1456)) ([c7d823f](https://github.com/pagopa/pn-frontend/commit/c7d823f72e9e64eeeccfcafebb0b05f4b188959c)) + + + + + ## [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 diff --git a/packages/pn-personafisica-webapp/openapitools.json b/packages/pn-personafisica-webapp/openapitools.json index 530927ec6d..f975ab3ed8 100644 --- a/packages/pn-personafisica-webapp/openapitools.json +++ b/packages/pn-personafisica-webapp/openapitools.json @@ -6,7 +6,7 @@ "generators": { "bff-notifications": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-recipient-notifications.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-recipient-notifications.yaml", "output": "./src/generated-client/notifications", "additionalProperties": { "supportsES6": true, @@ -17,7 +17,7 @@ }, "bff-tos-privacy": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-tos-privacy.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-tos-privacy.yaml", "output": "./src/generated-client/tos-privacy", "additionalProperties": { "supportsES6": true, @@ -28,7 +28,7 @@ }, "bff-downtime-logs": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-downtime-logs.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-downtime-logs.yaml", "output": "./src/generated-client/downtime-logs", "additionalProperties": { "supportsES6": true, @@ -39,7 +39,7 @@ }, "bff-payments": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-payments.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-payments.yaml", "output": "./src/generated-client/payments", "additionalProperties": { "supportsES6": true, @@ -50,7 +50,7 @@ }, "bff-digital-addresses": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-recipient-digital-addresses.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-recipient-digital-addresses.yaml", "output": "./src/generated-client/digital-addresses", "additionalProperties": { "supportsES6": true, @@ -61,7 +61,7 @@ }, "bff-mandate": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-recipient-mandate.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-recipient-mandate.yaml", "output": "./src/generated-client/mandate", "additionalProperties": { "supportsES6": true, @@ -72,7 +72,7 @@ }, "bff-recipient-info": { "generatorName": "typescript-axios", - "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/0c7599ecae180c1c27b62073c236eed02157c7bc/docs/openapi/api-external-pn-bff-recipient-info.yaml", + "inputSpec": "https://raw.githubusercontent.com/pagopa/pn-bff/5355801397984dd3c3187e417d49e90c6c4076ae/docs/openapi/api-external-pn-bff-recipient-info.yaml", "output": "./src/generated-client/recipient-info", "additionalProperties": { "supportsES6": true, diff --git a/packages/pn-personafisica-webapp/package.json b/packages/pn-personafisica-webapp/package.json index c3ab329ce9..af6c93a531 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.1", + "version": "2.12.0", "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 b36be3c251..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": { @@ -54,13 +55,15 @@ "subtitle": "Inserisci i dati della persona fisica o giuridica a cui vuoi delegare la lettura delle tue notifiche.", "form": { "personType": "Soggetto giuridico*:", + "personType-content-subtitle": "Seleziona la tipologia di soggetto giuridico", "naturalPerson": "Persona fisica", "legalPerson": "Persona giuridica", "firstName": "Nome*", "lastName": "Cognome*", "fiscalCode": "Codice Fiscale*", "businessName": "Ragione Sociale*", - "viewFrom": "Potrà visualizzare le notifiche da parte di*:", + "viewFrom": "Potrà consultare le notifiche da parte di*:", + "viewFrom-content-subtitle": "Seleziona un’opzione", "allEntities": "Tutti gli enti", "onlySelected": "Solo enti selezionati", "selectEntities": "Seleziona enti*", @@ -87,7 +90,9 @@ "businessName": { "required": "La ragione sociale è obbligatoria" }, - "entiSelected": { "required": "Seleziona almeno un ente" }, + "entiSelected": { + "required": "Seleziona almeno un ente" + }, "expirationDate": { "required": "La data di termine delega è obbligatoria", "wrong": "Data errata" @@ -128,4 +133,4 @@ "message": "Uno o più dei dati inseriti non sono validi" } } -} +} \ No newline at end of file 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>(