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/CodeInput.tsx b/packages/pn-commons/src/components/CodeModal/CodeInput.tsx index 9f2e3afd01..a57e75adbe 100644 --- a/packages/pn-commons/src/components/CodeModal/CodeInput.tsx +++ b/packages/pn-commons/src/components/CodeModal/CodeInput.tsx @@ -1,5 +1,6 @@ import { ChangeEvent, + ClipboardEvent, Fragment, KeyboardEvent, memo, @@ -46,11 +47,14 @@ const CodeInput = ({ initialValues, isReadOnly, hasError, onChange }: Props) => return; } if (index > initialValues.length - 1) { - // the variable is to prevent test fail - const input = inputsRef.current[index - 1]; - setTimeout(() => { - input.blur(); - }, 25); + for (const input of inputsRef.current) { + if (input === document.activeElement) { + setTimeout(() => { + input.blur(); + }, 25); + break; + } + } return; } // the variable is to prevent test fail @@ -102,8 +106,8 @@ const CodeInput = ({ initialValues, isReadOnly, hasError, onChange }: Props) => changeInputValue(value, index); return; } - // remove non numeric char from value - value = value.replace(/[^\d]/g, ''); + // remove from value those characters that aren't letters neither numbers + value = value.replace(/[^a-z\d]/gi, ''); if (value !== '') { // case maxLength 2 if (value.length > 1) { @@ -116,6 +120,20 @@ const CodeInput = ({ initialValues, isReadOnly, hasError, onChange }: Props) => } }; + const pasteHandler = (event: ClipboardEvent) => { + event.preventDefault(); + // eslint-disable-next-line functional/no-let + let pastedCode = event.clipboardData.getData('text'); + pastedCode = pastedCode.replace(/[^a-z\d]/gi, ''); + const maxLengthRequiredCode = pastedCode.slice(0, initialValues.length); + const values = maxLengthRequiredCode.split(''); + // we create an array with empty values for those cases in which the copied values are less than required ones + // initialValues.length - values.length can be only >= 0 because of the slice of pastedCode + const emptyValues = new Array(initialValues.length - values.length).fill(''); + setCurrentValues(values.concat(emptyValues)); + focusInput(values.length); + }; + useEffect(() => { onChange(currentValues); }, [currentValues]); @@ -136,13 +154,13 @@ const CodeInput = ({ initialValues, isReadOnly, hasError, onChange }: Props) => maxLength: 2, sx: { padding: '16.5px 10px', textAlign: 'center' }, readOnly: isReadOnly, - pattern: '^[0-9]{1}$', - inputMode: 'numeric', + pattern: '^[0-9a-zA-Z]{1}$', 'data-testid': `code-input-${index}`, }} onKeyDown={(event) => keyDownHandler(event, index)} onChange={(event) => changeHandler(event, index)} onFocus={(event) => event.target.select()} + onPaste={(event) => pasteHandler(event)} value={currentValues[index]} // eslint-disable-next-line functional/immutable-data inputRef={(node) => (inputsRef.current[index] = node)} diff --git a/packages/pn-commons/src/components/CodeModal/CodeModal.tsx b/packages/pn-commons/src/components/CodeModal/CodeModal.tsx index f1594f8083..3c3c62258f 100644 --- a/packages/pn-commons/src/components/CodeModal/CodeModal.tsx +++ b/packages/pn-commons/src/components/CodeModal/CodeModal.tsx @@ -1,4 +1,4 @@ -import { ReactNode, memo, useCallback, useState } from 'react'; +import { ReactNode, memo, useCallback, useEffect, useState } from 'react'; import { Alert, @@ -68,10 +68,37 @@ const CodeModal = memo( errorMessage, }: Props) => { const [code, setCode] = useState(initialValues); - const codeIsValid = code.every((v) => v); + const [internalError, setInternalError] = useState({ + internalHasError: hasError, + internalErrorTitle: errorTitle, + internalErrorMessage: errorMessage, + }); + + const { internalHasError, internalErrorTitle, internalErrorMessage } = internalError; + + const codeIsValid = code.every((v) => (!isNaN(Number(v)) ? v : false)); const changeHandler = useCallback((inputsValues: Array) => { setCode(inputsValues); + if (isNaN(Number(inputsValues.join('')))) { + setInternalError({ + internalHasError: true, + internalErrorTitle: getLocalizedOrDefaultLabel( + 'recapiti', + `errors.invalid_type_code.title` + ), + internalErrorMessage: getLocalizedOrDefaultLabel( + 'recapiti', + `errors.invalid_type_code.message` + ), + }); + } else { + setInternalError({ + internalHasError: false, + internalErrorTitle: '', + internalErrorMessage: '', + }); + } }, []); const confirmHandler = () => { @@ -81,10 +108,17 @@ const CodeModal = memo( confirmCallback(code); }; + useEffect(() => { + setInternalError({ + internalHasError: hasError, + internalErrorTitle: errorTitle, + internalErrorMessage: errorMessage, + }); + }, [hasError, errorTitle, errorMessage]); + return ( {isReadOnly && ( @@ -119,12 +153,12 @@ const CodeModal = memo( )} {codeSectionAdditional && {codeSectionAdditional}} - {hasError && ( + {internalHasError && ( - {errorTitle} + {internalErrorTitle} - {errorMessage} + {internalErrorMessage} )} 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..8357f7ed1e 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,92 @@ 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(); + }); + }); + + it('handles paste event', async () => { + // render component + const { getAllByTestId } = render( + + ); + const codeInputs = getAllByTestId(/code-input-[0-4]/); + + // paste the value of the input and check that it is updated correctly + // set the cursor position to the beggining + act(() => (codeInputs[2] as HTMLInputElement).focus()); + (codeInputs[2] as HTMLInputElement).setSelectionRange(0, 0); + + const codePasted = '12345'; + await user.paste(codePasted); + + await waitFor(() => { + for (let i = 0; i < 5; i++) { + expect(codeInputs[i]).toHaveValue(codePasted[i]); + } + }); + }); + + it('accepts first 5 digits in case of too long code (pasted)', async () => { + // render component + const { getAllByTestId } = render( + + ); + const codeInputs = getAllByTestId(/code-input-[0-4]/); + act(() => (codeInputs[2] as HTMLInputElement).focus()); + (codeInputs[2] as HTMLInputElement).setSelectionRange(0, 0); + const codePasted = '123456'; // too long code + await user.paste(codePasted); + await waitFor(() => { + for (let i = 0; i < 5; i++) { + expect(codeInputs[i]).toHaveValue(codePasted[i]); + } }); + expect(handleChangeMock).toHaveBeenCalledWith(['1', '2', '3', '4', '5']); }); }); diff --git a/packages/pn-commons/src/components/CodeModal/__test__/CodeModal.test.tsx b/packages/pn-commons/src/components/CodeModal/__test__/CodeModal.test.tsx index 1c741eb6ce..27037c3318 100644 --- a/packages/pn-commons/src/components/CodeModal/__test__/CodeModal.test.tsx +++ b/packages/pn-commons/src/components/CodeModal/__test__/CodeModal.test.tsx @@ -1,17 +1,19 @@ import { vi } from 'vitest'; -import { fireEvent, render, screen, waitFor, within } from '../../../test-utils'; +import userEvent from '@testing-library/user-event'; + +import { act, fireEvent, render, screen, waitFor, within } from '../../../test-utils'; import CodeModal from '../CodeModal'; const cancelButtonMock = vi.fn(); const confirmButtonMock = vi.fn(); -const openedModalComponent = ( - open: boolean, - hasError: boolean = false, - readonly: boolean = false, - initialValues: string[] = new Array(5).fill('') -) => ( +const CodeModalWrapper: React.FC<{ + open: boolean; + hasError?: boolean; + readonly?: boolean; + initialValues?: string[]; +}> = ({ open, hasError = false, readonly = false, initialValues = new Array(5).fill('') }) => ( { it('renders CodeModal (closed)', () => { // render component - render(openedModalComponent(false)); + render(); const dialog = screen.queryByTestId('codeDialog'); expect(dialog).not.toBeInTheDocument(); }); it('renders CodeModal (opened)', () => { // render component - render(openedModalComponent(true)); + render(); const dialog = screen.getByTestId('codeDialog'); expect(dialog).toBeInTheDocument(); expect(dialog).toHaveTextContent(/mocked-title/i); @@ -65,13 +67,13 @@ describe('CodeModal Component', () => { }); const buttons = within(dialog).getAllByRole('button'); expect(buttons).toHaveLength(2); - expect(buttons![0]).toHaveTextContent('mocked-cancel'); - expect(buttons![1]).toHaveTextContent('mocked-confirm'); + expect(buttons[0]).toHaveTextContent('mocked-cancel'); + expect(buttons[1]).toHaveTextContent('mocked-confirm'); }); it('renders CodeModal (read only)', async () => { // render component - render(openedModalComponent(true, false, true, ['0', '1', '2', '3', '4'])); + render(); const dialog = screen.getByTestId('codeDialog'); expect(dialog).toBeInTheDocument(); expect(dialog).toHaveTextContent(/mocked-title/i); @@ -93,10 +95,10 @@ describe('CodeModal Component', () => { it('clicks on cancel', async () => { // render component - render(openedModalComponent(true)); + render(); const dialog = screen.getByTestId('codeDialog'); const button = within(dialog).getByTestId('codeCancelButton'); - fireEvent.click(button!); + fireEvent.click(button); await waitFor(() => { expect(cancelButtonMock).toBeCalledTimes(1); }); @@ -104,10 +106,10 @@ describe('CodeModal Component', () => { it('clicks on confirm', async () => { // render component - render(openedModalComponent(true)); + render(); const dialog = screen.getByTestId('codeDialog'); const button = within(dialog).getByTestId('codeConfirmButton'); - expect(button!).toBeDisabled(); + expect(button).toBeDisabled(); const codeInputs = within(dialog).getAllByTestId(/code-input-[0-4]/); // fill inputs with values codeInputs?.forEach((input, index) => { @@ -117,19 +119,63 @@ describe('CodeModal Component', () => { codeInputs?.forEach((input, index) => { expect(input).toHaveValue(index.toString()); }); - expect(button!).toBeEnabled(); + expect(button).toBeEnabled(); }); - fireEvent.click(button!); + fireEvent.click(button); expect(confirmButtonMock).toBeCalledTimes(1); expect(confirmButtonMock).toBeCalledWith(['0', '1', '2', '3', '4']); }); it('shows error', () => { // render component - render(openedModalComponent(true, true)); + const { rerender } = render( + + ); const dialog = screen.getByTestId('codeDialog'); - const errorAlert = within(dialog).getByTestId('errorAlert'); + let errorAlert = within(dialog).queryByTestId('errorAlert'); + expect(errorAlert).not.toBeInTheDocument(); + // simulate error from external + rerender(); + errorAlert = within(dialog).getByTestId('errorAlert'); expect(errorAlert).toBeInTheDocument(); expect(errorAlert).toHaveTextContent('mocked-errorMessage'); }); + + it('shows error in case of letters as input values', async () => { + // render component + render(); + const dialog = screen.getByTestId('codeDialog'); + const codeInputs = within(dialog).getAllByTestId(/code-input-[0-4]/); + fireEvent.change(codeInputs[0], { target: { value: 'A' } }); + const errorAlert = screen.getByTestId('errorAlert'); + expect(errorAlert).toBeInTheDocument(); + expect(errorAlert).toHaveTextContent('errors.invalid_type_code.message'); + }); + + it('error in case of letters (pasted)', async () => { + // render component + render(); + const dialog = screen.getByTestId('codeDialog'); + const codeInputs = within(dialog).getAllByTestId(/code-input-[0-4]/); + act(() => (codeInputs[2] as HTMLInputElement).focus()); + (codeInputs[2] as HTMLInputElement).setSelectionRange(0, 0); + const codePasted = 'abcd'; + await userEvent.paste(codePasted); + const errorAlert = screen.getByTestId('errorAlert'); + expect(errorAlert).toBeInTheDocument(); + expect(errorAlert).toHaveTextContent('errors.invalid_type_code.message'); + }); + + it('short code (pasted) - confirm disabled', async () => { + // render component + render(); + const dialog = screen.getByTestId('codeDialog'); + const codeInputs = within(dialog).getAllByTestId(/code-input-[0-4]/); + act(() => (codeInputs[2] as HTMLInputElement).focus()); + (codeInputs[2] as HTMLInputElement).setSelectionRange(0, 0); + const codePasted = '123'; + await userEvent.paste(codePasted); + const button = screen.getByTestId('codeConfirmButton'); + expect(button).toBeDisabled(); + }); }); diff --git a/packages/pn-commons/src/components/Header/Header.tsx b/packages/pn-commons/src/components/Header/Header.tsx index 36b16e7597..04351835c5 100644 --- a/packages/pn-commons/src/components/Header/Header.tsx +++ b/packages/pn-commons/src/components/Header/Header.tsx @@ -34,13 +34,13 @@ type HeaderProps = { userActions?: Array; /** Actions linked to user dropdown */ onAssistanceClick?: () => void; - /** Track product switch action */ - 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, @@ -51,9 +51,9 @@ const Header = ({ enableDropdown, userActions, onAssistanceClick, - eventTrackingCallbackProductSwitch, isLogged, -}: HeaderProps) => { + enableAssistanceButton, +}) => { const pagoPAHeaderLink: RootLinkType = { ...pagoPALink(), label: 'PagoPA S.p.A.', @@ -61,13 +61,11 @@ const Header = ({ }; const handleProductSelection = (product: ProductEntity) => { - if (eventTrackingCallbackProductSwitch) { - eventTrackingCallbackProductSwitch(product.productUrl); - } if (product.productUrl) { /* eslint-disable-next-line functional/immutable-data */ window.location.assign(product.productUrl); - /** Here is necessary to clear sessionStorage otherwise when navigating through Area Riservata + /** + * Here is necessary to clear sessionStorage otherwise when navigating through Area Riservata * we enter in a state where the user was previously logged in but Area Riservata and PN require * another token-exchange. Since the user "seems" to be logged in due to the presence of the old token * in session storage, the token-exchange doesn't happen and the application enters in a blank state. @@ -107,6 +105,7 @@ const Header = ({ onLogout={onExitAction} enableDropdown={enableDropdown} userActions={userActions} + enableAssistanceButton={enableAssistanceButton} /> {enableHeaderProduct && ( void; /** event callback on app crash */ eventTrackingCallbackAppCrash?: (_error: Error, _errorInfo: ErrorInfo) => void; - /** Track product switch action */ - eventTrackingCallbackProductSwitch?: (target: string) => void; /** event on assistance click button */ eventTrackingCallbackRefreshPage?: () => void; /** event on refresh page click button */ @@ -53,6 +51,8 @@ type Props = { privacyPolicyHref?: string; /** Url to terms of service page */ termsOfServiceHref?: string; + /** Enable assistance button */ + enableAssistanceButton?: boolean; }; const Layout: React.FC = ({ @@ -68,9 +68,8 @@ const Layout: React.FC = ({ loggedUser, enableUserDropdown, userActions, - onLanguageChanged = () => { }, + onLanguageChanged = () => {}, eventTrackingCallbackAppCrash, - eventTrackingCallbackProductSwitch, eventTrackingCallbackRefreshPage, onAssistanceClick, isLogged, @@ -79,6 +78,7 @@ const Layout: React.FC = ({ hasTermsOfService, privacyPolicyHref, termsOfServiceHref, + enableAssistanceButton = true, }) => ( = ({ enableDropdown={enableUserDropdown} userActions={userActions} onAssistanceClick={onAssistanceClick} - eventTrackingCallbackProductSwitch={eventTrackingCallbackProductSwitch} isLogged={isLogged} + enableAssistanceButton={enableAssistanceButton} /> )} diff --git a/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimeline.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimeline.tsx index 4e74fd60bb..6fdef53694 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimeline.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimeline.tsx @@ -25,7 +25,6 @@ type Props = { historyButtonLabel: string; showMoreButtonLabel: string; showLessButtonLabel: string; - eventTrackingCallbackShowMore?: () => void; disableDownloads?: boolean; isParty?: boolean; language?: string; diff --git a/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx index 0887adf466..fe13b5c1bc 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationDetailTimelineStep.tsx @@ -106,7 +106,6 @@ const TimelineStepCmp: React.FC = ({ * @param historyButtonClickHandler function called when user clicks on the history button * @param showMoreButtonLabel label of show more button * @param showLessButtonLabel label of show less button - * @param eventTrackingCallbackShowMore event tracking callback * @param completeStatusHistory the whole history, sometimes some information from a different status must be retrieved * @param disableDownloads if notification is disabled * @param isParty if is party chip rendered with opacity for status cancellation in progress diff --git a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentRecipient.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentRecipient.tsx index 6327d9ba06..0999cf3b9b 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentRecipient.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentRecipient.tsx @@ -6,7 +6,6 @@ import { Alert, Box, Button, CircularProgress, Link, RadioGroup, Typography } fr import { downloadDocument } from '../../hooks'; import { EventPaymentRecipientType, - EventPaymentStatusType, F24PaymentDetails, NotificationDetailPayment, PaginationData, @@ -14,7 +13,6 @@ import { PaymentAttachment, PaymentAttachmentSName, PaymentDetails, - PaymentInfoDetail, PaymentStatus, PaymentsData, } from '../../models'; @@ -28,32 +26,6 @@ import NotificationPaymentTitle from './NotificationPaymentTitle'; const FAQ_NOTIFICATION_CANCELLED_REFUND = '/faq#notifica-pagata-rimborso'; -const getPaymentsStatus = ( - paginationData: PaginationData, - pagoPaF24: Array -): EventPaymentStatusType => ({ - page_number: paginationData.page, - count_payment: pagoPaF24.length, - count_canceled: pagoPaF24.filter( - (f) => - f.pagoPa?.status === PaymentStatus.FAILED && - f.pagoPa.detail === PaymentInfoDetail.PAYMENT_CANCELED - ).length, - count_error: pagoPaF24.filter( - (f) => - f.pagoPa?.status === PaymentStatus.FAILED && - f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_CANCELED && - f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_EXPIRED - ).length, - count_expired: pagoPaF24.filter( - (f) => - f.pagoPa?.status === PaymentStatus.FAILED && - f.pagoPa.detail === PaymentInfoDetail.PAYMENT_EXPIRED - ).length, - count_paid: pagoPaF24.filter((f) => f.pagoPa?.status === PaymentStatus.SUCCEEDED).length, - count_unpaid: pagoPaF24.filter((f) => f.pagoPa?.status === PaymentStatus.REQUIRED).length, -}); - type Props = { payments: PaymentsData; isCancelled: boolean; @@ -160,8 +132,10 @@ const NotificationPaymentRecipient: React.FC = ({ const paymentsLoaded = paginatedPayments.every((payment) => !payment.isLoading); if (paymentsLoaded) { // the tracked event wants only the status of the current paged payments - const pagePaymentsStatus = getPaymentsStatus(paginationData, paginatedPayments); - handleTrackEventFn(EventPaymentRecipientType.SEND_PAYMENT_STATUS, pagePaymentsStatus); + handleTrackEventFn(EventPaymentRecipientType.SEND_PAYMENT_STATUS, { + paginationData, + paginatedPayments, + }); } }, [payments]); @@ -287,11 +261,19 @@ const NotificationPaymentRecipient: React.FC = ({ )} {selectedPayment && - pagoPaF24.find((payment) => payment.pagoPa?.noticeCode === selectedPayment.noticeCode) - ?.f24 ? ( + pagoPaF24.find( + (payment) => payment.pagoPa?.noticeCode === selectedPayment?.noticeCode + )?.f24 ? ( payment.pagoPa?.noticeCode === selectedPayment?.noticeCode + )?.f24 as F24PaymentDetails + } + getPaymentAttachmentAction={getPaymentAttachmentAction} + isPagoPaAttachment + handleTrackDownloadF24={() => void handleTrackEventFn(EventPaymentRecipientType.SEND_F24_DOWNLOAD) } handleTrackDownloadF24Success={() => @@ -300,13 +282,6 @@ const NotificationPaymentRecipient: React.FC = ({ handleTrackDownloadF24Timeout={() => void handleTrackEventFn(EventPaymentRecipientType.SEND_F24_DOWNLOAD_TIMEOUT) } - f24Item={ - pagoPaF24.find( - (payment) => payment.pagoPa?.noticeCode === selectedPayment.noticeCode - )?.f24 as F24PaymentDetails - } - getPaymentAttachmentAction={getPaymentAttachmentAction} - isPagoPaAttachment timerF24={timerF24} disableDownload={areOtherDowloading} handleDownload={setAreOtherDowloading} diff --git a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentTitle.tsx b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentTitle.tsx index a6f0cf130a..b80af7cd54 100644 --- a/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentTitle.tsx +++ b/packages/pn-commons/src/components/NotificationDetail/NotificationPaymentTitle.tsx @@ -22,6 +22,7 @@ const NotificationPaymentTitle: React.FC = ({ }) => { const FAQ_NOTIFICATION_COSTS = '/faq#costi-di-notifica'; const notificationCostsFaqLink = `${landingSiteUrl}${FAQ_NOTIFICATION_COSTS}`; + const FaqLink = ( void; tooltipProps?: Partial; chipProps?: SxProps; }) => { const tooltipContent = {tooltip}; return ( - + - ); + const { getByRole } = render(); const button = getByRole('button'); expect(button).toHaveTextContent(/mocked label/i); const buttonClass = `${classRoot}${color.charAt(0).toUpperCase() + color.slice(1)}`; @@ -26,7 +16,6 @@ async function testStatusTooltip( fireEvent.mouseOver(button); await waitFor(() => { expect(screen.getByRole('tooltip')).toHaveTextContent(/mocked tooltip test/i); - expect(mockEventTrackingCallback).toBeCalledTimes(1); }); } diff --git a/packages/pn-commons/src/components/Pagination/CustomPagination.tsx b/packages/pn-commons/src/components/Pagination/CustomPagination.tsx index 044723627f..50b692eccf 100644 --- a/packages/pn-commons/src/components/Pagination/CustomPagination.tsx +++ b/packages/pn-commons/src/components/Pagination/CustomPagination.tsx @@ -17,8 +17,6 @@ type Props = { pagesToShow?: Array; /** custom style */ sx?: SxProps; - /** event tracking function callback for page size */ - eventTrackingCallbackPageSize?: (pageSize: number) => void; /** hide size selector */ hideSizeSelector?: boolean; }; @@ -76,7 +74,6 @@ export default function CustomPagination({ elementsPerPage = [10, 20, 50], pagesToShow, sx, - eventTrackingCallbackPageSize, hideSizeSelector = false, }: Props) { const size = paginationData.size || elementsPerPage[0]; @@ -99,9 +96,6 @@ export default function CustomPagination({ // eslint-disable-next-line functional/immutable-data paginationData.page = 0; onPageRequest(paginationData); - if (eventTrackingCallbackPageSize) { - eventTrackingCallbackPageSize(selectedSize); - } } handleClose(); }; diff --git a/packages/pn-commons/src/components/Pagination/__test__/CustomPagination.test.tsx b/packages/pn-commons/src/components/Pagination/__test__/CustomPagination.test.tsx index 4c5e82bf0e..6fe7224d78 100644 --- a/packages/pn-commons/src/components/Pagination/__test__/CustomPagination.test.tsx +++ b/packages/pn-commons/src/components/Pagination/__test__/CustomPagination.test.tsx @@ -11,7 +11,6 @@ let paginationData: PaginationData = { }; const handlePageChange = vi.fn(); -const mockEventTrackingPageSize = vi.fn(); describe('CustomPagination Component', () => { afterEach(() => { @@ -26,11 +25,7 @@ describe('CustomPagination Component', () => { it('renders custom pagination', () => { // render component const { getByTestId } = render( - + ); const itemsPerPageSelector = getByTestId('itemsPerPageSelector'); expect(itemsPerPageSelector).toBeInTheDocument(); @@ -43,7 +38,6 @@ describe('CustomPagination Component', () => { const { queryByTestId } = render( @@ -54,11 +48,7 @@ describe('CustomPagination Component', () => { it('changes items per page', async () => { const { container, getAllByRole, getByRole } = render( - + ); const button = getById(container, 'rows-per-page'); expect(button).toHaveTextContent(/50/i); @@ -71,7 +61,6 @@ describe('CustomPagination Component', () => { await waitFor(() => { expect(button).toHaveTextContent(/20/i); expect(handlePageChange).toBeCalledTimes(1); - expect(mockEventTrackingPageSize).toBeCalledTimes(1); expect(handlePageChange).toBeCalledWith({ page: 0, size: 20, 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/Prompt.tsx b/packages/pn-commons/src/components/Prompt.tsx index 869f619b05..c0fd686d9b 100644 --- a/packages/pn-commons/src/components/Prompt.tsx +++ b/packages/pn-commons/src/components/Prompt.tsx @@ -11,9 +11,9 @@ import PnDialogContent from './PnDialog/PnDialogContent'; type Props = { title: string; message: string; - eventTrackingCallbackPromptOpened: () => void; - eventTrackingCallbackCancel: () => void; - eventTrackingCallbackConfirm: () => void; + eventTrackingCallbackPromptOpened?: () => void; + eventTrackingCallbackCancel?: () => void; + eventTrackingCallbackConfirm?: () => void; children?: React.ReactNode; }; @@ -33,7 +33,7 @@ const Prompt: React.FC = ({ useEffect(() => { if (showPrompt) { - eventTrackingCallbackPromptOpened(); + eventTrackingCallbackPromptOpened?.(); } }); diff --git a/packages/pn-commons/src/components/SideMenu/SideMenu.tsx b/packages/pn-commons/src/components/SideMenu/SideMenu.tsx index 7359d434a8..ee8ef814c9 100644 --- a/packages/pn-commons/src/components/SideMenu/SideMenu.tsx +++ b/packages/pn-commons/src/components/SideMenu/SideMenu.tsx @@ -12,10 +12,9 @@ import SideMenuList from './SideMenuList'; type Props = { menuItems: Array; selfCareItems?: Array; - eventTrackingCallback?: (target: string) => void; }; -const SideMenu: FC = ({ menuItems, selfCareItems, eventTrackingCallback }) => { +const SideMenu: FC = ({ menuItems, selfCareItems }) => { const [state, setState] = useState(false); const navigate = useNavigate(); const location = useLocation(); @@ -86,9 +85,6 @@ const SideMenu: FC = ({ menuItems, selfCareItems, eventTrackingCallback } }; const handleNavigation = (item: SideMenuItem, menuFlag?: boolean) => { - if (eventTrackingCallback) { - eventTrackingCallback(item.route); - } if (isMobile && !menuFlag) { setState(false); } diff --git a/packages/pn-commons/src/components/SideMenu/__test__/SideMenu.test.tsx b/packages/pn-commons/src/components/SideMenu/__test__/SideMenu.test.tsx index 144d8d509d..4453b359ca 100644 --- a/packages/pn-commons/src/components/SideMenu/__test__/SideMenu.test.tsx +++ b/packages/pn-commons/src/components/SideMenu/__test__/SideMenu.test.tsx @@ -103,21 +103,16 @@ describe('SideMenu', () => { it('menu navigation', async () => { const mockedAction = vi.fn(); - const eventTrackingCallback = vi.fn(); const menuItems = sideMenuItems.map((item) => item.action ? { ...item, action: mockedAction } : item ); - const { getByRole } = render( - - ); + const { getByRole } = render(); const ul = getByRole('navigation'); const buttons = within(ul).getAllByTestId(/^sideMenuItem-Item \d$/); // no children // navigation const noChildrenIdx = menuItems.findIndex((item) => !item.children); fireEvent.click(buttons[noChildrenIdx]); - expect(eventTrackingCallback).toBeCalledTimes(1); - expect(eventTrackingCallback).toBeCalledWith(menuItems[noChildrenIdx].route); expect(mockNavigate).toBeCalledTimes(1); expect(mockNavigate).toBeCalledWith(menuItems[noChildrenIdx].route); expect(buttons[noChildrenIdx]).toHaveClass('Mui-selected'); @@ -125,8 +120,6 @@ describe('SideMenu', () => { // navigate and open the accordion const withChildrenIdx = menuItems.findIndex((item) => item.children && !item.notSelectable); fireEvent.click(buttons[withChildrenIdx]); - expect(eventTrackingCallback).toBeCalledTimes(2); - expect(eventTrackingCallback).toBeCalledWith(menuItems[withChildrenIdx].route); expect(mockNavigate).toBeCalledTimes(2); expect(mockNavigate).toBeCalledWith(menuItems[withChildrenIdx].route); const accordion0 = within(ul).getByTestId(`collapse-${menuItems[withChildrenIdx].label}`); @@ -138,8 +131,6 @@ describe('SideMenu', () => { (item) => item.children && item.notSelectable ); fireEvent.click(buttons[withChildrenAndNotSelectableIdx]); - expect(eventTrackingCallback).toBeCalledTimes(3); - expect(eventTrackingCallback).toBeCalledWith(menuItems[withChildrenAndNotSelectableIdx].route); expect(mockNavigate).toBeCalledTimes(2); const accordion1 = within(ul).getByTestId( `collapse-${menuItems[withChildrenAndNotSelectableIdx].label}` @@ -153,8 +144,6 @@ describe('SideMenu', () => { // no navigation and action call const noChildrenAndNoRouteIdx = menuItems.findIndex((item) => !item.children && !item.route); fireEvent.click(buttons[noChildrenAndNoRouteIdx]); - expect(eventTrackingCallback).toBeCalledTimes(4); - expect(eventTrackingCallback).toBeCalledWith(menuItems[noChildrenAndNoRouteIdx].route); expect(mockNavigate).toBeCalledTimes(2); expect(mockedAction).toBeCalledTimes(1); expect(buttons[noChildrenAndNoRouteIdx]).toHaveClass('Mui-selected'); 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-commons/src/hooks/usePrompt.ts b/packages/pn-commons/src/hooks/usePrompt.ts index cfc53b80d3..f0a4aefb51 100644 --- a/packages/pn-commons/src/hooks/usePrompt.ts +++ b/packages/pn-commons/src/hooks/usePrompt.ts @@ -6,8 +6,8 @@ import { useBlocker } from './useBlocker'; export function usePrompt( when: boolean, - callbackCancel: () => void, - callbackConfirm: () => void + callbackCancel?: () => void, + callbackConfirm?: () => void ): [boolean, () => void, () => void] { const location = useLocation(); const navigate = useNavigate(); @@ -17,7 +17,7 @@ export function usePrompt( const cancelNavigation = useCallback(() => { setShowPrompt(false); - callbackCancel(); + callbackCancel?.(); }, []); // handle blocking when user click on another route prompt will be shown @@ -37,7 +37,7 @@ export function usePrompt( const confirmNavigation = useCallback(() => { setShowPrompt(false); setConfirmedNavigation(true); - callbackConfirm(); + callbackConfirm?.(); }, []); useEffect(() => { diff --git a/packages/pn-commons/src/models/EventStrategy.ts b/packages/pn-commons/src/models/EventStrategy.ts new file mode 100644 index 0000000000..164f9e1d9b --- /dev/null +++ b/packages/pn-commons/src/models/EventStrategy.ts @@ -0,0 +1,27 @@ +import { TrackedEvent } from '.'; + +/** + * It is the set of all those operations that taken the type of event + * and the data associated with it generates the event to track. + * + * @date 23/2/2024 - 17:40:31 + * + * @export + * @abstract + * @class EventStrategy + * @typedef {EventStrategy} + */ +interface EventStrategy { + /** + * This function manage receiving data as input + * of the event and make the necessary transformations + * + * @date 20/3/2024 - 10:23:25 + * + * @param {unknown} data + * @returns {StrategyEventType} + */ + performComputations(data: unknown): TrackedEvent; +} + +export default EventStrategy; diff --git a/packages/pn-commons/src/models/EventType.ts b/packages/pn-commons/src/models/EventType.ts new file mode 100644 index 0000000000..fbd3ab6b13 --- /dev/null +++ b/packages/pn-commons/src/models/EventType.ts @@ -0,0 +1,12 @@ +/** + * List of events to track. + * This is an enum containing events common for all applications. + * @date 23/2/2024 - 17:04:29 + * + * @export + * @class EventType + * @typedef {EventType} + */ +export enum EventType { + PAGE_CLICK = 'PAGE_CLICK', +} diff --git a/packages/pn-commons/src/models/MixpanelEvents.ts b/packages/pn-commons/src/models/MixpanelEvents.ts index 017875a46a..b9d25cd9d8 100644 --- a/packages/pn-commons/src/models/MixpanelEvents.ts +++ b/packages/pn-commons/src/models/MixpanelEvents.ts @@ -15,14 +15,19 @@ export type EventsType = { }; }; -export type ProfileMapAttributes = { - profilePropertyType: Array; - getAttributes: (payload?: any, meta?: any) => Record; - shouldBlock?: (payload?: any, meta?: any) => boolean; +type BaseTrackedEvent = { + event_category?: string; + event_type?: EventAction | string; }; -export type ProfilePropertiesActionsMap = { - [key: string]: ProfileMapAttributes; +export type TrackedEvent = T extends undefined + ? Partial> + : Partial>; + +export type ActionMeta = { + requestId: string; + requestStatus: string; + arg: any; }; export enum EventAction { @@ -43,7 +48,8 @@ export enum EventDowntimeType { IN_PROGRESS = 'in_progress', } -export enum ProfilePropertyType { +export enum EventPropertyType { + TRACK = 'track', PROFILE = 'profile', INCREMENTAL = 'incremental', SUPER_PROPERTY = 'superProperty', @@ -107,6 +113,7 @@ export type EventCreatedDelegationType = { mandate_type: string; }; +// TODO: review that enum when implementing PG events export enum EventPaymentRecipientType { SEND_PAYMENT_DETAIL_REFRESH = 'SEND_PAYMENT_DETAIL_REFRESH', SEND_CANCELLED_NOTIFICATION_REFOUND_INFO = 'SEND_CANCELLED_NOTIFICATION_REFOUND_INFO', diff --git a/packages/pn-commons/src/models/index.ts b/packages/pn-commons/src/models/index.ts index 087df2a0eb..6d1a913a78 100644 --- a/packages/pn-commons/src/models/index.ts +++ b/packages/pn-commons/src/models/index.ts @@ -18,9 +18,12 @@ import { isKnownFunctionality, } from './AppStatus'; import { KnownSentiment } from './EmptyState'; +import EventStrategy from './EventStrategy'; +import { EventType } from './EventType'; import { GetNotificationDowntimeEventsParams } from './GetNotificationDowntimeEventsParams'; import { Institution, PartyEntityWithUrl } from './Institutions'; import { + ActionMeta, EventAction, EventCategory, EventCreatedDelegationType, @@ -31,10 +34,9 @@ import { EventPageType, EventPaymentRecipientType, EventPaymentStatusType, + EventPropertyType, EventsType, - ProfileMapAttributes, - ProfilePropertiesActionsMap, - ProfilePropertyType, + TrackedEvent, } from './MixpanelEvents'; import { AarDetails, @@ -122,7 +124,7 @@ export { KnownFunctionality, AppIoCourtesyMessageEventType, PrivateRoute, - ProfilePropertyType, + EventPropertyType, }; export type { AnalogWorkflowDetails, @@ -195,6 +197,8 @@ export type { PartyEntityWithUrl, Product, PaymentCache, - ProfilePropertiesActionsMap, - ProfileMapAttributes, + TrackedEvent, + EventStrategy, + EventType, + ActionMeta, }; diff --git a/packages/pn-commons/src/services/index.ts b/packages/pn-commons/src/services/index.ts index dd4c542256..058951614f 100644 --- a/packages/pn-commons/src/services/index.ts +++ b/packages/pn-commons/src/services/index.ts @@ -1,15 +1,4 @@ import { Configuration } from './configuration.service'; -import { - interceptDispatch, - interceptDispatchSuperOrProfileProperty, - setSuperOrProfileProperty, - trackEvent, -} from './tracking.service'; +import { interceptDispatch, trackEvent } from './tracking.service'; -export { - trackEvent, - interceptDispatch, - Configuration, - setSuperOrProfileProperty, - interceptDispatchSuperOrProfileProperty, -}; +export { trackEvent, interceptDispatch, Configuration }; diff --git a/packages/pn-commons/src/services/tracking.service.ts b/packages/pn-commons/src/services/tracking.service.ts index c8d0f6ebd8..e8c1e1536c 100644 --- a/packages/pn-commons/src/services/tracking.service.ts +++ b/packages/pn-commons/src/services/tracking.service.ts @@ -1,117 +1,86 @@ // leave default import for mixpanel, using named once it won't work +import _ from 'lodash'; import mixpanel from 'mixpanel-browser'; import { AnyAction, Dispatch, PayloadAction } from '@reduxjs/toolkit'; -import { EventsType, ProfileMapAttributes, ProfilePropertyType } from '../models/MixpanelEvents'; +import { ActionMeta, EventPropertyType } from '../models/MixpanelEvents'; +import { EventStrategyFactory } from '../utility'; /** - * Function that tracks event - * @param event_name event name - * @param nodeEnv current environment - * @param properties event data + * Function that calls the mixpanel tracking method based on the property type + * @param propertyType the type of property + * @param event_name the event name to track + * @param properties the event data */ -export function trackEvent(event_name: string, nodeEnv: string, properties?: any): void { - if (nodeEnv === 'test') { - return; - } else if (!nodeEnv || nodeEnv === 'development') { - // eslint-disable-next-line no-console - console.log(event_name, properties); - } else { - try { +function callMixpanelTrackingMethod( + propertyType: EventPropertyType, + event_name: string, + properties?: any +) { + switch (propertyType) { + case EventPropertyType.TRACK: mixpanel.track(event_name, properties); - } catch (_) { - // eslint-disable-next-line no-console - console.log(event_name, properties); + break; + case EventPropertyType.PROFILE: + mixpanel.people.set(properties); + break; + case EventPropertyType.INCREMENTAL: { + const hasProperties = + !_.isNil(properties) && (typeof properties === 'object' || typeof properties === 'string') + ? !_.isEmpty(properties) + : true; + mixpanel.people.increment(hasProperties ? { event_name: properties } : event_name); + break; } + case EventPropertyType.SUPER_PROPERTY: + mixpanel.register(properties); + break; + default: + mixpanel.track(event_name, properties); } } /** - * Set mixpanel user properties + * Function that tracks event + * @param propertyType event property type + * @param nodeEnv current environment + * @param properties event data */ -export function setSuperOrProfileProperty( - propertyType: ProfilePropertyType, - property: any, - nodeEnv: string +export function trackEvent( + propertyType: EventPropertyType, + event_name: string, + nodeEnv: string, + properties?: any ): void { - if (!nodeEnv || nodeEnv === 'development') { - // eslint-disable-next-line no-console - console.log( - 'Mixpanel events mock on console log - profile properties', - { propertyType }, - property - ); - } else if (nodeEnv === 'test') { + if (nodeEnv === 'test') { return; + } else if (!nodeEnv || nodeEnv === 'development') { + // eslint-disable-next-line no-console + console.log(event_name, properties, propertyType); } else { try { - switch (propertyType) { - case 'profile': - mixpanel.people.set(property); - break; - case 'incremental': - mixpanel.people.increment(property); - break; - case 'superProperty': - mixpanel.register(property); - break; - default: - mixpanel.people.set(property); - } + callMixpanelTrackingMethod(propertyType, event_name, properties); } catch (_) { // eslint-disable-next-line no-console - console.log(property); + console.log(event_name, properties); } } } export const interceptDispatch = - ( + ( next: Dispatch, - events: EventsType, - eventsActionsMap: Record, - nodeEnv: string + eventStrategyFactory: EventStrategyFactory, + eventsActionsMap: Record ) => - (action: PayloadAction): any => { - if (eventsActionsMap[action.type]) { - // const idx = Object.values(trackEventType).indexOf(action.type as string); - const eventKey = eventsActionsMap[action.type]; - // TODO check payload - const attributes = events[eventKey].getAttributes?.(action.payload); - const eventParameters = attributes - ? { - event_category: events[eventKey].event_category, - event_type: events[eventKey].event_type, - ...attributes, - } - : events[eventKey]; - trackEvent(eventKey, nodeEnv, eventParameters); - } - - return next(action); - }; - -export const interceptDispatchSuperOrProfileProperty = ( - next: Dispatch, - eventsActionsMap: Record, - nodeEnv: string - ) => - (action: PayloadAction): any => { + action: PayloadAction + ): void | PayloadAction => { if (eventsActionsMap[action.type]) { - const eventKey = eventsActionsMap[action.type]; - const profilePropertyType = eventKey?.profilePropertyType; - const attributes = eventKey?.getAttributes?.(action?.payload, action?.meta); - - if (eventKey?.shouldBlock && eventKey?.shouldBlock(action?.payload, action?.meta)) { - return next(action); - } - - profilePropertyType.forEach((type) => { - setSuperOrProfileProperty(type, attributes, nodeEnv); - }); + const eventName = eventsActionsMap[action.type]; + const data = { payload: action.payload, params: action.meta?.arg }; + eventStrategyFactory.triggerEvent(eventName, data); } - return next(action); }; diff --git a/packages/pn-commons/src/utility/MixpanelUtils/CommonEventStrategyFactory.ts b/packages/pn-commons/src/utility/MixpanelUtils/CommonEventStrategyFactory.ts new file mode 100644 index 0000000000..0c9469464f --- /dev/null +++ b/packages/pn-commons/src/utility/MixpanelUtils/CommonEventStrategyFactory.ts @@ -0,0 +1,31 @@ +import { EventType } from '../../models/EventType'; +import EventStrategyFactory from './EventStrategyFactory'; + +/** + * The EventStrategyFactory for the pn-commons library. + * + * @date 20/3/2024 - 10:20:28 + * + * @class CommonEventStrategyFactory + * @typedef {CommonEventStrategyFactory} + * @extends {EventStrategyFactory} + * @see EventStrategyFactory + */ +class CommonEventStrategyFactory extends EventStrategyFactory { + /** + * The strategy management specific for all those events that must be tracked in pn-commons library. + * + * @date 20/3/2024 - 10:20:33 + * + * @param {EventType} eventType + * @returns {*} + */ + getStrategy(eventType: EventType) { + switch (eventType) { + default: + return null; + } + } +} + +export default new CommonEventStrategyFactory(); diff --git a/packages/pn-commons/src/utility/MixpanelUtils/EventStrategyFactory.ts b/packages/pn-commons/src/utility/MixpanelUtils/EventStrategyFactory.ts new file mode 100644 index 0000000000..e30aded7c4 --- /dev/null +++ b/packages/pn-commons/src/utility/MixpanelUtils/EventStrategyFactory.ts @@ -0,0 +1,55 @@ +import { EventPropertyType } from '../../models'; +import EventStrategy from '../../models/EventStrategy'; +import { trackEvent } from '../../services'; + +/** + * The abstract factory that must be extended by each application to define + * its own factory for event tracking management. + * + * @date 20/3/2024 - 10:16:08 + * + * @export + * @abstract + * @class EventStrategyFactory + * @typedef {EventStrategyFactory} + * @template {string} T + */ +export default abstract class EventStrategyFactory { + /** + * This method must be implemented by each applications. + * It defines the event strategy management. + * + * @date 20/3/2024 - 10:14:37 + * + * @abstract + * @param {T} eventType + * @returns {(EventStrategy | null)} + * @see EventStrategy + */ + abstract getStrategy(eventType: T): EventStrategy | null; + + /** + * This is the method that, given a specific event, gets the strategy, does the needed computations + * and track the event. + * It must not be overwritten unless strictly necessary. + * + * @date 20/3/2024 - 10:18:02 + * + * @public + * @param {T} eventType + * @param {?unknown} [data] + */ + public triggerEvent(eventType: T, data?: unknown) { + const strategy = this.getStrategy(eventType); + + if (!strategy) { + throw new Error('Unknown event type ' + eventType); + } + + const eventParameters = strategy.performComputations(data); + + for (const [type, parameters] of Object.entries(eventParameters)) { + trackEvent(type as EventPropertyType, eventType, process.env.NODE_ENV!, parameters); + } + } +} diff --git a/packages/pn-commons/src/utility/MixpanelUtils/Strategies/MultiPaymentsMoreInfoStrategy.ts b/packages/pn-commons/src/utility/MixpanelUtils/Strategies/MultiPaymentsMoreInfoStrategy.ts new file mode 100644 index 0000000000..f0594e3dae --- /dev/null +++ b/packages/pn-commons/src/utility/MixpanelUtils/Strategies/MultiPaymentsMoreInfoStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class MultiPaymentsMoreInfoStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + }, + }; + } +} diff --git a/packages/pn-commons/src/utility/__test__/localization.utility.test.ts b/packages/pn-commons/src/utility/__test__/localization.utility.test.ts index 8b69987ca0..8c37f1888c 100644 --- a/packages/pn-commons/src/utility/__test__/localization.utility.test.ts +++ b/packages/pn-commons/src/utility/__test__/localization.utility.test.ts @@ -18,6 +18,7 @@ describe('localization service', () => { notifications: 'different-namespace', appStatus: 'appStatus', delegations: 'deleghe', + recapiti: 'recapiti', }); const label = getLocalizedOrDefaultLabel('notifications', 'mocked.path', 'default label'); expect(label).toBe('different-namespace mocked.path'); diff --git a/packages/pn-commons/src/utility/index.ts b/packages/pn-commons/src/utility/index.ts index 3fd4b3a248..fa70d3499a 100644 --- a/packages/pn-commons/src/utility/index.ts +++ b/packages/pn-commons/src/utility/index.ts @@ -1,4 +1,5 @@ import { Configuration } from '../services/configuration.service'; +import EventStrategyFactory from '../utility/MixpanelUtils/EventStrategyFactory'; import { AppError, AppErrorFactory, UnknownAppError, errorFactoryManager } from './AppError'; import { AppResponsePublisher, ResponseEventDispatcher } from './AppResponse'; import { PRIVACY_LINK_RELATIVE_PATH, TOS_LINK_RELATIVE_PATH } from './costants'; @@ -126,4 +127,5 @@ export { rewriteLinks, dateIsLessThan10Years, checkRaddInTimeline, + EventStrategyFactory, }; diff --git a/packages/pn-commons/src/utility/localization.utility.ts b/packages/pn-commons/src/utility/localization.utility.ts index 3cb078b754..59d1006cf9 100644 --- a/packages/pn-commons/src/utility/localization.utility.ts +++ b/packages/pn-commons/src/utility/localization.utility.ts @@ -1,4 +1,9 @@ -type LocalizationNamespacesNames = 'common' | 'notifications' | 'appStatus' | 'delegations'; +type LocalizationNamespacesNames = + | 'common' + | 'notifications' + | 'appStatus' + | 'delegations' + | 'recapiti'; type LocalizationNamespaces = { [key in LocalizationNamespacesNames]: string; @@ -16,6 +21,7 @@ let localizationNamespaces: LocalizationNamespaces = { notifications: 'notifiche', appStatus: 'appStatus', delegations: 'deleghe', + recapiti: 'recapiti', }; /* eslint-disable-next-line functional/no-let */ 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 d216ece221..f8b1c0c7f0 100644 --- a/packages/pn-pa-webapp/src/App.tsx +++ b/packages/pn-pa-webapp/src/App.tsx @@ -1,4 +1,4 @@ -import { ErrorInfo, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; @@ -21,7 +21,6 @@ import { initLocalization, useMultiEvent, useTracking, - useUnload, } from '@pagopa-pn/pn-commons'; import { LinkType, ProductEntity } from '@pagopa/mui-italia'; @@ -33,8 +32,6 @@ import { useAppDispatch, useAppSelector } from './redux/hooks'; import { RootState } from './redux/store'; import { getConfiguration } from './services/configuration.service'; import { PAAppErrorFactory } from './utility/AppError/PAAppErrorFactory'; -import { TrackEventType } from './utility/events'; -import { trackEventByType } from './utility/mixpanel'; import './utility/onetrust'; import { getMenuItems } from './utility/role.utility'; @@ -61,10 +58,6 @@ const App = () => { }; const ActualApp = () => { - useUnload(() => { - trackEventByType(TrackEventType.APP_UNLOAD); - }); - const loggedUser = useAppSelector((state: RootState) => state.userState.user); const loggedUserOrganizationParty = loggedUser.organization; // TODO check if it can exist more than one role on user @@ -189,31 +182,16 @@ const ActualApp = () => { const { pathname } = useLocation(); const path = pathname.split('/'); - const source = path[path.length - 1]; const isPrivacyPage = path[1] === 'privacy-tos'; - const handleEventTrackingCallbackAppCrash = (e: Error, eInfo: ErrorInfo) => { - trackEventByType(TrackEventType.APP_CRASH, { - route: source, - stacktrace: { error: e, errorInfo: eInfo }, - }); - }; - - const handleEventTrackingCallbackProductSwitch = (target: string) => { - trackEventByType(TrackEventType.USER_PRODUCT_SWITCH, { target }); - }; - const handleLogout = () => { void dispatch(logout()); }; const handleAssistanceClick = () => { - trackEventByType(TrackEventType.CUSTOMER_CARE_MAILTO, { - source: sessionToken ? 'postlogin' : 'prelogin', - }); /* 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}`; }; @@ -238,20 +216,10 @@ const ActualApp = () => { showHeader={!isPrivacyPage} showFooter={!isPrivacyPage} onExitAction={handleLogout} - eventTrackingCallbackAppCrash={handleEventTrackingCallbackAppCrash} - eventTrackingCallbackProductSwitch={(target: string) => - handleEventTrackingCallbackProductSwitch(target) - } sideMenu={ role && menuItems && ( - - trackEventByType(TrackEventType.USER_NAV_ITEM, { target }) - } - /> + ) } showSideMenu={ diff --git a/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx b/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx index 79eaa31023..f1593b2222 100644 --- a/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx +++ b/packages/pn-pa-webapp/src/components/ApiKeys/ApiKeyDataSwitch.tsx @@ -229,12 +229,7 @@ const ApiKeyDataSwitch: React.FC<{ justifyContent: 'center', }} > - {}} - /> +
); } diff --git a/packages/pn-pa-webapp/src/components/NewNotification/PreliminaryInformations.tsx b/packages/pn-pa-webapp/src/components/NewNotification/PreliminaryInformations.tsx index df99ae5e53..8420e946d6 100644 --- a/packages/pn-pa-webapp/src/components/NewNotification/PreliminaryInformations.tsx +++ b/packages/pn-pa-webapp/src/components/NewNotification/PreliminaryInformations.tsx @@ -31,8 +31,6 @@ import { setPreliminaryInformations } from '../../redux/newNotification/reducers import { PreliminaryInformationsPayload } from '../../redux/newNotification/types'; import { RootState } from '../../redux/store'; import { getConfiguration } from '../../services/configuration.service'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import { requiredStringFieldValidation } from '../../utility/validation.utility'; import NewNotificationCard from './NewNotificationCard'; @@ -113,12 +111,10 @@ const PreliminaryInformations = ({ notification, onConfirm }: Props) => { const handleChangeDeliveryMode = (e: ChangeEvent & { target: { value: any } }) => { formik.handleChange(e); - trackEventByType(TrackEventType.NOTIFICATION_SEND_DELIVERY_MODE, { type: e.target.value }); }; const handleChangePaymentMode = (e: ChangeEvent & { target: { value: any } }) => { formik.handleChange(e); - trackEventByType(TrackEventType.NOTIFICATION_SEND_PAYMENT_MODE, { target: e.target.value }); }; const fetchGroups = useCallback(() => { diff --git a/packages/pn-pa-webapp/src/components/NewNotification/Recipient.tsx b/packages/pn-pa-webapp/src/components/NewNotification/Recipient.tsx index 267a881bcf..f12df6eeb3 100644 --- a/packages/pn-pa-webapp/src/components/NewNotification/Recipient.tsx +++ b/packages/pn-pa-webapp/src/components/NewNotification/Recipient.tsx @@ -28,8 +28,6 @@ import { ButtonNaked } from '@pagopa/mui-italia'; import { NewNotificationRecipient, PaymentModel } from '../../models/NewNotification'; import { useAppDispatch } from '../../redux/hooks'; import { saveRecipients } from '../../redux/newNotification/reducers'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import { denominationLengthAndCharacters, identicalIUV, @@ -250,13 +248,6 @@ const Recipient: React.FC = ({ ) => { const checked = (event.target as any).checked; const name = (event.target as any).name; - if (checked) { - trackEventByType( - name.endsWith('showPhysicalAddress') - ? TrackEventType.NOTIFICATION_SEND_PHYSICAL_ADDRESS - : TrackEventType.NOTIFICATION_SEND_DIGITAL_DOMICILE - ); - } if (!checked && name.endsWith('showPhysicalAddress')) { // reset physical address setFieldValue( @@ -297,9 +288,6 @@ const Recipient: React.FC = ({ ...values.recipients, { ...singleRecipient, idx: lastRecipientIdx + 1, id: `recipient.${lastRecipientIdx + 1}` }, ]); - trackEventByType(TrackEventType.NOTIFICATION_SEND_MULTIPLE_RECIPIENTS, { - recipients: lastRecipientIdx + 1, - }); }; const handleSubmit = (values: FormRecipients) => { @@ -361,9 +349,6 @@ const Recipient: React.FC = ({ // In fact, I would have liked to specify the change through a function, i.e. // setFieldValue(`recipients[${index}]`, (currentValue: any) => ({...currentValue, ...valuesToUpdate})); // but unfortunately Formik' setFieldValue is not capable of handling such kind of updates. - trackEventByType(TrackEventType.NOTIFICATION_SEND_RECIPIENT_TYPE, { - type: event.currentTarget.value, - }); }; useImperativeHandle(forwardedRef, () => ({ diff --git a/packages/pn-pa-webapp/src/components/Notifications/DesktopNotifications.tsx b/packages/pn-pa-webapp/src/components/Notifications/DesktopNotifications.tsx index 559b9077b3..67b0ce1948 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/DesktopNotifications.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/DesktopNotifications.tsx @@ -19,8 +19,6 @@ import { } from '@pagopa-pn/pn-commons'; import * as routes from '../../navigation/routes.const'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import FilterNotifications from './FilterNotifications'; import NotificationsDataSwitch from './NotificationsDataSwitch'; @@ -160,8 +158,6 @@ const DesktopNotifications = ({ // Navigation handlers const handleRowClick = (row: Row) => { navigate(routes.GET_DETTAGLIO_NOTIFICA_PATH(row.iun)); - // log event - trackEventByType(TrackEventType.NOTIFICATION_TABLE_ROW_INTERACTION); }; const filtersApplied: boolean = filterNotificationsRef.current.filtersApplied; diff --git a/packages/pn-pa-webapp/src/components/Notifications/FilterNotifications.tsx b/packages/pn-pa-webapp/src/components/Notifications/FilterNotifications.tsx index b9f49d4f6c..6d02725c79 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/FilterNotifications.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/FilterNotifications.tsx @@ -24,8 +24,6 @@ import { import { setNotificationFilters } from '../../redux/dashboard/reducers'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import FilterNotificationsFormActions from './FilterNotificationsFormActions'; import FilterNotificationsFormBody from './FilterNotificationsFormBody'; @@ -105,7 +103,6 @@ const FilterNotifications = forwardRef(({ showFilters }: Props, ref) => { if (_.isEqual(prevFilters, currentFilters)) { return; } - trackEventByType(TrackEventType.NOTIFICATION_FILTER_SEARCH); dispatch(setNotificationFilters(currentFilters)); setPrevFilters(currentFilters); dialogRef.current?.toggleOpen(); @@ -113,7 +110,6 @@ const FilterNotifications = forwardRef(({ showFilters }: Props, ref) => { }); const cancelSearch = () => { - trackEventByType(TrackEventType.NOTIFICATION_FILTER_REMOVE); dispatch(setNotificationFilters(emptyValues)); }; diff --git a/packages/pn-pa-webapp/src/components/Notifications/FilterNotificationsFormBody.tsx b/packages/pn-pa-webapp/src/components/Notifications/FilterNotificationsFormBody.tsx index db7edfcc89..2072d387e7 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/FilterNotificationsFormBody.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/FilterNotificationsFormBody.tsx @@ -14,9 +14,6 @@ import { useIsMobile, } from '@pagopa-pn/pn-commons'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; - type Props = { formikInstance: { values: FormikValues; @@ -81,7 +78,6 @@ const FilterNotificationsFormBody = ({ } else { await formikInstance.setFieldValue(e.target.id, (e.target as HTMLInputElement).value, false); } - trackEventByType(TrackEventType.NOTIFICATION_FILTER_TYPE, { target: e.target.id }); }; const handleChangeNotificationStatus = async (e: ChangeEvent) => { @@ -90,7 +86,6 @@ const FilterNotificationsFormBody = ({ (e.target as HTMLSelectElement).value, false ); - trackEventByType(TrackEventType.NOTIFICATION_FILTER_NOTIFICATION_STATE); }; return ( @@ -139,7 +134,6 @@ const FilterNotificationsFormBody = ({ onChange={(value: DatePickerTypes) => { void formikInstance.setFieldValue('startDate', value || tenYearsAgo).then(() => { setStartDate(value); - trackEventByType(TrackEventType.NOTIFICATION_FILTER_DATE, { source: 'from date' }); }); }} slotProps={{ @@ -169,7 +163,6 @@ const FilterNotificationsFormBody = ({ value={endDate} onChange={(value: DatePickerTypes) => { void formikInstance.setFieldValue('endDate', value || today).then(() => { - trackEventByType(TrackEventType.NOTIFICATION_FILTER_DATE, { source: 'to date' }); setEndDate(value); }); }} diff --git a/packages/pn-pa-webapp/src/components/Notifications/MobileNotifications.tsx b/packages/pn-pa-webapp/src/components/Notifications/MobileNotifications.tsx index 40c8fde913..74d8f581b9 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/MobileNotifications.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/MobileNotifications.tsx @@ -25,8 +25,6 @@ import { import { ButtonNaked } from '@pagopa/mui-italia'; import * as routes from '../../navigation/routes.const'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import FilterNotifications from './FilterNotifications'; import NotificationsDataSwitch from './NotificationsDataSwitch'; @@ -146,8 +144,6 @@ const MobileNotifications = ({ // Navigation handlers const handleRowClick = (row: Row) => { navigate(routes.GET_DETTAGLIO_NOTIFICA_PATH(row.iun)); - // log event - trackEventByType(TrackEventType.NOTIFICATION_TABLE_ROW_INTERACTION); }; const cardData: Array> = notifications.map((n) => ({ diff --git a/packages/pn-pa-webapp/src/components/Notifications/NotificationDetailTableSender.tsx b/packages/pn-pa-webapp/src/components/Notifications/NotificationDetailTableSender.tsx index 399de67693..61d21b8647 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/NotificationDetailTableSender.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/NotificationDetailTableSender.tsx @@ -19,8 +19,6 @@ import { Tag, TagGroup } from '@pagopa/mui-italia'; import { PNRole } from '../../models/user'; import { useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import ConfirmCancellationDialog from './ConfirmCancellationDialog'; import NotificationRecipientsDetail from './NotificationRecipientsDetail'; @@ -42,7 +40,6 @@ const NotificationDetailTableSender: React.FC = ({ notification, onCancel const role = currentUser.organization?.roles ? currentUser.organization?.roles[0] : null; const userHasAdminPermissions = useHasPermissions(role ? [role.role] : [], [PNRole.ADMIN]); const openModal = () => { - trackEventByType(TrackEventType.NOTIFICATION_DETAIL_CANCEL_NOTIFICATION); setShowModal(true); }; diff --git a/packages/pn-pa-webapp/src/components/Notifications/NotificationPaymentPagoPa.tsx b/packages/pn-pa-webapp/src/components/Notifications/NotificationPaymentPagoPa.tsx index f7851baacf..e34bac6030 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/NotificationPaymentPagoPa.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/NotificationPaymentPagoPa.tsx @@ -12,8 +12,6 @@ import { ButtonNaked } from '@pagopa/mui-italia'; import { useAppDispatch } from '../../redux/hooks'; import { getPaymentAttachment } from '../../redux/notification/actions'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; type Props = { iun: string; @@ -26,7 +24,6 @@ const NotificationPaymentPagoPa: React.FC = ({ iun, payment }) => { const dowloadHandler = () => { if (!_.isNil(payment.recIndex) && payment.attachment) { - trackEventByType(TrackEventType.NOTIFICATION_DETAIL_PAYMENT_PAGOPA_FILE); dispatch( getPaymentAttachment({ iun, 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/components/Notifications/NotificationsDataSwitch.tsx b/packages/pn-pa-webapp/src/components/Notifications/NotificationsDataSwitch.tsx index 786d6dd370..575f79fc1e 100644 --- a/packages/pn-pa-webapp/src/components/Notifications/NotificationsDataSwitch.tsx +++ b/packages/pn-pa-webapp/src/components/Notifications/NotificationsDataSwitch.tsx @@ -10,24 +10,11 @@ import { } from '@pagopa-pn/pn-commons'; import { Tag, TagGroup } from '@pagopa/mui-italia'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; - const NotificationStatusChip: React.FC<{ data: Row }> = ({ data }) => { - const handleEventTrackingTooltip = () => { - trackEventByType(TrackEventType.NOTIFICATION_TABLE_ROW_TOOLTIP); - }; const { label, tooltip, color } = getNotificationStatusInfos(data.notificationStatus, { recipients: data.recipients, }); - return ( - - ); + return ; }; const NotificationsDataSwitch: React.FC<{ data: Row; type: keyof Notification }> = ({ diff --git a/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx b/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx index 0e63cb9b2b..14f6e45693 100644 --- a/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx +++ b/packages/pn-pa-webapp/src/pages/ApiKeys.page.tsx @@ -29,8 +29,6 @@ import { setPagination } from '../redux/apiKeys/reducers'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; import { getConfiguration } from '../services/configuration.service'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; const LinkApiB2b: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const { API_B2B_LINK } = getConfiguration(); @@ -154,14 +152,9 @@ const ApiKeys = () => { // Pagination handlers const handleChangePage = (paginationData: PaginationData) => { - trackEventByType(TrackEventType.APIKEYS_TABLE_PAGINATION); dispatch(setPagination({ size: paginationData.size, page: paginationData.page })); }; - const handleEventTrackingCallbackPageSize = (pageSize: number) => { - trackEventByType(TrackEventType.APIKEYS_TABLE_SIZE, { pageSize }); - }; - return ( { totalElements, }} onPageRequest={handleChangePage} - eventTrackingCallbackPageSize={handleEventTrackingCallbackPageSize} pagesToShow={pagesToShow} /> )} diff --git a/packages/pn-pa-webapp/src/pages/Dashboard.page.tsx b/packages/pn-pa-webapp/src/pages/Dashboard.page.tsx index 29489dc658..5ee3facdae 100644 --- a/packages/pn-pa-webapp/src/pages/Dashboard.page.tsx +++ b/packages/pn-pa-webapp/src/pages/Dashboard.page.tsx @@ -1,7 +1,6 @@ import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { ButtonNaked } from '@pagopa/mui-italia'; import { Alert, Box, Button, Typography } from '@mui/material'; import { @@ -12,19 +11,15 @@ import { calculatePages, useIsMobile, } from '@pagopa-pn/pn-commons'; +import { ButtonNaked } from '@pagopa/mui-italia'; import DesktopNotifications from '../components/Notifications/DesktopNotifications'; import MobileNotifications from '../components/Notifications/MobileNotifications'; import * as routes from '../navigation/routes.const'; -import { - DASHBOARD_ACTIONS, - getSentNotifications, -} from '../redux/dashboard/actions'; +import { DASHBOARD_ACTIONS, getSentNotifications } from '../redux/dashboard/actions'; import { setPagination } from '../redux/dashboard/reducers'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; import { getConfiguration } from '../services/configuration.service'; const Dashboard = () => { @@ -53,14 +48,11 @@ const Dashboard = () => { // Pagination handlers const handleChangePage = (paginationData: PaginationData) => { - trackEventByType(TrackEventType.NOTIFICATION_TABLE_PAGINATION); dispatch(setPagination({ size: paginationData.size, page: paginationData.page })); }; - // route to Manual Send const handleRouteManualSend = () => { - trackEventByType(TrackEventType.NOTIFICATION_SEND); navigate(routes.NUOVA_NOTIFICA); }; @@ -84,10 +76,6 @@ const Dashboard = () => { fetchNotifications(); }, [fetchNotifications]); - const handleEventTrackingCallbackPageSize = (pageSize: number) => { - trackEventByType(TrackEventType.NOTIFICATION_TABLE_SIZE, { pageSize }); - }; - return ( { {t('subtitle')} - {getConfiguration().IS_MANUAL_SEND_ENABLED ? : - navigate(routes.APP_STATUS)}> - {t('manual-send-disabled-action')} - - }> + {getConfiguration().IS_MANUAL_SEND_ENABLED ? ( + + ) : ( + navigate(routes.APP_STATUS)} + > + {t('manual-send-disabled-action')} + + } + > {t('manual-send-disabled-message')} - } - + + )} } /> @@ -152,7 +149,6 @@ const Dashboard = () => { totalElements, }} onPageRequest={handleChangePage} - eventTrackingCallbackPageSize={handleEventTrackingCallbackPageSize} pagesToShow={pagesToShow} /> )} 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/NewNotification.page.tsx b/packages/pn-pa-webapp/src/pages/NewNotification.page.tsx index a933c33400..475fb0debc 100644 --- a/packages/pn-pa-webapp/src/pages/NewNotification.page.tsx +++ b/packages/pn-pa-webapp/src/pages/NewNotification.page.tsx @@ -15,8 +15,6 @@ import { createNewNotification } from '../redux/newNotification/actions'; import { resetState, setSenderInfos } from '../redux/newNotification/reducers'; import { RootState } from '../redux/store'; import { getConfiguration } from '../services/configuration.service'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; const SubTitle = () => { const { t } = useTranslation(['common', 'notifiche']); @@ -47,40 +45,12 @@ const NewNotification = () => { const childRef = useRef<{ confirm: () => void }>(); - const eventStep = [ - TrackEventType.NOTIFICATION_SEND_PRELIMINARY_INFO, - TrackEventType.NOTIFICATION_SEND_RECIPIENT_INFO, - TrackEventType.NOTIFICATION_SEND_ATTACHMENTS, - ]; - if (IS_PAYMENT_ENABLED) { - // eslint-disable-next-line functional/immutable-data - eventStep.push(TrackEventType.NOTIFICATION_SEND_PAYMENT_MODES); // eslint-disable-next-line functional/immutable-data steps.push(t('new-notification.steps.payment-methods.title', { ns: 'notifiche' })); } - const stepType = ['preliminary info', 'recipient', 'attachments', 'payment modes']; - - const handleEventTrackingCallbackPromptOpened = () => { - trackEventByType(TrackEventType.NOTIFICATION_SEND_EXIT_WARNING, { - source: stepType[activeStep], - }); - }; - - const handleEventTrackingCallbackCancel = () => { - trackEventByType(TrackEventType.NOTIFICATION_SEND_EXIT_CANCEL, { - source: stepType[activeStep], - }); - }; - - const handleEventTrackingCallbackConfirm = () => { - trackEventByType(TrackEventType.NOTIFICATION_SEND_EXIT_FLOW, { source: stepType[activeStep] }); - }; - const goToNextStep = () => { - trackEventByType(eventStep[activeStep]); - setActiveStep((previousStep) => previousStep + 1); }; @@ -139,9 +109,6 @@ const NewNotification = () => { diff --git a/packages/pn-pa-webapp/src/pages/NotificationDetail.page.tsx b/packages/pn-pa-webapp/src/pages/NotificationDetail.page.tsx index c1ca2d2c10..0a1aeb4221 100644 --- a/packages/pn-pa-webapp/src/pages/NotificationDetail.page.tsx +++ b/packages/pn-pa-webapp/src/pages/NotificationDetail.page.tsx @@ -46,8 +46,6 @@ import { } from '../redux/notification/reducers'; import { RootState } from '../redux/store'; import { ServerResponseErrorCode } from '../utility/AppError/types'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; type Props = { notification: NotificationDetailType; @@ -258,8 +256,6 @@ const NotificationDetail: React.FC = () => { [] ); - const viewMoreTimeline = () => trackEventByType(TrackEventType.NOTIFICATION_TIMELINE_VIEW_MORE); - useDownloadDocument({ url: legalFactDownloadUrl }); useDownloadDocument({ url: documentDownloadUrl }); useDownloadDocument({ url: otherDocumentDownloadUrl }); @@ -375,7 +371,6 @@ const NotificationDetail: React.FC = () => { historyButtonLabel={t('detail.show-history', { ns: 'notifiche' })} showMoreButtonLabel={t('detail.show-more', { ns: 'notifiche' })} showLessButtonLabel={t('detail.show-less', { ns: 'notifiche' })} - eventTrackingCallbackShowMore={viewMoreTimeline} /> 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-pa-webapp/src/redux/store.ts b/packages/pn-pa-webapp/src/redux/store.ts index d73c7b291e..80beddcf9b 100644 --- a/packages/pn-pa-webapp/src/redux/store.ts +++ b/packages/pn-pa-webapp/src/redux/store.ts @@ -4,7 +4,6 @@ import { appStateReducer } from '@pagopa-pn/pn-commons'; import { Middleware, MiddlewareArray, configureStore } from '@reduxjs/toolkit'; import { getConfiguration } from '../services/configuration.service'; -import { trackingMiddleware } from '../utility/mixpanel'; import newApiKeySlice from './NewApiKey/reducers'; import apiKeysSlice from './apiKeys/reducers'; import appStatusSlice from './appStatus/reducers'; @@ -28,7 +27,7 @@ export const appReducers = { const createStore = (logReduxActions?: boolean) => { const mustLogActions = logReduxActions ?? getConfiguration().LOG_REDUX_ACTIONS; - const additionalMiddlewares = [mustLogActions ? logger : undefined, trackingMiddleware]; + const additionalMiddlewares = [mustLogActions ? logger : undefined]; return configureStore({ reducer: appReducers, middleware: (getDefaultMiddleware) => diff --git a/packages/pn-pa-webapp/src/utility/events.ts b/packages/pn-pa-webapp/src/utility/events.ts deleted file mode 100644 index 4e886f0fd6..0000000000 --- a/packages/pn-pa-webapp/src/utility/events.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { EventsType } from '@pagopa-pn/pn-commons'; - -export enum TrackEventType { - NOTIFICATIONS_CHANGE_PAGE = 'setPagination', - NOTIFICATION_FILTER_TYPE = 'NOTIFICATION_FILTER_TYPE', - NOTIFICATION_FILTER_DATE = 'NOTIFICATION_FILTER_DATE', - NOTIFICATION_FILTER_NOTIFICATION_STATE = 'NOTIFICATION_FILTER_NOTIFICATION_STATE', - NOTIFICATION_FILTER_CODE_VALIDATION_RATE = 'NOTIFICATION_FILTER_CODE_VALIDATION_RATE', - NOTIFICATION_FILTER_SEARCH = 'NOTIFICATION_FILTER_SEARCH', - NOTIFICATION_FILTER_REMOVE = 'NOTIFICATION_FILTER_REMOVE', - NOTIFICATION_DETAIL_CANCEL_NOTIFICATION = 'NOTIFICATION_DETAIL_CANCEL_NOTIFICATION', - NOTIFICATION_DETAIL_CONFIRM_CANCEL_NOTIFICATION = 'setCancelledIun/fulfilled', - NOTIFICATION_DETAIL_ALL_ATTACHMENTS = 'NOTIFICATION_DETAIL_ALL_ATTACHMENTS', - NOTIFICATION_DETAIL_SINGLE_ATTACHMENT = 'getSentNotificationDocument/fulfilled', - NOTIFICATION_DETAIL_PAYMENT_PAGOPA_FILE = 'NOTIFICATION_DETAIL_PAYMENT_PAGOPA_FILE', - NOTIFICATION_SEND = 'NOTIFICATION_SEND', - NOTIFICATION_SEND_DELIVERY_MODE = 'NOTIFICATION_SEND_DELIVERY_MODE', - NOTIFICATION_SEND_PAYMENT_MODE = 'NOTIFICATION_SEND_PAYMENT_MODE', - NOTIFICATION_SEND_PRELIMINARY_INFO = 'NOTIFICATION_SEND_PRELIMINARY_INFO', - NOTIFICATION_SEND_RECIPIENT_TYPE = 'NOTIFICATION_SEND_RECIPIENT_TYPE', - NOTIFICATION_SEND_PHYSICAL_ADDRESS = 'NOTIFICATION_SEND_PHYSICAL_ADDRESS', - NOTIFICATION_SEND_DIGITAL_DOMICILE = 'NOTIFICATION_SEND_DIGITAL_DOMICILE', - NOTIFICATION_SEND_MULTIPLE_RECIPIENTS = 'NOTIFICATION_SEND_MULTIPLE_RECIPIENTS', - NOTIFICATION_SEND_RECIPIENT_INFO = 'NOTIFICATION_SEND_RECIPIENT_INFO', - NOTIFICATION_SEND_ATTACHMENTS = 'NOTIFICATION_SEND_ATTACHMENTS', - NOTIFICATION_SEND_PAYMENT_MODES = 'NOTIFICATION_SEND_PAYMENT_MODES', - NOTIFICATION_SEND_EXIT_WARNING = 'NOTIFICATION_SEND_EXIT_WARNING', - NOTIFICATION_SEND_EXIT_FLOW = 'NOTIFICATION_SEND_EXIT_FLOW', - NOTIFICATION_SEND_EXIT_CANCEL = 'NOTIFICATION_SEND_EXIT_CANCEL', - NOTIFICATION_TABLE_SORT = 'NOTIFICATION_TABLE_SORT', - NOTIFICATION_TABLE_ROW_INTERACTION = 'NOTIFICATION_TABLE_ROW_INTERACTION', - NOTIFICATION_TABLE_ROW_TOOLTIP = 'NOTIFICATION_TABLE_ROW_TOOLTIP', - NOTIFICATION_TABLE_SIZE = 'NOTIFICATION_TABLE_SIZE', - NOTIFICATION_TABLE_PAGINATION = 'NOTIFICATION_TABLE_PAGINATION', - NOTIFICATION_TIMELINE_ALL_ATTACHMENTS = 'NOTIFICATION_TIMELINE_ALL_ATTACHMENTS', - NOTIFICATION_TIMELINE_SINGLE_ATTACHMENT = 'getSentNotificationLegalfact/fulfilled', - NOTIFICATION_TIMELINE_VIEW_MORE = 'NOTIFICATION_TIMELINE_VIEW_MORE', - CUSTOMER_CARE_MAILTO = 'CUSTOMER_CARE_MAILTO', - CUSTOMER_CARE_CONTACT = 'CUSTOMER_CARE_CONTACT', - CUSTOMER_CARE_CONTACT_SUCCESS = 'CUSTOMER_CARE_CONTACT_SUCCESS', - CUSTOMER_CARE_CONTACT_FAILURE = 'CUSTOMER_CARE_CONTACT_FAILURE', - APP_CRASH = 'APP_CRASH', - APP_UNLOAD = 'APP_UNLOAD', - USER_PRODUCT_SWITCH = 'USER_PRODUCT_SWITCH', - USER_PARTY_SWITCH = 'USER_PARTY_SWITCH', - USER_LOGOUT = 'logout/fulfilled', - USER_NAV_ITEM = 'USER_NAV_ITEM', - APIKEYS_TABLE_PAGINATION = 'APIKEYS_TABLE_PAGINATION', - APIKEYS_TABLE_SIZE = 'APIKEYS_TABLE_SIZE' -} - -export const events: EventsType = { - [TrackEventType.NOTIFICATIONS_CHANGE_PAGE]: { - event_category: 'notifications', - event_type: 'change page', - }, - [TrackEventType.NOTIFICATION_FILTER_TYPE]: { - event_category: 'notifications', - event_type: 'filter by type', - }, - [TrackEventType.NOTIFICATION_FILTER_DATE]: { - event_category: 'notifications', - event_type: 'filter by date', - }, - [TrackEventType.NOTIFICATION_FILTER_NOTIFICATION_STATE]: { - event_category: 'notifications', - event_type: 'filter by notification status', - }, - [TrackEventType.NOTIFICATION_FILTER_CODE_VALIDATION_RATE]: { - event_category: 'notifications', - event_type: 'filter validation code', - }, - [TrackEventType.NOTIFICATION_FILTER_SEARCH]: { - event_category: 'notifications', - event_type: 'click on filter search', - }, - [TrackEventType.NOTIFICATION_FILTER_REMOVE]: { - event_category: 'notifications', - event_type: 'click on remove filters', - }, - [TrackEventType.NOTIFICATION_DETAIL_CANCEL_NOTIFICATION]: { - event_category: 'notifications', - event_type: 'cancel notification', - }, - [TrackEventType.NOTIFICATION_DETAIL_CONFIRM_CANCEL_NOTIFICATION]: { - event_category: 'notifications', - event_type: 'confirm cancel notification', - }, - [TrackEventType.NOTIFICATION_DETAIL_ALL_ATTACHMENTS]: { - event_category: 'notifications', - event_type: 'detail all attachments', - }, - [TrackEventType.NOTIFICATION_DETAIL_SINGLE_ATTACHMENT]: { - event_category: 'notifications', - event_type: 'detail single attachment', - }, - [TrackEventType.NOTIFICATION_DETAIL_PAYMENT_PAGOPA_FILE]: { - event_category: 'notification', - event_type: 'download the PagoPA file', - }, - [TrackEventType.NOTIFICATION_SEND]: { - event_category: 'notifications', - event_type: 'send notification', - }, - [TrackEventType.NOTIFICATION_SEND_DELIVERY_MODE]: { - event_category: 'notifications', - event_type: 'delivery mode', - }, - [TrackEventType.NOTIFICATION_SEND_PAYMENT_MODE]: { - event_category: 'notifications', - event_type: 'payment mode', - }, - [TrackEventType.NOTIFICATION_SEND_PRELIMINARY_INFO]: { - event_category: 'notifications', - event_type: 'preliminary info', - }, - [TrackEventType.NOTIFICATION_SEND_RECIPIENT_TYPE]: { - event_category: 'notifications', - event_type: 'recipient type', - }, - [TrackEventType.NOTIFICATION_SEND_PHYSICAL_ADDRESS]: { - event_category: 'notifications', - event_type: 'phsycal address', - }, - [TrackEventType.NOTIFICATION_SEND_DIGITAL_DOMICILE]: { - event_category: 'notifications', - event_type: 'digital domicile', - }, - [TrackEventType.NOTIFICATION_SEND_MULTIPLE_RECIPIENTS]: { - event_category: 'notifications', - event_type: 'multiple recipients', - }, - [TrackEventType.NOTIFICATION_SEND_RECIPIENT_INFO]: { - event_category: 'notifications', - event_type: 'recipient info', - }, - [TrackEventType.NOTIFICATION_SEND_ATTACHMENTS]: { - event_category: 'notifications', - event_type: 'attachments', - }, - [TrackEventType.NOTIFICATION_SEND_PAYMENT_MODES]: { - event_category: 'notifications', - event_type: 'payments mode', - }, - [TrackEventType.NOTIFICATION_SEND_EXIT_WARNING]: { - event_category: 'notifications', - event_type: 'confirm cancel send notification', - }, - [TrackEventType.NOTIFICATION_SEND_EXIT_FLOW]: { - event_category: 'notifications', - event_type: 'cancel send notification', - }, - [TrackEventType.NOTIFICATION_SEND_EXIT_CANCEL]: { - event_category: 'notifications', - event_type: 'abort cancel notification', - }, - [TrackEventType.NOTIFICATION_TABLE_SORT]: { - event_category: 'notifications', - event_type: 'sorting table', - }, - [TrackEventType.NOTIFICATION_TABLE_ROW_INTERACTION]: { - event_category: 'notifications', - event_type: 'click on row table', - }, - [TrackEventType.NOTIFICATION_TABLE_ROW_TOOLTIP]: { - event_category: 'notifications', - event_type: 'open table row tooltip', - }, - [TrackEventType.NOTIFICATION_TABLE_SIZE]: { - event_category: 'notifications', - event_type: 'table rows per page', - }, - [TrackEventType.NOTIFICATION_TABLE_PAGINATION]: { - event_category: 'notifications', - event_type: 'table pagination', - }, - [TrackEventType.NOTIFICATION_TIMELINE_ALL_ATTACHMENTS]: { - event_category: 'notifications', - event_type: 'timeline all attachments', - }, - [TrackEventType.NOTIFICATION_TIMELINE_SINGLE_ATTACHMENT]: { - event_category: 'notifications', - event_type: 'single single attachment', - }, - [TrackEventType.NOTIFICATION_TIMELINE_VIEW_MORE]: { - event_category: 'notifications', - event_type: 'timeline view mode', - }, - [TrackEventType.CUSTOMER_CARE_MAILTO]: { - event_category: 'customer care', - event_type: 'click on customer care email', - }, - [TrackEventType.CUSTOMER_CARE_CONTACT]: { - event_category: 'customer care', - event_type: 'click on customer care form', - }, - [TrackEventType.CUSTOMER_CARE_CONTACT_SUCCESS]: { - event_category: 'customer care', - event_type: 'send customer care form success', - }, - [TrackEventType.CUSTOMER_CARE_CONTACT_FAILURE]: { - event_category: 'customer care', - event_type: 'send customer care form failed', - }, - [TrackEventType.APP_CRASH]: { - event_category: 'app', - event_type: 'app crashed', - }, - [TrackEventType.APP_UNLOAD]: { - event_category: 'app', - event_type: 'app unloaded', - }, - [TrackEventType.USER_PRODUCT_SWITCH]: { - event_category: 'user', - event_type: 'switch product', - }, - [TrackEventType.USER_PARTY_SWITCH]: { - event_category: 'user', - event_type: 'switch ente', - }, - [TrackEventType.USER_LOGOUT]: { - event_category: 'user', - event_type: 'user logout', - }, - [TrackEventType.USER_NAV_ITEM]: { - event_category: 'user', - event_type: 'user menu navigation', - }, - [TrackEventType.APIKEYS_TABLE_PAGINATION]: { - event_category: 'apikeys', - event_type: 'table pagination', - }, - [TrackEventType.APIKEYS_TABLE_SIZE]: { - event_category: 'apikeys', - event_type: 'table rows per page', - }, -}; diff --git a/packages/pn-pa-webapp/src/utility/mixpanel.ts b/packages/pn-pa-webapp/src/utility/mixpanel.ts deleted file mode 100644 index 1dd011587f..0000000000 --- a/packages/pn-pa-webapp/src/utility/mixpanel.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { interceptDispatch, trackEvent } from '@pagopa-pn/pn-commons'; -import { AnyAction, Dispatch, Middleware } from '@reduxjs/toolkit'; -import { events, TrackEventType } from './events'; - -/** - * Redux middleware to track events - */ -export const trackingMiddleware: Middleware = - () => (next: Dispatch) => - interceptDispatch(next, events, {}, process.env.NODE_ENV); - -/** - * Function to track events outside redux - * @param trackEventType event name - * @param attributes optional additional attributes - */ -export const trackEventByType = (trackEventType: TrackEventType, attributes?: object) => { - const eventParameters = attributes - ? { ...events[trackEventType], attributes: { ...attributes } } - : events[trackEventType]; - - trackEvent(trackEventType, process.env.NODE_ENV, eventParameters); -}; 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/models/PFLoginEventsType.ts b/packages/pn-personafisica-login/src/models/PFLoginEventsType.ts new file mode 100644 index 0000000000..bfac047fed --- /dev/null +++ b/packages/pn-personafisica-login/src/models/PFLoginEventsType.ts @@ -0,0 +1,6 @@ +export enum PFLoginEventsType { + SEND_LOGIN = 'SEND_LOGIN', + SEND_IDP_SELECTED = 'SEND_IDP_SELECTED', + SEND_LOGIN_FAILURE = 'SEND_LOGIN_FAILURE', + SEND_LOGIN_METHOD = 'SEND_LOGIN_METHOD', +} diff --git a/packages/pn-personafisica-login/src/pages/login/Login.tsx b/packages/pn-personafisica-login/src/pages/login/Login.tsx index bded2f807b..4bc7e321cf 100644 --- a/packages/pn-personafisica-login/src/pages/login/Login.tsx +++ b/packages/pn-personafisica-login/src/pages/login/Login.tsx @@ -10,9 +10,9 @@ import { styled } from '@mui/material/styles'; import { AppRouteParams, Layout, useIsMobile } from '@pagopa-pn/pn-commons'; import { CieIcon, SpidIcon } from '@pagopa/mui-italia/dist/icons'; +import { PFLoginEventsType } from '../../models/PFLoginEventsType'; import { getConfiguration } from '../../services/configuration.service'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFLoginEventStrategyFactory from '../../utility/MixpanelUtils/PFLoginEventStrategyFactory'; import { storageAarOps } from '../../utility/storage'; import SpidSelect from './SpidSelect'; @@ -38,14 +38,15 @@ const Login = () => { } useEffect(() => { - trackEventByType(TrackEventType.SEND_LOGIN); + PFLoginEventStrategyFactory.triggerEvent(PFLoginEventsType.SEND_LOGIN); }, []); const goCIE = () => { window.location.assign( `${URL_API_LOGIN}/login?entityID=${SPID_CIE_ENTITY_ID}&authLevel=SpidL2&RelayState=send` ); - trackEventByType(TrackEventType.SEND_IDP_SELECTED, { + + PFLoginEventStrategyFactory.triggerEvent(PFLoginEventsType.SEND_IDP_SELECTED, { SPID_IDP_NAME: 'CIE', SPID_IDP_ID: SPID_CIE_ENTITY_ID, }); diff --git a/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx b/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx index 6fe8f6e5c1..7a27b8fc84 100644 --- a/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx +++ b/packages/pn-personafisica-login/src/pages/login/SpidSelect.tsx @@ -7,13 +7,12 @@ import Grid from '@mui/material/Grid'; import Icon from '@mui/material/Icon'; import Link from '@mui/material/Link'; import Typography from '@mui/material/Typography'; -import { ProfilePropertyType } from '@pagopa-pn/pn-commons'; import SpidBig from '../../assets/spid_big.svg'; +import { PFLoginEventsType } from '../../models/PFLoginEventsType'; import { getConfiguration } from '../../services/configuration.service'; import { IdentityProvider, getIDPS } from '../../utility/IDPS'; -import { TrackEventType } from '../../utility/events'; -import { setSuperOrProfilePropertyValues, trackEventByType } from '../../utility/mixpanel'; +import PFLoginEventStrategyFactory from '../../utility/MixpanelUtils/PFLoginEventStrategyFactory'; import { shuffleList } from '../../utility/utils'; const SpidSelect = ({ onBack }: { onBack: () => void }) => { @@ -25,16 +24,14 @@ const SpidSelect = ({ onBack }: { onBack: () => void }) => { const getSPID = (IDP: IdentityProvider) => { sessionStorage.setItem('IDP', IDP.entityId); - trackEventByType(TrackEventType.SEND_IDP_SELECTED, { + PFLoginEventStrategyFactory.triggerEvent(PFLoginEventsType.SEND_IDP_SELECTED, { SPID_IDP_NAME: IDP.name, SPID_IDP_ID: IDP.entityId, }); - setSuperOrProfilePropertyValues( - ProfilePropertyType.PROFILE, - 'SEND_LOGIN_METHOD', - IDP.entityId as any // FIX this any - ); + PFLoginEventStrategyFactory.triggerEvent(PFLoginEventsType.SEND_LOGIN_METHOD, { + entityID: IDP.entityId, + }); window.location.assign( `${URL_API_LOGIN}/login?entityID=${IDP.entityId}&authLevel=SpidL2&RelayState=send` diff --git a/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx b/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx index 8acb0037d2..afb5b7a917 100644 --- a/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx +++ b/packages/pn-personafisica-login/src/pages/loginError/LoginError.tsx @@ -6,14 +6,17 @@ import { Box, Button, Dialog, Typography } from '@mui/material'; import { getLocalizedOrDefaultLabel } from '@pagopa-pn/pn-commons/src/utility/localization.utility'; import { IllusError } from '@pagopa/mui-italia'; +import { PFLoginEventsType } from '../../models/PFLoginEventsType'; import { getConfiguration } from '../../services/configuration.service'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFLoginEventStrategyFactory from '../../utility/MixpanelUtils/PFLoginEventStrategyFactory'; const handleError = (queryParams: string, errorMessage: string) => { if (process.env.NODE_ENV !== 'test') { const IDP = sessionStorage.getItem('IDP'); - trackEventByType(TrackEventType.SEND_LOGIN_FAILURE, { reason: errorMessage, IDP }); + PFLoginEventStrategyFactory.triggerEvent(PFLoginEventsType.SEND_LOGIN_FAILURE, { + reason: errorMessage, + IDP, + }); sessionStorage.removeItem('IDP'); console.error(`login unsuccessfull! query params obtained from idp: ${queryParams}`); } diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/PFLoginEventStrategyFactory.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/PFLoginEventStrategyFactory.ts new file mode 100644 index 0000000000..e888d87531 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/PFLoginEventStrategyFactory.ts @@ -0,0 +1,26 @@ +import { EventStrategy, EventStrategyFactory } from '@pagopa-pn/pn-commons'; + +import { PFLoginEventsType } from '../../models/PFLoginEventsType'; +import { SendIDPSelectedStrategy } from './Strategies/SendIDPSelectedStrategy'; +import { SendLoginFailureStrategy } from './Strategies/SendLoginFailureStrategy'; +import { SendLoginMethodStrategy } from './Strategies/SendLoginMethodStrategy'; +import { UXScreenViewStrategy } from './Strategies/UXScreenViewStrategy'; + +class PFLoginEventStrategyFactory extends EventStrategyFactory { + getStrategy(eventType: PFLoginEventsType): EventStrategy | null { + switch (eventType) { + case PFLoginEventsType.SEND_LOGIN: + return new UXScreenViewStrategy(); + case PFLoginEventsType.SEND_IDP_SELECTED: + return new SendIDPSelectedStrategy(); + case PFLoginEventsType.SEND_LOGIN_FAILURE: + return new SendLoginFailureStrategy(); + case PFLoginEventsType.SEND_LOGIN_METHOD: + return new SendLoginMethodStrategy(); + default: + return null; + } + } +} + +export default new PFLoginEventStrategyFactory(); diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendIDPSelectedStrategy.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendIDPSelectedStrategy.ts new file mode 100644 index 0000000000..8c4c60b34d --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendIDPSelectedStrategy.ts @@ -0,0 +1,28 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendIDPSelected = { + SPID_IDP_NAME: string; + SPID_IDP_ID: string; +}; + +export class SendIDPSelectedStrategy implements EventStrategy { + performComputations({ + SPID_IDP_ID, + SPID_IDP_NAME, + }: SendIDPSelected): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + SPID_IDP_ID, + SPID_IDP_NAME, + }, + }; + } +} diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginFailureStrategy.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginFailureStrategy.ts new file mode 100644 index 0000000000..5fb66a2b22 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginFailureStrategy.ts @@ -0,0 +1,23 @@ +import { + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendLoginFailure = { + reason: string; + IDP: string | null; +}; + +export class SendLoginFailureStrategy implements EventStrategy { + performComputations({ reason, IDP }: SendLoginFailure): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + reason, + IDP, + }, + }; + } +} diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginMethodStrategy.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginMethodStrategy.ts new file mode 100644 index 0000000000..424f14e029 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/SendLoginMethodStrategy.ts @@ -0,0 +1,29 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type SendLoginMethod = { + entityID: + | 'cie' + | 'posteid' + | 'timid' + | 'spiditalia' + | 'sielteid' + | 'namirialid' + | 'lepidaid' + | 'instesaid' + | 'infocertid' + | 'arubaid'; +}; + +type SendLoginMethodReturn = { + SEND_LOGIN_METHOD: string; +}; + +export class SendLoginMethodStrategy implements EventStrategy { + performComputations({ entityID }: SendLoginMethod): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_LOGIN_METHOD: entityID, + }, + }; + } +} diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts new file mode 100644 index 0000000000..aa3a01ab40 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class UXScreenViewStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + }, + }; + } +} diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendIDPSelectedStrategy.test.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendIDPSelectedStrategy.test.ts new file mode 100644 index 0000000000..31fdf6c377 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendIDPSelectedStrategy.test.ts @@ -0,0 +1,22 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendIDPSelectedStrategy } from '../SendIDPSelectedStrategy'; + +describe('Mixpanel - Send IDP Selected Strategy', () => { + it('should return IDP selected event', () => { + const strategy = new SendIDPSelectedStrategy(); + const event = strategy.performComputations({ + SPID_IDP_ID: 'idp_id', + SPID_IDP_NAME: 'idp_name', + }); + + expect(event).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + SPID_IDP_ID: 'idp_id', + SPID_IDP_NAME: 'idp_name', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginFailureStrategy.test.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginFailureStrategy.test.ts new file mode 100644 index 0000000000..92f52977c0 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginFailureStrategy.test.ts @@ -0,0 +1,21 @@ +import { EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendLoginFailureStrategy } from '../SendLoginFailureStrategy'; + +describe('Mixpanel - Send Login Failure Strategy', () => { + it('should return login failure event', () => { + const strategy = new SendLoginFailureStrategy(); + const event = strategy.performComputations({ + reason: 'error', + IDP: 'test', + }); + + expect(event).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + reason: 'error', + IDP: 'test', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginMethodStrategy.test.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginMethodStrategy.test.ts new file mode 100644 index 0000000000..5515a82815 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/SendLoginMethodStrategy.test.ts @@ -0,0 +1,18 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendLoginMethodStrategy } from '../SendLoginMethodStrategy'; + +describe('Mixpanel - Send Login Method Strategy', () => { + it('should return login method event', () => { + const strategy = new SendLoginMethodStrategy(); + const event = strategy.performComputations({ + entityID: 'cie', + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_LOGIN_METHOD: 'cie', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts new file mode 100644 index 0000000000..751108cba5 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts @@ -0,0 +1,17 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { UXScreenViewStrategy } from '../UXScreenViewStrategy'; + +describe('Mixpanel - UX Screen View Strategy', () => { + it('should return UX screen view event', () => { + const strategy = new UXScreenViewStrategy(); + + const uxScreenViewEvent = strategy.performComputations(); + expect(uxScreenViewEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-login/src/utility/MixpanelUtils/__test__/PFLoginEventStrategyFactory.test.ts b/packages/pn-personafisica-login/src/utility/MixpanelUtils/__test__/PFLoginEventStrategyFactory.test.ts new file mode 100644 index 0000000000..73e5ea0b21 --- /dev/null +++ b/packages/pn-personafisica-login/src/utility/MixpanelUtils/__test__/PFLoginEventStrategyFactory.test.ts @@ -0,0 +1,36 @@ +import { PFLoginEventsType } from '../../../models/PFLoginEventsType'; +import PFLoginEventStrategyFactory from '../PFLoginEventStrategyFactory'; +import { SendIDPSelectedStrategy } from '../Strategies/SendIDPSelectedStrategy'; +import { SendLoginFailureStrategy } from '../Strategies/SendLoginFailureStrategy'; +import { SendLoginMethodStrategy } from '../Strategies/SendLoginMethodStrategy'; +import { UXScreenViewStrategy } from '../Strategies/UXScreenViewStrategy'; + +describe('Event Strategy Factory', () => { + const factory = PFLoginEventStrategyFactory; + + it('should return UXScreenViewStrategy for SEND_LOGIN event', () => { + expect(factory.getStrategy(PFLoginEventsType.SEND_LOGIN)).toBeInstanceOf(UXScreenViewStrategy); + }); + + it('should return SendIDPSelectedStrategy for SEND_IDP_SELECTED event', () => { + expect(factory.getStrategy(PFLoginEventsType.SEND_IDP_SELECTED)).toBeInstanceOf( + SendIDPSelectedStrategy + ); + }); + + it('should return SendLoginFailureStrategy for SEND_LOGIN_FAILURE event', () => { + expect(factory.getStrategy(PFLoginEventsType.SEND_LOGIN_FAILURE)).toBeInstanceOf( + SendLoginFailureStrategy + ); + }); + + it('should return SendLoginMethodStrategy for SEND_LOGIN_METHOD event', () => { + expect(factory.getStrategy(PFLoginEventsType.SEND_LOGIN_METHOD)).toBeInstanceOf( + SendLoginMethodStrategy + ); + }); + + it('should return null for unknown event type', () => { + expect(factory.getStrategy('UNKNOWN_EVENT' as PFLoginEventsType)).toBeNull(); + }); +}); diff --git a/packages/pn-personafisica-login/src/utility/events.ts b/packages/pn-personafisica-login/src/utility/events.ts deleted file mode 100644 index e40ae34dbf..0000000000 --- a/packages/pn-personafisica-login/src/utility/events.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { EventAction, EventCategory, EventsType } from '@pagopa-pn/pn-commons'; - -export enum TrackEventType { - SEND_LOGIN = 'SEND_LOGIN', - SEND_IDP_SELECTED = 'SEND_IDP_SELECTED', - SEND_LOGIN_FAILURE = 'SEND_LOGIN_FAILURE', -} - -export const events: EventsType = { - [TrackEventType.SEND_LOGIN]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_IDP_SELECTED]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_LOGIN_FAILURE]: { - event_category: EventCategory.TECH, - }, -}; diff --git a/packages/pn-personafisica-login/src/utility/mixpanel.ts b/packages/pn-personafisica-login/src/utility/mixpanel.ts deleted file mode 100644 index 33d4a75bdc..0000000000 --- a/packages/pn-personafisica-login/src/utility/mixpanel.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ProfilePropertyType, setSuperOrProfileProperty, trackEvent } from '@pagopa-pn/pn-commons'; - -import { TrackEventType, events } from './events'; -import { ProfilePropertyParams } from './profileProperties'; - -/** - * Function to track events outside redux - * @param trackEventType event name - * @param attributes event attributes - */ -export const trackEventByType = (trackEventType: TrackEventType, attributes?: object) => { - const eventParameters = attributes - ? { ...events[trackEventType], ...attributes } - : events[trackEventType]; - - trackEvent(trackEventType, process.env.NODE_ENV, eventParameters); -}; - -export function setSuperOrProfilePropertyValues( - type: ProfilePropertyType, - propertyName: TProperty, - attributes?: ProfilePropertyParams[TProperty] -) { - const property = attributes ? { [propertyName]: attributes } : propertyName; - - setSuperOrProfileProperty(type, property, process.env.NODE_ENV); -} diff --git a/packages/pn-personafisica-login/src/utility/profileProperties.ts b/packages/pn-personafisica-login/src/utility/profileProperties.ts deleted file mode 100644 index b7cbeb12b9..0000000000 --- a/packages/pn-personafisica-login/src/utility/profileProperties.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type ProfilePropertyParams = { - SEND_LOGIN_METHOD: - | 'cie' - | 'posteid' - | 'timid' - | 'spiditalia' - | 'sielteid' - | 'namirialid' - | 'lepidaid' - | 'instesaid' - | 'infocertid' - | 'arubaid'; -}; 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/recapiti.json b/packages/pn-personafisica-webapp/public/locales/it/recapiti.json index 0ba17b0a3d..8aed9da46f 100644 --- a/packages/pn-personafisica-webapp/public/locales/it/recapiti.json +++ b/packages/pn-personafisica-webapp/public/locales/it/recapiti.json @@ -132,6 +132,10 @@ "expired_verification_code": { "title": "Il codice รจ scaduto", "message": "Generane uno nuovo e inseriscilo." + }, + "invalid_type_code": { + "title": "Questo campo accetta solo valori numerici", + "message": "Controlla il codice di verifica e inseriscilo di nuovo" } } } 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 4468b63911..ea2368e3cf 100644 --- a/packages/pn-personafisica-webapp/src/App.tsx +++ b/packages/pn-personafisica-webapp/src/App.tsx @@ -28,6 +28,7 @@ import { } from '@pagopa-pn/pn-commons'; import { ProductEntity } from '@pagopa/mui-italia'; +import { PFEventsType } from './models/PFEventsType'; import { getCurrentEventTypePage } from './navigation/navigation.utility'; import Router from './navigation/routes'; import * as routes from './navigation/routes.const'; @@ -39,8 +40,8 @@ import { getDomicileInfo, getSidemenuInformation } from './redux/sidemenu/action import { RootState } from './redux/store'; import { getConfiguration } from './services/configuration.service'; import { PFAppErrorFactory } from './utility/AppError/PFAppErrorFactory'; -import { TrackEventType } from './utility/events'; -import { trackEventByType } from './utility/mixpanel'; +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,14 +93,25 @@ 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 = { id: 'profile', label: t('menu.profilo'), onClick: () => { - trackEventByType(TrackEventType.SEND_VIEW_PROFILE, { source: 'user_menu' }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_PROFILE, { + source: 'user_menu', + }); navigate(routes.PROFILO); }, icon: , @@ -138,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. @@ -222,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}`; }; @@ -241,7 +230,7 @@ const ActualApp = () => { }; const handleEventTrackingCallbackAppCrash = (e: Error, eInfo: ErrorInfo) => { - trackEventByType(TrackEventType.SEND_GENERIC_ERROR, { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_GENERIC_ERROR, { reason: { error: e, errorInfo: eInfo }, }); }; @@ -249,7 +238,7 @@ const ActualApp = () => { const handleEventTrackingCallbackRefreshPage = () => { const pageType = getCurrentEventTypePage(pathname); if (pageType) { - trackEventByType(TrackEventType.SEND_REFRESH_PAGE, { page: pageType }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_REFRESH_PAGE, { page: pageType }); } }; @@ -259,7 +248,7 @@ const ActualApp = () => { ) => { const { traceId, status, action } = response; - trackEventByType(TrackEventType.SEND_TOAST_ERROR, { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_TOAST_ERROR, { reason: error.code, traceid: traceId, page_name: getCurrentEventTypePage(pathname), @@ -269,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} @@ -299,6 +299,7 @@ const ActualApp = () => { hasTermsOfService={true} eventTrackingCallbackAppCrash={handleEventTrackingCallbackAppCrash} eventTrackingCallbackRefreshPage={handleEventTrackingCallbackRefreshPage} + enableAssistanceButton={showAssistanceButton} > {/* await dispatch(logout())} /> */} diff --git a/packages/pn-personafisica-webapp/src/__mocks__/NotificationDetail.mock.ts b/packages/pn-personafisica-webapp/src/__mocks__/NotificationDetail.mock.ts index f8f4ef74da..9ac36a1609 100644 --- a/packages/pn-personafisica-webapp/src/__mocks__/NotificationDetail.mock.ts +++ b/packages/pn-personafisica-webapp/src/__mocks__/NotificationDetail.mock.ts @@ -202,7 +202,7 @@ const notificationStatusHistory: Array = [ }, ]; -const timeline: Array = [ +export const timeline: Array = [ { elementId: 'REQUEST_ACCEPTED.IUN_DAPQ-LWQV-DKQH-202308-A-1', timestamp: '2023-08-23T07:39:54.877429741Z', 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/DigitalContactElem.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactElem.tsx index 4498786e5f..e42167dce4 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactElem.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactElem.tsx @@ -18,7 +18,8 @@ import { CourtesyChannelType, LegalChannelType } from '../../models/contacts'; import { deleteCourtesyAddress, deleteLegalAddress } from '../../redux/contact/actions'; import { DeleteDigitalAddressParams } from '../../redux/contact/types'; import { useAppDispatch } from '../../redux/hooks'; -import { trackDeleteContactEvent } from '../../utility/contacts.utility'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; +import { getEventByContactType } from '../../utility/contacts.utility'; import { useDigitalContactsCodeVerificationContext } from './DigitalContactsCodeVerification.context'; type Props = { @@ -173,7 +174,9 @@ const DigitalContactElem = forwardRef<{ editContact: () => void }, Props>( if (onDeleteCbk) { onDeleteCbk(); } - trackDeleteContactEvent(contactType, senderId); + PFEventStrategyFactory.triggerEvent(getEventByContactType(contactType), { + senderId, + }); }) .catch((error) => { console.error('Error occurred:', error); diff --git a/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx index fda51d20d5..cc9f9c1a75 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx @@ -16,6 +16,7 @@ import { } from '@pagopa-pn/pn-commons'; import { ButtonNaked } from '@pagopa/mui-italia'; +import { PFEventsType } from '../../models/PFEventsType'; import { CourtesyChannelType, LegalChannelType } from '../../models/contacts'; import { createOrUpdateCourtesyAddress, @@ -24,8 +25,7 @@ import { import { SaveDigitalAddressParams } from '../../redux/contact/types'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; type ModalProps = { labelRoot: string; @@ -54,10 +54,6 @@ const DigitalContactsCodeVerificationContext = createContext< IDigitalContactsCodeVerificationContext | undefined >(undefined); -const eventAttributes = (isSpecialContact?: boolean) => ({ - other_contact: isSpecialContact ? 'yes' : 'no', -}); - const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ children }) => { const { t } = useTranslation(['common', 'recapiti']); const digitalAddresses = useAppSelector( @@ -116,17 +112,18 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ const sendSuccessEvent = (type: LegalChannelType | CourtesyChannelType) => { if (type === LegalChannelType.PEC) { - trackEventByType( - TrackEventType.SEND_ADD_PEC_UX_SUCCESS, - eventAttributes(isSpecialContactMode) - ); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_PEC_UX_SUCCESS, { + isSpecialContact: isSpecialContactMode, + }); return; } - trackEventByType( + PFEventStrategyFactory.triggerEvent( type === CourtesyChannelType.SMS - ? TrackEventType.SEND_ADD_SMS_UX_SUCCESS - : TrackEventType.SEND_ADD_EMAIL_UX_SUCCESS, - eventAttributes(isSpecialContactMode) + ? PFEventsType.SEND_ADD_SMS_UX_SUCCESS + : PFEventsType.SEND_ADD_EMAIL_UX_SUCCESS, + { + isSpecialContact: isSpecialContactMode, + } ); }; const handleCodeVerification = (verificationCode?: string, noCallback: boolean = false) => { @@ -139,20 +136,17 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ } if (verificationCode) { if (modalProps.digitalDomicileType === LegalChannelType.PEC) { - trackEventByType( - TrackEventType.SEND_ADD_PEC_UX_CONVERSION, - eventAttributes(isSpecialContactMode) - ); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_PEC_UX_CONVERSION, { + isSpecialContact: isSpecialContactMode, + }); } else if (modalProps.digitalDomicileType === CourtesyChannelType.SMS) { - trackEventByType( - TrackEventType.SEND_ADD_SMS_UX_CONVERSION, - eventAttributes(isSpecialContactMode) - ); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_SMS_UX_CONVERSION, { + isSpecialContact: isSpecialContactMode, + }); } else if (modalProps.digitalDomicileType === CourtesyChannelType.EMAIL) { - trackEventByType( - TrackEventType.SEND_ADD_EMAIL_UX_CONVERSION, - eventAttributes(isSpecialContactMode) - ); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_EMAIL_UX_CONVERSION, { + isSpecialContact: isSpecialContactMode, + }); } } if (!actionToBeDispatched) { @@ -220,15 +214,19 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ if (digitalDomicileType === LegalChannelType.PEC) { labelRoot = 'legal-contacts'; labelType = 'pec'; - trackEventByType(TrackEventType.SEND_ADD_PEC_START, eventAttributes(isSpecialContact)); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_PEC_START, { + isSpecialContact, + }); } else { labelRoot = 'courtesy-contacts'; labelType = digitalDomicileType === CourtesyChannelType.SMS ? 'phone' : 'email'; - trackEventByType( + PFEventStrategyFactory.triggerEvent( digitalDomicileType === CourtesyChannelType.SMS - ? TrackEventType.SEND_ADD_SMS_START - : TrackEventType.SEND_ADD_EMAIL_START, - eventAttributes(isSpecialContact) + ? PFEventsType.SEND_ADD_SMS_START + : PFEventsType.SEND_ADD_EMAIL_START, + { + isSpecialContact, + } ); } setModalProps({ @@ -282,11 +280,11 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ }); setCodeNotValid(true); if (modalProps.digitalDomicileType === LegalChannelType.PEC) { - trackEventByType(TrackEventType.SEND_ADD_PEC_CODE_ERROR); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_PEC_CODE_ERROR); } else if (modalProps.digitalDomicileType === CourtesyChannelType.SMS) { - trackEventByType(TrackEventType.SEND_ADD_SMS_CODE_ERROR); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_SMS_CODE_ERROR); } else if (modalProps.digitalDomicileType === CourtesyChannelType.EMAIL) { - trackEventByType(TrackEventType.SEND_ADD_EMAIL_CODE_ERROR); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_EMAIL_CODE_ERROR); } } return false; diff --git a/packages/pn-personafisica-webapp/src/components/Contacts/IOContact.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/IOContact.tsx index 35b3ae1ddf..7102b7b208 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/IOContact.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/IOContact.tsx @@ -7,11 +7,11 @@ import { Alert, Box, Stack, Typography } from '@mui/material'; import { DisclaimerModal } from '@pagopa-pn/pn-commons'; import { ButtonNaked, IllusSms } from '@pagopa/mui-italia'; +import { PFEventsType } from '../../models/PFEventsType'; import { DigitalAddress, IOAllowedValues } from '../../models/contacts'; import { disableIOAddress, enableIOAddress } from '../../redux/contact/actions'; import { useAppDispatch } from '../../redux/hooks'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; import DigitalContactsCard from './DigitalContactsCard'; interface Props { @@ -46,26 +46,26 @@ const IOContact: React.FC = ({ recipientId, contact }) => { const status = parseContact(); const enableIO = () => { - trackEventByType(TrackEventType.SEND_ACTIVE_IO_UX_CONVERSION); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ACTIVE_IO_UX_CONVERSION); void dispatch(enableIOAddress(recipientId)).then(() => { setIsConfirmModalOpen(false); - trackEventByType(TrackEventType.SEND_ACTIVE_IO_UX_SUCCESS); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ACTIVE_IO_UX_SUCCESS); }); }; const disableIO = () => { - trackEventByType(TrackEventType.SEND_DEACTIVE_IO_UX_CONVERSION); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_DEACTIVE_IO_UX_CONVERSION); void dispatch(disableIOAddress(recipientId)).then(() => { setIsConfirmModalOpen(false); - trackEventByType(TrackEventType.SEND_DEACTIVE_IO_UX_SUCCESS); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_DEACTIVE_IO_UX_SUCCESS); }); }; const handleConfirmationModal = () => { - trackEventByType( + PFEventStrategyFactory.triggerEvent( status === IOContactStatus.ENABLED - ? TrackEventType.SEND_DEACTIVE_IO_START - : TrackEventType.SEND_ACTIVE_IO_START + ? PFEventsType.SEND_DEACTIVE_IO_START + : PFEventsType.SEND_ACTIVE_IO_START ); setIsConfirmModalOpen(true); }; 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: ( { ]; const handleAddDelegationClick = () => { - trackEventByType(TrackEventType.SEND_ADD_MANDATE_START); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_MANDATE_START); navigate(routes.NUOVA_DELEGA); }; diff --git a/packages/pn-personafisica-webapp/src/components/Deleghe/DelegationsElements.tsx b/packages/pn-personafisica-webapp/src/components/Deleghe/DelegationsElements.tsx index e563a35bcf..6d773561cc 100644 --- a/packages/pn-personafisica-webapp/src/components/Deleghe/DelegationsElements.tsx +++ b/packages/pn-personafisica-webapp/src/components/Deleghe/DelegationsElements.tsx @@ -7,10 +7,10 @@ import { Variant } from '@mui/material/styles/createTypography'; import { CustomTagGroup } from '@pagopa-pn/pn-commons'; import { Tag } from '@pagopa/mui-italia'; +import { PFEventsType } from '../../models/PFEventsType'; import { openAcceptModal, openRevocationModal } from '../../redux/delegation/reducers'; import { useAppDispatch } from '../../redux/hooks'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; export const Menu = (props: any) => { const [anchorEl, setAnchorEl] = useState(null); @@ -24,7 +24,7 @@ export const Menu = (props: any) => { }; const handleOpenVerificationCodeModal = () => { - trackEventByType(TrackEventType.SEND_SHOW_MANDATE_CODE); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_SHOW_MANDATE_CODE); props.setCodeModal({ open: true, name: props.name, code: props.verificationCode }); setAnchorEl(null); }; diff --git a/packages/pn-personafisica-webapp/src/components/Deleghe/MobileDelegates.tsx b/packages/pn-personafisica-webapp/src/components/Deleghe/MobileDelegates.tsx index f912985469..ad274b28a1 100644 --- a/packages/pn-personafisica-webapp/src/components/Deleghe/MobileDelegates.tsx +++ b/packages/pn-personafisica-webapp/src/components/Deleghe/MobileDelegates.tsx @@ -19,13 +19,13 @@ import { } from '@pagopa-pn/pn-commons'; import { DelegationColumnData, DelegationData } from '../../models/Deleghe'; +import { PFEventsType } from '../../models/PFEventsType'; import * as routes from '../../navigation/routes.const'; import { DELEGATION_ACTIONS, getDelegates } from '../../redux/delegation/actions'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; import delegationToItem from '../../utility/delegation.utility'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import DelegatorsDataSwitch from './DelegationDataSwitch'; type Props = { @@ -83,7 +83,7 @@ const MobileDelegates = () => { ]; const handleAddDelegationClick = () => { - trackEventByType(TrackEventType.SEND_ADD_MANDATE_START); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_MANDATE_START); navigate(routes.NUOVA_DELEGA); }; diff --git a/packages/pn-personafisica-webapp/src/components/DomicileBanner/DomicileBanner.tsx b/packages/pn-personafisica-webapp/src/components/DomicileBanner/DomicileBanner.tsx index c2c47af2d5..c941826b0b 100644 --- a/packages/pn-personafisica-webapp/src/components/DomicileBanner/DomicileBanner.tsx +++ b/packages/pn-personafisica-webapp/src/components/DomicileBanner/DomicileBanner.tsx @@ -4,13 +4,13 @@ import { useNavigate } from 'react-router-dom'; import { Alert, Box, Link, Typography } from '@mui/material'; +import { PFEventsType } from '../../models/PFEventsType'; import { CourtesyChannelType, LegalChannelType } from '../../models/contacts'; import * as routes from '../../navigation/routes.const'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { closeDomicileBanner } from '../../redux/sidemenu/reducers'; import { RootState } from '../../redux/store'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; type Props = { source?: string; @@ -30,7 +30,7 @@ const DomicileBanner = forwardRef(({ source = 'home_notifiche' }: Props, ref) => }, [closeDomicileBanner]); const handleAddDomicile = useCallback(() => { - trackEventByType(TrackEventType.SEND_VIEW_CONTACT_DETAILS, { source }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_CONTACT_DETAILS, { source }); navigate(routes.RECAPITI); }, []); diff --git a/packages/pn-personafisica-webapp/src/components/Notifications/DesktopNotifications.tsx b/packages/pn-personafisica-webapp/src/components/Notifications/DesktopNotifications.tsx index 499c58e839..ba1b487237 100644 --- a/packages/pn-personafisica-webapp/src/components/Notifications/DesktopNotifications.tsx +++ b/packages/pn-personafisica-webapp/src/components/Notifications/DesktopNotifications.tsx @@ -20,10 +20,10 @@ import { Sort, } from '@pagopa-pn/pn-commons'; +import { PFEventsType } from '../../models/PFEventsType'; import * as routes from '../../navigation/routes.const'; import { Delegator } from '../../redux/delegation/types'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; import FilterNotifications from './FilterNotifications'; type Props = { @@ -62,7 +62,9 @@ const LinkRouteContacts: React.FC<{ children?: React.ReactNode }> = ({ children const { t } = useTranslation('notifiche'); const navigate = useNavigate(); const goToContactsPage = () => { - trackEventByType(TrackEventType.SEND_VIEW_CONTACT_DETAILS, { source: 'home_notifiche' }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_CONTACT_DETAILS, { + source: 'home_notifiche', + }); navigate(routes.RECAPITI); }; return ( diff --git a/packages/pn-personafisica-webapp/src/components/Notifications/MobileNotifications.tsx b/packages/pn-personafisica-webapp/src/components/Notifications/MobileNotifications.tsx index d7e903f82c..86bedc61c9 100644 --- a/packages/pn-personafisica-webapp/src/components/Notifications/MobileNotifications.tsx +++ b/packages/pn-personafisica-webapp/src/components/Notifications/MobileNotifications.tsx @@ -25,10 +25,10 @@ import { } from '@pagopa-pn/pn-commons'; import { ButtonNaked } from '@pagopa/mui-italia'; +import { PFEventsType } from '../../models/PFEventsType'; import * as routes from '../../navigation/routes.const'; import { Delegator } from '../../redux/delegation/types'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; import FilterNotifications from './FilterNotifications'; type Props = { @@ -79,7 +79,9 @@ const LinkRouteContacts: React.FC<{ children?: React.ReactNode }> = ({ children const { t } = useTranslation('notifiche'); const navigate = useNavigate(); const goToContactsPage = () => { - trackEventByType(TrackEventType.SEND_VIEW_CONTACT_DETAILS, { source: 'home_notifiche' }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_CONTACT_DETAILS, { + source: 'home_notifiche', + }); navigate(routes.RECAPITI); }; return ( diff --git a/packages/pn-personafisica-webapp/src/components/Support/ZendeskForm.tsx b/packages/pn-personafisica-webapp/src/components/Support/ZendeskForm.tsx new file mode 100644 index 0000000000..9dbc8f333a --- /dev/null +++ b/packages/pn-personafisica-webapp/src/components/Support/ZendeskForm.tsx @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; + +import { ZendeskAuthorizationDTO } from '../../models/Support'; + +const ZendeskForm: React.FC<{ data: ZendeskAuthorizationDTO }> = ({ 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/models/PFEventsType.ts b/packages/pn-personafisica-webapp/src/models/PFEventsType.ts new file mode 100644 index 0000000000..7bfd84b7da --- /dev/null +++ b/packages/pn-personafisica-webapp/src/models/PFEventsType.ts @@ -0,0 +1,98 @@ +export enum PFEventsType { + SEND_VIEW_PROFILE = 'SEND_VIEW_PROFILE', + SEND_PROFILE = 'SEND_PROFILE', + SEND_VIEW_CONTACT_DETAILS = 'SEND_VIEW_CONTACT_DETAILS', + SEND_NOTIFICATION_DETAIL = 'SEND_NOTIFICATION_DETAIL', + SEND_YOUR_NOTIFICATIONS = 'SEND_YOUR_NOTIFICATIONS', + SEND_NOTIFICATION_DELEGATED = 'SEND_NOTIFICATION_DELEGATED', + SEND_DOWNLOAD_ATTACHMENT = 'SEND_DOWNLOAD_ATTACHMENT', + SEND_DOWNLOAD_RECEIPT_NOTICE = 'SEND_DOWNLOAD_RECEIPT_NOTICE', + SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES = 'SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES', + SEND_START_PAYMENT = 'SEND_START_PAYMENT', + SEND_PAYMENT_DETAIL_REFRESH = 'SEND_PAYMENT_DETAIL_REFRESH', + SEND_NOTIFICATION_STATUS_DETAIL = 'SEND_NOTIFICATION_STATUS_DETAIL', + SEND_YOUR_MANDATES = 'SEND_YOUR_MANDATES', + SEND_ADD_MANDATE_START = 'SEND_ADD_MANDATE_START', + SEND_ADD_MANDATE_BACK = 'SEND_ADD_MANDATE_BACK', + SEND_ADD_MANDATE_DATA_INPUT = 'SEND_ADD_MANDATE_DATA_INPUT', + SEND_ADD_MANDATE_UX_CONVERSION = 'SEND_ADD_MANDATE_UX_CONVERSION', + SEND_ADD_MANDATE_UX_SUCCESS = 'SEND_ADD_MANDATE_UX_SUCCESS', + SEND_SHOW_MANDATE_CODE = 'SEND_SHOW_MANDATE_CODE', + SEND_MANDATE_REVOKED = 'SEND_MANDATE_REVOKED', + SEND_MANDATE_REJECTED = 'SEND_MANDATE_REJECTED', + SEND_MANDATE_ACCEPTED = 'SEND_MANDATE_ACCEPTED', + SEND_MANDATE_ACCEPT_CODE_ERROR = 'SEND_MANDATE_ACCEPT_CODE_ERROR', + SEND_YOUR_CONTACT_DETAILS = 'SEND_YOUR_CONTACT_DETAILS', + SEND_ADD_PEC_START = 'SEND_ADD_PEC_START', + SEND_ADD_SMS_START = 'SEND_ADD_SMS_START', + SEND_ADD_EMAIL_START = 'SEND_ADD_EMAIL_START', + SEND_ADD_PEC_UX_CONVERSION = 'SEND_ADD_PEC_UX_CONVERSION', + SEND_ADD_PEC_CODE_ERROR = 'SEND_ADD_PEC_CODE_ERROR', + SEND_ADD_PEC_UX_SUCCESS = 'SEND_ADD_PEC_UX_SUCCESS', + SEND_ACTIVE_IO_START = 'SEND_ACTIVE_IO_START', + SEND_DEACTIVE_IO_START = 'SEND_DEACTIVE_IO_START', + SEND_ACTIVE_IO_UX_CONVERSION = 'SEND_ACTIVE_IO_UX_CONVERSION', + SEND_ACTIVE_IO_UX_SUCCESS = 'SEND_ACTIVE_IO_UX_SUCCESS', + SEND_DEACTIVE_IO_UX_CONVERSION = 'SEND_DEACTIVE_IO_UX_CONVERSION', + SEND_DEACTIVE_IO_UX_SUCCESS = 'SEND_DEACTIVE_IO_UX_SUCCESS', + SEND_ADD_SMS_UX_CONVERSION = 'SEND_ADD_SMS_UX_CONVERSION', + SEND_ADD_SMS_CODE_ERROR = 'SEND_ADD_SMS_CODE_ERROR', + SEND_ADD_SMS_UX_SUCCESS = 'SEND_ADD_SMS_UX_SUCCESS', + SEND_ADD_EMAIL_UX_SUCCESS = 'SEND_ADD_EMAIL_UX_SUCCESS', + SEND_ADD_EMAIL_UX_CONVERSION = 'SEND_ADD_EMAIL_UX_CONVERSION', + SEND_ADD_EMAIL_CODE_ERROR = 'SEND_ADD_EMAIL_CODE_ERROR', + SEND_SERVICE_STATUS = 'SEND_SERVICE_STATUS', + SEND_REFRESH_PAGE = 'SEND_REFRESH_PAGE', + SEND_TOAST_ERROR = 'SEND_TOAST_ERROR', + SEND_GENERIC_ERROR = 'SEND_GENERIC_ERROR', + SEND_PAYMENT_OUTCOME = 'SEND_PAYMENT_OUTCOME', + SEND_NOTIFICATION_NOT_ALLOWED = 'SEND_NOTIFICATION_NOT_ALLOWED', + SEND_RAPID_ACCESS = 'SEND_RAPID_ACCESS', + SEND_REMOVE_PEC_SUCCESS = 'SEND_REMOVE_PEC_SUCCESS', + SEND_REMOVE_SMS_SUCCESS = 'SEND_REMOVE_SMS_SUCCESS', + SEND_REMOVE_EMAIL_SUCCESS = 'SEND_REMOVE_EMAIL_SUCCESS', + SEND_AUTH_SUCCESS = 'SEND_AUTH_SUCCESS', + SEND_DOWNLOAD_RESPONSE = 'SEND_DOWNLOAD_RESPONSE', + SEND_PAYMENT_STATUS = 'SEND_PAYMENT_STATUS', + SEND_PAYMENT_DETAIL_ERROR = 'SEND_PAYMENT_DETAIL_ERROR', + SEND_CANCELLED_NOTIFICATION_REFOUND_INFO = 'SEND_CANCELLED_NOTIFICATION_REFOUND_INFO', + SEND_MULTIPAYMENT_MORE_INFO = 'SEND_MULTIPAYMENT_MORE_INFO', + SEND_PAYMENT_LIST_CHANGE_PAGE = 'SEND_PAYMENT_LIST_CHANGE_PAGE', + SEND_F24_DOWNLOAD = 'SEND_F24_DOWNLOAD', + SEND_F24_DOWNLOAD_SUCCESS = 'SEND_F24_DOWNLOAD_SUCCESS', + SEND_DOWNLOAD_PAYMENT_NOTICE = 'SEND_DOWNLOAD_PAYMENT_NOTICE', + SEND_F24_DOWNLOAD_TIMEOUT = 'SEND_F24_DOWNLOAD_TIMEOUT', + + // --- PROFILE_PROPERTY + SEND_PAYMENTS_COUNT = 'SEND_PAYMENTS_COUNT', + SEND_NOTIFICATIONS_COUNT = 'SEND_NOTIFICATIONS_COUNT', + SEND_HAS_MANDATE = 'SEND_HAS_MANDATE', + SEND_MANDATE_GIVEN = 'SEND_MANDATE_GIVEN', + SEND_HAS_ADDRESSES = 'SEND_HAS_ADDRESSES', + SEND_HAS_MANDATE_LOGIN = 'SEND_HAS_MANDATE_LOGIN', + SEND_ENABLE_IO = 'SEND_ENABLE_IO', + SEND_DISABLE_IO = 'SEND_DISABLE_IO', + SEND_ACCEPT_DELEGATION = 'SEND_ACCEPT_DELEGATION', + SEND_ADD_LEGAL_ADDRESS = 'SEND_ADD_LEGAL_ADDRESS', + SEND_REMOVE_LEGAL_ADDRESS = 'SEND_REMOVE_LEGAL_ADDRESS', + SEND_REMOVE_COURTESY_ADDRESS = 'SEND_REMOVE_COURTESY_ADDRESS', + SEND_ADD_COURTESY_ADDRESS = 'SEND_ADD_COURTESY_ADDRESS', +} + +export const eventsActionsMap: Record = { + 'getReceivedNotificationOtherDocument/fulfilled': PFEventsType.SEND_DOWNLOAD_RESPONSE, + 'getReceivedNotificationLegalfact/fulfilled': PFEventsType.SEND_DOWNLOAD_RESPONSE, + 'exchangeToken/fulfilled': PFEventsType.SEND_AUTH_SUCCESS, + + // --- PROFILE_PROPERTY + 'getDomicileInfo/fulfilled': PFEventsType.SEND_HAS_ADDRESSES, + 'getSidemenuInformation/fulfilled': PFEventsType.SEND_HAS_MANDATE_LOGIN, + 'getDelegates/fulfilled': PFEventsType.SEND_MANDATE_GIVEN, + 'enableIOAddress/fulfilled': PFEventsType.SEND_ENABLE_IO, + 'disableIOAddress/fulfilled': PFEventsType.SEND_DISABLE_IO, + 'acceptDelegation/fulfilled': PFEventsType.SEND_ACCEPT_DELEGATION, + 'createOrUpdateLegalAddress/fulfilled': PFEventsType.SEND_ADD_LEGAL_ADDRESS, + 'deleteLegalAddress/fulfilled': PFEventsType.SEND_REMOVE_LEGAL_ADDRESS, + 'deleteCourtesyAddress/fulfilled': PFEventsType.SEND_REMOVE_COURTESY_ADDRESS, + 'createOrUpdateCourtesyAddress/fulfilled': PFEventsType.SEND_ADD_COURTESY_ADDRESS, +}; 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/AARGuard.tsx b/packages/pn-personafisica-webapp/src/navigation/AARGuard.tsx index 4a00f738f9..62c47f0a73 100644 --- a/packages/pn-personafisica-webapp/src/navigation/AARGuard.tsx +++ b/packages/pn-personafisica-webapp/src/navigation/AARGuard.tsx @@ -6,8 +6,8 @@ import { AccessDenied, IllusQuestion, LoadingPage } from '@pagopa-pn/pn-commons' import { NotificationsApi } from '../api/notifications/Notifications.api'; import { NotificationId } from '../models/Notifications'; -import { trackEventByType } from '../utility/mixpanel'; -import { TrackEventType } from '../utility/events'; +import { PFEventsType } from '../models/PFEventsType'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; import { DETTAGLIO_NOTIFICA_QRCODE_QUERY_PARAM, GET_DETTAGLIO_NOTIFICA_DELEGATO_PATH, @@ -15,7 +15,6 @@ import { NOTIFICHE, } from './routes.const'; - function notificationDetailPath(notificationId: NotificationId): string { return notificationId.mandateId ? GET_DETTAGLIO_NOTIFICA_DELEGATO_PATH(notificationId.iun, notificationId.mandateId) @@ -50,7 +49,7 @@ const AARGuard = () => { useEffect(() => { if (notificationId) { - trackEventByType(TrackEventType.SEND_RAPID_ACCESS); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_RAPID_ACCESS); navigate(notificationDetailPath(notificationId), { replace: true, state: { fromQrCode: true }, @@ -62,7 +61,7 @@ const AARGuard = () => { return ; } if (fetchError) { - trackEventByType(TrackEventType.SEND_NOTIFICATION_NOT_ALLOWED); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_NOTIFICATION_NOT_ALLOWED); return ( } @@ -70,7 +69,7 @@ const AARGuard = () => { subtitle={t('from-qrcode.not-found-subtitle')} isLogged={true} goToHomePage={() => navigate(NOTIFICHE, { replace: true })} - goToLogin={() => { }} + goToLogin={() => {}} /> ); } 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/AppStatus.page.tsx b/packages/pn-personafisica-webapp/src/pages/AppStatus.page.tsx index f70b926fd0..a689367b84 100644 --- a/packages/pn-personafisica-webapp/src/pages/AppStatus.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/AppStatus.page.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { AppStatusRender, GetDowntimeHistoryParams, PaginationData } from '@pagopa-pn/pn-commons'; +import { PFEventsType } from '../models/PFEventsType'; import { getCurrentAppStatus, getDowntimeLegalFactDocumentDetails, @@ -16,8 +17,7 @@ import { } from '../redux/appStatus/reducers'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; const AppStatus = () => { const dispatch = useAppDispatch(); @@ -44,13 +44,16 @@ const AppStatus = () => { ); const handleTrackDownloadCertificateOpposable3dparties = () => { - trackEventByType(TrackEventType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES, { - source: 'stato_piattaforma', - }); + PFEventStrategyFactory.triggerEvent( + PFEventsType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES, + { + source: 'stato_piattaforma', + } + ); }; useEffect(() => { - trackEventByType(TrackEventType.SEND_SERVICE_STATUS, { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_SERVICE_STATUS, { service_status_OK: appStatus.currentStatus?.appIsFullyOperative, }); }, [getCurrentAppStatus]); diff --git a/packages/pn-personafisica-webapp/src/pages/Contacts.page.tsx b/packages/pn-personafisica-webapp/src/pages/Contacts.page.tsx index a6d3c3f533..1a2fbecb77 100644 --- a/packages/pn-personafisica-webapp/src/pages/Contacts.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/Contacts.page.tsx @@ -12,7 +12,8 @@ import InsertLegalContact from '../components/Contacts/InsertLegalContact'; import LegalContactsList from '../components/Contacts/LegalContactsList'; import SpecialContacts from '../components/Contacts/SpecialContacts'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; -import { CourtesyChannelType, DigitalAddress, IOAllowedValues } from '../models/contacts'; +import { PFEventsType } from '../models/PFEventsType'; +import { CourtesyChannelType, DigitalAddress } from '../models/contacts'; import { FAQ_WHAT_IS_AAR, FAQ_WHAT_IS_COURTESY_MESSAGE } from '../navigation/externalRoutes.const'; import { PROFILO } from '../navigation/routes.const'; import { CONTACT_ACTIONS, getDigitalAddresses } from '../redux/contact/actions'; @@ -20,8 +21,7 @@ import { resetState } from '../redux/contact/reducers'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; import { getConfiguration } from '../services/configuration.service'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; const Contacts = () => { const navigate = useNavigate(); @@ -51,25 +51,17 @@ const Contacts = () => { useEffect(() => { if (pageReady) { - trackEventByType(TrackEventType.SEND_YOUR_CONTACT_DETAILS, { - PEC_exists: digitalAddresses.legal.length > 0, - email_exists: - digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.EMAIL) - .length > 0, - telephone_exists: - digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.SMS) - .length > 0, - appIO_status: contactIO - ? contactIO.value === IOAllowedValues.ENABLED - ? 'activated' - : 'deactivated' - : 'nd', + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_YOUR_CONTACT_DETAILS, { + digitalAddresses, + contactIO, }); } }, [pageReady]); const handleRedirectToProfilePage = () => { - trackEventByType(TrackEventType.SEND_VIEW_PROFILE, { source: 'tuoi_recapiti' }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_PROFILE, { + source: 'tuoi_recapiti', + }); navigate(PROFILO); }; diff --git a/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx b/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx index 3f13839010..0378be71ee 100644 --- a/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx @@ -6,8 +6,6 @@ import { AppResponse, AppResponsePublisher, CodeModal, - EventMandateNotificationsListType, - ProfilePropertyType, TitleBox, useIsMobile, } from '@pagopa-pn/pn-commons'; @@ -18,6 +16,7 @@ import Delegators from '../components/Deleghe/Delegators'; import MobileDelegates from '../components/Deleghe/MobileDelegates'; import MobileDelegators from '../components/Deleghe/MobileDelegators'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; +import { PFEventsType } from '../models/PFEventsType'; import { acceptDelegation, getDelegates, @@ -26,13 +25,10 @@ import { revokeDelegation, } from '../redux/delegation/actions'; import { closeAcceptModal, closeRevocationModal, resetState } from '../redux/delegation/reducers'; -import { Delegation } from '../redux/delegation/types'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { getSidemenuInformation } from '../redux/sidemenu/actions'; import { RootState } from '../redux/store'; -import { TrackEventType } from '../utility/events'; -import { setSuperOrProfilePropertyValues, trackEventByType } from '../utility/mixpanel'; -import { DelegationStatus } from '../utility/status.utility'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; const Deleghe = () => { const isMobile = useIsMobile(); @@ -61,26 +57,18 @@ const Deleghe = () => { const handleConfirmClick = () => { if (type === 'delegates') { - trackEventByType(TrackEventType.SEND_MANDATE_REVOKED); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_MANDATE_REVOKED); void dispatch(revokeDelegation(id)) .unwrap() .then(() => - setSuperOrProfilePropertyValues( - ProfilePropertyType.PROFILE, - 'SEND_MANDATE_GIVEN', - delegators.filter((d) => d.status === DelegationStatus.ACTIVE).length > 0 ? 'yes' : 'no' - ) + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_MANDATE_GIVEN, { delegators }) ); } else { - trackEventByType(TrackEventType.SEND_MANDATE_REJECTED); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_MANDATE_REJECTED); void dispatch(rejectDelegation(id)) .unwrap() .then(() => - setSuperOrProfilePropertyValues( - ProfilePropertyType.PROFILE, - 'SEND_HAS_MANDATE', - delegates.filter((d) => d.status === DelegationStatus.ACTIVE).length > 0 ? 'yes' : 'no' - ) + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_HAS_MANDATE, { delegates }) ); } }; @@ -89,10 +77,14 @@ const Deleghe = () => { dispatch(closeAcceptModal()); }; - const handleAccept = async (code: Array) => { - trackEventByType(TrackEventType.SEND_MANDATE_ACCEPTED); - await dispatch(acceptDelegation({ id: acceptId, code: code.join('') })); - void dispatch(getSidemenuInformation()); + const handleAccept = (code: Array) => { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_MANDATE_ACCEPTED); + dispatch(acceptDelegation({ id: acceptId, code: code.join('') })) + .unwrap() + .then(() => { + void dispatch(getSidemenuInformation()); + }) + .catch(() => {}); }; const delegates = useAppSelector( @@ -109,22 +101,6 @@ const Deleghe = () => { setPageReady(true); }; - const getDelegatorsDelegationCounts = ( - delegates: Array, - delegators: Array - ): EventMandateNotificationsListType => ({ - total_mandates_given_count: delegates.length, - pending_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.PENDING) - .length, - active_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.ACTIVE) - .length, - total_mandates_received_count: delegators.length, - pending_mandates_received_count: delegators.filter((d) => d.status === DelegationStatus.PENDING) - .length, - active_mandates_received_count: delegators.filter((d) => d.status === DelegationStatus.ACTIVE) - .length, - }); - useEffect(() => { void retrieveData(); return () => { @@ -134,17 +110,17 @@ const Deleghe = () => { useEffect(() => { if (pageReady) { - trackEventByType( - TrackEventType.SEND_YOUR_MANDATES, - getDelegatorsDelegationCounts(delegates, delegators) - ); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_YOUR_MANDATES, { + delegates, + delegators, + }); } }, [pageReady]); const handleAcceptDelegationError = useCallback((errorResponse: AppResponse) => { const error = errorResponse.errors ? errorResponse.errors[0] : null; setErrorMessage(error?.message); - trackEventByType(TrackEventType.SEND_MANDATE_ACCEPT_CODE_ERROR); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_MANDATE_ACCEPT_CODE_ERROR); }, []); useEffect(() => { diff --git a/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx b/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx index e994f47573..83d95495cd 100644 --- a/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx @@ -7,13 +7,8 @@ import { Alert, AlertTitle, Box, Grid, Paper, Stack, Typography } from '@mui/mat import { ApiError, ApiErrorWrapper, - Downtime, - EventDowntimeType, - EventNotificationDetailType, EventPaymentRecipientType, - F24PaymentDetails, GetNotificationDowntimeEventsParams, - INotificationDetailTimeline, LegalFactId, NotificationDetailDocuments, NotificationDetailOtherDocument, @@ -23,12 +18,9 @@ import { NotificationDetailTimeline, NotificationPaymentRecipient, NotificationRelatedDowntimes, - NotificationStatus, PaymentAttachmentSName, PaymentDetails, PnBreadcrumb, - ProfilePropertyType, - TimelineCategory, TitleBox, appStateActions, dateIsLessThan10Years, @@ -41,6 +33,7 @@ import { import DomicileBanner from '../components/DomicileBanner/DomicileBanner'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; +import { PFEventsType } from '../models/PFEventsType'; import * as routes from '../navigation/routes.const'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { @@ -62,46 +55,7 @@ import { } from '../redux/notification/reducers'; import { RootState } from '../redux/store'; import { getConfiguration } from '../services/configuration.service'; -import { TrackEventType } from '../utility/events'; -import { setSuperOrProfilePropertyValues, trackEventByType } from '../utility/mixpanel'; - -const getNotificationDetailData = ( - downtimeEvents: Array, - mandateId: string | undefined, - notificationStatus: NotificationStatus, - checkIfUserHasPayments: boolean, - userPayments: { pagoPaF24: Array; f24Only: Array }, - fromQrCode: boolean, - timeline: Array -): EventNotificationDetailType => { - // eslint-disable-next-line functional/no-let - let typeDowntime: EventDowntimeType; - if (downtimeEvents.length === 0) { - typeDowntime = EventDowntimeType.NOT_DISSERVICE; - } else { - typeDowntime = - downtimeEvents.filter((downtime) => !!downtime.endDate).length === downtimeEvents.length - ? EventDowntimeType.COMPLETED - : EventDowntimeType.IN_PROGRESS; - } - const hasF24 = - userPayments.f24Only.length > 0 || - userPayments.pagoPaF24.filter((payment) => payment.f24).length > 0; - - return { - notification_owner: !mandateId, - notification_status: notificationStatus, - contains_payment: checkIfUserHasPayments, - disservice_status: typeDowntime, - contains_multipayment: - userPayments.f24Only.length + userPayments.pagoPaF24.length > 1 ? 'yes' : 'no', - count_payment: userPayments.pagoPaF24.filter((payment) => payment.pagoPa).length, - contains_f24: hasF24 ? 'yes' : 'no', - first_time_opening: - timeline.findIndex((el) => el.category === TimelineCategory.NOTIFICATION_VIEWED) === -1, - source: fromQrCode ? 'QRcode' : 'LISTA_NOTIFICHE', - }; -}; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; // state for the invocations to this component // (to include in navigation or Link to the route/s arriving to it) @@ -232,13 +186,13 @@ const NotificationDetail = () => { ); } }); - trackEventByType(TrackEventType.SEND_DOWNLOAD_RECEIPT_NOTICE); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_DOWNLOAD_RECEIPT_NOTICE); } else { const documentIndex = document as string; void dispatch( getReceivedNotificationDocument({ iun: notification.iun, documentIndex, mandateId }) ); - trackEventByType(TrackEventType.SEND_DOWNLOAD_ATTACHMENT); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_DOWNLOAD_ATTACHMENT); } }; @@ -271,9 +225,12 @@ const NotificationDetail = () => { ); } }); - trackEventByType(TrackEventType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES, { - source: 'dettaglio_notifica', - }); + PFEventStrategyFactory.triggerEvent( + PFEventsType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES, + { + source: 'dettaglio_notifica', + } + ); } else if ((legalFact as NotificationDetailOtherDocument).documentId) { const otherDocument = legalFact as NotificationDetailOtherDocument; void dispatch( @@ -294,7 +251,7 @@ const NotificationDetail = () => { const onPayClick = (noticeCode?: string, creditorTaxId?: string, amount?: number) => { if (noticeCode && creditorTaxId && amount && notification.senderDenomination) { - trackEventByType(TrackEventType.SEND_START_PAYMENT); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_START_PAYMENT); dispatch( getNotificationPaymentUrl({ paymentNotice: { @@ -443,42 +400,41 @@ const NotificationDetail = () => { ); const trackEventPaymentRecipient = (event: EventPaymentRecipientType, param?: object) => { - // eslint-disable-next-line functional/no-let - trackEventByType( - event as unknown as TrackEventType, - event === EventPaymentRecipientType.SEND_PAYMENT_STATUS ? param : undefined + PFEventStrategyFactory.triggerEvent( + PFEventsType[event], + event === EventPaymentRecipientType.SEND_PAYMENT_STATUS || + event === EventPaymentRecipientType.SEND_PAYMENT_DETAIL_ERROR + ? param + : undefined ); }; const reloadPaymentsInfo = (data: Array) => { fetchPaymentsInfo(data); - trackEventByType(TrackEventType.SEND_PAYMENT_DETAIL_REFRESH); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_PAYMENT_DETAIL_REFRESH); }; const trackShowMoreLess = (collapsed: boolean) => { - trackEventByType(TrackEventType.SEND_NOTIFICATION_STATUS_DETAIL, { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_NOTIFICATION_STATUS_DETAIL, { accordion: collapsed ? 'collapsed' : 'expanded', }); }; useEffect(() => { if (downtimesReady && pageReady) { - const notificationDetailData = getNotificationDetailData( + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_NOTIFICATION_DETAIL, { downtimeEvents, mandateId, - notification.notificationStatus, + notificationStatus: notification.notificationStatus, checkIfUserHasPayments, userPayments, fromQrCode, - notification.timeline - ); - trackEventByType(TrackEventType.SEND_NOTIFICATION_DETAIL, notificationDetailData); - if (notificationDetailData.first_time_opening) { - setSuperOrProfilePropertyValues( - ProfilePropertyType.INCREMENTAL, - 'SEND_NOTIFICATIONS_COUNT' - ); - } + timeline: notification.timeline, + }); + + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_NOTIFICATIONS_COUNT, { + timeline: notification.timeline, + }); } }, [downtimesReady, pageReady]); diff --git a/packages/pn-personafisica-webapp/src/pages/Notifiche.page.tsx b/packages/pn-personafisica-webapp/src/pages/Notifiche.page.tsx index ba7334b7ff..c240b4b5ef 100644 --- a/packages/pn-personafisica-webapp/src/pages/Notifiche.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/Notifiche.page.tsx @@ -6,15 +6,11 @@ import { Box } from '@mui/material'; import { ApiErrorWrapper, CustomPagination, - EventNotificationsListType, - Notification, NotificationColumnData, - NotificationStatus, PaginationData, Sort, TitleBox, calculatePages, - isNewNotification, useIsMobile, } from '@pagopa-pn/pn-commons'; @@ -22,45 +18,13 @@ import DomicileBanner from '../components/DomicileBanner/DomicileBanner'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; import DesktopNotifications from '../components/Notifications/DesktopNotifications'; import MobileNotifications from '../components/Notifications/MobileNotifications'; +import { PFEventsType } from '../models/PFEventsType'; import { DASHBOARD_ACTIONS, getReceivedNotifications } from '../redux/dashboard/actions'; import { setMandateId, setPagination, setSorting } from '../redux/dashboard/reducers'; import { Delegator } from '../redux/delegation/types'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; - -const getEventNotifications = ( - notifications: Array, - delegators: Array, - pagination: { - nextPagesKey: Array; - size: number; - page: number; - moreResult: boolean; - }, - domicileBannerType: string -): EventNotificationsListType => ({ - ...(domicileBannerType && { banner: domicileBannerType }), - delegate: delegators.length > 0, - page_number: pagination.page, - total_count: notifications.length, - unread_count: notifications.filter((n) => isNewNotification(n.notificationStatus)).length, - delivered_count: notifications.filter( - (n) => n.notificationStatus === NotificationStatus.DELIVERED - ).length, - opened_count: notifications.filter((n) => n.notificationStatus === NotificationStatus.VIEWED) - .length, - expired_count: notifications.filter( - (n) => n.notificationStatus === NotificationStatus.EFFECTIVE_DATE - ).length, - not_found_count: notifications.filter( - (n) => n.notificationStatus === NotificationStatus.UNREACHABLE - ).length, - cancelled_count: notifications.filter( - (n) => n.notificationStatus === NotificationStatus.CANCELLED - ).length, -}); +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; const Notifiche = () => { const dispatch = useAppDispatch(); @@ -112,16 +76,16 @@ const Notifiche = () => { .unwrap() .then((data) => { setPageReady(true); - trackEventByType( + PFEventStrategyFactory.triggerEvent( currentDelegator - ? TrackEventType.SEND_NOTIFICATION_DELEGATED - : TrackEventType.SEND_YOUR_NOTIFICATION, - getEventNotifications( - data.resultsPage, + ? PFEventsType.SEND_NOTIFICATION_DELEGATED + : PFEventsType.SEND_YOUR_NOTIFICATIONS, + { + notifications: data.resultsPage, delegators, pagination, - domicileBannerTypeRef.current - ) + domicileBannerType: domicileBannerTypeRef.current, + } ); }) .catch(() => setPageReady(true)); diff --git a/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx b/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx index a4c4aff2e5..99c278f434 100644 --- a/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/NuovaDelega.page.tsx @@ -41,6 +41,7 @@ import { IllusCompleted } from '@pagopa/mui-italia'; import VerificationCodeComponent from '../components/Deleghe/VerificationCodeComponent'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; import DropDownPartyMenuItem from '../components/Party/DropDownParty'; +import { PFEventsType } from '../models/PFEventsType'; import { Party } from '../models/party'; import * as routes from '../navigation/routes.const'; import { NewDelegationFormProps } from '../redux/delegation/types'; @@ -49,9 +50,8 @@ import { createDelegation, getAllEntities } from '../redux/newDelegation/actions import { resetNewDelegation } from '../redux/newDelegation/reducers'; import { RootState } from '../redux/store'; import { getConfiguration } from '../services/configuration.service'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; import { generateVCode } from '../utility/delegation.utility'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; const getError = ( fieldTouched: FormikTouched | boolean | undefined, @@ -72,7 +72,10 @@ const NuovaDelega = () => { useEffect(() => { if (createdDelegation && created) { - trackEventByType(TrackEventType.SEND_ADD_MANDATE_UX_SUCCESS, createdDelegation); + PFEventStrategyFactory.triggerEvent( + PFEventsType.SEND_ADD_MANDATE_UX_SUCCESS, + createdDelegation + ); } }, [createdDelegation, created]); @@ -83,10 +86,9 @@ const NuovaDelega = () => { values.selectTuttiEntiOrSelezionati === 'tuttiGliEnti' ? 'all' : 'selected_party', }); - trackEventByType(TrackEventType.SEND_ADD_MANDATE_UX_CONVERSION, { - person_type: values.selectPersonaFisicaOrPersonaGiuridica, - mandate_type: - values.selectTuttiEntiOrSelezionati === 'tuttiGliEnti' ? 'all' : 'selected_party', + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_MANDATE_UX_CONVERSION, { + selectPersonaFisicaOrPersonaGiuridica: values.selectPersonaFisicaOrPersonaGiuridica, + selectTuttiEntiOrSelezionati: values.selectTuttiEntiOrSelezionati, }); void dispatch(createDelegation({ ...values, codiceFiscale: 'a' })); // void dispatch(createDelegation(values)); @@ -182,7 +184,7 @@ const NuovaDelega = () => { const [loadAllEntities, setLoadAllEntities] = useState(false); useEffect(() => { - trackEventByType(TrackEventType.SEND_ADD_MANDATE_DATA_INPUT); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_MANDATE_DATA_INPUT); }, []); useEffect(() => { @@ -212,7 +214,7 @@ const NuovaDelega = () => { const getOptionLabel = (option: Party) => option.name || ''; const handleGoBackAction = () => { - trackEventByType(TrackEventType.SEND_ADD_MANDATE_BACK); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_ADD_MANDATE_BACK); navigate(routes.DELEGHE); }; diff --git a/packages/pn-personafisica-webapp/src/pages/Profile.page.tsx b/packages/pn-personafisica-webapp/src/pages/Profile.page.tsx index e88dea06c5..f9830f3e6f 100644 --- a/packages/pn-personafisica-webapp/src/pages/Profile.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/Profile.page.tsx @@ -16,11 +16,11 @@ import { } from '@mui/material'; import { TitleBox, useIsMobile } from '@pagopa-pn/pn-commons'; +import { PFEventsType } from '../models/PFEventsType'; import { RECAPITI } from '../navigation/routes.const'; import { useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; -import { TrackEventType } from '../utility/events'; -import { trackEventByType } from '../utility/mixpanel'; +import PFEventStrategyFactory from '../utility/MixpanelUtils/PFEventStrategyFactory'; const Profile = () => { const navigate = useNavigate(); @@ -28,7 +28,7 @@ const Profile = () => { const currentUser = useAppSelector((state: RootState) => state.userState.user); useEffect(() => { - trackEventByType(TrackEventType.SEND_PROFILE); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_PROFILE); }, []); const alertButtonStyle: SxProps = useIsMobile() @@ -36,7 +36,9 @@ const Profile = () => { : { textAlign: 'center', minWidth: 'max-content' }; const handleRedirectToContactsPage = () => { - trackEventByType(TrackEventType.SEND_VIEW_CONTACT_DETAILS, { source: 'profilo' }); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_VIEW_CONTACT_DETAILS, { + source: 'profilo', + }); navigate(RECAPITI); }; 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__/Deleghe.page.test.tsx b/packages/pn-personafisica-webapp/src/pages/__test__/Deleghe.page.test.tsx index 331693520b..f13979ea1f 100644 --- a/packages/pn-personafisica-webapp/src/pages/__test__/Deleghe.page.test.tsx +++ b/packages/pn-personafisica-webapp/src/pages/__test__/Deleghe.page.test.tsx @@ -243,7 +243,7 @@ describe('Deleghe page', async () => { fireEvent.change(codeInput, { target: { value: codes[index] } }); }); const dialogButtons = within(dialog).getByRole('button', { name: 'deleghe.accept' }); - // confirm rejection + // confirm accept fireEvent.click(dialogButtons); await waitFor(() => { expect(mock.history.patch).toHaveLength(1); @@ -252,7 +252,9 @@ describe('Deleghe page', async () => { verificationCode: arrayOfDelegators[0].verificationCode, }); }); - // check that nothing is changed + const error = await waitFor(() => within(dialog).getByTestId('errorAlert')); + expect(error).toBeInTheDocument(); + // check that accept button is still active in deleghe page delegatorsRows = result.getAllByTestId('delegatorsTable.body.row'); expect(delegatorsRows).toHaveLength(arrayOfDelegators.length); delegatorsRows.forEach((row, index) => { @@ -260,7 +262,5 @@ describe('Deleghe page', async () => { }); acceptButton = within(delegatorsRows[0]).getByTestId('acceptButton'); expect(acceptButton).toBeInTheDocument(); - const error = await waitFor(() => within(dialog).queryByTestId('errorAlert')); - expect(error).toBeInTheDocument(); }); }); 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/notification/actions.ts b/packages/pn-personafisica-webapp/src/redux/notification/actions.ts index 8520c4e1eb..c4f0e52771 100644 --- a/packages/pn-personafisica-webapp/src/redux/notification/actions.ts +++ b/packages/pn-personafisica-webapp/src/redux/notification/actions.ts @@ -10,7 +10,6 @@ import { PaymentDetails, PaymentNotice, PaymentStatus, - ProfilePropertyType, checkIfPaymentsIsAlreadyInCache, getPaymentCache, performThunkAction, @@ -23,8 +22,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { AppStatusApi } from '../../api/appStatus/AppStatus.api'; import { NotificationsApi } from '../../api/notifications/Notifications.api'; import { NotificationDetailForRecipient } from '../../models/NotificationDetail'; -import { TrackEventType } from '../../utility/events'; -import { setSuperOrProfilePropertyValues, trackEventByType } from '../../utility/mixpanel'; +import { PFEventsType } from '../../models/PFEventsType'; +import PFEventStrategyFactory from '../../utility/MixpanelUtils/PFEventStrategyFactory'; import { RootState, store } from '../store'; import { DownloadFileResponse, GetReceivedNotificationParams } from './types'; @@ -133,12 +132,12 @@ export const getNotificationPaymentInfo = createAsyncThunk< paymentCache.currentPayment, ]); - trackEventByType(TrackEventType.SEND_PAYMENT_OUTCOME, { + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_PAYMENT_OUTCOME, { outcome: updatedPayment[0].status, }); if (updatedPayment[0].status === PaymentStatus.SUCCEEDED) { - setSuperOrProfilePropertyValues(ProfilePropertyType.INCREMENTAL, 'SEND_PAYMENTS_COUNT'); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_PAYMENTS_COUNT); } const payments = populatePaymentsPagoPaF24( diff --git a/packages/pn-personafisica-webapp/src/redux/store.ts b/packages/pn-personafisica-webapp/src/redux/store.ts index cc370f4168..9cf6574240 100644 --- a/packages/pn-personafisica-webapp/src/redux/store.ts +++ b/packages/pn-personafisica-webapp/src/redux/store.ts @@ -4,7 +4,7 @@ import { appStateReducer } from '@pagopa-pn/pn-commons'; import { Middleware, MiddlewareArray, configureStore } from '@reduxjs/toolkit'; import { getConfiguration } from '../services/configuration.service'; -import { trackingMiddleware, trackingProfileMiddleware } from '../utility/mixpanel'; +import { trackingMiddleware } from '../utility/mixpanel'; import appStatusSlice from './appStatus/reducers'; import userSlice from './auth/reducers'; import contactsSlice from './contact/reducers'; @@ -28,11 +28,7 @@ export const appReducers = { const createStore = (logReduxActions?: boolean) => { const mustLogActions = logReduxActions ?? getConfiguration().LOG_REDUX_ACTIONS; - const additionalMiddlewares = [ - mustLogActions ? logger : undefined, - trackingMiddleware, - trackingProfileMiddleware, - ]; + const additionalMiddlewares = [mustLogActions ? logger : undefined, trackingMiddleware]; return configureStore({ reducer: appReducers, middleware: (getDefaultMiddleware) => 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/MixpanelUtils/PFEventStrategyFactory.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/PFEventStrategyFactory.ts new file mode 100644 index 0000000000..99e41d462d --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/PFEventStrategyFactory.ts @@ -0,0 +1,205 @@ +import { EventStrategy, EventStrategyFactory } from '@pagopa-pn/pn-commons'; + +import { PFEventsType } from '../../models/PFEventsType'; +import { SendAcceptDelegationStrategy } from './Strategies/SendAcceptDelegationStrategy'; +import { SendAddContactActionStrategy } from './Strategies/SendAddContactActionStrategy'; +import { SendAddContactScreenViewStrategy } from './Strategies/SendAddContactScreenViewStrategy'; +import { SendAddCourtesyAddressStrategy } from './Strategies/SendAddCourtesyAddressStrategy'; +import { SendAddLegalAddressStrategy } from './Strategies/SendAddLegalAddressStrategy'; +import { SendAddMandateUXConversionStrategy } from './Strategies/SendAddMandateUXConversionStrategy'; +import { SendAddMandateUXSuccessStrategy } from './Strategies/SendAddMandateUXSuccessStrategy'; +import { SendDisableIOStrategy } from './Strategies/SendDisableIOStrategy'; +import { SendDownloadCertificateOpposable } from './Strategies/SendDownloadCertificateOpposable'; +import { SendDownloadResponseStrategy } from './Strategies/SendDownloadResponse'; +import { SendEnableIOStrategy } from './Strategies/SendEnableIOStrategy'; +import { SendGenericErrorStrategy } from './Strategies/SendGenericErrorStrategy'; +import { SendHasAddressesStrategy } from './Strategies/SendHasAddressesStrategy'; +import { SendHasMandateGivenStrategy } from './Strategies/SendHasMandateGivensStrategy'; +import { SendHasMandateLoginStrategy } from './Strategies/SendHasMandateLoginStrategy'; +import { SendHasMandateStrategy } from './Strategies/SendHasMandateStrategy'; +import { SendNotificationCountStrategy } from './Strategies/SendNotificationCount'; +import { SendNotificationDetailStrategy } from './Strategies/SendNotificationDetailStrategy'; +import { SendNotificationStatusDetailStrategy } from './Strategies/SendNotificationStatusDetail'; +import { SendPaymentDetailErrorStrategy } from './Strategies/SendPaymentDetailErrorStrategy'; +import { SendPaymentOutcomeStrategy } from './Strategies/SendPaymentOutcomeStrategy'; +import { SendPaymentStatusStrategy } from './Strategies/SendPaymentStatusStrategy'; +import { SendPaymentsCountStrategy } from './Strategies/SendPaymentsCountStrategy'; +import { SendRefreshPageStrategy } from './Strategies/SendRefreshPageStrategy'; +import { SendRemoveContactSuccessStrategy } from './Strategies/SendRemoveContactSuccess'; +import { SendRemoveCourtesyAddressStrategy } from './Strategies/SendRemoveCourtesyAddress'; +import { SendRemoveLegalAddressStrategy } from './Strategies/SendRemoveLegalAddress'; +import { SendServiceStatusStrategy } from './Strategies/SendServiceStatusStrategy'; +import { SendToastErrorStrategy } from './Strategies/SendToastErrorStrategy'; +import { SendViewContactDetailsStrategy } from './Strategies/SendViewContactDetailsStrategy'; +import { SendViewProfileStrategy } from './Strategies/SendViewProfileStrategy'; +import { SendYourContactDetailsStrategy } from './Strategies/SendYourContactDetailsStrategy'; +import { SendYourMandatesStrategy } from './Strategies/SendYourMandatesStrategy'; +import { SendYourNotificationsStrategy } from './Strategies/SendYourNotificationsStrategy'; +import { TechScreenViewStrategy } from './Strategies/TechScreenViewStrategy'; +import { TechStrategy } from './Strategies/TechStrategy'; +import { UXActionStrategy } from './Strategies/UXActionStrategy'; +import { UXErrorStrategy } from './Strategies/UXErrorStrategy'; +import { UXScreenViewStrategy } from './Strategies/UXScreenViewStrategy'; + +const uxActionStrategy = [ + PFEventsType.SEND_DOWNLOAD_ATTACHMENT, + PFEventsType.SEND_DOWNLOAD_RECEIPT_NOTICE, + PFEventsType.SEND_START_PAYMENT, + PFEventsType.SEND_PAYMENT_DETAIL_REFRESH, + PFEventsType.SEND_ADD_MANDATE_START, + PFEventsType.SEND_ADD_MANDATE_BACK, + PFEventsType.SEND_SHOW_MANDATE_CODE, + PFEventsType.SEND_MANDATE_REVOKED, + PFEventsType.SEND_MANDATE_REJECTED, + PFEventsType.SEND_MANDATE_ACCEPTED, + PFEventsType.SEND_ACTIVE_IO_START, + PFEventsType.SEND_DEACTIVE_IO_START, + PFEventsType.SEND_ACTIVE_IO_UX_CONVERSION, + PFEventsType.SEND_DEACTIVE_IO_UX_CONVERSION, + PFEventsType.SEND_CANCELLED_NOTIFICATION_REFOUND_INFO, + PFEventsType.SEND_MULTIPAYMENT_MORE_INFO, + PFEventsType.SEND_PAYMENT_LIST_CHANGE_PAGE, + PFEventsType.SEND_F24_DOWNLOAD, + PFEventsType.SEND_DOWNLOAD_PAYMENT_NOTICE, +] as const; + +const sendAddContactActionStrategy = [ + PFEventsType.SEND_ADD_EMAIL_START, + PFEventsType.SEND_ADD_SMS_START, + PFEventsType.SEND_ADD_PEC_START, + PFEventsType.SEND_ADD_PEC_UX_CONVERSION, + PFEventsType.SEND_ADD_SMS_UX_CONVERSION, + PFEventsType.SEND_ADD_EMAIL_UX_CONVERSION, +] as const; + +const sendRemoveContactSuccessStrategy = [ + PFEventsType.SEND_REMOVE_EMAIL_SUCCESS, + PFEventsType.SEND_REMOVE_SMS_SUCCESS, + PFEventsType.SEND_REMOVE_PEC_SUCCESS, +] as const; + +const sendAddContactScreenViewStrategy = [ + PFEventsType.SEND_ADD_EMAIL_UX_SUCCESS, + PFEventsType.SEND_ADD_SMS_UX_SUCCESS, + PFEventsType.SEND_ADD_PEC_UX_SUCCESS, +] as const; + +const uxScreenViewStrategy = [ + PFEventsType.SEND_PROFILE, + PFEventsType.SEND_ADD_MANDATE_DATA_INPUT, + PFEventsType.SEND_ACTIVE_IO_UX_SUCCESS, + PFEventsType.SEND_DEACTIVE_IO_UX_SUCCESS, +] as const; + +const uxErrorStrategy = [ + PFEventsType.SEND_MANDATE_ACCEPT_CODE_ERROR, + PFEventsType.SEND_ADD_PEC_CODE_ERROR, + PFEventsType.SEND_ADD_SMS_CODE_ERROR, + PFEventsType.SEND_ADD_EMAIL_CODE_ERROR, +] as const; + +const techStrategy = [ + PFEventsType.SEND_RAPID_ACCESS, + PFEventsType.SEND_AUTH_SUCCESS, + PFEventsType.SEND_F24_DOWNLOAD_SUCCESS, + PFEventsType.SEND_F24_DOWNLOAD_TIMEOUT, +] as const; + +type ArrayToTuple> = keyof { + [K in T extends ReadonlyArray ? U : never]: string; +}; + +const eventStrategy: Record< + Exclude< + PFEventsType, + | ArrayToTuple + | ArrayToTuple + | ArrayToTuple + | ArrayToTuple + | ArrayToTuple + | ArrayToTuple + | ArrayToTuple + >, + EventStrategy +> = { + [PFEventsType.SEND_VIEW_PROFILE]: new SendViewProfileStrategy(), + [PFEventsType.SEND_VIEW_CONTACT_DETAILS]: new SendViewContactDetailsStrategy(), + [PFEventsType.SEND_NOTIFICATION_DETAIL]: new SendNotificationDetailStrategy(), + [PFEventsType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES]: + new SendDownloadCertificateOpposable(), + [PFEventsType.SEND_YOUR_NOTIFICATIONS]: new SendYourNotificationsStrategy(), + [PFEventsType.SEND_NOTIFICATION_DELEGATED]: new SendYourNotificationsStrategy(), + [PFEventsType.SEND_NOTIFICATION_STATUS_DETAIL]: new SendNotificationStatusDetailStrategy(), + [PFEventsType.SEND_YOUR_MANDATES]: new SendYourMandatesStrategy(), + [PFEventsType.SEND_ADD_MANDATE_UX_CONVERSION]: new SendAddMandateUXConversionStrategy(), + [PFEventsType.SEND_ADD_MANDATE_UX_SUCCESS]: new SendAddMandateUXSuccessStrategy(), + [PFEventsType.SEND_YOUR_CONTACT_DETAILS]: new SendYourContactDetailsStrategy(), + [PFEventsType.SEND_SERVICE_STATUS]: new SendServiceStatusStrategy(), + [PFEventsType.SEND_REFRESH_PAGE]: new SendRefreshPageStrategy(), + [PFEventsType.SEND_TOAST_ERROR]: new SendToastErrorStrategy(), + [PFEventsType.SEND_GENERIC_ERROR]: new SendGenericErrorStrategy(), + [PFEventsType.SEND_PAYMENT_OUTCOME]: new SendPaymentOutcomeStrategy(), + [PFEventsType.SEND_DOWNLOAD_RESPONSE]: new SendDownloadResponseStrategy(), + [PFEventsType.SEND_PAYMENT_STATUS]: new SendPaymentStatusStrategy(), + [PFEventsType.SEND_PAYMENT_DETAIL_ERROR]: new SendPaymentDetailErrorStrategy(), + [PFEventsType.SEND_NOTIFICATION_NOT_ALLOWED]: new TechScreenViewStrategy(), + [PFEventsType.SEND_HAS_ADDRESSES]: new SendHasAddressesStrategy(), + [PFEventsType.SEND_HAS_MANDATE_LOGIN]: new SendHasMandateLoginStrategy(), + [PFEventsType.SEND_MANDATE_GIVEN]: new SendHasMandateGivenStrategy(), + [PFEventsType.SEND_HAS_MANDATE]: new SendHasMandateStrategy(), + [PFEventsType.SEND_DISABLE_IO]: new SendDisableIOStrategy(), + [PFEventsType.SEND_ENABLE_IO]: new SendEnableIOStrategy(), + [PFEventsType.SEND_ACCEPT_DELEGATION]: new SendAcceptDelegationStrategy(), + [PFEventsType.SEND_REMOVE_LEGAL_ADDRESS]: new SendRemoveLegalAddressStrategy(), + [PFEventsType.SEND_ADD_LEGAL_ADDRESS]: new SendAddLegalAddressStrategy(), + [PFEventsType.SEND_NOTIFICATIONS_COUNT]: new SendNotificationCountStrategy(), + [PFEventsType.SEND_PAYMENTS_COUNT]: new SendPaymentsCountStrategy(), + [PFEventsType.SEND_REMOVE_COURTESY_ADDRESS]: new SendRemoveCourtesyAddressStrategy(), + [PFEventsType.SEND_ADD_COURTESY_ADDRESS]: new SendAddCourtesyAddressStrategy(), +}; + +const isInEventStrategyMap = (value: PFEventsType): value is keyof typeof eventStrategy => { + if (Object.keys(eventStrategy).includes(value)) { + return true; + } + return false; +}; + +class PFEventStrategyFactory extends EventStrategyFactory { + getStrategy(eventType: PFEventsType): EventStrategy | null { + if (uxActionStrategy.findIndex((el) => el === eventType) > -1) { + return new UXActionStrategy(); + } + + if (sendAddContactActionStrategy.findIndex((el) => el === eventType) > -1) { + return new SendAddContactActionStrategy(); + } + + if (sendRemoveContactSuccessStrategy.findIndex((el) => el === eventType) > -1) { + return new SendRemoveContactSuccessStrategy(); + } + + if (sendAddContactScreenViewStrategy.findIndex((el) => el === eventType) > -1) { + return new SendAddContactScreenViewStrategy(); + } + + if (uxScreenViewStrategy.findIndex((el) => el === eventType) > -1) { + return new UXScreenViewStrategy(); + } + + if (uxErrorStrategy.findIndex((el) => el === eventType) > -1) { + return new UXErrorStrategy(); + } + + if (techStrategy.findIndex((el) => el === eventType) > -1) { + return new TechStrategy(); + } + + if (isInEventStrategyMap(eventType)) { + return eventStrategy[eventType]; + } + return null; + } +} + +export default new PFEventStrategyFactory(); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAcceptDelegationStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAcceptDelegationStrategy.ts new file mode 100644 index 0000000000..a6789583d9 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAcceptDelegationStrategy.ts @@ -0,0 +1,15 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type SendAcceptDelegation = { + SEND_HAS_MANDATE: 'yes'; +}; + +export class SendAcceptDelegationStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_MANDATE: 'yes', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactActionStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactActionStrategy.ts new file mode 100644 index 0000000000..97d4076ba2 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactActionStrategy.ts @@ -0,0 +1,19 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendAddContactActionStrategy implements EventStrategy { + performComputations(isSpecialContact: boolean): TrackedEvent<{ other_contact: string }> { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: isSpecialContact ? 'yes' : 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactScreenViewStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactScreenViewStrategy.ts new file mode 100644 index 0000000000..e406f867dc --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddContactScreenViewStrategy.ts @@ -0,0 +1,19 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendAddContactScreenViewStrategy implements EventStrategy { + performComputations(isSpecialContact: boolean): TrackedEvent<{ other_contact: string }> { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + other_contact: isSpecialContact ? 'yes' : 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddCourtesyAddressStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddCourtesyAddressStrategy.ts new file mode 100644 index 0000000000..ea39cbae40 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddCourtesyAddressStrategy.ts @@ -0,0 +1,44 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { CourtesyChannelType, DigitalAddress } from '../../../models/contacts'; +import { SaveDigitalAddressParams } from '../../../redux/contact/types'; + +type SendAddCourtesyAddressReturn = + | { + SEND_HAS_EMAIL: 'yes'; + } + | { + SEND_HAS_SMS: 'yes'; + }; + +type SendAddCourtesyAddressData = { + payload: DigitalAddress | void; + params: SaveDigitalAddressParams; +}; + +export class SendAddCourtesyAddressStrategy implements EventStrategy { + performComputations({ + payload, + params, + }: SendAddCourtesyAddressData): TrackedEvent { + // Check payload to distinguish between the action called before PIN validation and after + // We have to track only the action after the PIN validation + if (!payload) { + return {}; + } + + if (params.channelType === CourtesyChannelType.EMAIL) { + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_EMAIL: 'yes', + }, + }; + } + + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_SMS: 'yes', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddLegalAddressStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddLegalAddressStrategy.ts new file mode 100644 index 0000000000..886cb7ba43 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddLegalAddressStrategy.ts @@ -0,0 +1,23 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { DigitalAddress } from '../../../models/contacts'; + +type SendAddLegalAddress = { + SEND_HAS_PEC: 'yes'; +}; + +type SendAddLegalAddressData = { + payload: DigitalAddress | void; +}; + +export class SendAddLegalAddressStrategy implements EventStrategy { + performComputations({ payload }: SendAddLegalAddressData): TrackedEvent { + if (!(payload && payload.pecValid)) { + return {}; + } + + return { + [EventPropertyType.PROFILE]: { SEND_HAS_PEC: 'yes' }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXConversionStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXConversionStrategy.ts new file mode 100644 index 0000000000..0b0be74107 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXConversionStrategy.ts @@ -0,0 +1,34 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + RecipientType, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendAddMandateUXConversion = { + selectPersonaFisicaOrPersonaGiuridica: RecipientType; + selectTuttiEntiOrSelezionati: string; +}; + +type SendAddMandateUXConversionReturn = { + person_type: RecipientType; + mandate_type: string; +}; + +export class SendAddMandateUXConversionStrategy implements EventStrategy { + performComputations( + data: SendAddMandateUXConversion + ): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + person_type: data.selectPersonaFisicaOrPersonaGiuridica, + mandate_type: + data.selectTuttiEntiOrSelezionati === 'tuttiGliEnti' ? 'all' : 'selected_party', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXSuccessStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXSuccessStrategy.ts new file mode 100644 index 0000000000..a3e21e3758 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendAddMandateUXSuccessStrategy.ts @@ -0,0 +1,24 @@ +import { + EventAction, + EventCategory, + EventCreatedDelegationType, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendAddMandateUXSuccessStrategy implements EventStrategy { + performComputations({ + person_type, + mandate_type, + }: EventCreatedDelegationType): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + person_type, + mandate_type, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDisableIOStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDisableIOStrategy.ts new file mode 100644 index 0000000000..41c3a3a86a --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDisableIOStrategy.ts @@ -0,0 +1,15 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type SendDisableIO = { + SEND_APPIO_STATUS: 'deactivated'; +}; + +export class SendDisableIOStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadCertificateOpposable.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadCertificateOpposable.ts new file mode 100644 index 0000000000..434d65aa0e --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadCertificateOpposable.ts @@ -0,0 +1,25 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendDownloadCertificateSource = { + source: 'dettaglio_notifica' | 'stato_piattaforma'; +}; + +export class SendDownloadCertificateOpposable implements EventStrategy { + performComputations({ + source, + }: SendDownloadCertificateSource): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadResponse.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadResponse.ts new file mode 100644 index 0000000000..4823a6dcbc --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendDownloadResponse.ts @@ -0,0 +1,34 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendDownloadResponse = { + payload: { + url?: string; + docType?: string; + }; +}; + +type SendDownloadResponseReturn = { + doc_type: string; + url_available: 'ready' | 'retry_after'; +}; + +export class SendDownloadResponseStrategy implements EventStrategy { + performComputations({ + payload: { url, docType }, + }: SendDownloadResponse): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + doc_type: docType ? docType : '', + url_available: url ? 'ready' : 'retry_after', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendEnableIOStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendEnableIOStrategy.ts new file mode 100644 index 0000000000..81ce2e27a6 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendEnableIOStrategy.ts @@ -0,0 +1,15 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type SendEnableIO = { + SEND_APPIO_STATUS: 'activated'; +}; + +export class SendEnableIOStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'activated', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendGenericErrorStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendGenericErrorStrategy.ts new file mode 100644 index 0000000000..ad2c62e120 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendGenericErrorStrategy.ts @@ -0,0 +1,26 @@ +import { ErrorInfo } from 'react'; + +import { + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendGenericError = { + reason: { + error: Error; + errorInfo: ErrorInfo; + }; +}; + +export class SendGenericErrorStrategy implements EventStrategy { + performComputations(data: SendGenericError): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + reason: data.reason, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasAddressesStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasAddressesStrategy.ts new file mode 100644 index 0000000000..5bec2d5474 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasAddressesStrategy.ts @@ -0,0 +1,50 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { + CourtesyChannelType, + DigitalAddress, + IOAllowedValues, + LegalChannelType, +} from '../../../models/contacts'; + +type SendHasAddresses = { + SEND_HAS_PEC: 'yes' | 'no'; + SEND_HAS_EMAIL: 'yes' | 'no'; + SEND_HAS_SMS: 'yes' | 'no'; + SEND_APPIO_STATUS: 'nd' | 'deactivated' | 'activated'; +}; + +type SendHasAddressesData = { + payload: Array; +}; + +export class SendHasAddressesStrategy implements EventStrategy { + performComputations({ payload }: SendHasAddressesData): TrackedEvent { + const hasLegalAddresses = + payload.filter((address) => address.channelType === LegalChannelType.PEC).length > 0; + const hasCourtesyEmailAddresses = + payload.filter((address) => address.channelType === CourtesyChannelType.EMAIL).length > 0; + const hasCourtesySmsAddresses = + payload?.filter((address) => address.channelType === CourtesyChannelType.SMS).length > 0; + const contactIO = payload?.find((address) => address.channelType === CourtesyChannelType.IOMSG); + + // eslint-disable-next-line functional/no-let + let ioStatus: 'nd' | 'deactivated' | 'activated'; + + if (!contactIO) { + ioStatus = 'nd'; + } else if (contactIO?.value === IOAllowedValues.DISABLED) { + ioStatus = 'deactivated'; + } else { + ioStatus = 'activated'; + } + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_PEC: hasLegalAddresses ? 'yes' : 'no', + SEND_HAS_EMAIL: hasCourtesyEmailAddresses ? 'yes' : 'no', + SEND_HAS_SMS: hasCourtesySmsAddresses ? 'yes' : 'no', + SEND_APPIO_STATUS: ioStatus, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateGivensStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateGivensStrategy.ts new file mode 100644 index 0000000000..e986ab6f30 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateGivensStrategy.ts @@ -0,0 +1,26 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { Delegation } from '../../../redux/delegation/types'; +import { DelegationStatus } from '../../status.utility'; + +type SendHasMandateGiven = { + SEND_MANDATE_GIVEN: 'yes' | 'no'; +}; + +type SendHasMandateGivenData = { + payload: Array; +}; + +export class SendHasMandateGivenStrategy implements EventStrategy { + performComputations({ payload }: SendHasMandateGivenData): TrackedEvent { + const hasDelegates = payload?.filter( + (delegation) => delegation.status === DelegationStatus.ACTIVE + ); + + return { + [EventPropertyType.PROFILE]: { + SEND_MANDATE_GIVEN: hasDelegates?.length > 0 ? 'yes' : 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateLoginStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateLoginStrategy.ts new file mode 100644 index 0000000000..cc0f6aace0 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateLoginStrategy.ts @@ -0,0 +1,21 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { Delegator } from '../../../redux/delegation/types'; + +type SendHasMandateLogin = { + SEND_HAS_MANDATE: 'yes' | 'no'; +}; + +type SendHasMandateLoginData = { + payload: Array; +}; + +export class SendHasMandateLoginStrategy implements EventStrategy { + performComputations({ payload }: SendHasMandateLoginData): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_MANDATE: payload.length > 0 ? 'yes' : 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateStrategy.ts new file mode 100644 index 0000000000..ce3bae862c --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendHasMandateStrategy.ts @@ -0,0 +1,17 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { Delegation } from '../../../redux/delegation/types'; +import { DelegationStatus } from '../../status.utility'; + +type SendHasMandate = { + delegates: Array; +}; + +export class SendHasMandateStrategy implements EventStrategy { + performComputations({ delegates }: SendHasMandate): TrackedEvent { + return { + [EventPropertyType.PROFILE]: + delegates.filter((d) => d.status === DelegationStatus.ACTIVE).length > 0 ? 'yes' : 'no', + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationCount.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationCount.ts new file mode 100644 index 0000000000..538a79c613 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationCount.ts @@ -0,0 +1,22 @@ +import { + EventPropertyType, + EventStrategy, + INotificationDetailTimeline, + TimelineCategory, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendGenericError = { + timeline: Array; +}; + +export class SendNotificationCountStrategy implements EventStrategy { + performComputations({ timeline }: SendGenericError): TrackedEvent { + if (timeline.findIndex((el) => el.category === TimelineCategory.NOTIFICATION_VIEWED) === -1) { + return { + [EventPropertyType.INCREMENTAL]: {}, + }; + } + return {}; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts new file mode 100644 index 0000000000..8b8f381ac5 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts @@ -0,0 +1,68 @@ +import { + Downtime, + EventDowntimeType, + EventNotificationDetailType, + EventPropertyType, + F24PaymentDetails, + INotificationDetailTimeline, + NotificationStatus, + PaymentDetails, + TimelineCategory, +} from '@pagopa-pn/pn-commons'; +import { EventAction, EventCategory, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type NotificationData = { + downtimeEvents: Array; + mandateId: string | undefined; + notificationStatus: NotificationStatus; + checkIfUserHasPayments: boolean; + userPayments: { pagoPaF24: Array; f24Only: Array }; + fromQrCode: boolean; + timeline: Array; +}; + +export class SendNotificationDetailStrategy implements EventStrategy { + performComputations({ + downtimeEvents, + mandateId, + notificationStatus, + checkIfUserHasPayments, + userPayments, + fromQrCode, + timeline, + }: NotificationData): TrackedEvent { + // eslint-disable-next-line functional/no-let + let typeDowntime: EventDowntimeType; + + if (downtimeEvents.length === 0) { + typeDowntime = EventDowntimeType.NOT_DISSERVICE; + } else { + typeDowntime = + downtimeEvents.filter((downtime) => !!downtime.endDate).length === downtimeEvents.length + ? EventDowntimeType.COMPLETED + : EventDowntimeType.IN_PROGRESS; + } + + const hasF24 = + userPayments.f24Only.length > 0 || + userPayments.pagoPaF24.filter((payment) => payment.f24).length > 0; + + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + notification_owner: !mandateId, + notification_status: notificationStatus, + contains_payment: checkIfUserHasPayments, + disservice_status: typeDowntime, + contains_multipayment: + userPayments.f24Only.length + userPayments.pagoPaF24.length > 1 ? 'yes' : 'no', + count_payment: userPayments.pagoPaF24.filter((payment) => payment.pagoPa).length, + contains_f24: hasF24 ? 'yes' : 'no', + first_time_opening: + timeline.findIndex((el) => el.category === TimelineCategory.NOTIFICATION_VIEWED) === -1, + source: fromQrCode ? 'QRcode' : 'LISTA_NOTIFICHE', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationStatusDetail.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationStatusDetail.ts new file mode 100644 index 0000000000..2ac6244328 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationStatusDetail.ts @@ -0,0 +1,25 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendNotificationStatusDetail = { + accordion: 'collapse' | 'expand'; +}; + +export class SendNotificationStatusDetailStrategy implements EventStrategy { + performComputations({ + accordion, + }: SendNotificationStatusDetail): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + accordion, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentDetailErrorStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentDetailErrorStrategy.ts new file mode 100644 index 0000000000..ea469f69c2 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentDetailErrorStrategy.ts @@ -0,0 +1,24 @@ +import { + EventCategory, + EventPropertyType, + EventStrategy, + PaymentInfoDetail, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendPaymentError = { + detail?: PaymentInfoDetail; + errorCode?: string; +}; + +export class SendPaymentDetailErrorStrategy implements EventStrategy { + performComputations({ detail, errorCode }: SendPaymentError): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + detail, + errorCode, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentOutcomeStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentOutcomeStrategy.ts new file mode 100644 index 0000000000..a44aa47565 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentOutcomeStrategy.ts @@ -0,0 +1,22 @@ +import { + EventCategory, + EventPropertyType, + EventStrategy, + PaymentStatus, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendPaymentOutcome = { + outcome: PaymentStatus; +}; + +export class SendPaymentOutcomeStrategy implements EventStrategy { + performComputations({ outcome }: SendPaymentOutcome): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + outcome, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentStatusStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentStatusStrategy.ts new file mode 100644 index 0000000000..d2ccb3b119 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentStatusStrategy.ts @@ -0,0 +1,60 @@ +import { + EventCategory, + EventPropertyType, + EventStrategy, + PaginationData, + PaymentDetails, + PaymentInfoDetail, + PaymentStatus, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendPaymentStatus = { + paginationData: PaginationData; + paginatedPayments: Array; +}; + +type SendPaymentStatusReturn = { + page_number: number; + count_payment: number; + count_canceled: number; + count_error: number; + count_expired: number; + count_paid: number; + count_unpaid: number; +}; + +export class SendPaymentStatusStrategy implements EventStrategy { + performComputations({ + paginationData, + paginatedPayments, + }: SendPaymentStatus): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + page_number: paginationData.page, + count_payment: paginatedPayments.length, + count_canceled: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail === PaymentInfoDetail.PAYMENT_CANCELED + ).length, + count_error: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_CANCELED && + f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_EXPIRED + ).length, + count_expired: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail === PaymentInfoDetail.PAYMENT_EXPIRED + ).length, + count_paid: paginatedPayments.filter((f) => f.pagoPa?.status === PaymentStatus.SUCCEEDED) + .length, + count_unpaid: paginatedPayments.filter((f) => f.pagoPa?.status === PaymentStatus.REQUIRED) + .length, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentsCountStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentsCountStrategy.ts new file mode 100644 index 0000000000..7a966834ae --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendPaymentsCountStrategy.ts @@ -0,0 +1,9 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +export class SendPaymentsCountStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.INCREMENTAL]: {}, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRefreshPageStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRefreshPageStrategy.ts new file mode 100644 index 0000000000..2206258eda --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRefreshPageStrategy.ts @@ -0,0 +1,20 @@ +import { + EventAction, + EventCategory, + EventPageType, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendRefreshPageStrategy implements EventStrategy { + performComputations(page: EventPageType): TrackedEvent<{ page: EventPageType }> { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + page, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveContactSuccess.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveContactSuccess.ts new file mode 100644 index 0000000000..4e2bd13a5a --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveContactSuccess.ts @@ -0,0 +1,19 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendRemoveContactSuccessStrategy implements EventStrategy { + performComputations(senderId: string): TrackedEvent<{ other_contact: string }> { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: senderId !== 'default' ? 'yes' : 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveCourtesyAddress.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveCourtesyAddress.ts new file mode 100644 index 0000000000..3e60ad1f5b --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveCourtesyAddress.ts @@ -0,0 +1,37 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +import { CourtesyChannelType } from '../../../models/contacts'; +import { DeleteDigitalAddressParams } from '../../../redux/contact/types'; + +type SendRemoveCourtesyAddressReturn = + | { + SEND_HAS_EMAIL: 'no'; + } + | { + SEND_HAS_SMS: 'no'; + }; + +type SendRemoveCourtesyAddressData = { + payload: string; + params: DeleteDigitalAddressParams; +}; + +export class SendRemoveCourtesyAddressStrategy implements EventStrategy { + performComputations({ + params, + }: SendRemoveCourtesyAddressData): TrackedEvent { + if (params.channelType === CourtesyChannelType.EMAIL) { + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_EMAIL: 'no', + }, + }; + } + + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_SMS: 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveLegalAddress.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveLegalAddress.ts new file mode 100644 index 0000000000..7f42c1dcd7 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendRemoveLegalAddress.ts @@ -0,0 +1,15 @@ +import { EventPropertyType, EventStrategy, TrackedEvent } from '@pagopa-pn/pn-commons'; + +type SendRemoveLegalAddress = { + SEND_HAS_PEC: 'no'; +}; + +export class SendRemoveLegalAddressStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.PROFILE]: { + SEND_HAS_PEC: 'no', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendServiceStatusStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendServiceStatusStrategy.ts new file mode 100644 index 0000000000..2f4c52f7c2 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendServiceStatusStrategy.ts @@ -0,0 +1,19 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class SendServiceStatusStrategy implements EventStrategy { + performComputations(service_status_OK?: boolean): TrackedEvent<{ service_status_OK?: boolean }> { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + service_status_OK, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendToastErrorStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendToastErrorStrategy.ts new file mode 100644 index 0000000000..0b7eb5e878 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendToastErrorStrategy.ts @@ -0,0 +1,35 @@ +import { + EventCategory, + EventPageType, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendToastError = { + reason: string; + traceId?: string; + page_name?: EventPageType; + message: { + title: string; + content: string; + }; + httpStatusCode?: number; + action: string; +}; + +export class SendToastErrorStrategy implements EventStrategy { + performComputations(data: SendToastError): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + reason: data.reason, + traceId: data.traceId, + page_name: data.page_name, + message: data.message, + httpStatusCode: data.httpStatusCode, + action: data.action, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewContactDetailsStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewContactDetailsStrategy.ts new file mode 100644 index 0000000000..78fde42665 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewContactDetailsStrategy.ts @@ -0,0 +1,23 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendViewContactDetails = { + source: 'home_notifiche' | 'profilo'; +}; + +export class SendViewContactDetailsStrategy implements EventStrategy { + performComputations({ source }: SendViewContactDetails): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewProfileStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewProfileStrategy.ts new file mode 100644 index 0000000000..bc144acf64 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendViewProfileStrategy.ts @@ -0,0 +1,23 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +type SendViewProfile = { + source: 'user_menu' | 'tuoi_recapiti'; +}; + +export class SendViewProfileStrategy implements EventStrategy { + performComputations({ source }: SendViewProfile): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourContactDetailsStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourContactDetailsStrategy.ts new file mode 100644 index 0000000000..e41e30d45e --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourContactDetailsStrategy.ts @@ -0,0 +1,52 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +import { + CourtesyChannelType, + DigitalAddress, + DigitalAddresses, + IOAllowedValues, +} from '../../../models/contacts'; + +type SendYourContactDetails = { + digitalAddresses: DigitalAddresses; + contactIO: DigitalAddress | null; +}; + +type SendYourContactDetailsReturn = { + PEC_exists: boolean; + email_exists: boolean; + telephone_exists: boolean; + appIO_status: 'activated' | 'deactivated' | 'nd'; +}; + +export class SendYourContactDetailsStrategy implements EventStrategy { + performComputations({ + digitalAddresses, + contactIO, + }: SendYourContactDetails): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + PEC_exists: digitalAddresses.legal.length > 0, + email_exists: + digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.EMAIL) + .length > 0, + telephone_exists: + digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.SMS) + .length > 0, + appIO_status: contactIO + ? contactIO.value === IOAllowedValues.ENABLED + ? 'activated' + : 'deactivated' + : 'nd', + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourMandatesStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourMandatesStrategy.ts new file mode 100644 index 0000000000..df1ca2eadc --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourMandatesStrategy.ts @@ -0,0 +1,42 @@ +import { + EventAction, + EventCategory, + EventMandateNotificationsListType, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +import { Delegation } from '../../../redux/delegation/types'; +import { DelegationStatus } from '../../status.utility'; + +type SendYourMandate = { + delegates: Array; + delegators: Array; +}; + +export class SendYourMandatesStrategy implements EventStrategy { + performComputations({ + delegates, + delegators, + }: SendYourMandate): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + total_mandates_given_count: delegates.length, + pending_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.PENDING) + .length, + active_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.ACTIVE) + .length, + total_mandates_received_count: delegators.length, + pending_mandates_received_count: delegators.filter( + (d) => d.status === DelegationStatus.PENDING + ).length, + active_mandates_received_count: delegators.filter( + (d) => d.status === DelegationStatus.ACTIVE + ).length, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourNotificationsStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourNotificationsStrategy.ts new file mode 100644 index 0000000000..cafae5cde0 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendYourNotificationsStrategy.ts @@ -0,0 +1,61 @@ +import { + EventAction, + EventCategory, + EventNotificationsListType, + EventPropertyType, + EventStrategy, + Notification, + NotificationStatus, + TrackedEvent, + isNewNotification, +} from '@pagopa-pn/pn-commons'; + +import { Delegator } from '../../../redux/delegation/types'; + +type SendYourNotifications = { + notifications: Array; + delegators: Array; + pagination: { + nextPagesKey: Array; + size: number; + page: number; + moreResult: boolean; + }; + domicileBannerType: string; +}; + +export class SendYourNotificationsStrategy implements EventStrategy { + performComputations({ + notifications, + delegators, + pagination, + domicileBannerType, + }: SendYourNotifications): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + ...(domicileBannerType && { banner: domicileBannerType }), + delegate: delegators.length > 0, + page_number: pagination.page, + total_count: notifications.length, + unread_count: notifications.filter((n) => isNewNotification(n.notificationStatus)).length, + delivered_count: notifications.filter( + (n) => n.notificationStatus === NotificationStatus.DELIVERED + ).length, + opened_count: notifications.filter( + (n) => n.notificationStatus === NotificationStatus.VIEWED + ).length, + expired_count: notifications.filter( + (n) => n.notificationStatus === NotificationStatus.EFFECTIVE_DATE + ).length, + not_found_count: notifications.filter( + (n) => n.notificationStatus === NotificationStatus.UNREACHABLE + ).length, + cancelled_count: notifications.filter( + (n) => n.notificationStatus === NotificationStatus.CANCELLED + ).length, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechScreenViewStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechScreenViewStrategy.ts new file mode 100644 index 0000000000..23e1818602 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechScreenViewStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class TechScreenViewStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + event_type: EventAction.SCREEN_VIEW, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts new file mode 100644 index 0000000000..e03aed1484 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts @@ -0,0 +1,16 @@ +import { + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class TechStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXActionStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXActionStrategy.ts new file mode 100644 index 0000000000..6190377346 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXActionStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class UXActionStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXErrorStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXErrorStrategy.ts new file mode 100644 index 0000000000..fed2b200f6 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXErrorStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class UXErrorStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ERROR, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts new file mode 100644 index 0000000000..aa3a01ab40 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/UXScreenViewStrategy.ts @@ -0,0 +1,18 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + EventStrategy, + TrackedEvent, +} from '@pagopa-pn/pn-commons'; + +export class UXScreenViewStrategy implements EventStrategy { + performComputations(): TrackedEvent { + return { + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + }, + }; + } +} diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAcceptDelegationStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAcceptDelegationStrategy.test.ts new file mode 100644 index 0000000000..102f14afe7 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAcceptDelegationStrategy.test.ts @@ -0,0 +1,16 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendAcceptDelegationStrategy } from '../SendAcceptDelegationStrategy'; + +describe('Mixpanel - Accept Delegation Strategy', () => { + it('should return accept delegation event', () => { + const strategy = new SendAcceptDelegationStrategy(); + const event = strategy.performComputations(); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_MANDATE: 'yes', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactActionStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactActionStrategy.test.ts new file mode 100644 index 0000000000..3e463ba3b0 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactActionStrategy.test.ts @@ -0,0 +1,27 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendAddContactActionStrategy } from '../SendAddContactActionStrategy'; + +describe('Mixpanel - Add contact action Strategy', () => { + it('should return add contact action event', () => { + const strategy = new SendAddContactActionStrategy(); + + const isOtherContactEvent = strategy.performComputations(true); + expect(isOtherContactEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: 'yes', + }, + }); + + const isNotOtherContactEvent = strategy.performComputations(false); + expect(isNotOtherContactEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactScreenViewStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactScreenViewStrategy.test.ts new file mode 100644 index 0000000000..e84a9720fa --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddContactScreenViewStrategy.test.ts @@ -0,0 +1,27 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendAddContactScreenViewStrategy } from '../SendAddContactScreenViewStrategy'; + +describe('Mixpanel - Add contact screen view Strategy', () => { + it('should return add contact screen view event', () => { + const strategy = new SendAddContactScreenViewStrategy(); + + const isOtherContactEvent = strategy.performComputations(true); + expect(isOtherContactEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + other_contact: 'yes', + }, + }); + + const isNotOtherContactEvent = strategy.performComputations(false); + expect(isNotOtherContactEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + other_contact: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddCourtesyAddressStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddCourtesyAddressStrategy.test.ts new file mode 100644 index 0000000000..f2bbc6f3bd --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddCourtesyAddressStrategy.test.ts @@ -0,0 +1,68 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { digitalAddresses } from '../../../../__mocks__/Contacts.mock'; +import { CourtesyChannelType } from '../../../../models/contacts'; +import { SendAddCourtesyAddressStrategy } from '../SendAddCourtesyAddressStrategy'; + +describe('Mixpanel - Add Courtesy Address Strategy', () => { + it('should return empty object if payload is empty', () => { + const strategy = new SendAddCourtesyAddressStrategy(); + const event = strategy.performComputations({ + payload: undefined, + params: { + channelType: CourtesyChannelType.EMAIL, + recipientId: '123', + senderId: 'default', + value: '', + }, + }); + + expect(event).toEqual({}); + }); + + it('should return has email when adding an email', () => { + const strategy = new SendAddCourtesyAddressStrategy(); + const address = digitalAddresses.courtesy.find( + (a) => a.channelType === CourtesyChannelType.EMAIL + ); + + const event = strategy.performComputations({ + payload: address, + params: { + channelType: address!.channelType, + recipientId: address!.recipientId, + senderId: address!.senderId, + value: address!.value, + }, + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_EMAIL: 'yes', + }, + }); + }); + + it('should return has sms when adding an sms', () => { + const strategy = new SendAddCourtesyAddressStrategy(); + const address = digitalAddresses.courtesy.find( + (a) => a.channelType === CourtesyChannelType.SMS + ); + + const event = strategy.performComputations({ + payload: address, + params: { + channelType: address!.channelType, + recipientId: address!.recipientId, + senderId: address!.senderId, + value: address!.value, + }, + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_SMS: 'yes', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddLegalAddressStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddLegalAddressStrategy.test.ts new file mode 100644 index 0000000000..4c5c18f139 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddLegalAddressStrategy.test.ts @@ -0,0 +1,30 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { digitalAddresses } from '../../../../__mocks__/Contacts.mock'; +import { SendAddLegalAddressStrategy } from '../SendAddLegalAddressStrategy'; + +describe('Mixpanel - Add Legal Address Strategy', () => { + it('should return empty object if payload is empty', () => { + const strategy = new SendAddLegalAddressStrategy(); + const event = strategy.performComputations({ + payload: undefined, + }); + + expect(event).toEqual({}); + }); + + it('should return has pec when adding a PEC address', () => { + const strategy = new SendAddLegalAddressStrategy(); + const address = digitalAddresses.legal[0]; + + const event = strategy.performComputations({ + payload: address, + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_PEC: 'yes', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXConversionStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXConversionStrategy.test.ts new file mode 100644 index 0000000000..6d618b4363 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXConversionStrategy.test.ts @@ -0,0 +1,41 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + RecipientType, +} from '@pagopa-pn/pn-commons'; + +import { SendAddMandateUXConversionStrategy } from '../SendAddMandateUXConversionStrategy'; + +describe('Mixpanel - Add mandate UX conversion Strategy', () => { + it('should return add mandate UX conversion event', () => { + const recipientType = RecipientType.PF; + const strategy = new SendAddMandateUXConversionStrategy(); + + const allMandateTypeEvent = strategy.performComputations({ + selectPersonaFisicaOrPersonaGiuridica: recipientType, + selectTuttiEntiOrSelezionati: 'tuttiGliEnti', + }); + expect(allMandateTypeEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + person_type: recipientType, + mandate_type: 'all', + }, + }); + + const selectedPartyEvent = strategy.performComputations({ + selectPersonaFisicaOrPersonaGiuridica: recipientType, + selectTuttiEntiOrSelezionati: 'comune di milano', + }); + expect(selectedPartyEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + person_type: recipientType, + mandate_type: 'selected_party', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXSuccessStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXSuccessStrategy.test.ts new file mode 100644 index 0000000000..49c5a3e6d3 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendAddMandateUXSuccessStrategy.test.ts @@ -0,0 +1,29 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + RecipientType, +} from '@pagopa-pn/pn-commons'; + +import { SendAddMandateUXSuccessStrategy } from '../SendAddMandateUXSuccessStrategy'; + +describe('Mixpanel - Add mandate UX success Strategy', () => { + it('should return add mandate UX success event', () => { + const person_type = RecipientType.PF; + const mandate_type = 'all'; + const strategy = new SendAddMandateUXSuccessStrategy(); + + const addMandateSuccessEvent = strategy.performComputations({ + person_type, + mandate_type, + }); + expect(addMandateSuccessEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + person_type, + mandate_type, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDisableIOStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDisableIOStrategy.test.ts new file mode 100644 index 0000000000..68bc01416e --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDisableIOStrategy.test.ts @@ -0,0 +1,16 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendDisableIOStrategy } from '../SendDisableIOStrategy'; + +describe('Mixpanel - Disable IO Strategy', () => { + it('should return disable io event', () => { + const strategy = new SendDisableIOStrategy(); + const event = strategy.performComputations(); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadCertificateOpposable.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadCertificateOpposable.test.ts new file mode 100644 index 0000000000..a60cc02e16 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadCertificateOpposable.test.ts @@ -0,0 +1,21 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendDownloadCertificateOpposable } from '../SendDownloadCertificateOpposable'; + +describe('Mixpanel - Download Certificate Opposable Strategy', () => { + it('should return download certificate opposable event', () => { + const source = 'dettaglio_notifica'; + const strategy = new SendDownloadCertificateOpposable(); + + const downloadCertificateEvent = strategy.performComputations({ + source, + }); + expect(downloadCertificateEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadResponse.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadResponse.test.ts new file mode 100644 index 0000000000..a6d35b466f --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendDownloadResponse.test.ts @@ -0,0 +1,43 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendDownloadResponseStrategy } from '../SendDownloadResponse'; + +describe('Mixpanel - Download Response Strategy', () => { + it('should return ready if url is available', () => { + const strategy = new SendDownloadResponseStrategy(); + + const downloadCertificate = strategy.performComputations({ + payload: { + url: 'url', + docType: 'docType', + }, + }); + expect(downloadCertificate).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + doc_type: 'docType', + url_available: 'ready', + }, + }); + }); + + it('should return retry_after if url is not available and empty docType if is not present', () => { + const strategy = new SendDownloadResponseStrategy(); + + const downloadResponseEvent = strategy.performComputations({ + payload: { + url: undefined, + docType: undefined, + }, + }); + expect(downloadResponseEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + doc_type: '', + url_available: 'retry_after', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendEnableIOStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendEnableIOStrategy.test.ts new file mode 100644 index 0000000000..4c29919fd8 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendEnableIOStrategy.test.ts @@ -0,0 +1,16 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendEnableIOStrategy } from '../SendEnableIOStrategy'; + +describe('Mixpanel - Enable IO Strategy', () => { + it('should return enable io event', () => { + const strategy = new SendEnableIOStrategy(); + const event = strategy.performComputations(); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'activated', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendGenericErrorStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendGenericErrorStrategy.test.ts new file mode 100644 index 0000000000..d36240580b --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendGenericErrorStrategy.test.ts @@ -0,0 +1,29 @@ +import { EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendGenericErrorStrategy } from '../SendGenericErrorStrategy'; + +describe('Mixpanel - Generic Error Strategy', () => { + it('should return error', () => { + const strategy = new SendGenericErrorStrategy(); + + const reason = { + error: { + name: 'error', + message: 'error', + }, + errorInfo: { + componentStack: 'componentStack', + }, + }; + + const genericErrorEvent = strategy.performComputations({ + reason, + }); + expect(genericErrorEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + reason, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasAddressesStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasAddressesStrategy.test.ts new file mode 100644 index 0000000000..b52dc9f6e2 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasAddressesStrategy.test.ts @@ -0,0 +1,131 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { digitalAddresses } from '../../../../__mocks__/Contacts.mock'; +import { CourtesyChannelType, IOAllowedValues } from '../../../../models/contacts'; +import { SendHasAddressesStrategy } from '../SendHasAddressesStrategy'; + +describe('Mixpanel - Has Addresses Strategy', () => { + it('should return has email, pec and sms filled addresses event', () => { + const strategy = new SendHasAddressesStrategy(); + const hasPecEvent = strategy.performComputations({ + payload: [...digitalAddresses.legal, ...digitalAddresses.courtesy], + }); + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + SEND_HAS_EMAIL: 'yes', + SEND_HAS_PEC: 'yes', + SEND_HAS_SMS: 'yes', + }, + }); + }); + + it('should return has no pec address event', () => { + const strategy = new SendHasAddressesStrategy(); + + const addresses = [...digitalAddresses.courtesy]; + + const hasPecEvent = strategy.performComputations({ + payload: addresses, + }); + + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + SEND_HAS_EMAIL: 'yes', + SEND_HAS_PEC: 'no', + SEND_HAS_SMS: 'yes', + }, + }); + }); + + it('should return has no email address event', () => { + const strategy = new SendHasAddressesStrategy(); + + const addressesWithoutEmail = digitalAddresses.courtesy.filter( + (address) => address.channelType !== CourtesyChannelType.EMAIL + ); + + const hasPecEvent = strategy.performComputations({ + payload: [...addressesWithoutEmail, ...digitalAddresses.legal], + }); + + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + SEND_HAS_EMAIL: 'no', + SEND_HAS_PEC: 'yes', + SEND_HAS_SMS: 'yes', + }, + }); + }); + + it('should return has no sms address event', () => { + const strategy = new SendHasAddressesStrategy(); + + const addressesWithoutSMS = digitalAddresses.courtesy.filter( + (address) => address.channelType !== CourtesyChannelType.SMS + ); + + const hasPecEvent = strategy.performComputations({ + payload: [...addressesWithoutSMS, ...digitalAddresses.legal], + }); + + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'deactivated', + SEND_HAS_EMAIL: 'yes', + SEND_HAS_PEC: 'yes', + SEND_HAS_SMS: 'no', + }, + }); + }); + + it('should return has nd IO address event', () => { + const strategy = new SendHasAddressesStrategy(); + + const addressesWithoutSMS = digitalAddresses.courtesy.filter( + (address) => address.channelType !== CourtesyChannelType.IOMSG + ); + + const hasPecEvent = strategy.performComputations({ + payload: [...addressesWithoutSMS, ...digitalAddresses.legal], + }); + + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'nd', + SEND_HAS_EMAIL: 'yes', + SEND_HAS_PEC: 'yes', + SEND_HAS_SMS: 'yes', + }, + }); + }); + + it('should return has activated IO address event', () => { + const strategy = new SendHasAddressesStrategy(); + + const addressesWithActivatedIO = digitalAddresses.courtesy.map((address) => { + if (address.channelType === CourtesyChannelType.IOMSG) { + return { + ...address, + value: IOAllowedValues.ENABLED, + }; + } + return address; + }); + + const hasPecEvent = strategy.performComputations({ + payload: [...addressesWithActivatedIO, ...digitalAddresses.legal], + }); + + expect(hasPecEvent).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_APPIO_STATUS: 'activated', + SEND_HAS_EMAIL: 'yes', + SEND_HAS_PEC: 'yes', + SEND_HAS_SMS: 'yes', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateGivenStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateGivenStrategy.test.ts new file mode 100644 index 0000000000..4fed0c5b38 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateGivenStrategy.test.ts @@ -0,0 +1,28 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { arrayOfDelegators } from '../../../../__mocks__/Delegations.mock'; +import { SendHasMandateGivenStrategy } from '../SendHasMandateGivensStrategy'; + +describe('Mixpanel - Has Mandate Given Strategy', () => { + it('should return has mandate given event', () => { + const strategy = new SendHasMandateGivenStrategy(); + + const mandateGiven = strategy.performComputations({ payload: arrayOfDelegators }); + expect(mandateGiven).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_MANDATE_GIVEN: 'yes', + }, + }); + }); + + it('should return no if there are no active mandates', () => { + const strategy = new SendHasMandateGivenStrategy(); + + const mandateGiven = strategy.performComputations({ payload: [] }); + expect(mandateGiven).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_MANDATE_GIVEN: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateLoginStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateLoginStrategy.test.ts new file mode 100644 index 0000000000..f5c7f4c5cb --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendHasMandateLoginStrategy.test.ts @@ -0,0 +1,28 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { arrayOfDelegators } from '../../../../__mocks__/Delegations.mock'; +import { SendHasMandateLoginStrategy } from '../SendHasMandateLoginStrategy'; + +describe('Mixpanel - Has Mandate Login Strategy', () => { + it('should return has mandate login event', () => { + const strategy = new SendHasMandateLoginStrategy(); + + const mandateGiven = strategy.performComputations({ payload: arrayOfDelegators }); + expect(mandateGiven).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_MANDATE: 'yes', + }, + }); + }); + + it('should return no if there are no active mandates', () => { + const strategy = new SendHasMandateLoginStrategy(); + + const mandateGiven = strategy.performComputations({ payload: [] }); + expect(mandateGiven).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_MANDATE: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationCount.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationCount.test.ts new file mode 100644 index 0000000000..5be831ad3b --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationCount.test.ts @@ -0,0 +1,24 @@ +import { EventPropertyType, TimelineCategory } from '@pagopa-pn/pn-commons'; + +import { timeline } from '../../../../__mocks__/NotificationDetail.mock'; +import { SendNotificationCountStrategy } from '../SendNotificationCount'; + +describe('Mixpanel - Notification Count Strategy', () => { + it('should return notification count event - viewed event', () => { + const strategy = new SendNotificationCountStrategy(); + + const notificationCountEvent = strategy.performComputations({ timeline: timeline }); + expect(notificationCountEvent).toEqual({}); + }); + + it('should return notification count event - no viewed event', () => { + const strategy = new SendNotificationCountStrategy(); + const noViewedTimeline = timeline.filter( + (el) => el.category !== TimelineCategory.NOTIFICATION_VIEWED + ); + const notificationCountEvent = strategy.performComputations({ timeline: noViewedTimeline }); + expect(notificationCountEvent).toEqual({ + [EventPropertyType.INCREMENTAL]: {}, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts new file mode 100644 index 0000000000..ee5f75a92f --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts @@ -0,0 +1,78 @@ +import { + DowntimeStatus, + EventAction, + EventCategory, + EventDowntimeType, + EventPropertyType, + NotificationStatus, + TimelineCategory, +} from '@pagopa-pn/pn-commons'; + +import { paymentsData, timeline } from '../../../../__mocks__/NotificationDetail.mock'; +import { SendNotificationDetailStrategy } from '../SendNotificationDetailStrategy'; + +describe('Mixpanel - Notification detail Strategy', () => { + it('should return notification detail event', () => { + const strategy = new SendNotificationDetailStrategy(); + + const notificationData = { + downtimeEvents: [ + { + rawFunctionality: 'rawFunctionality', + status: DowntimeStatus.KO, + startDate: 'startDate', + endDate: 'endDate', + }, + ], + mandateId: 'mandateId', + notificationStatus: NotificationStatus.VIEWED, + checkIfUserHasPayments: true, + userPayments: { + pagoPaF24: paymentsData.pagoPaF24, + f24Only: paymentsData.f24Only, + }, + fromQrCode: false, + timeline: timeline, + }; + + let typeDowntime: EventDowntimeType; + + if (notificationData.downtimeEvents.length === 0) { + typeDowntime = EventDowntimeType.NOT_DISSERVICE; + } else { + typeDowntime = + notificationData.downtimeEvents.filter((downtime) => !!downtime.endDate).length === + notificationData.downtimeEvents.length + ? EventDowntimeType.COMPLETED + : EventDowntimeType.IN_PROGRESS; + } + + const hasF24 = + paymentsData.f24Only.length > 0 || + paymentsData.pagoPaF24.filter((payment) => payment.f24).length > 0; + + const notificationDetailEvent = strategy.performComputations(notificationData); + expect(notificationDetailEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + notification_owner: !notificationData.mandateId, + notification_status: notificationData.notificationStatus, + contains_payment: notificationData.checkIfUserHasPayments, + disservice_status: typeDowntime, + contains_multipayment: + notificationData.userPayments.f24Only.length + + notificationData.userPayments.pagoPaF24.length > + 1 + ? 'yes' + : 'no', + count_payment: notificationData.userPayments.pagoPaF24.filter((payment) => payment.pagoPa) + .length, + contains_f24: hasF24 ? 'yes' : 'no', + first_time_opening: + timeline.findIndex((el) => el.category === TimelineCategory.NOTIFICATION_VIEWED) === -1, + source: notificationData.fromQrCode ? 'QRcode' : 'LISTA_NOTIFICHE', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationStatusDetail.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationStatusDetail.test.ts new file mode 100644 index 0000000000..aa0306fd4e --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationStatusDetail.test.ts @@ -0,0 +1,22 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendNotificationStatusDetailStrategy } from '../SendNotificationStatusDetail'; + +describe('Mixpanel - Notification Status Detail Strategy', () => { + it('should return notification status detail event', () => { + const strategy = new SendNotificationStatusDetailStrategy(); + + const accordion = 'collapse'; + + const notificationStatusDetailEvent = strategy.performComputations({ + accordion, + }); + expect(notificationStatusDetailEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + accordion, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentDetailErrorStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentDetailErrorStrategy.test.ts new file mode 100644 index 0000000000..38dce46d57 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentDetailErrorStrategy.test.ts @@ -0,0 +1,21 @@ +import { EventCategory, EventPropertyType, PaymentInfoDetail } from '@pagopa-pn/pn-commons'; + +import { SendPaymentDetailErrorStrategy } from '../SendPaymentDetailErrorStrategy'; + +describe('Mixpanel - Payment Detail Error Strategy', () => { + it('should return payment detail error event', () => { + const strategy = new SendPaymentDetailErrorStrategy(); + + const paymentDetailErrorEvent = strategy.performComputations({ + detail: PaymentInfoDetail.PAYMENT_EXPIRED, + errorCode: '500', + }); + expect(paymentDetailErrorEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + detail: PaymentInfoDetail.PAYMENT_EXPIRED, + errorCode: '500', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentOutcomeStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentOutcomeStrategy.test.ts new file mode 100644 index 0000000000..b62e1cf944 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentOutcomeStrategy.test.ts @@ -0,0 +1,21 @@ +import { EventCategory, EventPropertyType, PaymentStatus } from '@pagopa-pn/pn-commons'; + +import { SendPaymentOutcomeStrategy } from '../SendPaymentOutcomeStrategy'; + +describe('Mixpanel - Payment Outcome Strategy', () => { + it('should return payment outcome event', () => { + const strategy = new SendPaymentOutcomeStrategy(); + + const outcome = PaymentStatus.REQUIRED; + + const paymentOutcomeEvent = strategy.performComputations({ + outcome, + }); + expect(paymentOutcomeEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + outcome, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentStatusStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentStatusStrategy.test.ts new file mode 100644 index 0000000000..adb64c03cf --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentStatusStrategy.test.ts @@ -0,0 +1,55 @@ +import { + EventCategory, + EventPropertyType, + PaginationData, + PaymentInfoDetail, + PaymentStatus, +} from '@pagopa-pn/pn-commons'; + +import { paymentsData } from '../../../../__mocks__/NotificationDetail.mock'; +import { SendPaymentStatusStrategy } from '../SendPaymentStatusStrategy'; + +describe('Mixpanel - Payment Status Strategy', () => { + it('should return payment status event', () => { + const paginationData: PaginationData = { + page: 0, + size: 5, + totalElements: 1, + }; + const paginatedPayments = paymentsData.pagoPaF24; + + const strategy = new SendPaymentStatusStrategy(); + + const paymentStatusEvent = strategy.performComputations({ + paginationData, + paginatedPayments, + }); + expect(paymentStatusEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + page_number: paginationData.page, + count_payment: paginatedPayments.length, + count_canceled: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail === PaymentInfoDetail.PAYMENT_CANCELED + ).length, + count_error: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_CANCELED && + f.pagoPa.detail !== PaymentInfoDetail.PAYMENT_EXPIRED + ).length, + count_expired: paginatedPayments.filter( + (f) => + f.pagoPa?.status === PaymentStatus.FAILED && + f.pagoPa.detail === PaymentInfoDetail.PAYMENT_EXPIRED + ).length, + count_paid: paginatedPayments.filter((f) => f.pagoPa?.status === PaymentStatus.SUCCEEDED) + .length, + count_unpaid: paginatedPayments.filter((f) => f.pagoPa?.status === PaymentStatus.REQUIRED) + .length, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentsCountStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentsCountStrategy.test.ts new file mode 100644 index 0000000000..b31038038b --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendPaymentsCountStrategy.test.ts @@ -0,0 +1,14 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendPaymentsCountStrategy } from '../SendPaymentsCountStrategy'; + +describe('Mixpanel - Payments Count Strategy', () => { + it('should return payment status event', () => { + const strategy = new SendPaymentsCountStrategy(); + + const paymentCountEvent = strategy.performComputations(); + expect(paymentCountEvent).toEqual({ + [EventPropertyType.INCREMENTAL]: {}, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRefreshPageStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRefreshPageStrategy.test.ts new file mode 100644 index 0000000000..af501d7573 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRefreshPageStrategy.test.ts @@ -0,0 +1,25 @@ +import { + EventAction, + EventCategory, + EventPageType, + EventPropertyType, +} from '@pagopa-pn/pn-commons'; + +import { SendRefreshPageStrategy } from '../SendRefreshPageStrategy'; + +describe('Mixpanel - Refresh Page Strategy', () => { + it('should return refresh page event', () => { + const strategy = new SendRefreshPageStrategy(); + + const page = EventPageType.STATUS_PAGE; + + const refreshPageEvent = strategy.performComputations(page); + expect(refreshPageEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + page, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveContactSuccess.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveContactSuccess.test.ts new file mode 100644 index 0000000000..f3f7bd64d1 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveContactSuccess.test.ts @@ -0,0 +1,31 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendRemoveContactSuccessStrategy } from '../SendRemoveContactSuccess'; + +describe('Mixpanel - Remove contact success Strategy', () => { + it('should return remove contact success event', () => { + const strategy = new SendRemoveContactSuccessStrategy(); + + const defaultSender = 'default'; + + const removeDefaultSenderEvent = strategy.performComputations(defaultSender); + expect(removeDefaultSenderEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: 'no', + }, + }); + + const senderId = 'senderId'; + + const removeSenderEvent = strategy.performComputations(senderId); + expect(removeSenderEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + other_contact: 'yes', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveCourtesyAddress.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveCourtesyAddress.test.ts new file mode 100644 index 0000000000..283d79b042 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveCourtesyAddress.test.ts @@ -0,0 +1,51 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { digitalAddresses } from '../../../../__mocks__/Contacts.mock'; +import { CourtesyChannelType } from '../../../../models/contacts'; +import { SendRemoveCourtesyAddressStrategy } from '../SendRemoveCourtesyAddress'; + +describe('Mixpanel - Remove Courtesy Address Strategy', () => { + it('should return has email when removing an email', () => { + const strategy = new SendRemoveCourtesyAddressStrategy(); + const address = digitalAddresses.courtesy.find( + (a) => a.channelType === CourtesyChannelType.EMAIL + ); + + const event = strategy.performComputations({ + payload: 'default', + params: { + channelType: address!.channelType, + recipientId: address!.recipientId, + senderId: address!.senderId, + }, + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_EMAIL: 'no', + }, + }); + }); + + it('should return has sms when removing an sms', () => { + const strategy = new SendRemoveCourtesyAddressStrategy(); + const address = digitalAddresses.courtesy.find( + (a) => a.channelType === CourtesyChannelType.SMS + ); + + const event = strategy.performComputations({ + payload: 'default', + params: { + channelType: address!.channelType, + recipientId: address!.recipientId, + senderId: address!.senderId, + }, + }); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_SMS: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveLegalAddress.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveLegalAddress.test.ts new file mode 100644 index 0000000000..041eae66f3 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendRemoveLegalAddress.test.ts @@ -0,0 +1,17 @@ +import { EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendRemoveLegalAddressStrategy } from '../SendRemoveLegalAddress'; + +describe('Mixpanel - Remove Legal Address Strategy', () => { + it('should return remove legal address event', () => { + const strategy = new SendRemoveLegalAddressStrategy(); + + const event = strategy.performComputations(); + + expect(event).toEqual({ + [EventPropertyType.PROFILE]: { + SEND_HAS_PEC: 'no', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendServiceStatusStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendServiceStatusStrategy.test.ts new file mode 100644 index 0000000000..65383ccba8 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendServiceStatusStrategy.test.ts @@ -0,0 +1,27 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendServiceStatusStrategy } from '../SendServiceStatusStrategy'; + +describe('Mixpanel - Service status Strategy', () => { + it('should return service status event', () => { + const strategy = new SendServiceStatusStrategy(); + + const serviceStatusOKEvent = strategy.performComputations(true); + expect(serviceStatusOKEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + service_status_OK: true, + }, + }); + + const serviceStatusKOEvent = strategy.performComputations(false); + expect(serviceStatusKOEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + service_status_OK: false, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendToastErrorStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendToastErrorStrategy.test.ts new file mode 100644 index 0000000000..d8668fff5d --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendToastErrorStrategy.test.ts @@ -0,0 +1,34 @@ +import { EventCategory, EventPageType, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendToastErrorStrategy } from '../SendToastErrorStrategy'; + +describe('Mixpanel - Toast error Strategy', () => { + it('should return toast error event', () => { + const strategy = new SendToastErrorStrategy(); + + const error = { + reason: 'exception', + traceId: 'traceId', + page_name: EventPageType.LISTA_DELEGHE, + message: { + title: 'title', + content: 'content', + }, + httpStatusCode: 500, + action: 'action', + }; + + const toastErrorEvent = strategy.performComputations(error); + expect(toastErrorEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.KO, + reason: error.reason, + traceId: error.traceId, + page_name: error.page_name, + message: error.message, + httpStatusCode: error.httpStatusCode, + action: error.action, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewContactDetailsStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewContactDetailsStrategy.test.ts new file mode 100644 index 0000000000..f36fbf7292 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewContactDetailsStrategy.test.ts @@ -0,0 +1,19 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendViewContactDetailsStrategy } from '../SendViewContactDetailsStrategy'; + +describe('Mixpanel - View Contact Details Strategy', () => { + it('should return view contact details event', () => { + const strategy = new SendViewContactDetailsStrategy(); + const source = 'home_notifiche'; + + const viewContactDetailsEvent = strategy.performComputations({ source }); + expect(viewContactDetailsEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewProfileStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewProfileStrategy.test.ts new file mode 100644 index 0000000000..ed763133e6 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendViewProfileStrategy.test.ts @@ -0,0 +1,19 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { SendViewProfileStrategy } from '../SendViewProfileStrategy'; + +describe('Mixpanel - View Profile Strategy', () => { + it('should return view profile event', () => { + const strategy = new SendViewProfileStrategy(); + const source = 'user_menu'; + + const viewProfileEvent = strategy.performComputations({ source }); + expect(viewProfileEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + source, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourContactDetailsStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourContactDetailsStrategy.test.ts new file mode 100644 index 0000000000..a51ea5ef0d --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourContactDetailsStrategy.test.ts @@ -0,0 +1,67 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { digitalAddresses } from '../../../../__mocks__/Contacts.mock'; +import { + CourtesyChannelType, + DigitalAddress, + DigitalAddresses, + IOAllowedValues, +} from '../../../../models/contacts'; +import { SendYourContactDetailsStrategy } from '../SendYourContactDetailsStrategy'; + +describe('Mixpanel - Your Contact Details Strategy', () => { + it('empty addresses', () => { + const strategy = new SendYourContactDetailsStrategy(); + + const digitalAddresses: DigitalAddresses = { + legal: [], + courtesy: [], + }; + + const contactIO: DigitalAddress | null = null; + + const yourContactDetailsEvent = strategy.performComputations({ digitalAddresses, contactIO }); + expect(yourContactDetailsEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + PEC_exists: false, + email_exists: false, + telephone_exists: false, + appIO_status: 'nd', + }, + }); + }); + + it('filled addresses', () => { + const strategy = new SendYourContactDetailsStrategy(); + + const contactIO: DigitalAddress | null = { + addressType: 'addressType', + recipientId: 'recipientId', + senderId: 'senderId', + channelType: CourtesyChannelType.IOMSG, + value: IOAllowedValues.ENABLED, + }; + + const yourContactDetailsEvent = strategy.performComputations({ digitalAddresses, contactIO }); + expect(yourContactDetailsEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + PEC_exists: digitalAddresses.legal.length > 0, + email_exists: + digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.EMAIL) + .length > 0, + telephone_exists: + digitalAddresses.courtesy.filter((c) => c.channelType === CourtesyChannelType.SMS) + .length > 0, + appIO_status: contactIO + ? contactIO.value === IOAllowedValues.ENABLED + ? 'activated' + : 'deactivated' + : 'nd', + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourMandatesStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourMandatesStrategy.test.ts new file mode 100644 index 0000000000..785676685b --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourMandatesStrategy.test.ts @@ -0,0 +1,34 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { arrayOfDelegates, arrayOfDelegators } from '../../../../__mocks__/Delegations.mock'; +import { DelegationStatus } from '../../../status.utility'; +import { SendYourMandatesStrategy } from '../SendYourMandatesStrategy'; + +describe('Mixpanel - Send Your Mandates Strategy', () => { + it('should return your mandates event', () => { + const strategy = new SendYourMandatesStrategy(); + + const delegates = arrayOfDelegates; + const delegators = arrayOfDelegators; + + const yourMandatesEvents = strategy.performComputations({ delegates, delegators }); + expect(yourMandatesEvents).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + total_mandates_given_count: delegates.length, + pending_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.PENDING) + .length, + active_mandates_given_count: delegates.filter((d) => d.status === DelegationStatus.ACTIVE) + .length, + total_mandates_received_count: delegators.length, + pending_mandates_received_count: delegators.filter( + (d) => d.status === DelegationStatus.PENDING + ).length, + active_mandates_received_count: delegators.filter( + (d) => d.status === DelegationStatus.ACTIVE + ).length, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourNotificationsStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourNotificationsStrategy.test.ts new file mode 100644 index 0000000000..a2d4a46b85 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendYourNotificationsStrategy.test.ts @@ -0,0 +1,70 @@ +import { + EventAction, + EventCategory, + EventPropertyType, + NotificationStatus, + isNewNotification, +} from '@pagopa-pn/pn-commons'; + +import { arrayOfDelegators } from '../../../../__mocks__/Delegations.mock'; +import { SendYourNotificationsStrategy } from '../SendYourNotificationsStrategy'; + +describe('Mixpanel - Send Your Notification Strategy', () => { + it('should return your notification strategy event', () => { + const strategy = new SendYourNotificationsStrategy(); + + const yourNotification = { + notifications: [ + { + iun: 'PDJL-WNLU-QEVX-202403-X-1', + paProtocolNumber: '202403221213', + sender: 'Comune di Sappada', + sentAt: '2024-03-22T11:14:31.634488517Z', + subject: 'test mixpanel', + notificationStatus: NotificationStatus.VIEWED, + recipients: ['LVLDAA85T50G702B'], + requestAcceptedAt: '2024-03-22T11:17:40.361638886Z', + group: '', + }, + ], + delegators: arrayOfDelegators, + pagination: { + nextPagesKey: [], + size: 1, + page: 1, + moreResult: false, + }, + domicileBannerType: 'EMAIL', + }; + + const yourNotificationEvent = strategy.performComputations(yourNotification); + expect(yourNotificationEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + ...(yourNotification.domicileBannerType && { banner: yourNotification.domicileBannerType }), + delegate: yourNotification.delegators.length > 0, + page_number: yourNotification.pagination.page, + total_count: yourNotification.notifications.length, + unread_count: yourNotification.notifications.filter((n) => + isNewNotification(n.notificationStatus) + ).length, + delivered_count: yourNotification.notifications.filter( + (n) => n.notificationStatus === NotificationStatus.DELIVERED + ).length, + opened_count: yourNotification.notifications.filter( + (n) => n.notificationStatus === NotificationStatus.VIEWED + ).length, + expired_count: yourNotification.notifications.filter( + (n) => n.notificationStatus === NotificationStatus.EFFECTIVE_DATE + ).length, + not_found_count: yourNotification.notifications.filter( + (n) => n.notificationStatus === NotificationStatus.UNREACHABLE + ).length, + cancelled_count: yourNotification.notifications.filter( + (n) => n.notificationStatus === NotificationStatus.CANCELLED + ).length, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechScreenViewStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechScreenViewStrategy.test.ts new file mode 100644 index 0000000000..2a1a2e3626 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechScreenViewStrategy.test.ts @@ -0,0 +1,17 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { TechScreenViewStrategy } from '../TechScreenViewStrategy'; + +describe('Mixpanel - Tech Screen View Strategy', () => { + it('should return tech screen view event', () => { + const strategy = new TechScreenViewStrategy(); + + const techScreenViewEvent = strategy.performComputations(); + expect(techScreenViewEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + event_type: EventAction.SCREEN_VIEW, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts new file mode 100644 index 0000000000..6d65d987eb --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts @@ -0,0 +1,16 @@ +import { EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { TechStrategy } from '../TechStrategy'; + +describe('Mixpanel - Tech Strategy', () => { + it('should return tech event', () => { + const strategy = new TechStrategy(); + + const techEvent = strategy.performComputations(); + expect(techEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXActionStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXActionStrategy.test.ts new file mode 100644 index 0000000000..6cd3704d59 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXActionStrategy.test.ts @@ -0,0 +1,17 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { UXActionStrategy } from '../UXActionStrategy'; + +describe('Mixpanel - UX Action Strategy', () => { + it('should return UX action event', () => { + const strategy = new UXActionStrategy(); + + const uxActionEvent = strategy.performComputations(); + expect(uxActionEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ACTION, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXErrorStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXErrorStrategy.test.ts new file mode 100644 index 0000000000..8e11cee713 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXErrorStrategy.test.ts @@ -0,0 +1,17 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { UXErrorStrategy } from '../UXErrorStrategy'; + +describe('Mixpanel - UX Error Strategy', () => { + it('should return UX error event', () => { + const strategy = new UXErrorStrategy(); + + const uxErrorEvent = strategy.performComputations(); + expect(uxErrorEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.ERROR, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts new file mode 100644 index 0000000000..751108cba5 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/UXScreenViewStrategy.test.ts @@ -0,0 +1,17 @@ +import { EventAction, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; + +import { UXScreenViewStrategy } from '../UXScreenViewStrategy'; + +describe('Mixpanel - UX Screen View Strategy', () => { + it('should return UX screen view event', () => { + const strategy = new UXScreenViewStrategy(); + + const uxScreenViewEvent = strategy.performComputations(); + expect(uxScreenViewEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.UX, + event_type: EventAction.SCREEN_VIEW, + }, + }); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/__test__/PFEventStrategyFactory.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/__test__/PFEventStrategyFactory.test.ts new file mode 100644 index 0000000000..d4b043e902 --- /dev/null +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/__test__/PFEventStrategyFactory.test.ts @@ -0,0 +1,334 @@ +import { PFEventsType } from '../../../models/PFEventsType'; +import PFEventStrategyFactory from '../PFEventStrategyFactory'; +import { SendAcceptDelegationStrategy } from '../Strategies/SendAcceptDelegationStrategy'; +import { SendAddContactActionStrategy } from '../Strategies/SendAddContactActionStrategy'; +import { SendAddContactScreenViewStrategy } from '../Strategies/SendAddContactScreenViewStrategy'; +import { SendAddCourtesyAddressStrategy } from '../Strategies/SendAddCourtesyAddressStrategy'; +import { SendAddLegalAddressStrategy } from '../Strategies/SendAddLegalAddressStrategy'; +import { SendAddMandateUXConversionStrategy } from '../Strategies/SendAddMandateUXConversionStrategy'; +import { SendAddMandateUXSuccessStrategy } from '../Strategies/SendAddMandateUXSuccessStrategy'; +import { SendDisableIOStrategy } from '../Strategies/SendDisableIOStrategy'; +import { SendDownloadCertificateOpposable } from '../Strategies/SendDownloadCertificateOpposable'; +import { SendDownloadResponseStrategy } from '../Strategies/SendDownloadResponse'; +import { SendEnableIOStrategy } from '../Strategies/SendEnableIOStrategy'; +import { SendGenericErrorStrategy } from '../Strategies/SendGenericErrorStrategy'; +import { SendHasAddressesStrategy } from '../Strategies/SendHasAddressesStrategy'; +import { SendHasMandateGivenStrategy } from '../Strategies/SendHasMandateGivensStrategy'; +import { SendHasMandateLoginStrategy } from '../Strategies/SendHasMandateLoginStrategy'; +import { SendHasMandateStrategy } from '../Strategies/SendHasMandateStrategy'; +import { SendNotificationCountStrategy } from '../Strategies/SendNotificationCount'; +import { SendNotificationStatusDetailStrategy } from '../Strategies/SendNotificationStatusDetail'; +import { SendPaymentDetailErrorStrategy } from '../Strategies/SendPaymentDetailErrorStrategy'; +import { SendPaymentOutcomeStrategy } from '../Strategies/SendPaymentOutcomeStrategy'; +import { SendPaymentStatusStrategy } from '../Strategies/SendPaymentStatusStrategy'; +import { SendPaymentsCountStrategy } from '../Strategies/SendPaymentsCountStrategy'; +import { SendRefreshPageStrategy } from '../Strategies/SendRefreshPageStrategy'; +import { SendRemoveContactSuccessStrategy } from '../Strategies/SendRemoveContactSuccess'; +import { SendRemoveCourtesyAddressStrategy } from '../Strategies/SendRemoveCourtesyAddress'; +import { SendRemoveLegalAddressStrategy } from '../Strategies/SendRemoveLegalAddress'; +import { SendServiceStatusStrategy } from '../Strategies/SendServiceStatusStrategy'; +import { SendToastErrorStrategy } from '../Strategies/SendToastErrorStrategy'; +import { SendViewContactDetailsStrategy } from '../Strategies/SendViewContactDetailsStrategy'; +import { SendViewProfileStrategy } from '../Strategies/SendViewProfileStrategy'; +import { SendYourContactDetailsStrategy } from '../Strategies/SendYourContactDetailsStrategy'; +import { SendYourMandatesStrategy } from '../Strategies/SendYourMandatesStrategy'; +import { SendYourNotificationsStrategy } from '../Strategies/SendYourNotificationsStrategy'; +import { TechScreenViewStrategy } from '../Strategies/TechScreenViewStrategy'; +import { TechStrategy } from '../Strategies/TechStrategy'; +import { UXActionStrategy } from '../Strategies/UXActionStrategy'; +import { UXErrorStrategy } from '../Strategies/UXErrorStrategy'; +import { UXScreenViewStrategy } from '../Strategies/UXScreenViewStrategy'; + +describe('Event Strategy Factory', () => { + const factory = PFEventStrategyFactory; + + it('should return SendViewProfileStrategy for SEND_VIEW_PROFILE event', () => { + expect(factory.getStrategy(PFEventsType.SEND_VIEW_PROFILE)).toBeInstanceOf( + SendViewProfileStrategy + ); + }); + + it('should return SendViewContactDetailsStrategy for SEND_VIEW_CONTACT_DETAILS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_VIEW_CONTACT_DETAILS)).toBeInstanceOf( + SendViewContactDetailsStrategy + ); + }); + + it('should return SendDownloadCertificateOpposable for SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES event', () => { + expect( + factory.getStrategy(PFEventsType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES) + ).toBeInstanceOf(SendDownloadCertificateOpposable); + }); + + it('should return SendYourNotificationsStrategy for SEND_YOUR_NOTIFICATIONS and SEND_NOTIFICATION_DELEGATED', () => { + const eventTypes = [ + PFEventsType.SEND_YOUR_NOTIFICATIONS, + PFEventsType.SEND_NOTIFICATION_DELEGATED, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(SendYourNotificationsStrategy); + }); + }); + + it('should return SendNotificationStatusDetailStrategy for SEND_NOTIFICATION_STATUS_DETAIL event', () => { + expect(factory.getStrategy(PFEventsType.SEND_NOTIFICATION_STATUS_DETAIL)).toBeInstanceOf( + SendNotificationStatusDetailStrategy + ); + }); + + it('should return SendYourMandatesStrategy for SEND_YOUR_MANDATES event', () => { + expect(factory.getStrategy(PFEventsType.SEND_YOUR_MANDATES)).toBeInstanceOf( + SendYourMandatesStrategy + ); + }); + + it('should return SendAddMandateUXConversionStrategy for SEND_ADD_MANDATE_UX_CONVERSION event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ADD_MANDATE_UX_CONVERSION)).toBeInstanceOf( + SendAddMandateUXConversionStrategy + ); + }); + + it('should return SendAddMandateUXSuccessStrategy for SEND_ADD_MANDATE_UX_SUCCESS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ADD_MANDATE_UX_SUCCESS)).toBeInstanceOf( + SendAddMandateUXSuccessStrategy + ); + }); + + it('should return SendYourContactDetailsStrategy for SEND_YOUR_CONTACT_DETAILS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_YOUR_CONTACT_DETAILS)).toBeInstanceOf( + SendYourContactDetailsStrategy + ); + }); + + it('should return SendServiceStatusStrategy for SEND_SERVICE_STATUS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_SERVICE_STATUS)).toBeInstanceOf( + SendServiceStatusStrategy + ); + }); + + it('should return SendRefreshPageStrategy for SEND_REFRESH_PAGE event', () => { + expect(factory.getStrategy(PFEventsType.SEND_REFRESH_PAGE)).toBeInstanceOf( + SendRefreshPageStrategy + ); + }); + + it('should return SendToastErrorStrategy for SEND_TOAST_ERROR event', () => { + expect(factory.getStrategy(PFEventsType.SEND_TOAST_ERROR)).toBeInstanceOf( + SendToastErrorStrategy + ); + }); + + it('should return SendGenericErrorStrategy for SEND_GENERIC_ERROR event', () => { + expect(factory.getStrategy(PFEventsType.SEND_GENERIC_ERROR)).toBeInstanceOf( + SendGenericErrorStrategy + ); + }); + + it('should return SendPaymentOutcomeStrategy for SEND_PAYMENT_OUTCOME event', () => { + expect(factory.getStrategy(PFEventsType.SEND_PAYMENT_OUTCOME)).toBeInstanceOf( + SendPaymentOutcomeStrategy + ); + }); + + it('should return SendDownloadResponseStrategy for SEND_DOWNLOAD_RESPONSE event', () => { + expect(factory.getStrategy(PFEventsType.SEND_DOWNLOAD_RESPONSE)).toBeInstanceOf( + SendDownloadResponseStrategy + ); + }); + + it('should return SendPaymentStatusStrategy for SEND_PAYMENT_STATUS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_PAYMENT_STATUS)).toBeInstanceOf( + SendPaymentStatusStrategy + ); + }); + + it('should return SendPaymentDetailErrorStrategy for SEND_PAYMENT_DETAIL_ERROR event', () => { + expect(factory.getStrategy(PFEventsType.SEND_PAYMENT_DETAIL_ERROR)).toBeInstanceOf( + SendPaymentDetailErrorStrategy + ); + }); + + it('should return SendRemoveContactSuccessStrategy for remove contacts success events', () => { + const eventTypes = [ + PFEventsType.SEND_REMOVE_EMAIL_SUCCESS, + PFEventsType.SEND_REMOVE_SMS_SUCCESS, + PFEventsType.SEND_REMOVE_PEC_SUCCESS, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(SendRemoveContactSuccessStrategy); + }); + }); + + it('should return SendAddContactActionStrategy for add contacts start events', () => { + const eventTypes = [ + PFEventsType.SEND_ADD_EMAIL_START, + PFEventsType.SEND_ADD_SMS_START, + PFEventsType.SEND_ADD_PEC_START, + PFEventsType.SEND_ADD_PEC_UX_CONVERSION, + PFEventsType.SEND_ADD_SMS_UX_CONVERSION, + PFEventsType.SEND_ADD_EMAIL_UX_CONVERSION, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(SendAddContactActionStrategy); + }); + }); + + it('should return SendAddContactScreenViewStrategy for add contacts success events', () => { + const eventTypes = [ + PFEventsType.SEND_ADD_PEC_UX_SUCCESS, + PFEventsType.SEND_ADD_SMS_UX_SUCCESS, + PFEventsType.SEND_ADD_EMAIL_UX_SUCCESS, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(SendAddContactScreenViewStrategy); + }); + }); + + it('should return UXScreenViewStrategy for UX Screen View events', () => { + const eventTypes = [ + PFEventsType.SEND_PROFILE, + PFEventsType.SEND_ADD_MANDATE_DATA_INPUT, + PFEventsType.SEND_ACTIVE_IO_UX_SUCCESS, + PFEventsType.SEND_DEACTIVE_IO_UX_SUCCESS, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(UXScreenViewStrategy); + }); + }); + + it('should return UXActionStrategy for UX Action events', () => { + const eventTypes = [ + PFEventsType.SEND_DOWNLOAD_ATTACHMENT, + PFEventsType.SEND_DOWNLOAD_RECEIPT_NOTICE, + PFEventsType.SEND_START_PAYMENT, + PFEventsType.SEND_PAYMENT_DETAIL_REFRESH, + PFEventsType.SEND_ADD_MANDATE_START, + PFEventsType.SEND_ADD_MANDATE_BACK, + PFEventsType.SEND_SHOW_MANDATE_CODE, + PFEventsType.SEND_MANDATE_REVOKED, + PFEventsType.SEND_MANDATE_REJECTED, + PFEventsType.SEND_MANDATE_ACCEPTED, + PFEventsType.SEND_ACTIVE_IO_START, + PFEventsType.SEND_DEACTIVE_IO_START, + PFEventsType.SEND_ACTIVE_IO_UX_CONVERSION, + PFEventsType.SEND_DEACTIVE_IO_UX_CONVERSION, + PFEventsType.SEND_CANCELLED_NOTIFICATION_REFOUND_INFO, + PFEventsType.SEND_MULTIPAYMENT_MORE_INFO, + PFEventsType.SEND_PAYMENT_LIST_CHANGE_PAGE, + PFEventsType.SEND_F24_DOWNLOAD, + PFEventsType.SEND_DOWNLOAD_PAYMENT_NOTICE, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(UXActionStrategy); + }); + }); + + it('should return UXErrorStrategy for UX Error events', () => { + const eventTypes = [ + PFEventsType.SEND_MANDATE_ACCEPT_CODE_ERROR, + PFEventsType.SEND_ADD_PEC_CODE_ERROR, + PFEventsType.SEND_ADD_SMS_CODE_ERROR, + PFEventsType.SEND_ADD_EMAIL_CODE_ERROR, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(UXErrorStrategy); + }); + }); + + it('should return TechStrategy for tech events', () => { + const eventTypes = [ + PFEventsType.SEND_RAPID_ACCESS, + PFEventsType.SEND_AUTH_SUCCESS, + PFEventsType.SEND_F24_DOWNLOAD_SUCCESS, + PFEventsType.SEND_F24_DOWNLOAD_TIMEOUT, + ]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(TechStrategy); + }); + }); + + it('should return TechScreenViewStrategy for tech screen view events', () => { + const eventTypes = [PFEventsType.SEND_NOTIFICATION_NOT_ALLOWED]; + eventTypes.forEach((eventType) => { + expect(factory.getStrategy(eventType)).toBeInstanceOf(TechScreenViewStrategy); + }); + }); + + it('should return SendHasAddressesStrategy for SEND_HAS_ADDRESSES event', () => { + expect(factory.getStrategy(PFEventsType.SEND_HAS_ADDRESSES)).toBeInstanceOf( + SendHasAddressesStrategy + ); + }); + + it('should return SendHasMandateGivenStrategy for SEND_HAS_MANDATE_LOGIN event', () => { + expect(factory.getStrategy(PFEventsType.SEND_HAS_MANDATE_LOGIN)).toBeInstanceOf( + SendHasMandateLoginStrategy + ); + }); + + it('should return SendMandateGivenStrategy for SEND_MANDATE_GIVEN event', () => { + expect(factory.getStrategy(PFEventsType.SEND_MANDATE_GIVEN)).toBeInstanceOf( + SendHasMandateGivenStrategy + ); + }); + + it('should return SendHasMandateStrategy for SEND_HAS_MANDATE event', () => { + expect(factory.getStrategy(PFEventsType.SEND_HAS_MANDATE)).toBeInstanceOf( + SendHasMandateStrategy + ); + }); + + it('should return SendDisableIOStrategy for SEND_DISABLE_IO event', () => { + expect(factory.getStrategy(PFEventsType.SEND_DISABLE_IO)).toBeInstanceOf(SendDisableIOStrategy); + }); + + it('should return SendEnableIOStrategy for SEND_ENABLE_IO event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ENABLE_IO)).toBeInstanceOf(SendEnableIOStrategy); + }); + + it('should return SendAcceptDelegationStrategy for SEND_ACCEPT_DELEGATION event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ACCEPT_DELEGATION)).toBeInstanceOf( + SendAcceptDelegationStrategy + ); + }); + + it('should return SendRemoveLegalAddressStrategy for SEND_REMOVE_LEGAL_ADDRESS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_REMOVE_LEGAL_ADDRESS)).toBeInstanceOf( + SendRemoveLegalAddressStrategy + ); + }); + + it('should return SendAddLegalAddressStrategy for SEND_ADD_LEGAL_ADDRESS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ADD_LEGAL_ADDRESS)).toBeInstanceOf( + SendAddLegalAddressStrategy + ); + }); + + it('should return SendNotificationCountStrategy for SEND_NOTIFICATIONS_COUNT event', () => { + expect(factory.getStrategy(PFEventsType.SEND_NOTIFICATIONS_COUNT)).toBeInstanceOf( + SendNotificationCountStrategy + ); + }); + + it('should return SendPaymentsCountStrategy for SEND_PAYMENTS_COUNT event', () => { + expect(factory.getStrategy(PFEventsType.SEND_PAYMENTS_COUNT)).toBeInstanceOf( + SendPaymentsCountStrategy + ); + }); + + it('should return SendRemoveCourtesyAddressStrategy for SEND_REMOVE_COURTESY_ADDRESS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_REMOVE_COURTESY_ADDRESS)).toBeInstanceOf( + SendRemoveCourtesyAddressStrategy + ); + }); + + it('should return SendAddCourtesyAddressStrategy for SEND_ADD_COURTESY_ADDRESS event', () => { + expect(factory.getStrategy(PFEventsType.SEND_ADD_COURTESY_ADDRESS)).toBeInstanceOf( + SendAddCourtesyAddressStrategy + ); + }); + + it('should return null for unknown event type', () => { + expect(factory.getStrategy('UNKNOWN_EVENT' as PFEventsType)).toBeNull(); + }); +}); diff --git a/packages/pn-personafisica-webapp/src/utility/__test__/events.test.ts b/packages/pn-personafisica-webapp/src/utility/__test__/events.test.ts deleted file mode 100644 index c26e52212e..0000000000 --- a/packages/pn-personafisica-webapp/src/utility/__test__/events.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EventCategory } from '@pagopa-pn/pn-commons'; - -import { TrackEventType, events } from '../events'; - -describe('test track events', () => { - it('app crash event', () => { - const event = events[TrackEventType.SEND_GENERIC_ERROR]; - expect(event).toEqual({ - event_category: EventCategory.KO, - }); - }); - - it('undefined event', () => { - const event = events['TEST']; - expect(event).toEqual(undefined); - }); -}); 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/contacts.utility.ts b/packages/pn-personafisica-webapp/src/utility/contacts.utility.ts index c841cffca7..4b1e94692a 100644 --- a/packages/pn-personafisica-webapp/src/utility/contacts.utility.ts +++ b/packages/pn-personafisica-webapp/src/utility/contacts.utility.ts @@ -1,6 +1,5 @@ +import { PFEventsType } from '../models/PFEventsType'; import { CourtesyChannelType, DigitalAddress, LegalChannelType } from '../models/contacts'; -import { TrackEventType } from './events'; -import { trackEventByType } from './mixpanel'; export const internationalPhonePrefix = '+39'; @@ -8,26 +7,13 @@ export function countContactsByType(contacts: Array, type: Court return contacts.reduce((total, contact) => (contact.channelType === type ? total + 1 : total), 0); } -/** - * Mixpanel event tracking for contact deletion success - * TODO: remove this during mixpanel refactor - * @param contactType - * @param senderId - */ -export function trackDeleteContactEvent( - contactType: CourtesyChannelType | LegalChannelType, - senderId: string -) { - const other_contact = senderId !== 'default' ? 'yes' : 'no'; - - switch (contactType) { - case LegalChannelType.PEC: - return trackEventByType(TrackEventType.SEND_REMOVE_PEC_SUCCESS, { other_contact }); - case CourtesyChannelType.EMAIL: - return trackEventByType(TrackEventType.SEND_REMOVE_EMAIL_SUCCESS, { other_contact }); - case CourtesyChannelType.SMS: - return trackEventByType(TrackEventType.SEND_REMOVE_SMS_SUCCESS, { other_contact }); - default: - return; +export const getEventByContactType = ( + contactType: CourtesyChannelType | LegalChannelType +): PFEventsType => { + if (contactType === LegalChannelType.PEC) { + return PFEventsType.SEND_REMOVE_PEC_SUCCESS; + } else if (contactType === CourtesyChannelType.EMAIL) { + return PFEventsType.SEND_REMOVE_EMAIL_SUCCESS; } -} + return PFEventsType.SEND_REMOVE_SMS_SUCCESS; +}; diff --git a/packages/pn-personafisica-webapp/src/utility/events.ts b/packages/pn-personafisica-webapp/src/utility/events.ts deleted file mode 100644 index 1ce3ca138f..0000000000 --- a/packages/pn-personafisica-webapp/src/utility/events.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { EventAction, EventCategory, EventsType } from '@pagopa-pn/pn-commons'; - -export enum TrackEventType { - SEND_VIEW_PROFILE = 'SEND_VIEW_PROFILE', - SEND_PROFILE = 'SEND_PROFILE', - SEND_VIEW_CONTACT_DETAILS = 'SEND_VIEW_CONTACT_DETAILS', - SEND_YOUR_NOTIFICATION = 'SEND_YOUR_NOTIFICATION', - SEND_NOTIFICATION_DELEGATED = 'SEND_NOTIFICATION_DELEGATED', - SEND_NOTIFICATION_DETAIL = 'SEND_NOTIFICATION_DETAIL', - SEND_PAYMENT_STATUS = 'SEND_PAYMENT_STATUS', - SEND_PAYMENT_DETAIL_ERROR = 'SEND_PAYMENT_DETAIL_ERROR', - SEND_PAYMENT_DETAIL_REFRESH = 'SEND_PAYMENT_DETAIL_REFRESH', - SEND_CANCELLED_NOTIFICATION_REFOUND_INFO = 'SEND_CANCELLED_NOTIFICATION_REFOUND_INFO', - SEND_MULTIPAYMENT_MORE_INFO = 'SEND_MULTIPAYMENT_MORE_INFO', - SEND_PAYMENT_LIST_CHANGE_PAGE = 'SEND_PAYMENT_LIST_CHANGE_PAGE', - SEND_F24_DOWNLOAD = 'SEND_F24_DOWNLOAD', - SEND_F24_DOWNLOAD_SUCCESS = 'SEND_F24_DOWNLOAD_SUCCESS', - SEND_DOWNLOAD_ATTACHMENT = 'SEND_DOWNLOAD_ATTACHMENT', - SEND_DOWNLOAD_RECEIPT_NOTICE = 'SEND_DOWNLOAD_RECEIPT_NOTICE', - SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES = 'SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES', - SEND_DOWNLOAD_PAYMENT_NOTICE = 'SEND_DOWNLOAD_PAYMENT_NOTICE', - SEND_START_PAYMENT = 'SEND_START_PAYMENT', - SEND_NOTIFICATION_STATUS_DETAIL = 'SEND_NOTIFICATION_STATUS_DETAIL', - SEND_YOUR_MANDATES = 'SEND_YOUR_MANDATES', - SEND_ADD_MANDATE_START = 'SEND_ADD_MANDATE_START', - SEND_ADD_MANDATE_BACK = 'SEND_ADD_MANDATE_BACK', - SEND_ADD_MANDATE_DATA_INPUT = 'SEND_ADD_MANDATE_DATA_INPUT', - SEND_ADD_MANDATE_UX_CONVERSION = 'SEND_ADD_MANDATE_UX_CONVERSION', - SEND_ADD_MANDATE_UX_SUCCESS = 'SEND_ADD_MANDATE_UX_SUCCESS', - SEND_SHOW_MANDATE_CODE = 'SEND_SHOW_MANDATE_CODE', - SEND_MANDATE_REVOKED = 'SEND_MANDATE_REVOKED', - SEND_MANDATE_REJECTED = 'SEND_MANDATE_REJECTED', - SEND_MANDATE_ACCEPTED = 'SEND_MANDATE_ACCEPTED', - SEND_MANDATE_ACCEPT_CODE_ERROR = 'SEND_MANDATE_ACCEPT_CODE_ERROR', - SEND_YOUR_CONTACT_DETAILS = 'SEND_YOUR_CONTACT_DETAILS', - SEND_ADD_PEC_START = 'SEND_ADD_PEC_START', - SEND_ADD_PEC_UX_CONVERSION = 'SEND_ADD_PEC_UX_CONVERSION', - SEND_ADD_PEC_CODE_ERROR = 'SEND_ADD_PEC_CODE_ERROR', - SEND_ADD_PEC_UX_SUCCESS = 'SEND_ADD_PEC_UX_SUCCESS', - SEND_ACTIVE_IO_START = 'SEND_ACTIVE_IO_START', - SEND_ACTIVE_IO_UX_CONVERSION = 'SEND_ACTIVE_IO_UX_CONVERSION', - SEND_ACTIVE_IO_UX_SUCCESS = 'SEND_ACTIVE_IO_UX_SUCCESS', - SEND_DEACTIVE_IO_START = 'SEND_DEACTIVE_IO_START', - SEND_DEACTIVE_IO_UX_CONVERSION = 'SEND_DEACTIVE_IO_UX_CONVERSION', - SEND_DEACTIVE_IO_UX_SUCCESS = 'SEND_DEACTIVE_IO_UX_SUCCESS', - SEND_ADD_SMS_START = 'SEND_ADD_SMS_START', - SEND_ADD_SMS_UX_CONVERSION = 'SEND_ADD_SMS_UX_CONVERSION', - SEND_ADD_SMS_CODE_ERROR = 'SEND_ADD_SMS_CODE_ERROR', - SEND_ADD_SMS_UX_SUCCESS = 'SEND_ADD_SMS_UX_SUCCESS', - SEND_ADD_EMAIL_START = 'SEND_ADD_EMAIL_START', - SEND_ADD_EMAIL_UX_CONVERSION = 'SEND_ADD_EMAIL_UX_CONVERSION', - SEND_ADD_EMAIL_CODE_ERROR = 'SEND_ADD_EMAIL_CODE_ERROR', - SEND_ADD_EMAIL_UX_SUCCESS = 'SEND_ADD_EMAIL_UX_SUCCESS', - SEND_SERVICE_STATUS = 'SEND_SERVICE_STATUS', - SEND_REFRESH_PAGE = 'SEND_REFRESH_PAGE', - SEND_TOAST_ERROR = 'SEND_TOAST_ERROR', - SEND_GENERIC_ERROR = 'SEND_GENERIC_ERROR', - SEND_F24_DOWNLOAD_TIMEOUT = 'SEND_F24_DOWNLOAD_TIMEOUT', - SEND_DOWNLOAD_RESPONSE = 'SEND_DOWNLOAD_RESPONSE', - SEND_PAYMENT_OUTCOME = 'SEND_PAYMENT_OUTCOME', - SEND_AUTH_SUCCESS = 'SEND_AUTH_SUCCESS', - SEND_NOTIFICATION_NOT_ALLOWED = 'SEND_NOTIFICATION_NOT_ALLOWED', - SEND_RAPID_ACCESS = 'SEND_RAPID_ACCESS', - SEND_REMOVE_PEC_SUCCESS = 'SEND_REMOVE_PEC_SUCCESS', - SEND_REMOVE_SMS_SUCCESS = 'SEND_REMOVE_SMS_SUCCESS', - SEND_REMOVE_EMAIL_SUCCESS = 'SEND_REMOVE_EMAIL_SUCCESS', -} - -export const events: EventsType = { - [TrackEventType.SEND_VIEW_PROFILE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_PROFILE]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_VIEW_CONTACT_DETAILS]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_YOUR_NOTIFICATION]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_NOTIFICATION_DELEGATED]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_NOTIFICATION_DETAIL]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_PAYMENT_STATUS]: { - event_category: EventCategory.TECH, - }, - [TrackEventType.SEND_PAYMENT_DETAIL_ERROR]: { - event_category: EventCategory.KO, - }, - [TrackEventType.SEND_PAYMENT_DETAIL_REFRESH]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_CANCELLED_NOTIFICATION_REFOUND_INFO]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_MULTIPAYMENT_MORE_INFO]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_PAYMENT_LIST_CHANGE_PAGE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_F24_DOWNLOAD]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_F24_DOWNLOAD_SUCCESS]: { - event_category: EventCategory.TECH, - }, - [TrackEventType.SEND_DOWNLOAD_ATTACHMENT]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_DOWNLOAD_RECEIPT_NOTICE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_DOWNLOAD_CERTIFICATE_OPPOSABLE_TO_THIRD_PARTIES]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_DOWNLOAD_PAYMENT_NOTICE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_START_PAYMENT]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_NOTIFICATION_STATUS_DETAIL]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_YOUR_MANDATES]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ADD_MANDATE_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_MANDATE_BACK]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_MANDATE_DATA_INPUT]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ADD_MANDATE_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_MANDATE_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_SHOW_MANDATE_CODE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_MANDATE_REVOKED]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_MANDATE_REJECTED]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_MANDATE_ACCEPTED]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_MANDATE_ACCEPT_CODE_ERROR]: { - event_category: EventCategory.UX, - event_type: EventAction.ERROR, - }, - [TrackEventType.SEND_YOUR_CONTACT_DETAILS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ADD_PEC_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_PEC_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_PEC_CODE_ERROR]: { - event_category: EventCategory.UX, - event_type: EventAction.ERROR, - }, - [TrackEventType.SEND_ADD_PEC_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ACTIVE_IO_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ACTIVE_IO_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ACTIVE_IO_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_DEACTIVE_IO_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_DEACTIVE_IO_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_DEACTIVE_IO_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ADD_SMS_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_SMS_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_SMS_CODE_ERROR]: { - event_category: EventCategory.UX, - event_type: EventAction.ERROR, - }, - [TrackEventType.SEND_ADD_SMS_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_ADD_EMAIL_START]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_EMAIL_UX_CONVERSION]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_ADD_EMAIL_CODE_ERROR]: { - event_category: EventCategory.UX, - event_type: EventAction.ERROR, - }, - [TrackEventType.SEND_ADD_EMAIL_UX_SUCCESS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_SERVICE_STATUS]: { - event_category: EventCategory.UX, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_REFRESH_PAGE]: { - event_category: EventCategory.UX, - event_type: EventAction.ACTION, - }, - [TrackEventType.SEND_TOAST_ERROR]: { - event_category: EventCategory.KO, - }, - [TrackEventType.SEND_GENERIC_ERROR]: { - event_category: EventCategory.KO, - }, - [TrackEventType.SEND_F24_DOWNLOAD_TIMEOUT]: { - event_category: EventCategory.TECH, - }, - [TrackEventType.SEND_DOWNLOAD_RESPONSE]: { - event_category: EventCategory.TECH, - getAttributes(payload: { - url: string; - retryAfter?: number; - docType?: string; - }): Record { - return { - doc_type: payload.docType ? payload.docType : '', - url_available: payload.url ? 'ready' : 'retry_after', - }; - }, - }, - [TrackEventType.SEND_PAYMENT_OUTCOME]: { - event_category: EventCategory.TECH, - }, - [TrackEventType.SEND_AUTH_SUCCESS]: { - event_category: EventCategory.TECH, - }, - [TrackEventType.SEND_NOTIFICATION_NOT_ALLOWED]: { - event_category: EventCategory.TECH, - event_type: EventAction.SCREEN_VIEW, - }, - [TrackEventType.SEND_RAPID_ACCESS]: { - event_category: EventCategory.TECH, - }, -}; - -export const eventsActionsMap: Record = { - 'getReceivedNotificationOtherDocument/fulfilled': TrackEventType.SEND_DOWNLOAD_RESPONSE, - 'getReceivedNotificationLegalfact/fulfilled': TrackEventType.SEND_DOWNLOAD_RESPONSE, - 'exchangeToken/fulfilled': TrackEventType.SEND_AUTH_SUCCESS, -}; 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/utility/mixpanel.ts b/packages/pn-personafisica-webapp/src/utility/mixpanel.ts index a41ebc821b..4317ae68d6 100644 --- a/packages/pn-personafisica-webapp/src/utility/mixpanel.ts +++ b/packages/pn-personafisica-webapp/src/utility/mixpanel.ts @@ -1,49 +1,11 @@ -import { - ProfilePropertyType, - interceptDispatch, - interceptDispatchSuperOrProfileProperty, - setSuperOrProfileProperty, - trackEvent, -} from '@pagopa-pn/pn-commons'; +import { interceptDispatch } from '@pagopa-pn/pn-commons'; import { AnyAction, Dispatch, Middleware } from '@reduxjs/toolkit'; -import { TrackEventType, events, eventsActionsMap } from './events'; -import { ProfilePropertyParams, profilePropertiesActionsMap } from './profileProperties'; +import { PFEventsType, eventsActionsMap } from '../models/PFEventsType'; +import PFEventStrategyFactory from './MixpanelUtils/PFEventStrategyFactory'; /** * Redux middleware to track events */ export const trackingMiddleware: Middleware = () => (next: Dispatch) => - interceptDispatch(next, events, eventsActionsMap, process.env.NODE_ENV); - -export const trackingProfileMiddleware: Middleware = () => (next: Dispatch) => - interceptDispatchSuperOrProfileProperty(next, profilePropertiesActionsMap, process.env.NODE_ENV); - -/** - * Function to track events outside redux - * @param trackEventType event name - * @param attributes event attributes - */ -export const trackEventByType = (trackEventType: TrackEventType, attributes?: object) => { - const eventParameters = attributes - ? { ...events[trackEventType], ...attributes } - : events[trackEventType]; - - trackEvent(trackEventType, process.env.NODE_ENV, eventParameters); -}; - -/** - * Function to set super or profile property values - * @param type type of property (Super, Profile or Incremental) - * @param propertyName name of the property to set - * @param attributes values of the property to set. If not provided, the property will be set to the name of the property - */ -export function setSuperOrProfilePropertyValues( - type: ProfilePropertyType, - propertyName: TProperty, - attributes?: ProfilePropertyParams[TProperty] -) { - const property = attributes ? { [propertyName]: attributes } : propertyName; - - setSuperOrProfileProperty(type, property, process.env.NODE_ENV); -} + interceptDispatch(next, PFEventStrategyFactory, eventsActionsMap); diff --git a/packages/pn-personafisica-webapp/src/utility/profileProperties.ts b/packages/pn-personafisica-webapp/src/utility/profileProperties.ts deleted file mode 100644 index 64825f7517..0000000000 --- a/packages/pn-personafisica-webapp/src/utility/profileProperties.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { - ProfileMapAttributes, - ProfilePropertiesActionsMap, - ProfilePropertyType, -} from '@pagopa-pn/pn-commons'; - -import { - CourtesyChannelType, - DigitalAddress, - DigitalAddresses, - IOAllowedValues, -} from '../models/contacts'; -import { DeleteDigitalAddressParams, SaveDigitalAddressParams } from '../redux/contact/types'; -import { Delegation } from '../redux/delegation/types'; -import { DelegationStatus } from './status.utility'; - -export type ProfilePropertyParams = { - SEND_APPIO_STATUS: 'nd' | 'activated' | 'deactivated'; - SEND_HAS_PEC: 'yes' | 'no'; - SEND_HAS_EMAIL: 'yes' | 'no'; - SEND_HAS_SMS: 'yes' | 'no'; - SEND_HAS_MANDATE: 'yes' | 'no'; - SEND_MANDATE_GIVEN: 'yes' | 'no'; - SEND_PAYMENTS_COUNT: number; - SEND_NOTIFICATIONS_COUNT: number; -}; - -const profileProperties: ProfilePropertiesActionsMap = { - ['SEND_HAS_ADDRESSES']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes(payload: DigitalAddresses): Record { - const hasLegalAddresses = payload?.legal?.length > 0; - const hasCourtesyEmailAddresses = - payload?.courtesy?.filter((address) => address.channelType === CourtesyChannelType.EMAIL) - .length > 0; - const hasCourtesySmsAddresses = - payload?.courtesy?.filter((address) => address.channelType === CourtesyChannelType.SMS) - .length > 0; - const contactIO = payload?.courtesy?.find( - (address) => address.channelType === CourtesyChannelType.IOMSG - ); - - // eslint-disable-next-line functional/no-let - let ioStatus; - - if (!contactIO) { - ioStatus = 'nd'; - } else if (contactIO?.value === IOAllowedValues.DISABLED) { - ioStatus = 'deactivated'; - } else { - ioStatus = 'activated'; - } - - return { - SEND_HAS_PEC: hasLegalAddresses ? 'yes' : 'no', - SEND_HAS_EMAIL: hasCourtesyEmailAddresses ? 'yes' : 'no', - SEND_HAS_SMS: hasCourtesySmsAddresses ? 'yes' : 'no', - SEND_APPIO_STATUS: ioStatus, - }; - }, - }, - ['ADD_COURTESY_ADDRESS']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes( - _, - meta: { requestStatus: string; requestId: string; arg: SaveDigitalAddressParams } - ): Record { - if (meta?.arg?.channelType === CourtesyChannelType.EMAIL) { - return { SEND_HAS_EMAIL: 'yes' }; - } - - return { SEND_HAS_SMS: 'yes' }; - }, - shouldBlock(payload: DigitalAddress | void): boolean { - // Check payload to distinguish between the action called before PIN validation and after - // We have to track only the action after the PIN validation - return !payload; - }, - }, - ['REMOVE_COURTESY_ADDRESS']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes( - _, - meta: { requestStatus: string; requestId: string; arg: DeleteDigitalAddressParams } - ): Record { - if (meta?.arg?.channelType === CourtesyChannelType.EMAIL) { - return { SEND_HAS_EMAIL: 'no' }; - } - - return { SEND_HAS_SMS: 'no' }; - }, - }, - ['ADD_LEGAL_ADDRESS']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes(): Record { - return { SEND_HAS_PEC: 'yes' }; - }, - shouldBlock(payload: DigitalAddress | void): boolean { - // Check payload to distinguish between the action called before PIN validation and after - // We have to track only the action after the PIN validation and PEC validation - return !(payload && payload.pecValid); - }, - }, - ['REMOVE_LEGAL_ADDRESS']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes(): Record { - return { SEND_HAS_PEC: 'no' }; - }, - }, - ['ENABLE_APP_IO']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes(): Record { - return { SEND_APPIO_STATUS: 'activated' }; - }, - }, - ['DISABLE_APP_IO']: { - profilePropertyType: [ProfilePropertyType.PROFILE, ProfilePropertyType.SUPER_PROPERTY], - getAttributes(): Record { - return { SEND_APPIO_STATUS: 'deactivated' }; - }, - }, - ['GET_DELEGATES']: { - profilePropertyType: [ProfilePropertyType.PROFILE], - getAttributes(payload: Array): Record { - const hasDelegates = payload?.filter( - (delegation) => delegation.status === DelegationStatus.ACTIVE - ); - - return hasDelegates.length > 0 ? { SEND_MANDATE_GIVEN: 'yes' } : { SEND_MANDATE_GIVEN: 'no' }; - }, - }, - ['GET_DELEGATORS']: { - profilePropertyType: [ProfilePropertyType.PROFILE], - getAttributes(payload: Array): Record { - const hasDelegators = payload?.filter( - (delegator) => delegator.status === DelegationStatus.ACTIVE - ); - - return hasDelegators.length > 0 ? { SEND_HAS_MANDATE: 'yes' } : { SEND_HAS_MANDATE: 'no' }; - }, - }, - ['ACCEPT_DELEGATION']: { - profilePropertyType: [ProfilePropertyType.PROFILE], - getAttributes(): Record { - return { SEND_HAS_MANDATE: 'yes' }; - }, - }, -}; - -export const profilePropertiesActionsMap: Record = { - 'getDigitalAddresses/fulfilled': profileProperties.SEND_HAS_ADDRESSES, - 'createOrUpdateCourtesyAddress/fulfilled': profileProperties.ADD_COURTESY_ADDRESS, - 'deleteCourtesyAddress/fulfilled': profileProperties.REMOVE_COURTESY_ADDRESS, - 'createOrUpdateLegalAddress/fulfilled': profileProperties.ADD_LEGAL_ADDRESS, - 'deleteLegalAddress/fulfilled': profileProperties.REMOVE_LEGAL_ADDRESS, - 'enableIOAddress/fulfilled': profileProperties.ENABLE_APP_IO, - 'disableIOAddress/fulfilled': profileProperties.DISABLE_APP_IO, - 'getDelegates/fulfilled': profileProperties.GET_DELEGATES, - 'getDelegators/fulfilled': profileProperties.GET_DELEGATORS, - 'acceptDelegation/fulfilled': profileProperties.ACCEPT_DELEGATION, -}; 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/public/locales/it/recapiti.json b/packages/pn-personagiuridica-webapp/public/locales/it/recapiti.json index a429fdfdf1..05a36cd24d 100644 --- a/packages/pn-personagiuridica-webapp/public/locales/it/recapiti.json +++ b/packages/pn-personagiuridica-webapp/public/locales/it/recapiti.json @@ -117,6 +117,10 @@ "expired_verification_code": { "title": "Il codice รจ scaduto", "message": "Generane uno nuovo e inseriscilo." + }, + "invalid_type_code": { + "title": "Questo campo accetta solo valori numerici", + "message": "Controlla il codice di verifica e inseriscilo di nuovo" } } } diff --git a/packages/pn-personagiuridica-webapp/src/App.tsx b/packages/pn-personagiuridica-webapp/src/App.tsx index ef8c362e6a..9933fde0a9 100644 --- a/packages/pn-personagiuridica-webapp/src/App.tsx +++ b/packages/pn-personagiuridica-webapp/src/App.tsx @@ -1,4 +1,4 @@ -import { ErrorInfo, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; @@ -23,7 +23,6 @@ import { useHasPermissions, useMultiEvent, useTracking, - useUnload, } from '@pagopa-pn/pn-commons'; import { PartyEntity, ProductEntity } from '@pagopa/mui-italia'; @@ -37,8 +36,6 @@ import { getDomicileInfo, getSidemenuInformation } from './redux/sidemenu/action import { RootState } from './redux/store'; import { getConfiguration } from './services/configuration.service'; import { PGAppErrorFactory } from './utility/AppError/PGAppErrorFactory'; -import { TrackEventType } from './utility/events'; -import { trackEventByType } from './utility/mixpanel'; import './utility/onetrust'; // Cfr. PN-6096 @@ -77,7 +74,6 @@ const ActualApp = () => { const currentStatus = useAppSelector((state: RootState) => state.appStatus.currentStatus); const { pathname } = useLocation(); const path = pathname.split('/'); - const source = path[path.length - 1]; const sessionToken = loggedUser.sessionToken; const jwtUser = useMemo( @@ -114,10 +110,6 @@ const ActualApp = () => { [t, organization?.id, i18n.language] ); - useUnload(() => { - trackEventByType(TrackEventType.APP_UNLOAD); - }); - useTracking(MIXPANEL_TOKEN, process.env.NODE_ENV); useEffect(() => { @@ -185,7 +177,7 @@ const ActualApp = () => { menuItems.splice(1, 0, { label: t('menu.deleghe'), icon: () => , - route: routes.DELEGHEACARICO, + route: routes.DELEGHE, rightBadgeNotification: pendingDelegators ? pendingDelegators : undefined, }); } @@ -227,24 +219,11 @@ const ActualApp = () => { }; const handleAssistanceClick = () => { - trackEventByType(TrackEventType.CUSTOMER_CARE_MAILTO, { - source: sessionToken ? 'postlogin' : 'prelogin', - }); /* eslint-disable-next-line functional/immutable-data */ window.location.href = sessionToken ? `${SELFCARE_BASE_URL}/assistenza` : `mailto:${PAGOPA_HELP_EMAIL}`; }; - const handleEventTrackingCallbackAppCrash = (e: Error, eInfo: ErrorInfo) => { - trackEventByType(TrackEventType.APP_CRASH, { - route: source, - stacktrace: { error: e, errorInfo: eInfo }, - }); - }; - - const handleEventTrackingCallbackProductSwitch = (target: string) => { - trackEventByType(TrackEventType.USER_PRODUCT_SWITCH, { target }); - }; const [clickVersion] = useMultiEvent({ callback: () => @@ -267,19 +246,7 @@ const ActualApp = () => { showHeader={!isPrivacyPage} showFooter={!isPrivacyPage} onExitAction={handleUserLogout} - eventTrackingCallbackAppCrash={handleEventTrackingCallbackAppCrash} - eventTrackingCallbackProductSwitch={(target) => - handleEventTrackingCallbackProductSwitch(target) - } - sideMenu={ - - trackEventByType(TrackEventType.USER_NAV_ITEM, { target }) - } - /> - } + sideMenu={} showSideMenu={ !!sessionToken && tosConsent && diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactElem.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactElem.tsx index 05d294a252..bd064dbdec 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactElem.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactElem.tsx @@ -18,9 +18,6 @@ import { CourtesyChannelType, LegalChannelType } from '../../models/contacts'; import { deleteCourtesyAddress, deleteLegalAddress } from '../../redux/contact/actions'; import { DeleteDigitalAddressParams } from '../../redux/contact/types'; import { useAppDispatch } from '../../redux/hooks'; -import { getContactEventType } from '../../utility/contacts.utility'; -import { EventActions, TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import { useDigitalContactsCodeVerificationContext } from './DigitalContactsCodeVerification.context'; type Props = { @@ -165,11 +162,8 @@ const DigitalContactElem = forwardRef<{ editContact: () => void }, Props>( /* eslint-disable-next-line functional/no-let */ let actionToDispatch: AsyncThunk; if (contactType === LegalChannelType.PEC) { - trackEventByType(TrackEventType.CONTACT_LEGAL_CONTACT, { action: EventActions.DELETE }); actionToDispatch = deleteLegalAddress; } else { - const eventTypeByChannel = getContactEventType(contactType); - trackEventByType(eventTypeByChannel, { action: EventActions.DELETE }); actionToDispatch = deleteCourtesyAddress; } void dispatch(actionToDispatch({ recipientId, senderId, channelType: contactType })) diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx index ab8782dbd1..61b04c627d 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactsCodeVerification.context.tsx @@ -24,9 +24,6 @@ import { import { SaveDigitalAddressParams } from '../../redux/contact/types'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; -import { getContactEventType } from '../../utility/contacts.utility'; -import { EventActions, TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; type ModalProps = { labelRoot: string; @@ -111,14 +108,10 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ const handleCodeVerification = (verificationCode?: string, noCallback: boolean = false) => { /* eslint-disable functional/no-let */ let actionToBeDispatched; - let eventTypeByChannel; - /* eslint-enable functional/no-let */ if (modalProps.digitalDomicileType === LegalChannelType.PEC) { actionToBeDispatched = createOrUpdateLegalAddress; - eventTypeByChannel = TrackEventType.CONTACT_LEGAL_CONTACT; } else { actionToBeDispatched = createOrUpdateCourtesyAddress; - eventTypeByChannel = getContactEventType(modalProps.digitalDomicileType); } if (!actionToBeDispatched) { return; @@ -132,7 +125,6 @@ const DigitalContactsCodeVerificationProvider: FC<{ children?: ReactNode }> = ({ code: verificationCode, }; - trackEventByType(eventTypeByChannel, { action: EventActions.ADD }); void dispatch(actionToBeDispatched(digitalAddressParams)) .unwrap() .then((res) => { diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/SpecialContactElem.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/SpecialContactElem.tsx index 0d6725efca..27b825e90f 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/SpecialContactElem.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/SpecialContactElem.tsx @@ -7,8 +7,6 @@ import { TableCell, TableRow, TextField, Typography } from '@mui/material'; import { dataRegex, useIsMobile, useSpecialContactsContext } from '@pagopa-pn/pn-commons'; import { CourtesyChannelType, LegalChannelType } from '../../models/contacts'; -import { EventActions, TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import DigitalContactElem from './DigitalContactElem'; type Props = { @@ -105,7 +103,6 @@ const SpecialContactElem = memo(({ address, recipientId }: Props) => { if (status === 'cancelled') { await formik.setFieldValue(id, initialValues[id], true); } - trackEventByType(TrackEventType.CONTACT_SPECIAL_CONTACTS, { action: EventActions.ADD }); }; const formik = useFormik({ diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx index b9e54b44de..1f6b1b5df7 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/__test__/DigitalContactElem.test.tsx @@ -12,8 +12,6 @@ import { } from '../../../__test__/test-utils'; import * as api from '../../../api/contacts/Contacts.api'; import { DigitalAddress, LegalChannelType } from '../../../models/contacts'; -import { TrackEventType } from '../../../utility/events'; -import * as trackingFunctions from '../../../utility/mixpanel'; import DigitalContactElem from '../DigitalContactElem'; import { DigitalContactsCodeVerificationProvider } from '../DigitalContactsCodeVerification.context'; @@ -55,9 +53,6 @@ const fields = [ const mockResetModifyValue = vi.fn(); const mockDeleteCbk = vi.fn(); const mockOnConfirm = vi.fn(); -// mock tracking -const createTrackEventSpy = vi.spyOn(trackingFunctions, 'trackEventByType'); -const mockTrackEventFn = vi.fn(); /* In questo test viene testato solo il rendering dei componenti e non il flusso. @@ -69,10 +64,6 @@ Andrea Cimini - 11/09/2023 describe('DigitalContactElem Component', () => { let result: RenderResult | undefined; - beforeEach(() => { - createTrackEventSpy.mockImplementation(mockTrackEventFn); - }); - afterEach(() => { result = undefined; vi.clearAllMocks(); @@ -203,10 +194,6 @@ describe('DigitalContactElem Component', () => { dialog = await waitFor(() => screen.getByRole('dialog')); dialogButtons = dialog?.querySelectorAll('button'); fireEvent.click(dialogButtons![1]); - expect(mockTrackEventFn).toBeCalledTimes(1); - expect(mockTrackEventFn).toBeCalledWith(TrackEventType.CONTACT_LEGAL_CONTACT, { - action: 'delete', - }); await waitFor(() => { expect(dialog).not.toBeInTheDocument(); }); diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegatesByCompany.tsx b/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegatesByCompany.tsx index 691bdf189a..50bdaa26a7 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegatesByCompany.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegatesByCompany.tsx @@ -26,8 +26,6 @@ import { DELEGATION_ACTIONS, getDelegatesByCompany } from '../../redux/delegatio import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; import delegationToItem from '../../utility/delegation.utility'; -import { TrackEventType } from '../../utility/events'; -import { trackEventByType } from '../../utility/mixpanel'; import DelegationDataSwitch from './DelegationDataSwitch'; type Props = { @@ -69,9 +67,8 @@ const DelegatesByCompany = () => { const data = delegationToItem(delegatesByCompany) as Array>; const rows = sortArray(sort.order, sort.orderBy, data); - const handleAddDelegationClick = (source: string) => { + const handleAddDelegationClick = () => { navigate(routes.NUOVA_DELEGA); - trackEventByType(TrackEventType.DELEGATION_DELEGATE_ADD_CTA, { source }); }; const delegatesColumn: Array> = [ @@ -160,7 +157,7 @@ const DelegatesByCompany = () => {