diff --git a/CHANGELOG.md b/CHANGELOG.md index a4dd77afda..af4813bf5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package send-monorepo + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + + +### Bug Fixes + +* **PN-10025:** added aria-label to button with IDP name ([#1138](https://github.com/pagopa/pn-frontend/issues/1138)) ([c24c02b](https://github.com/pagopa/pn-frontend/commit/c24c02be95d92e1e44e5128070d98f0a3c5f3061)) +* **pn-9145:** added test case for duplicated protocol error ([#1120](https://github.com/pagopa/pn-frontend/issues/1120)) ([d2ca754](https://github.com/pagopa/pn-frontend/commit/d2ca754e07ed665211dab7a6d9e92d35b89ddee6)) + + +### Features + +* **PN-9684:** implemented alert in notificationDetail for alternative-RADD ([#1134](https://github.com/pagopa/pn-frontend/issues/1134)) ([bfc1d4a](https://github.com/pagopa/pn-frontend/commit/bfc1d4ad8d085d5691853fb54d1dad6049ca5f92)) + + +### Reverts + +* Revert "Release/v2.3.2" (#1143) ([7cfe17e](https://github.com/pagopa/pn-frontend/commit/7cfe17e1dffd43d0ffc7c0081dbdd538e0691fb6)), closes [#1143](https://github.com/pagopa/pn-frontend/issues/1143) +* Revert "fix(PN-10025): added aria-label to button with IDP name (#1138)" (#1140) ([2559495](https://github.com/pagopa/pn-frontend/commit/25594959e7184c0ae7dc59839845eda7cbd900d5)), closes [#1138](https://github.com/pagopa/pn-frontend/issues/1138) [#1140](https://github.com/pagopa/pn-frontend/issues/1140) + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-20) diff --git a/lerna.json b/lerna.json index c8dd74545d..c43139337a 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.3.2", + "version": "2.4.0", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/pn-commons/CHANGELOG.md b/packages/pn-commons/CHANGELOG.md index 11202f9d15..a63d0ed06d 100644 --- a/packages/pn-commons/CHANGELOG.md +++ b/packages/pn-commons/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-commons + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + + +### Features + +* **PN-9684:** implemented alert in notificationDetail for alternative-RADD ([#1134](https://github.com/pagopa/pn-frontend/issues/1134)) ([bfc1d4a](https://github.com/pagopa/pn-frontend/commit/bfc1d4ad8d085d5691853fb54d1dad6049ca5f92)) + + +### Reverts + +* Revert "Release/v2.3.2" (#1143) ([7cfe17e](https://github.com/pagopa/pn-frontend/commit/7cfe17e1dffd43d0ffc7c0081dbdd538e0691fb6)), closes [#1143](https://github.com/pagopa/pn-frontend/issues/1143) + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-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 5e980cd972..df939766e2 100644 --- a/packages/pn-commons/package.json +++ b/packages/pn-commons/package.json @@ -1,6 +1,6 @@ { "name": "@pagopa-pn/pn-commons", - "version": "2.3.2", + "version": "2.4.0", "private": true, "main": "./src/index.ts", "dependencies": { diff --git a/packages/pn-commons/src/components/ApiError/__test__/ApiErrorWrapper.test.tsx b/packages/pn-commons/src/components/ApiError/__test__/ApiErrorWrapper.test.tsx index 1a79755dec..29c085db3c 100644 --- a/packages/pn-commons/src/components/ApiError/__test__/ApiErrorWrapper.test.tsx +++ b/packages/pn-commons/src/components/ApiError/__test__/ApiErrorWrapper.test.tsx @@ -16,6 +16,7 @@ vi.mock('../../../hooks', () => ({ describe('ApiErrorWrapper', () => { const original = window.location; const reloadText = 'Ricarica'; + const user = userEvent.setup(); beforeAll(() => { Object.defineProperty(window, 'location', { @@ -67,7 +68,7 @@ describe('ApiErrorWrapper', () => { const reloadItemComponent = screen.getByText(reloadText); expect(reloadItemComponent).toBeInTheDocument(); - await userEvent.click(reloadItemComponent); + await user.click(reloadItemComponent); await waitFor(() => { expect(reloadActionMock).toHaveBeenCalled(); @@ -83,7 +84,7 @@ describe('ApiErrorWrapper', () => { const reloadItemComponent = screen.getByText(reloadText); expect(reloadItemComponent).toBeInTheDocument(); - await userEvent.click(reloadItemComponent); + await user.click(reloadItemComponent); await waitFor(() => { expect(window.location.reload).toHaveBeenCalled(); diff --git a/packages/pn-commons/src/components/CodeModal/__test__/CodeInput.test.tsx b/packages/pn-commons/src/components/CodeModal/__test__/CodeInput.test.tsx index 22b2a185e8..fe58883811 100644 --- a/packages/pn-commons/src/components/CodeModal/__test__/CodeInput.test.tsx +++ b/packages/pn-commons/src/components/CodeModal/__test__/CodeInput.test.tsx @@ -8,6 +8,8 @@ import CodeInput from '../CodeInput'; const handleChangeMock = vi.fn(); describe('CodeInput Component', () => { + const user = userEvent.setup(); + afterEach(() => { vi.clearAllMocks(); }); @@ -82,7 +84,7 @@ describe('CodeInput Component', () => { await waitFor(() => { expect(handleChangeMock).toBeCalledTimes(2); // check focus on next elem - expect(codeInputs[3]).toBe(document.activeElement); + expect(codeInputs[3]).toHaveFocus(); }); // change the value of the input and check that it is updated correctly // set the cursor position to the end @@ -90,20 +92,32 @@ describe('CodeInput Component', () => { (codeInputs[2] as HTMLInputElement).setSelectionRange(1, 1); // when we try to edit an input, we insert a second value and after, based on cursor position, we change the value // we must use userEvent because the keyboard event must trigger also the change event (fireEvent doesn't do that) - await userEvent.keyboard('4'); + await user.keyboard('4'); + // next element will be focused + await waitFor(() => { + expect(codeInputs[3]).toHaveFocus(); + }); await waitFor(() => { expect(codeInputs[2]).toHaveValue('4'); }); // move the cursor at the start of the input and try to edit again act(() => (codeInputs[2] as HTMLInputElement).focus()); (codeInputs[2] as HTMLInputElement).setSelectionRange(0, 0); - await userEvent.keyboard('3'); + await user.keyboard('3'); + // next element will be focused + await waitFor(() => { + expect(codeInputs[3]).toHaveFocus(); + }); await waitFor(() => { expect(codeInputs[2]).toHaveValue('3'); }); // delete the value act(() => (codeInputs[2] as HTMLInputElement).focus()); - await userEvent.keyboard('{Backspace}'); + await user.keyboard('{Backspace}'); + // previous element will be focused + await waitFor(() => { + expect(codeInputs[1]).toHaveFocus(); + }); await waitFor(() => { expect(codeInputs[2]).toHaveValue(''); }); @@ -120,52 +134,52 @@ describe('CodeInput Component', () => { // press enter fireEvent.keyDown(codeInputs[0], { key: 'Enter', code: 'Enter' }); await waitFor(() => { - expect(codeInputs[1]).toBe(document.activeElement); + expect(codeInputs[1]).toHaveFocus(); }); // press tab fireEvent.keyDown(codeInputs[1], { key: 'Tab', code: 'Tab' }); await waitFor(() => { - expect(codeInputs[2]).toBe(document.activeElement); + expect(codeInputs[2]).toHaveFocus(); }); // press arrow right fireEvent.keyDown(codeInputs[2], { key: 'ArrowRight', code: 'ArrowRight' }); await waitFor(() => { - expect(codeInputs[3]).toBe(document.activeElement); + expect(codeInputs[3]).toHaveFocus(); }); // press delete fireEvent.keyDown(codeInputs[3], { key: 'Delete', code: 'Delete' }); await waitFor(() => { - expect(codeInputs[4]).toBe(document.activeElement); + expect(codeInputs[4]).toHaveFocus(); }); // press the same value of the input // we reach the end, so we have to lost the focus fireEvent.keyDown(codeInputs[4], { key: '1', code: 'Digit1' }); await waitFor(() => { - expect(document.body).toBe(document.activeElement); + expect(document.body).toHaveFocus(); }); // focus on last input and moove back act(() => (codeInputs[4] as HTMLInputElement).focus()); // press backspace fireEvent.keyDown(codeInputs[4], { key: 'Backspace', code: 'Backspace' }); await waitFor(() => { - expect(codeInputs[3]).toBe(document.activeElement); + expect(codeInputs[3]).toHaveFocus(); }); // press arrow left fireEvent.keyDown(codeInputs[3], { key: 'ArrowLeft', code: 'ArrowLeft' }); await waitFor(() => { - expect(codeInputs[2]).toBe(document.activeElement); + expect(codeInputs[2]).toHaveFocus(); }); // press tab and shift key fireEvent.keyDown(codeInputs[2], { key: 'Tab', code: 'Tab', shiftKey: true }); await waitFor(() => { - expect(codeInputs[1]).toBe(document.activeElement); + expect(codeInputs[1]).toHaveFocus(); }); // focus on first element and try to go back act(() => (codeInputs[0] as HTMLInputElement).focus()); fireEvent.keyDown(codeInputs[0], { key: 'Backspace', code: 'Backspace' }); // nothing happens await waitFor(() => { - expect(codeInputs[0]).toBe(document.activeElement); + expect(codeInputs[0]).toHaveFocus(); }); }); }); diff --git a/packages/pn-commons/src/components/Header/Header.tsx b/packages/pn-commons/src/components/Header/Header.tsx index 36b16e7597..20c0787b12 100644 --- a/packages/pn-commons/src/components/Header/Header.tsx +++ b/packages/pn-commons/src/components/Header/Header.tsx @@ -38,9 +38,11 @@ type HeaderProps = { eventTrackingCallbackProductSwitch?: (target: string) => void; /** Whether there is a logged user */ isLogged?: boolean; + /** Enable assistance button */ + enableAssistanceButton?: boolean; }; -const Header = ({ +const Header: React.FC = ({ onExitAction = () => window.location.assign(''), productsList, showHeaderProduct = true, @@ -53,7 +55,8 @@ const Header = ({ onAssistanceClick, eventTrackingCallbackProductSwitch, isLogged, -}: HeaderProps) => { + enableAssistanceButton, +}) => { const pagoPAHeaderLink: RootLinkType = { ...pagoPALink(), label: 'PagoPA S.p.A.', @@ -107,6 +110,7 @@ const Header = ({ onLogout={onExitAction} enableDropdown={enableDropdown} userActions={userActions} + enableAssistanceButton={enableAssistanceButton} /> {enableHeaderProduct && ( = ({ @@ -68,7 +70,7 @@ const Layout: React.FC = ({ loggedUser, enableUserDropdown, userActions, - onLanguageChanged = () => { }, + onLanguageChanged = () => {}, eventTrackingCallbackAppCrash, eventTrackingCallbackProductSwitch, eventTrackingCallbackRefreshPage, @@ -79,6 +81,7 @@ const Layout: React.FC = ({ hasTermsOfService, privacyPolicyHref, termsOfServiceHref, + enableAssistanceButton = true, }) => ( = ({ onAssistanceClick={onAssistanceClick} eventTrackingCallbackProductSwitch={eventTrackingCallbackProductSwitch} isLogged={isLogged} + enableAssistanceButton={enableAssistanceButton} /> )} diff --git a/packages/pn-commons/src/components/PnDialog/PnDialog.tsx b/packages/pn-commons/src/components/PnDialog/PnDialog.tsx index eb0530ae0d..ecb1df33f7 100644 --- a/packages/pn-commons/src/components/PnDialog/PnDialog.tsx +++ b/packages/pn-commons/src/components/PnDialog/PnDialog.tsx @@ -1,4 +1,4 @@ -import { Children, cloneElement, isValidElement, useMemo } from 'react'; +import { Children, cloneElement, isValidElement } from 'react'; import { Dialog, DialogProps, DialogTitle } from '@mui/material'; @@ -8,9 +8,8 @@ import PnDialogActions from './PnDialogActions'; import PnDialogContent from './PnDialogContent'; const PnDialog: React.FC = (props) => { - const isMobile = useIsMobile(); - const paddingSize = useMemo(() => (isMobile ? 3 : 4), [isMobile]); - + const isMobile = useIsMobile('sm'); + const paddingSize = isMobile ? 3 : 4; const title: ReactComponent = Children.toArray(props.children).find( (child) => isValidElement(child) && child.type === DialogTitle ); @@ -26,11 +25,10 @@ const PnDialog: React.FC = (props) => { (child) => isValidElement(child) && child.type === PnDialogContent ); - const paddingTop = isMobile ? 3 : 4; const enrichedContent = isValidElement(content) ? cloneElement(content, { ...content.props, - sx: { pt: title ? 0 : paddingTop, ...content.props.sx }, + sx: { pt: title ? 0 : paddingSize, ...content.props.sx }, }) : content; diff --git a/packages/pn-commons/src/components/PnDialog/PnDialogActions.tsx b/packages/pn-commons/src/components/PnDialog/PnDialogActions.tsx index b8588a82e4..713412cf95 100644 --- a/packages/pn-commons/src/components/PnDialog/PnDialogActions.tsx +++ b/packages/pn-commons/src/components/PnDialog/PnDialogActions.tsx @@ -1,4 +1,4 @@ -import { Children, cloneElement, isValidElement, useMemo } from 'react'; +import { Children, cloneElement, isValidElement } from 'react'; import { Button, DialogActions, DialogActionsProps } from '@mui/material'; @@ -6,10 +6,7 @@ import { useIsMobile } from '../../hooks'; import { ReactComponent } from '../../models/PnDialog'; const PnDialogActions: React.FC = (props) => { - const isMobile = useIsMobile(); - const paddingSize = useMemo(() => (isMobile ? 3 : 4), [isMobile]); - const gapSize = useMemo(() => (isMobile ? 0 : 1), [isMobile]); - + const isMobile = useIsMobile('sm'); const buttons: Array | undefined = Children.toArray(props.children).filter( (child) => isValidElement(child) && child.type === Button ); @@ -19,7 +16,10 @@ const PnDialogActions: React.FC = (props) => { ? cloneElement(button, { ...button.props, fullWidth: isMobile, - sx: { marginBottom: isMobile && index > 0 ? 2 : 0, ...button.props.sx }, + sx: { + marginBottom: index > 0 && isMobile ? 2 : 0, + ...button.props.sx, + }, }) : button ); @@ -31,9 +31,9 @@ const PnDialogActions: React.FC = (props) => { {...props} sx={{ flexDirection: isMobile ? 'column-reverse' : 'row', - p: paddingSize, + p: isMobile ? 3 : 4, pt: 0, - gap: gapSize, + gap: isMobile ? 0 : 1, ...props.sx, }} > diff --git a/packages/pn-commons/src/components/PnDialog/PnDialogContent.tsx b/packages/pn-commons/src/components/PnDialog/PnDialogContent.tsx index bfe82036ae..4708956809 100644 --- a/packages/pn-commons/src/components/PnDialog/PnDialogContent.tsx +++ b/packages/pn-commons/src/components/PnDialog/PnDialogContent.tsx @@ -1,4 +1,4 @@ -import { Children, cloneElement, isValidElement, useMemo } from 'react'; +import { Children, cloneElement, isValidElement } from 'react'; import { DialogContent, DialogContentProps, DialogContentText } from '@mui/material'; @@ -6,9 +6,7 @@ import { useIsMobile } from '../../hooks'; import { ReactComponent } from '../../models/PnDialog'; const PnDialogContent: React.FC = (props) => { - const isMobile = useIsMobile(); - const paddingSize = useMemo(() => (isMobile ? 3 : 4), [isMobile]); - + const isMobile = useIsMobile('sm'); const subtitle: ReactComponent = Children.toArray(props.children).find( (child) => isValidElement(child) && child.type === DialogContentText ); @@ -28,7 +26,7 @@ const PnDialogContent: React.FC = (props) => { {subtitle && enrichedSubTitle} {othersChildren} diff --git a/packages/pn-commons/src/components/PnDialog/__test__/PnDialog.test.tsx b/packages/pn-commons/src/components/PnDialog/__test__/PnDialog.test.tsx index b43fc40e81..190e409d7c 100644 --- a/packages/pn-commons/src/components/PnDialog/__test__/PnDialog.test.tsx +++ b/packages/pn-commons/src/components/PnDialog/__test__/PnDialog.test.tsx @@ -26,7 +26,7 @@ describe('PnDialog Component', () => { }); it('renders component - mobile', () => { - window.matchMedia = createMatchMedia(800); + window.matchMedia = createMatchMedia(500); const { queryByTestId } = render( Title diff --git a/packages/pn-commons/src/components/PnDialog/__test__/PnDialogActions.test.tsx b/packages/pn-commons/src/components/PnDialog/__test__/PnDialogActions.test.tsx index 7b4536bcd6..cefb46a00b 100644 --- a/packages/pn-commons/src/components/PnDialog/__test__/PnDialogActions.test.tsx +++ b/packages/pn-commons/src/components/PnDialog/__test__/PnDialogActions.test.tsx @@ -26,7 +26,7 @@ describe('PnDialogActions Component', () => { }); it('renders component - mobile', () => { - window.matchMedia = createMatchMedia(800); + window.matchMedia = createMatchMedia(500); const { queryByTestId, queryAllByTestId } = render( @@ -41,8 +41,9 @@ describe('PnDialogActions Component', () => { const buttons = queryAllByTestId('button'); expect(buttons).toHaveLength(2); buttons.forEach((button, index) => { - expect(button).toHaveClass('MuiButton-fullWidth'); - expect(button).toHaveStyle(index === 0 ? 'margin-bottom: 0' : 'margin-bottom: 16px'); + expect(button).toHaveStyle( + index === 0 ? 'margin-bottom: 0' : 'margin-bottom: 16px; width: 100%' + ); }); }); }); diff --git a/packages/pn-commons/src/components/__test__/Prompt.test.tsx b/packages/pn-commons/src/components/__test__/Prompt.test.tsx index b9b06b0b5b..d241b86b64 100644 --- a/packages/pn-commons/src/components/__test__/Prompt.test.tsx +++ b/packages/pn-commons/src/components/__test__/Prompt.test.tsx @@ -15,13 +15,7 @@ const WrappedPrompt = () => { {}} - eventTrackingCallbackCancel={() => {}} - eventTrackingCallbackConfirm={() => {}} - > +
navigate
} diff --git a/packages/pn-commons/src/hooks/useIsMobile.ts b/packages/pn-commons/src/hooks/useIsMobile.ts index 87f96bde76..da4b4e49fc 100644 --- a/packages/pn-commons/src/hooks/useIsMobile.ts +++ b/packages/pn-commons/src/hooks/useIsMobile.ts @@ -1,10 +1,10 @@ -import { useTheme } from '@mui/material'; +import { Breakpoint, useTheme } from '@mui/material'; import useMediaQuery from '@mui/material/useMediaQuery'; /** * Checks if we are on a mobile device */ -export const useIsMobile = () => { +export const useIsMobile = (breakpoint: Breakpoint | number = 'lg') => { const theme = useTheme(); - return useMediaQuery(theme.breakpoints.down('lg')); + return useMediaQuery(theme.breakpoints.down(breakpoint)); }; diff --git a/packages/pn-pa-webapp/CHANGELOG.md b/packages/pn-pa-webapp/CHANGELOG.md index 620e581afe..d0603dab89 100644 --- a/packages/pn-pa-webapp/CHANGELOG.md +++ b/packages/pn-pa-webapp/CHANGELOG.md @@ -3,6 +3,35 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-pa-webapp + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + + +### Bug Fixes + +* **pn-9145:** added test case for duplicated protocol error ([#1120](https://github.com/pagopa/pn-frontend/issues/1120)) ([d2ca754](https://github.com/pagopa/pn-frontend/commit/d2ca754e07ed665211dab7a6d9e92d35b89ddee6)) + + +### Features + +* **PN-9684:** implemented alert in notificationDetail for alternative-RADD ([#1134](https://github.com/pagopa/pn-frontend/issues/1134)) ([bfc1d4a](https://github.com/pagopa/pn-frontend/commit/bfc1d4ad8d085d5691853fb54d1dad6049ca5f92)) + + +### Reverts + +* Revert "Release/v2.3.2" (#1143) ([7cfe17e](https://github.com/pagopa/pn-frontend/commit/7cfe17e1dffd43d0ffc7c0081dbdd538e0691fb6)), closes [#1143](https://github.com/pagopa/pn-frontend/issues/1143) + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-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 7c64a23f0e..0f4337e255 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.3.2", + "version": "2.4.0", "private": true, "dependencies": { "@emotion/react": "^11.11.1", diff --git a/packages/pn-pa-webapp/src/App.tsx b/packages/pn-pa-webapp/src/App.tsx index 5740af4e1a..f8b1c0c7f0 100644 --- a/packages/pn-pa-webapp/src/App.tsx +++ b/packages/pn-pa-webapp/src/App.tsx @@ -191,7 +191,7 @@ const ActualApp = () => { const handleAssistanceClick = () => { /* eslint-disable-next-line functional/immutable-data */ window.location.href = sessionToken - ? `${SELFCARE_BASE_URL}/assistenza` + ? `${SELFCARE_BASE_URL}/assistenza?productId=${productId}` : `mailto:${configuration.PAGOPA_HELP_EMAIL}`; }; diff --git a/packages/pn-pa-webapp/src/components/Notifications/NotificationRecipientsDetail.tsx b/packages/pn-pa-webapp/src/components/Notifications/NotificationRecipientsDetail.tsx index 29b3ec76c3..f2284d9e51 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/NotificationRecipientsDetail.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/NotificationRecipientsDetail.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; -import { Box, Button, DialogTitle, Grid, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; + +import { Box, Button, DialogTitle, Grid, Typography } from '@mui/material'; import { CollapsedList, NotificationDetailRecipient, diff --git a/packages/pn-pa-webapp/src/index.css b/packages/pn-pa-webapp/src/index.css index 8edd71ab23..61b9f4a6f4 100644 --- a/packages/pn-pa-webapp/src/index.css +++ b/packages/pn-pa-webapp/src/index.css @@ -31,18 +31,18 @@ body { background-color: transparent; } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section { +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section { margin: 0; padding: 1rem; width: 100%; } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section:hover, +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section:hover, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-section-mobile:hover { background-color: rgba(23, 50, 77, 0.08); } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section a, +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section a, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-selected, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-section-mobile a { font-size: 1.125rem; @@ -59,11 +59,13 @@ body { .otnotice .otnotice-content .otnotice-section:first-child h2 { font-size: 2.375rem; color: #17324d; + line-height: 3.25rem; } .otnotice .otnotice-content .otnotice-section:not(:first-child) h2 { font-size: 1.75rem; color: #17324d; + line-height: 2.25rem; } -/* END: ONETRUST STYLES */ \ No newline at end of file +/* END: ONETRUST STYLES */ diff --git a/packages/pn-pa-webapp/src/pages/NewApiKey.page.tsx b/packages/pn-pa-webapp/src/pages/NewApiKey.page.tsx index a924462edd..92b5baf2dc 100644 --- a/packages/pn-pa-webapp/src/pages/NewApiKey.page.tsx +++ b/packages/pn-pa-webapp/src/pages/NewApiKey.page.tsx @@ -90,13 +90,7 @@ const NewApiKey = () => { return ( <> {!apiKeySent && ( - {}} // impostare eventi tracking previsti - eventTrackingCallbackCancel={() => {}} // impostare eventi tracking previsti - eventTrackingCallbackConfirm={() => {}} // impostare eventi tracking previsti - > + diff --git a/packages/pn-pa-webapp/src/pages/__test__/NewApiKey.page.test.tsx b/packages/pn-pa-webapp/src/pages/__test__/NewApiKey.page.test.tsx index 33ab91ae24..e19503915d 100644 --- a/packages/pn-pa-webapp/src/pages/__test__/NewApiKey.page.test.tsx +++ b/packages/pn-pa-webapp/src/pages/__test__/NewApiKey.page.test.tsx @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; import { Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; @@ -113,13 +114,14 @@ describe('NewApiKey component', async () => { it('clicks on the breadcrumb button', async () => { // simulate the current URL - window.history.pushState({}, '', '/new-api-key'); + const history = createBrowserHistory(); + history.push(routes.NUOVA_API_KEY); // render using an ad-hoc router await act(async () => { result = render( - } /> + } /> hello} diff --git a/packages/pn-pa-webapp/src/pages/__test__/NewNotification.page.test.tsx b/packages/pn-pa-webapp/src/pages/__test__/NewNotification.page.test.tsx index d07d2464e4..3ed0642555 100644 --- a/packages/pn-pa-webapp/src/pages/__test__/NewNotification.page.test.tsx +++ b/packages/pn-pa-webapp/src/pages/__test__/NewNotification.page.test.tsx @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; import { Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; @@ -86,7 +87,8 @@ describe('NewNotification Page without payment', async () => { it('clicks on the breadcrumb button', async () => { // insert one entry into the history, so the initial render will refer // to the path /new-notification - window.history.pushState({}, '', '/new-notification'); + const history = createBrowserHistory(); + history.push(routes.NUOVA_NOTIFICA); // render with an ad-hoc router, will render initially NewNotification // since it corresponds to the top of the mocked history stack @@ -97,7 +99,7 @@ describe('NewNotification Page without payment', async () => { path={routes.DASHBOARD} element={
hello
} /> - } /> + } />
, { preloadedState: { userState: { user: userResponse } } } ); @@ -129,7 +131,8 @@ describe('NewNotification Page without payment', async () => { it('clicks on api keys button', async () => { // insert one entry into the history, so the initial render will refer // to the path /new-notification - window.history.pushState({}, '', '/new-notification'); + const history = createBrowserHistory(); + history.push(routes.NUOVA_NOTIFICA); // render with an ad-hoc router, will render initially NewNotification // since it corresponds to the top of the mocked history stack @@ -140,7 +143,7 @@ describe('NewNotification Page without payment', async () => { path={routes.API_KEYS} element={
hello
} /> - } /> + } /> , { preloadedState: { userState: { user: userResponse } } } ); diff --git a/packages/pn-pa-webapp/src/pages/__test__/NotificationDetail.page.test.tsx b/packages/pn-pa-webapp/src/pages/__test__/NotificationDetail.page.test.tsx index c81ceb083f..4b2021fe4b 100644 --- a/packages/pn-pa-webapp/src/pages/__test__/NotificationDetail.page.test.tsx +++ b/packages/pn-pa-webapp/src/pages/__test__/NotificationDetail.page.test.tsx @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; import { Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; @@ -349,8 +350,9 @@ describe('NotificationDetail Page', async () => { // insert two entries into the history, so the initial render will refer to the path / // and when the back button is pressed and so navigate(-1) is invoked, // the path will change to /mock-path - window.history.pushState({}, '', '/mock-path'); - window.history.pushState({}, '', '/'); + const history = createBrowserHistory(); + history.push('/mock-path'); + history.push('/'); // render with an ad-hoc router await act(async () => { diff --git a/packages/pn-personafisica-login/CHANGELOG.md b/packages/pn-personafisica-login/CHANGELOG.md index 66f94538bc..8569061cec 100644 --- a/packages/pn-personafisica-login/CHANGELOG.md +++ b/packages/pn-personafisica-login/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-login + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-login + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-20) diff --git a/packages/pn-personafisica-login/package.json b/packages/pn-personafisica-login/package.json index edfadf8285..d27b811c59 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.3.2", + "version": "2.4.0", "private": true, "homepage": "auth", "dependencies": { diff --git a/packages/pn-personafisica-login/src/index.css b/packages/pn-personafisica-login/src/index.css index 9dd0820ff7..61b9f4a6f4 100644 --- a/packages/pn-personafisica-login/src/index.css +++ b/packages/pn-personafisica-login/src/index.css @@ -6,8 +6,8 @@ body { /* START: ONETRUST STYLES */ .otnotice .otnotice-content { - font-family: "Titillium Web", sans-serif; - color: #17324D; + font-family: 'Titillium Web', sans-serif; + color: #17324d; padding-top: 64px; } @@ -31,39 +31,41 @@ body { background-color: transparent; } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section { +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section { margin: 0; padding: 1rem; width: 100%; } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section:hover, +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section:hover, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-section-mobile:hover { background-color: rgba(23, 50, 77, 0.08); } -.otnotice .otnotice-content .otnotice-menu>.otnotice-menu-section a, +.otnotice .otnotice-content .otnotice-menu > .otnotice-menu-section a, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-selected, .otnotice .otnotice-content .otnotice-menu-mobile .otnotice-menu-section-mobile a { font-size: 1.125rem; font-weight: 600; line-height: 1.5; - color: #17324D !important; + color: #17324d !important; } .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__expand::after, .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__collapse::after { - color: #17324D !important; + color: #17324d !important; } .otnotice .otnotice-content .otnotice-section:first-child h2 { font-size: 2.375rem; - color: #17324D; + color: #17324d; + line-height: 3.25rem; } .otnotice .otnotice-content .otnotice-section:not(:first-child) h2 { font-size: 1.75rem; - color: #17324D; + color: #17324d; + line-height: 2.25rem; } -/* END: ONETRUST STYLES */ \ No newline at end of file +/* END: ONETRUST STYLES */ diff --git a/packages/pn-personafisica-webapp/CHANGELOG.md b/packages/pn-personafisica-webapp/CHANGELOG.md index 8182b9ed3c..42ea53b202 100644 --- a/packages/pn-personafisica-webapp/CHANGELOG.md +++ b/packages/pn-personafisica-webapp/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-personafisica-webapp + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + + +### Features + +* **PN-9684:** implemented alert in notificationDetail for alternative-RADD ([#1134](https://github.com/pagopa/pn-frontend/issues/1134)) ([bfc1d4a](https://github.com/pagopa/pn-frontend/commit/bfc1d4ad8d085d5691853fb54d1dad6049ca5f92)) + + +### Reverts + +* Revert "Release/v2.3.2" (#1143) ([7cfe17e](https://github.com/pagopa/pn-frontend/commit/7cfe17e1dffd43d0ffc7c0081dbdd538e0691fb6)), closes [#1143](https://github.com/pagopa/pn-frontend/issues/1143) + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-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 2558bde325..22458b19f1 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.3.2", + "version": "2.4.0", "private": true, "dependencies": { "@emotion/react": "^11.11.1", diff --git a/packages/pn-personafisica-webapp/public/conf/env/config.dev.json b/packages/pn-personafisica-webapp/public/conf/env/config.dev.json index 10c0168cbb..2d25f4f91b 100644 --- a/packages/pn-personafisica-webapp/public/conf/env/config.dev.json +++ b/packages/pn-personafisica-webapp/public/conf/env/config.dev.json @@ -11,5 +11,6 @@ "LANDING_SITE_URL": "https://www.dev.notifichedigitali.it", "DELEGATIONS_TO_PG_ENABLED": true, "WORK_IN_PROGRESS": false, - "F24_DOWNLOAD_WAIT_TIME": 15000 + "F24_DOWNLOAD_WAIT_TIME": 15000, + "PAGOPA_HELP_PP": "https://www.pagopa.it/it/privacy-policy-assistenza" } diff --git a/packages/pn-personafisica-webapp/public/conf/env/config.hotfix.json b/packages/pn-personafisica-webapp/public/conf/env/config.hotfix.json index 93851a33ee..944fa5dc27 100644 --- a/packages/pn-personafisica-webapp/public/conf/env/config.hotfix.json +++ b/packages/pn-personafisica-webapp/public/conf/env/config.hotfix.json @@ -11,5 +11,6 @@ "LANDING_SITE_URL": "https://www.hotfix.notifichedigitali.it", "DELEGATIONS_TO_PG_ENABLED": true, "WORK_IN_PROGRESS": false, - "F24_DOWNLOAD_WAIT_TIME": 15000 + "F24_DOWNLOAD_WAIT_TIME": 15000, + "PAGOPA_HELP_PP": "https://www.pagopa.it/it/privacy-policy-assistenza" } diff --git a/packages/pn-personafisica-webapp/public/conf/env/config.prod.json b/packages/pn-personafisica-webapp/public/conf/env/config.prod.json index b3c58c7fe6..9bb9aa3efc 100644 --- a/packages/pn-personafisica-webapp/public/conf/env/config.prod.json +++ b/packages/pn-personafisica-webapp/public/conf/env/config.prod.json @@ -11,5 +11,6 @@ "LANDING_SITE_URL": "https://notifichedigitali.pagopa.it", "DELEGATIONS_TO_PG_ENABLED": true, "WORK_IN_PROGRESS": false, - "F24_DOWNLOAD_WAIT_TIME": 15000 + "F24_DOWNLOAD_WAIT_TIME": 15000, + "PAGOPA_HELP_PP": "https://www.pagopa.it/it/privacy-policy-assistenza" } diff --git a/packages/pn-personafisica-webapp/public/conf/env/config.test.json b/packages/pn-personafisica-webapp/public/conf/env/config.test.json index a3bd8c8d5b..bff3b53aea 100644 --- a/packages/pn-personafisica-webapp/public/conf/env/config.test.json +++ b/packages/pn-personafisica-webapp/public/conf/env/config.test.json @@ -11,5 +11,6 @@ "LANDING_SITE_URL": "https://www.test.notifichedigitali.it", "DELEGATIONS_TO_PG_ENABLED": true, "WORK_IN_PROGRESS": false, - "F24_DOWNLOAD_WAIT_TIME": 15000 + "F24_DOWNLOAD_WAIT_TIME": 15000, + "PAGOPA_HELP_PP": "https://www.pagopa.it/it/privacy-policy-assistenza" } diff --git a/packages/pn-personafisica-webapp/public/conf/env/config.uat.json b/packages/pn-personafisica-webapp/public/conf/env/config.uat.json index 3c53c5338a..94ff65bd57 100644 --- a/packages/pn-personafisica-webapp/public/conf/env/config.uat.json +++ b/packages/pn-personafisica-webapp/public/conf/env/config.uat.json @@ -11,5 +11,6 @@ "LANDING_SITE_URL": "https://www.uat.notifichedigitali.it", "DELEGATIONS_TO_PG_ENABLED": true, "WORK_IN_PROGRESS": false, - "F24_DOWNLOAD_WAIT_TIME": 15000 + "F24_DOWNLOAD_WAIT_TIME": 15000, + "PAGOPA_HELP_PP": "https://www.pagopa.it/it/privacy-policy-assistenza" } diff --git a/packages/pn-personafisica-webapp/public/locales/it/common.json b/packages/pn-personafisica-webapp/public/locales/it/common.json index f6e7c351e4..45355799f2 100644 --- a/packages/pn-personafisica-webapp/public/locales/it/common.json +++ b/packages/pn-personafisica-webapp/public/locales/it/common.json @@ -15,6 +15,7 @@ "close": "Chiudi", "enable": "Attiva", "disable": "Disattiva", + "continue": "Avanti", "go-to-home": "Vai alla homepage", "go-to-login": "Accedi" }, diff --git a/packages/pn-personafisica-webapp/public/locales/it/support.json b/packages/pn-personafisica-webapp/public/locales/it/support.json new file mode 100644 index 0000000000..6834abc9d4 --- /dev/null +++ b/packages/pn-personafisica-webapp/public/locales/it/support.json @@ -0,0 +1,16 @@ +{ + "title": "Come possiamo aiutarti?", + "sub-title": "Indica l’indirizzo email in cui desideri ricevere le risposte dell’assistenza.", + "form": { + "email": "Inserisci l'indirizzo email", + "confirm-email": "Conferma l'indirizzo email", + "errors": { + "not-valid": "L'indirizzo email non è valido", + "required": "Campo obbligatorio", + "not-the-same": "L'indirizzo email di conferma non è uguale all'indirizzo email inserito" + } + }, + "disclaimer": "Proseguendo dichiari di aver letto la <0>Privacy Policy Assistenza", + "exit-title": "Vuoi davvero uscire?", + "exit-message": "Se esci, la richiesta di assistenza andrà persa." +} diff --git a/packages/pn-personafisica-webapp/src/App.tsx b/packages/pn-personafisica-webapp/src/App.tsx index 1cd038adbd..ea2368e3cf 100644 --- a/packages/pn-personafisica-webapp/src/App.tsx +++ b/packages/pn-personafisica-webapp/src/App.tsx @@ -41,6 +41,7 @@ import { RootState } from './redux/store'; import { getConfiguration } from './services/configuration.service'; import { PFAppErrorFactory } from './utility/AppError/PFAppErrorFactory'; import PFEventStrategyFactory from './utility/MixpanelUtils/PFEventStrategyFactory'; +import showLayoutParts from './utility/layout.utility'; import './utility/onetrust'; // TODO: get products list from be (?) @@ -61,34 +62,13 @@ const productsList: Array = [ // E.g. if a user types the URL with the path /non-accessbile, the App component runs just once. // In "normal" cases, the SessionGuard initialization forces App to render more than one // and therefore to make i18n to be initialized by the time something actually renders. -// -// In turn, adding the ternary operator in the return statement provokes -// the "too high computational complexity" warning to appear -// (in fact it jumps to <= 15 to 30!!). -// The only way I found to prevent it is to split the initialization in a separate React component. // ---------------------------------------- // Carlos Lombardi, 2023.05.26 // ---------------------------------------- const App = () => { - const { t } = useTranslation(['common', 'notifiche']); - const [isInitialized, setIsInitialized] = useState(false); - - useEffect(() => { - if (!isInitialized) { - setIsInitialized(true); - // init localization - initLocalization((namespace, path, data) => t(path, { ns: namespace, ...data })); - // eslint-disable-next-line functional/immutable-data - errorFactoryManager.factory = new PFAppErrorFactory((path, ns) => t(path, { ns })); - } - }, [isInitialized]); - - return isInitialized ? :
; -}; - -const ActualApp = () => { const dispatch = useAppDispatch(); const { t, i18n } = useTranslation(['common', 'notifiche']); + const [isInitialized, setIsInitialized] = useState(false); const loggedUser = useAppSelector((state: RootState) => state.userState.user); const { tosConsent, fetchedTos, privacyConsent, fetchedPrivacy } = useAppSelector( (state: RootState) => state.userState @@ -113,7 +93,16 @@ const ActualApp = () => { [loggedUser] ); - const isPrivacyPage = path[1] === 'privacy-tos'; + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = useMemo( + () => + showLayoutParts( + path[1], + !!sessionToken, + tosConsent && tosConsent.accepted && fetchedTos, + privacyConsent && privacyConsent.accepted && fetchedPrivacy + ), + [path[1], sessionToken, tosConsent, fetchedTos, privacyConsent, fetchedPrivacy] + ); const userActions = useMemo(() => { const profiloAction = { @@ -140,14 +129,6 @@ const ActualApp = () => { useTracking(MIXPANEL_TOKEN, process.env.NODE_ENV); - useEffect(() => { - if (sessionToken !== '') { - void dispatch(getDomicileInfo()); - void dispatch(getSidemenuInformation()); - void dispatch(getCurrentAppStatus()); - } - }, [sessionToken]); - const mapDelegatorSideMenuItem = (): Array | undefined => { // Implementazione esplorativa su come potrebbe gestire l'errore dell'API // che restituisce i delegators per il sideMenu. @@ -224,6 +205,12 @@ const ActualApp = () => { }; const handleAssistanceClick = () => { + // if user is logged in, we redirect to support page + // otherwise, we open the email provider + if (sessionToken) { + navigate(routes.SUPPORT); + return; + } /* eslint-disable-next-line functional/immutable-data */ window.location.href = `mailto:${PAGOPA_HELP_EMAIL}`; }; @@ -271,27 +258,38 @@ const ActualApp = () => { }); }; + useEffect(() => { + if (sessionToken !== '') { + void dispatch(getDomicileInfo()); + void dispatch(getSidemenuInformation()); + void dispatch(getCurrentAppStatus()); + } + }, [sessionToken]); + + useEffect(() => { + if (!isInitialized) { + setIsInitialized(true); + // init localization + initLocalization((namespace, path, data) => t(path, { ns: namespace, ...data })); + // eslint-disable-next-line functional/immutable-data + errorFactoryManager.factory = new PFAppErrorFactory((path, ns) => t(path, { ns })); + } + }, [isInitialized]); + + if (!isInitialized) { + return
; + } + return ( <> } - showSideMenu={ - !!sessionToken && - tosConsent && - tosConsent.accepted && - fetchedTos && - privacyConsent && - privacyConsent.accepted && - fetchedPrivacy && - !isPrivacyPage - } + showSideMenu={showSideMenu} productsList={productsList} - showHeaderProduct={ - tosConsent && tosConsent.accepted && privacyConsent && privacyConsent.accepted - } + showHeaderProduct={showHeaderProduct} loggedUser={jwtUser} enableUserDropdown userActions={userActions} @@ -301,6 +299,7 @@ const ActualApp = () => { hasTermsOfService={true} eventTrackingCallbackAppCrash={handleEventTrackingCallbackAppCrash} eventTrackingCallbackRefreshPage={handleEventTrackingCallbackRefreshPage} + enableAssistanceButton={showAssistanceButton} > {/* await dispatch(logout())} /> */} diff --git a/packages/pn-personafisica-webapp/src/api/support/Support.api.ts b/packages/pn-personafisica-webapp/src/api/support/Support.api.ts new file mode 100644 index 0000000000..91ff132a2f --- /dev/null +++ b/packages/pn-personafisica-webapp/src/api/support/Support.api.ts @@ -0,0 +1,10 @@ +import { ZendeskAuthorizationDTO } from '../../models/Support'; +import { apiClient } from '../apiClients'; +import { ZENDESK_AUTHORIZATION } from './support.routes'; + +export const SupportApi = { + zendeskAuthorization: (params: string): Promise => + apiClient + .post(ZENDESK_AUTHORIZATION(), { email: params }) + .then((response) => response.data), +}; diff --git a/packages/pn-personafisica-webapp/src/api/support/__test__/Support.api.test.ts b/packages/pn-personafisica-webapp/src/api/support/__test__/Support.api.test.ts new file mode 100644 index 0000000000..e37da55bc6 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/api/support/__test__/Support.api.test.ts @@ -0,0 +1,37 @@ +import MockAdapter from 'axios-mock-adapter'; + +import { mockAuthentication } from '../../../__mocks__/Auth.mock'; +import { apiClient } from '../../apiClients'; +import { SupportApi } from '../Support.api'; +import { ZENDESK_AUTHORIZATION } from '../support.routes'; + +describe('Support api tests', () => { + let mock: MockAdapter; + + mockAuthentication(); + + beforeAll(() => { + mock = new MockAdapter(apiClient); + }); + + afterEach(() => { + mock.reset(); + }); + + afterAll(() => { + mock.restore(); + }); + + it('zendeskAuthorization', async () => { + const mail = 'mail@di-prova.it'; + const response = { + action: 'https://zendesk-url.com', + jwt: 'zendesk-jwt', + return_to: 'https://suuport-url.com', + }; + + mock.onPost(ZENDESK_AUTHORIZATION(), { email: mail }).reply(200, response); + const res = await SupportApi.zendeskAuthorization(mail); + expect(res).toStrictEqual(response); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/api/support/__test__/support.routes.test.ts b/packages/pn-personafisica-webapp/src/api/support/__test__/support.routes.test.ts new file mode 100644 index 0000000000..63245ae7be --- /dev/null +++ b/packages/pn-personafisica-webapp/src/api/support/__test__/support.routes.test.ts @@ -0,0 +1,8 @@ +import { ZENDESK_AUTHORIZATION } from '../support.routes'; + +describe('Support routes', () => { + it('should compile ZENDESK_AUTHORIZATION', () => { + const route = ZENDESK_AUTHORIZATION(); + expect(route).toEqual('/zendesk-authorization/new-support-request'); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/api/support/support.routes.ts b/packages/pn-personafisica-webapp/src/api/support/support.routes.ts new file mode 100644 index 0000000000..a644b7e275 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/api/support/support.routes.ts @@ -0,0 +1,16 @@ +import { compileRoute } from '@pagopa-pn/pn-commons'; + +// Segments +const API_SUPPORT_AUTHORIZATION_BASE = 'zendesk-authorization'; +const API_NEW_SUPPORT_REQUEST = 'new-support-request'; + +// Paths +const API_SUPPORT_AUTHORIZATION_PATH = `${API_SUPPORT_AUTHORIZATION_BASE}/${API_NEW_SUPPORT_REQUEST}`; + +// APIs +export function ZENDESK_AUTHORIZATION() { + return compileRoute({ + prefix: '', + path: API_SUPPORT_AUTHORIZATION_PATH, + }); +} diff --git a/packages/pn-personafisica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx index 3829586c2e..687eb238e9 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx @@ -28,11 +28,11 @@ const fields = [ id: 'label', component: 'PEC', size: 'variable' as 'variable' | 'auto', - key: 'key', + key: 'label-key', }, { id: 'value', - key: 'key', + key: 'value-key', component: ( = ({ data }) => { + const { action_url: url, jwt, return_to: returnTo } = data; + + useEffect(() => { + if (url && jwt && returnTo) { + const form = document.getElementById('jwtForm') as HTMLFormElement; + if (form) { + form.submit(); + } + } + }, [url, jwt, returnTo]); + + return ( +
+ + +
+ ); +}; + +export default ZendeskForm; diff --git a/packages/pn-personafisica-webapp/src/index.css b/packages/pn-personafisica-webapp/src/index.css index 8f0316c1ca..649c9c6f0c 100644 --- a/packages/pn-personafisica-webapp/src/index.css +++ b/packages/pn-personafisica-webapp/src/index.css @@ -6,8 +6,8 @@ body { /* START: ONETRUST STYLES */ .otnotice .otnotice-content { - font-family: "Titillium Web",sans-serif; - color: #17324D; + font-family: 'Titillium Web', sans-serif; + color: #17324d; padding-top: 64px; } @@ -48,25 +48,27 @@ body { font-size: 1.125rem; font-weight: 600; line-height: 1.5; - color: #17324D !important; + color: #17324d !important; } .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__expand::after, .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__collapse::after { - color: #17324D !important; + color: #17324d !important; } .otnotice .otnotice-content .otnotice-section:first-child h2 { font-size: 2.375rem; - color: #17324D; + color: #17324d; + line-height: 3.25rem; } .otnotice .otnotice-content .otnotice-section:not(:first-child) h2 { font-size: 1.75rem; - color: #17324D; + color: #17324d; + line-height: 2.25rem; } .otnotice-content .otnotice-sections::after { - font-size: 6rem!important; + font-size: 6rem !important; } -/* END: ONETRUST STYLES */ \ No newline at end of file +/* END: ONETRUST STYLES */ diff --git a/packages/pn-personafisica-webapp/src/models/Support.ts b/packages/pn-personafisica-webapp/src/models/Support.ts new file mode 100644 index 0000000000..aa0f36d4c3 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/models/Support.ts @@ -0,0 +1,10 @@ +export interface SupportForm { + email: string; + confirmEmail: string; +} + +export interface ZendeskAuthorizationDTO { + action_url: string; + jwt: string; + return_to: string; +} diff --git a/packages/pn-personafisica-webapp/src/navigation/routes.const.ts b/packages/pn-personafisica-webapp/src/navigation/routes.const.ts index 95271e62fb..5654c629c7 100644 --- a/packages/pn-personafisica-webapp/src/navigation/routes.const.ts +++ b/packages/pn-personafisica-webapp/src/navigation/routes.const.ts @@ -22,3 +22,4 @@ export const APP_STATUS = '/app-status'; export { PRIVACY_POLICY, TERMS_OF_SERVICE }; export const PARTICIPATING_ENTITIES = '/informativa-aderenti'; export const NOT_ACCESSIBLE = '/non-accessibile'; +export const SUPPORT = '/assistenza'; diff --git a/packages/pn-personafisica-webapp/src/navigation/routes.tsx b/packages/pn-personafisica-webapp/src/navigation/routes.tsx index 2cb14cc61a..7e8f4b83bd 100644 --- a/packages/pn-personafisica-webapp/src/navigation/routes.tsx +++ b/packages/pn-personafisica-webapp/src/navigation/routes.tsx @@ -1,13 +1,14 @@ import { Suspense } from 'react'; -import { Routes, Route } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; + import { AppNotAccessible, LoadingPage, NotFound, lazyRetry } from '@pagopa-pn/pn-commons'; import { getConfiguration } from '../services/configuration.service'; -import * as routes from './routes.const'; -import SessionGuard from './SessionGuard'; +import AARGuard from './AARGuard'; import RouteGuard from './RouteGuard'; +import SessionGuard from './SessionGuard'; import ToSGuard from './ToSGuard'; -import AARGuard from './AARGuard'; +import * as routes from './routes.const'; const Profile = lazyRetry(() => import('../pages/Profile.page')); const Notifiche = lazyRetry(() => import('../pages/Notifiche.page')); @@ -19,6 +20,7 @@ const PrivacyPolicyPage = lazyRetry(() => import('../pages/PrivacyPolicy.page')) const TermsOfServicePage = lazyRetry(() => import('../pages/TermsOfService.page')); const AppStatus = lazyRetry(() => import('../pages/AppStatus.page')); const ParticipatingEntitiesPage = lazyRetry(() => import('../pages/ParticipatingEntities.page')); +const SupportPage = lazyRetry(() => import('../pages/Support.page')); const handleAssistanceClick = () => { /* eslint-disable-next-line functional/immutable-data */ @@ -43,6 +45,7 @@ function Router() { } /> } /> } /> + } /> {/* not found - non-logged users will see the common AccessDenied component */} diff --git a/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx b/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx index d419ff15f2..5b9aeed07f 100644 --- a/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx @@ -306,7 +306,7 @@ const NuovaDelega = () => { alignItems="center" direction={isMobile ? 'column' : 'row'} spacing={1} - flex="1 0 100px" + flex="1 0" > {values.selectPersonaFisicaOrPersonaGiuridica === RecipientType.PF && ( { { diff --git a/packages/pn-personafisica-webapp/src/pages/Support.page.tsx b/packages/pn-personafisica-webapp/src/pages/Support.page.tsx new file mode 100644 index 0000000000..e36b37d367 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/pages/Support.page.tsx @@ -0,0 +1,208 @@ +import { useReducer, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import { Box, Button, Paper, Stack, TextField, Typography } from '@mui/material'; +import { Prompt, TitleBox } from '@pagopa-pn/pn-commons'; +import { ValidationError } from '@pagopa-pn/pn-validator'; +import { ButtonNaked } from '@pagopa/mui-italia'; + +import ZendeskForm from '../components/Support/ZendeskForm'; +import { SupportForm, ZendeskAuthorizationDTO } from '../models/Support'; +import { useAppDispatch } from '../redux/hooks'; +import { zendeskAuthorization } from '../redux/support/actions'; +import { getConfiguration } from '../services/configuration.service'; +import validator from '../validators/SupportFormValidator'; + +type FormState = { + email: { + touched: boolean; + value: string; + }; + confirmEmail: { + touched: boolean; + value: string; + }; + errors: null | ValidationError; +}; + +const reducer = (state: FormState, action: { type: string; payload: string }) => { + switch (action.type) { + case 'UPDATE_EMAIL': + return { + ...state, + email: { + value: action.payload, + touched: true, + }, + errors: validator.validate({ + email: action.payload, + confirmEmail: state.confirmEmail.value, + }), + }; + case 'UPDATE_CONFIRM_EMAIL': + return { + ...state, + confirmEmail: { + value: action.payload, + touched: true, + }, + errors: validator.validate({ + email: state.email.value, + confirmEmail: action.payload, + }), + }; + default: + return state; + } +}; + +const SupportPPButton: React.FC<{ children?: React.ReactNode }> = ({ children }) => { + const { PAGOPA_HELP_PP } = getConfiguration(); + const handleClick = () => { + window.location.assign(PAGOPA_HELP_PP); + }; + + return ( + + + {children} + + + ); +}; + +const SupportPage: React.FC = () => { + const { t } = useTranslation(['support', 'common']); + const [formData, formDispatch] = useReducer(reducer, { + email: { + value: '', + touched: false, + }, + confirmEmail: { + value: '', + touched: false, + }, + errors: validator.validate({ email: '', confirmEmail: '' }), + }); + const [zendeskAuthData, setZendeskAuthData] = useState({ + action_url: '', + return_to: '', + jwt: '', + }); + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + + const handleChange = ( + type: 'UPDATE_EMAIL' | 'UPDATE_CONFIRM_EMAIL', + event: React.ChangeEvent + ) => { + formDispatch({ type, payload: event.target.value }); + }; + + const handleCancel = () => { + navigate(-1); + }; + + const handleConfirm = () => { + if (!formData.errors) { + dispatch(zendeskAuthorization(formData.email.value)) + .unwrap() + .then((response) => { + setZendeskAuthData(response); + }) + .catch(() => {}); + } + }; + + return ( + + + + + +
+ handleChange('UPDATE_EMAIL', e)} + value={formData.email.value} + error={formData.email.touched && !!formData.errors?.email} + helperText={ + formData.email.touched && + !!formData.errors?.email && + t('form.errors.' + formData.errors?.email) + } + /> + handleChange('UPDATE_CONFIRM_EMAIL', e)} + value={formData.confirmEmail.value} + error={formData.confirmEmail.touched && !!formData.errors?.confirmEmail} + helperText={ + formData.confirmEmail.touched && + !!formData.errors?.confirmEmail && + t('form.errors.' + formData.errors?.confirmEmail) + } + /> + +
+ + ]} + variant="body2" + /> + + + + + +
+ +
+
+ ); +}; + +export default SupportPage; diff --git a/packages/pn-personafisica-webapp/src/pages/__test__/NuovaDelega.test.tsx b/packages/pn-personafisica-webapp/src/pages/__test__/NuovaDelega.test.tsx index 4e1a6ee546..3a64ecc374 100644 --- a/packages/pn-personafisica-webapp/src/pages/__test__/NuovaDelega.test.tsx +++ b/packages/pn-personafisica-webapp/src/pages/__test__/NuovaDelega.test.tsx @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; import { Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; @@ -133,15 +134,16 @@ describe('NuovaDelega page', async () => { let result: RenderResult | undefined; // simulate the current URL - window.history.pushState({}, '', '/nuova-delega'); + const history = createBrowserHistory(); + history.push(routes.NUOVA_DELEGA); // render with an ad-hoc router, will render initially NuovaDelega // since it corresponds to the top of the mocked history stack await act(async () => { result = render( - hello
} /> - } /> + hello} /> + } /> ); }); diff --git a/packages/pn-personafisica-webapp/src/pages/__test__/Support.page.test.tsx b/packages/pn-personafisica-webapp/src/pages/__test__/Support.page.test.tsx new file mode 100644 index 0000000000..42c4f6a445 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/pages/__test__/Support.page.test.tsx @@ -0,0 +1,161 @@ +import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; +import { ReactNode } from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { vi } from 'vitest'; + +import { getById, testInput } from '@pagopa-pn/pn-commons/src/test-utils'; + +import { RenderResult, act, fireEvent, render, waitFor, within } from '../../__test__/test-utils'; +import { apiClient } from '../../api/apiClients'; +import { ZENDESK_AUTHORIZATION } from '../../api/support/support.routes'; +import * as routes from '../../navigation/routes.const'; +import SupportPage from '../Support.page'; + +// mock imports +vi.mock('react-i18next', () => ({ + // this mock makes sure any components using the translate hook can use it without a warning being shown + useTranslation: () => ({ + t: (str: string) => str, + }), + Trans: (props: { i18nKey: string; components?: Array }) => ( + <> + {props.i18nKey} {props.components!.map((c) => c)} + + ), +})); + +describe('Support page', async () => { + let result: RenderResult; + let mock: MockAdapter; + + beforeAll(() => { + mock = new MockAdapter(apiClient); + }); + + afterEach(() => { + mock.reset(); + }); + + afterAll(() => { + mock.restore(); + }); + + it('render page', () => { + const { container, getByTestId } = render(); + expect(container).toHaveTextContent(/title/); + expect(container).toHaveTextContent(/sub-title/); + const form = getByTestId('supportForm'); + expect(form).toBeInTheDocument(); + const email = getById(form, 'mail'); + expect(email).toBeInTheDocument(); + expect(email).toHaveValue(''); + const confirmEmail = getById(form, 'confirmMail'); + expect(confirmEmail).toBeInTheDocument(); + expect(confirmEmail).toHaveValue(''); + const backButton = getByTestId('backButton'); + expect(backButton).toBeInTheDocument(); + const continueButton = getByTestId('continueButton'); + expect(continueButton).toBeInTheDocument(); + expect(continueButton).toBeDisabled(); + }); + + it('fill values - OK', () => { + const { getByTestId } = render(); + const form = getByTestId('supportForm'); + testInput(form, 'mail', 'mail@example.it'); + testInput(form, 'confirmMail', 'mail@example.it'); + const continueButton = getByTestId('continueButton'); + expect(continueButton).toBeEnabled(); + }); + + it('fill values - KO - emails not the same', () => { + const { getByTestId } = render(); + const form = getByTestId('supportForm'); + testInput(form, 'mail', 'mail@example.it'); + testInput(form, 'confirmMail', 'mail-divergent@example.it'); + const continueButton = getByTestId('continueButton'); + expect(continueButton).toBeDisabled(); + const helperText = getById(form, 'confirmMail-helper-text'); + expect(helperText).toHaveTextContent('form.errors.not-the-same'); + }); + + it('fill values - KO - invalid emails', () => { + const { getByTestId } = render(); + const form = getByTestId('supportForm'); + testInput(form, 'mail', 'mail-wrong.it'); + testInput(form, 'confirmMail', 'mail-wrong.it'); + const continueButton = getByTestId('continueButton'); + expect(continueButton).toBeDisabled(); + const helperTextMail = getById(form, 'mail-helper-text'); + expect(helperTextMail).toHaveTextContent('form.errors.not-valid'); + const helperTextConfirmMail = getById(form, 'confirmMail-helper-text'); + expect(helperTextConfirmMail).toHaveTextContent('form.errors.not-valid'); + }); + + it('click back button and show prompt', async () => { + // insert two entries into the history, so the initial render will refer to the path /assistenza + // and when the back button is pressed and so navigate(-1) is invoked, + // the path will change to /notifiche + const history = createBrowserHistory(); + history.push(routes.NOTIFICHE); + history.push(routes.SUPPORT); + + // render with an ad-hoc router, will render initially SupportPage + // since it corresponds to the top of the mocked history stack + await act(async () => { + result = render( + + hello} + /> + } /> + + ); + }); + + // before clicking the button - mocked dashboard not present + const mockedPageBefore = result.queryByTestId('mocked-dashboard'); + expect(mockedPageBefore).not.toBeInTheDocument(); + + // simulate clicking the button + const backButton = result.getByTestId('backButton'); + fireEvent.click(backButton); + + // prompt must be shown + const promptDialog = await waitFor(() => result.getByTestId('promptDialog')); + expect(promptDialog).toBeInTheDocument(); + const confirmExitBtn = within(promptDialog!).getByTestId('confirmExitBtn'); + fireEvent.click(confirmExitBtn); + + // after clicking button - mocked dashboard present + await waitFor(() => { + const mockedPageAfter = result.queryByTestId('mocked-dashboard'); + expect(mockedPageAfter).toBeInTheDocument(); + }); + }); + + it('submit form', async () => { + const mail = 'mail@example.it'; + const response = { + action: 'https://zendesk-url.com', + jwt: 'zendesk-jwt', + return_to: 'https://suuport-url.com', + }; + + mock.onPost(ZENDESK_AUTHORIZATION(), { email: mail }).reply(200, response); + + const { getByTestId } = render(); + const form = getByTestId('supportForm'); + testInput(form, 'mail', mail); + testInput(form, 'confirmMail', mail); + const continueButton = getByTestId('continueButton'); + expect(continueButton).toBeEnabled(); + fireEvent.click(continueButton); + await waitFor(() => { + expect(mock.history.post).toHaveLength(1); + expect(mock.history.post[0].url).toBe(ZENDESK_AUTHORIZATION()); + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/redux/support/actions.ts b/packages/pn-personafisica-webapp/src/redux/support/actions.ts new file mode 100644 index 0000000000..f42280739a --- /dev/null +++ b/packages/pn-personafisica-webapp/src/redux/support/actions.ts @@ -0,0 +1,10 @@ +import { performThunkAction } from '@pagopa-pn/pn-commons'; +import { createAsyncThunk } from '@reduxjs/toolkit'; + +import { SupportApi } from '../../api/support/Support.api'; +import { ZendeskAuthorizationDTO } from '../../models/Support'; + +export const zendeskAuthorization = createAsyncThunk( + 'zendeskAuthorization', + performThunkAction((params: string) => SupportApi.zendeskAuthorization(params)) +); diff --git a/packages/pn-personafisica-webapp/src/services/configuration.service.ts b/packages/pn-personafisica-webapp/src/services/configuration.service.ts index 1dbf9afb45..a1e49b978b 100644 --- a/packages/pn-personafisica-webapp/src/services/configuration.service.ts +++ b/packages/pn-personafisica-webapp/src/services/configuration.service.ts @@ -19,26 +19,22 @@ interface PfConfigurationFromFile { DELEGATIONS_TO_PG_ENABLED: boolean; WORK_IN_PROGRESS?: boolean; F24_DOWNLOAD_WAIT_TIME: number; + PAGOPA_HELP_PP: string; } interface PfConfiguration extends PfConfigurationFromFile { DISABLE_INACTIVITY_HANDLER: boolean; IS_DEVELOP: boolean; LOG_REDUX_ACTIONS: boolean; - MOCK_USER: boolean; ONE_TRUST_DRAFT_MODE: boolean; ONE_TRUST_PARTICIPATING_ENTITIES: string; ONE_TRUST_PP: string; ONE_TRUST_TOS: string; OT_DOMAIN_ID: string; - PAGOPA_HELP_EMAIL: string; PAYMENT_DISCLAIMER_URL: string; VERSION: string; URL_FE_LOGOUT: string; - LANDING_SITE_URL: string; - DELEGATIONS_TO_PG_ENABLED: boolean; WORK_IN_PROGRESS: boolean; - F24_DOWNLOAD_WAIT_TIME: number; } class PfConfigurationValidator extends Validator { @@ -58,6 +54,7 @@ class PfConfigurationValidator extends Validator { this.ruleFor('DELEGATIONS_TO_PG_ENABLED').isBoolean(); this.ruleFor('WORK_IN_PROGRESS').isBoolean(); this.ruleFor('F24_DOWNLOAD_WAIT_TIME').isNumber(); + this.ruleFor('PAGOPA_HELP_PP').isString(); } makeRequired(rule: StringRuleValidator): void { @@ -80,9 +77,8 @@ export function getConfiguration(): PfConfiguration { OT_DOMAIN_ID: configurationFromFile.OT_DOMAIN_ID || '', PAYMENT_DISCLAIMER_URL: configurationFromFile.PAYMENT_DISCLAIMER_URL || '', IS_DEVELOP, - MOCK_USER: IS_DEVELOP, LOG_REDUX_ACTIONS: IS_DEVELOP, - URL_FE_LOGOUT: "/auth/logout", + URL_FE_LOGOUT: '/auth/logout', VERSION, LANDING_SITE_URL: configurationFromFile.LANDING_SITE_URL || '', DELEGATIONS_TO_PG_ENABLED: Boolean(configurationFromFile.DELEGATIONS_TO_PG_ENABLED), diff --git a/packages/pn-personafisica-webapp/src/setupTests.ts b/packages/pn-personafisica-webapp/src/setupTests.ts index 3f2288caac..77f3f1db22 100644 --- a/packages/pn-personafisica-webapp/src/setupTests.ts +++ b/packages/pn-personafisica-webapp/src/setupTests.ts @@ -21,7 +21,6 @@ beforeAll(() => { PAGOPA_HELP_EMAIL: 'assistenza@pn.it', IS_DEVELOP: false, MIXPANEL_TOKEN: 'DUMMY', - MOCK_USER: false, LOG_REDUX_ACTIONS: false, APP_VERSION: 'mock-version', SELFCARE_URL_FE_LOGIN: 'mock-selfcare-login', @@ -29,6 +28,7 @@ beforeAll(() => { IS_PAYMENT_ENABLED: false, DELEGATIONS_TO_PG_ENABLED: true, LANDING_SITE_URL: 'https://www.dev.notifichedigitali.it', + PAGOPA_HELP_PP: 'https://www.fake-page.it', }); initStore(false); initAxiosClients(); diff --git a/packages/pn-personafisica-webapp/src/utility/__test__/layout.utility.test.ts b/packages/pn-personafisica-webapp/src/utility/__test__/layout.utility.test.ts new file mode 100644 index 0000000000..84fec649c0 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/__test__/layout.utility.test.ts @@ -0,0 +1,53 @@ +import showLayoutParts from '../layout.utility'; + +describe('Tests layout utility', () => { + it('everything is shown', () => { + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = + showLayoutParts('test-path', true, true, true); + expect(showHeader).toBeTruthy(); + expect(showFooter).toBeTruthy(); + expect(showSideMenu).toBeTruthy(); + expect(showHeaderProduct).toBeTruthy(); + expect(showAssistanceButton).toBeTruthy(); + }); + + it('privacy-tos page', () => { + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = + showLayoutParts('privacy-tos', true, true, true); + expect(showHeader).toBeFalsy(); + expect(showFooter).toBeFalsy(); + expect(showSideMenu).toBeFalsy(); + expect(showHeaderProduct).toBeTruthy(); + expect(showAssistanceButton).toBeTruthy(); + }); + + it('assistance page', () => { + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = + showLayoutParts('assistenza', true, true, true); + expect(showHeader).toBeTruthy(); + expect(showFooter).toBeTruthy(); + expect(showSideMenu).toBeFalsy(); + expect(showHeaderProduct).toBeFalsy(); + expect(showAssistanceButton).toBeFalsy(); + }); + + it('not logged user', () => { + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = + showLayoutParts('test-path', false, true, true); + expect(showHeader).toBeTruthy(); + expect(showFooter).toBeTruthy(); + expect(showSideMenu).toBeFalsy(); + expect(showHeaderProduct).toBeTruthy(); + expect(showAssistanceButton).toBeTruthy(); + }); + + it('privacy and tos not accepted', () => { + const [showHeader, showFooter, showSideMenu, showHeaderProduct, showAssistanceButton] = + showLayoutParts('test-path', true, false, false); + expect(showHeader).toBeTruthy(); + expect(showFooter).toBeTruthy(); + expect(showSideMenu).toBeFalsy(); + expect(showHeaderProduct).toBeFalsy(); + expect(showAssistanceButton).toBeTruthy(); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/layout.utility.ts b/packages/pn-personafisica-webapp/src/utility/layout.utility.ts new file mode 100644 index 0000000000..3bae2e7d0a --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/layout.utility.ts @@ -0,0 +1,47 @@ +import * as routes from '../navigation/routes.const'; + +function showHeader(path: string): boolean { + return path !== 'privacy-tos'; +} + +function showFooter(path: string): boolean { + return path !== 'privacy-tos'; +} + +function showSideMenu( + path: string, + isLogged: boolean, + tosAccepted: boolean, + privacyAccepted: boolean +): boolean { + return ( + path !== 'privacy-tos' && + path !== routes.SUPPORT.slice(1) && + isLogged && + tosAccepted && + privacyAccepted + ); +} + +function showHeaderProduct(path: string, tosAccepted: boolean, privacyAccepted: boolean): boolean { + return path !== routes.SUPPORT.slice(1) && tosAccepted && privacyAccepted; +} + +function showAssistanceButton(path: string): boolean { + return path !== routes.SUPPORT.slice(1); +} + +export default function showLayoutParts( + path: string, + isLogged: boolean, + tosAccepted: boolean, + privacyAccepted: boolean +) { + return [ + showHeader(path), + showFooter(path), + showSideMenu(path, isLogged, tosAccepted, privacyAccepted), + showHeaderProduct(path, tosAccepted, privacyAccepted), + showAssistanceButton(path), + ]; +} diff --git a/packages/pn-personafisica-webapp/src/validators/SupportFormValidator.ts b/packages/pn-personafisica-webapp/src/validators/SupportFormValidator.ts new file mode 100644 index 0000000000..9072434af3 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/validators/SupportFormValidator.ts @@ -0,0 +1,28 @@ +import { dataRegex } from '@pagopa-pn/pn-commons'; +import { Validator } from '@pagopa-pn/pn-validator'; + +import { SupportForm } from '../models/Support'; + +function makeRequired(rule: any): void { + rule.not().isEmpty('required').not().isUndefined('required').not().isNull('required'); +} + +class SupportFormValidator extends Validator { + constructor() { + super(); + makeRequired(this.ruleFor('email').isString().matches(dataRegex.email, 'not-valid')); + makeRequired( + this.ruleFor('confirmEmail') + .isString() + .matches(dataRegex.email, 'not-valid') + .customValidator((value: string, model: SupportForm) => { + if (value === model.email) { + return null; + } + return 'not-the-same'; + }) + ); + } +} + +export default new SupportFormValidator(); diff --git a/packages/pn-personafisica-webapp/src/validators/__test__/SupportFormValidator.test.ts b/packages/pn-personafisica-webapp/src/validators/__test__/SupportFormValidator.test.ts new file mode 100644 index 0000000000..f84a834601 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/validators/__test__/SupportFormValidator.test.ts @@ -0,0 +1,35 @@ +import validator from '../SupportFormValidator'; + +describe('Tests SupportFormValidator', () => { + it('validation OK', () => { + const result = validator.validate({ email: 'test@mail.it', confirmEmail: 'test@mail.it' }); + expect(result).toBeNull(); + }); + + it('validation KO - divergent emails', () => { + const result = validator.validate({ + email: 'test@mail.it', + confirmEmail: 'test-divergent@mail.it', + }); + expect(result).toEqual({ confirmEmail: 'not-the-same' }); + }); + + it('validation KO - empty email', () => { + const result = validator.validate({ + email: '', + confirmEmail: 'test@mail.it', + }); + expect(result).toEqual({ + email: 'not-valid', + confirmEmail: 'not-the-same', + }); + }); + + it('validation KO - empty confirmation email', () => { + const result = validator.validate({ + email: 'test@mail.it', + confirmEmail: '', + }); + expect(result).toEqual({ confirmEmail: 'not-valid' }); + }); +}); diff --git a/packages/pn-personagiuridica-webapp/CHANGELOG.md b/packages/pn-personagiuridica-webapp/CHANGELOG.md index 6e18ce1d00..b1a046463b 100644 --- a/packages/pn-personagiuridica-webapp/CHANGELOG.md +++ b/packages/pn-personagiuridica-webapp/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-personagiuridica-webapp + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + + +### Features + +* **PN-9684:** implemented alert in notificationDetail for alternative-RADD ([#1134](https://github.com/pagopa/pn-frontend/issues/1134)) ([bfc1d4a](https://github.com/pagopa/pn-frontend/commit/bfc1d4ad8d085d5691853fb54d1dad6049ca5f92)) + + +### Reverts + +* Revert "Release/v2.3.2" (#1143) ([7cfe17e](https://github.com/pagopa/pn-frontend/commit/7cfe17e1dffd43d0ffc7c0081dbdd538e0691fb6)), closes [#1143](https://github.com/pagopa/pn-frontend/issues/1143) + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-20) **Note:** Version bump only for package @pagopa-pn/pn-personagiuridica-webapp diff --git a/packages/pn-personagiuridica-webapp/package.json b/packages/pn-personagiuridica-webapp/package.json index 1f9190c3b6..5e6bd67672 100644 --- a/packages/pn-personagiuridica-webapp/package.json +++ b/packages/pn-personagiuridica-webapp/package.json @@ -1,7 +1,7 @@ { "name": "@pagopa-pn/pn-personagiuridica-webapp", "description": "SEND per la persona giuridica", - "version": "2.3.2", + "version": "2.4.0", "private": true, "dependencies": { "@emotion/react": "^11.11.1", diff --git a/packages/pn-personagiuridica-webapp/src/App.tsx b/packages/pn-personagiuridica-webapp/src/App.tsx index 08cc7d469b..9933fde0a9 100644 --- a/packages/pn-personagiuridica-webapp/src/App.tsx +++ b/packages/pn-personagiuridica-webapp/src/App.tsx @@ -177,7 +177,7 @@ const ActualApp = () => { menuItems.splice(1, 0, { label: t('menu.deleghe'), icon: () => , - route: routes.DELEGHEACARICO, + route: routes.DELEGHE, rightBadgeNotification: pendingDelegators ? pendingDelegators : undefined, }); } diff --git a/packages/pn-personagiuridica-webapp/src/index.css b/packages/pn-personagiuridica-webapp/src/index.css index 2e040f0774..6b40bce565 100644 --- a/packages/pn-personagiuridica-webapp/src/index.css +++ b/packages/pn-personagiuridica-webapp/src/index.css @@ -6,8 +6,8 @@ body { /* START: ONETRUST STYLES */ .otnotice .otnotice-content { - font-family: "Titillium Web",sans-serif; - color: #17324D; + font-family: 'Titillium Web', sans-serif; + color: #17324d; padding-top: 64px; } @@ -48,21 +48,23 @@ body { font-size: 1.125rem; font-weight: 600; line-height: 1.5; - color: #17324D !important; + color: #17324d !important; } .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__expand::after, .otnotice-menu-mobile .otnotice-menu-selected-container .otnotice-menu-display__collapse::after { - color: #17324D !important; + color: #17324d !important; } .otnotice .otnotice-content .otnotice-section:first-child h2 { font-size: 2.375rem; - color: #17324D; + color: #17324d; + line-height: 3.25rem; } .otnotice .otnotice-content .otnotice-section:not(:first-child) h2 { font-size: 1.75rem; - color: #17324D; + color: #17324d; + line-height: 2.25rem; } -/* END: ONETRUST STYLES */ \ No newline at end of file +/* END: ONETRUST STYLES */ diff --git a/packages/pn-personagiuridica-webapp/src/pages/Deleghe.page.tsx b/packages/pn-personagiuridica-webapp/src/pages/Deleghe.page.tsx index 65b1f07819..6de0212349 100644 --- a/packages/pn-personagiuridica-webapp/src/pages/Deleghe.page.tsx +++ b/packages/pn-personagiuridica-webapp/src/pages/Deleghe.page.tsx @@ -52,6 +52,12 @@ const Deleghe = () => { }; }, []); + useEffect(() => { + const tabToBeSelected = pathname === routes.DELEGATI ? 1 : 0; + if (value !== tabToBeSelected) { + setValue(tabToBeSelected); + } + }, [pathname]); return ( { centered variant="fullWidth" > - - + + diff --git a/packages/pn-personagiuridica-webapp/src/pages/NuovaDelega.page.tsx b/packages/pn-personagiuridica-webapp/src/pages/NuovaDelega.page.tsx index feb226c858..e040f00e49 100644 --- a/packages/pn-personagiuridica-webapp/src/pages/NuovaDelega.page.tsx +++ b/packages/pn-personagiuridica-webapp/src/pages/NuovaDelega.page.tsx @@ -273,7 +273,7 @@ const NuovaDelega = () => { alignItems="center" direction={isMobile ? 'column' : 'row'} spacing={1} - flex="1 0 100px" + flex="1 0" > {values.selectPersonaFisicaOrPersonaGiuridica === RecipientType.PF && ( {
{ diff --git a/packages/pn-personagiuridica-webapp/src/pages/__test__/NuovaDelega.page.test.tsx b/packages/pn-personagiuridica-webapp/src/pages/__test__/NuovaDelega.page.test.tsx index 205e62dc0d..ada564f71f 100644 --- a/packages/pn-personagiuridica-webapp/src/pages/__test__/NuovaDelega.page.test.tsx +++ b/packages/pn-personagiuridica-webapp/src/pages/__test__/NuovaDelega.page.test.tsx @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import { createBrowserHistory } from 'history'; import { Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; @@ -16,6 +17,7 @@ import { RenderResult, act, fireEvent, render, waitFor } from '../../__test__/te import { apiClient } from '../../api/apiClients'; import { CREATE_DELEGATION } from '../../api/delegations/delegations.routes'; import { GET_ALL_ACTIVATED_PARTIES } from '../../api/external-registries/external-registries-routes'; +import * as routes from '../../navigation/routes.const'; import { createDelegationMapper } from '../../redux/newDelegation/actions'; import NuovaDelega from '../NuovaDelega.page'; @@ -125,16 +127,17 @@ describe('NuovaDelega page', async () => { // insert two entries into the history, so the initial render will refer to the path / // and when the back button is pressed and so navigate(-1) is invoked, // the path will change to /mock-path - window.history.pushState({}, '', '/mock-path'); - window.history.pushState({}, '', '/deleghe/delegati/nuova'); + const history = createBrowserHistory(); + history.push(routes.NOTIFICHE); + history.push(routes.NUOVA_DELEGA); // render with an ad-hoc router, will render initially NuovaDelega // since it corresponds to the top of the mocked history stack await act(async () => { result = render( - hello} /> - } /> + hello} /> + } /> ); }); diff --git a/packages/pn-validator/CHANGELOG.md b/packages/pn-validator/CHANGELOG.md index 52144cbd7c..bc01188df8 100644 --- a/packages/pn-validator/CHANGELOG.md +++ b/packages/pn-validator/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.4.0](https://github.com/pagopa/pn-frontend/compare/v2.4.0-RC.0...v2.4.0) (2024-03-07) + +**Note:** Version bump only for package @pagopa-pn/pn-validator + + + + + +# [2.4.0-RC.0](https://github.com/pagopa/pn-frontend/compare/v2.3.2...v2.4.0-RC.0) (2024-02-27) + +**Note:** Version bump only for package @pagopa-pn/pn-validator + + + + + ## [2.3.2](https://github.com/pagopa/pn-frontend/compare/v2.3.1...v2.3.2) (2024-02-20) **Note:** Version bump only for package @pagopa-pn/pn-validator diff --git a/packages/pn-validator/package.json b/packages/pn-validator/package.json index d55033f167..d646435f8d 100644 --- a/packages/pn-validator/package.json +++ b/packages/pn-validator/package.json @@ -1,6 +1,6 @@ { "name": "@pagopa-pn/pn-validator", - "version": "2.3.2", + "version": "2.4.0", "private": true, "main": "./src/index.ts", "dependencies": { diff --git a/packages/pn-validator/src/index.ts b/packages/pn-validator/src/index.ts index 457534678b..0ac1beaaad 100644 --- a/packages/pn-validator/src/index.ts +++ b/packages/pn-validator/src/index.ts @@ -1,3 +1,5 @@ import { Validator } from './Validator'; +import { ValidationError } from './types/ValidationError'; -export { Validator} \ No newline at end of file +export { Validator }; +export type { ValidationError }; diff --git a/packages/pn-validator/src/rules/IsEqual.ts b/packages/pn-validator/src/rules/IsEqual.ts index 62107ac304..042a680180 100644 --- a/packages/pn-validator/src/rules/IsEqual.ts +++ b/packages/pn-validator/src/rules/IsEqual.ts @@ -26,12 +26,11 @@ export class IsEqual extends Rule { ): boolean => { if (isObject(value) && isObject(valueToCompare)) { if (Object.keys(value).length === Object.keys(valueToCompare).length) { - return Object.keys(value).every((key) => { - return ( - (valueToCompare as Object).hasOwnProperty(key) && + return Object.keys(value).every( + (key) => + Object.prototype.hasOwnProperty.call(valueToCompare, key) && this.compare(value[key as TPropertyName], valueToCompare[key as TPropertyName]) - ); - }); + ); } return false; } @@ -41,9 +40,7 @@ export class IsEqual extends Rule { private compareArray = (value: TArg, valueToCompare: TArg): boolean => { if (isArray(value) && isArray(valueToCompare)) { if (value.length === valueToCompare.length) { - return value.every((elem, index) => { - return this.compare(elem, valueToCompare[index]); - }); + return value.every((elem, index) => this.compare(elem, valueToCompare[index])); } return false; } @@ -77,7 +74,7 @@ export class IsEqual extends Rule { public valueValidator = (value: TValue) => { if (this.compare(value, this.valueToCompare)) { - return !this.not ? null : `Value mustn\'t be equal to ${this.printValue()}`; + return !this.not ? null : `Value mustn't be equal to ${this.printValue()}`; } return !this.not ? `Value must be equal to ${this.printValue()}` : null; }; diff --git a/packages/pn-validator/src/types/ValidationError.ts b/packages/pn-validator/src/types/ValidationError.ts index 1cea84de64..e1c156bad1 100644 --- a/packages/pn-validator/src/types/ValidationError.ts +++ b/packages/pn-validator/src/types/ValidationError.ts @@ -1,5 +1,5 @@ -import { ValidationResult } from "./ValidationResult"; +import { ValidationResult } from './ValidationResult'; export type ValidationError = { - [propertyName in keyof TModel]?: ValidationResult -} \ No newline at end of file + [propertyName in keyof TModel]?: ValidationResult; +};