diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/ConfirmationModal.tsx b/packages/pn-commons/src/components/ConfirmationModal.tsx similarity index 65% rename from packages/pn-personagiuridica-webapp/src/components/Deleghe/ConfirmationModal.tsx rename to packages/pn-commons/src/components/ConfirmationModal.tsx index 0294e58d71..2b58baa9d1 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/ConfirmationModal.tsx +++ b/packages/pn-commons/src/components/ConfirmationModal.tsx @@ -1,61 +1,59 @@ import * as React from 'react'; -import { Button, DialogContentText, DialogTitle } from '@mui/material'; +import { Button, DialogTitle } from '@mui/material'; import { PnDialog, PnDialogActions, PnDialogContent } from '@pagopa-pn/pn-commons'; type Props = { open: boolean; title: string; - subtitle?: string | JSX.Element; - onConfirm?: React.MouseEventHandler | undefined; + onConfirm?: React.MouseEventHandler; onConfirmLabel?: string; onClose: React.MouseEventHandler | undefined; onCloseLabel?: string; - minHeight?: string; width?: string; + children?: React.ReactNode; }; export default function ConfirmationModal({ open, title, - subtitle = '', onConfirm, onConfirmLabel = 'Riprova', onClose, onCloseLabel = 'Annulla', -}: Props) { + children, +}: Readonly) { return ( - {title} - - {subtitle} - + {title} + {children && {children}} - {onConfirm && ( )} + ); diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx b/packages/pn-commons/src/components/__test__/ConfirmationModal.test.tsx similarity index 52% rename from packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx rename to packages/pn-commons/src/components/__test__/ConfirmationModal.test.tsx index bd3fc9b567..f2bd78c88c 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx +++ b/packages/pn-commons/src/components/__test__/ConfirmationModal.test.tsx @@ -1,18 +1,14 @@ import { vi } from 'vitest'; -import { fireEvent, render } from '../../../__test__/test-utils'; +import { fireEvent, render } from '../../test-utils'; import ConfirmationModal from '../ConfirmationModal'; const mockCancelFunction = vi.fn(); const mockConfirmFunction = vi.fn(); describe('ConfirmationModal Component', () => { - afterEach(() => { - vi.clearAllMocks(); - }); - it('renders the component', () => { - const { getByRole, getAllByTestId } = render( + const { getByRole, getByTestId } = render( { onConfirmLabel={'Confirm'} /> ); + const dialog = getByRole('dialog'); expect(dialog).toBeInTheDocument(); expect(dialog).toHaveTextContent(/Test Title/i); - const dialogActions = getAllByTestId('dialogAction'); - expect(dialogActions).toHaveLength(2); - expect(dialogActions[1]).toHaveTextContent(/Confirm/i); - expect(dialogActions[0]).toHaveTextContent(/Cancel/i); + const confirmButton = getByTestId('confirmButton'); + const closeButton = getByTestId('closeButton'); + expect(confirmButton).toHaveTextContent(/Confirm/i); + expect(closeButton).toHaveTextContent(/Cancel/i); }); it('checks that the confirm and cancel functions are executed', () => { - const { getAllByTestId } = render( + const { getByTestId } = render( { onConfirmLabel={'Confirm'} /> ); - const dialogActions = getAllByTestId('dialogAction'); - const confirm = dialogActions[1]; - const cancel = dialogActions[0]; - fireEvent.click(confirm); - expect(mockConfirmFunction).toBeCalledTimes(1); - fireEvent.click(cancel); - expect(mockCancelFunction).toBeCalledTimes(1); + + const confirmButton = getByTestId('confirmButton'); + const cancelButton = getByTestId('closeButton'); + fireEvent.click(confirmButton); + expect(mockConfirmFunction).toHaveBeenCalledTimes(1); + fireEvent.click(cancelButton); + expect(mockCancelFunction).toHaveBeenCalledTimes(1); }); it('renders the dialog with default labels', () => { - const { getAllByTestId } = render( + const { getByTestId } = render( { onConfirm={mockConfirmFunction} /> ); - const dialogActions = getAllByTestId('dialogAction'); - const confirm = dialogActions[1]; - const cancel = dialogActions[0]; - expect(confirm).toHaveTextContent(/Riprova/i); - expect(cancel).toHaveTextContent(/Annulla/i); + + const confirmButton = getByTestId('confirmButton'); + const cancelButton = getByTestId('closeButton'); + expect(confirmButton).toHaveTextContent(/Riprova/i); + expect(cancelButton).toHaveTextContent(/Annulla/i); }); it('renders the dialog with no confirm button', () => { - const { getAllByTestId } = render( + const { getByTestId, queryByTestId } = render( ); - const dialogActions = getAllByTestId('dialogAction'); - expect(dialogActions).toHaveLength(1); - expect(dialogActions[0]).toHaveTextContent(/Annulla/i); + const confirmButton = queryByTestId('confirmButton'); + const cancelButton = getByTestId('closeButton'); + expect(confirmButton).not.toBeInTheDocument(); + expect(cancelButton).toHaveTextContent(/Annulla/i); }); }); diff --git a/packages/pn-commons/src/components/index.ts b/packages/pn-commons/src/components/index.ts index 233782d2da..53f8dc2e4a 100644 --- a/packages/pn-commons/src/components/index.ts +++ b/packages/pn-commons/src/components/index.ts @@ -6,6 +6,7 @@ import AppResponseMessage from './AppResponseMessage'; import { AppStatusRender } from './AppStatus/AppStatusRender'; import CodeModal from './CodeModal/CodeModal'; import CollapsedList from './CollapsedList'; +import ConfirmationModal from './ConfirmationModal'; import CopyToClipboard from './CopyToClipboard'; import CustomDatePicker from './CustomDatePicker'; import CustomDropdown from './CustomDropdown'; @@ -86,6 +87,7 @@ export { AppResponseMessage, AppStatusRender, CodeModal, + ConfirmationModal, CopyToClipboard, CustomDatePicker, CustomDropdown, diff --git a/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactActivation.tsx b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactActivation.tsx index d1803ab891..1d71d15e69 100644 --- a/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactActivation.tsx +++ b/packages/pn-personafisica-webapp/src/components/Contacts/DigitalContactActivation.tsx @@ -2,14 +2,8 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { Button, DialogContentText, DialogTitle, Typography } from '@mui/material'; -import { - PnDialog, - PnDialogActions, - PnDialogContent, - PnWizard, - PnWizardStep, -} from '@pagopa-pn/pn-commons'; +import { Button, DialogContentText, Typography } from '@mui/material'; +import { ConfirmationModal, PnWizard, PnWizardStep } from '@pagopa-pn/pn-commons'; import { ButtonNaked } from '@pagopa/mui-italia'; import IOContactWizard from '../../components/Contacts/IOContactWizard'; @@ -35,63 +29,6 @@ type ModalType = { exit?: boolean; }; -type DialogProps = { - open: boolean; - title: string; - content: string; - onConfirm: () => void; - confirmAction: string; - onDiscard?: () => void; -}; - -const CourtesyContactConfirmationDialog: React.FC = ({ - open, - title, - content, - onConfirm, - confirmAction, - onDiscard, -}) => { - const { t } = useTranslation(['common', 'recapiti']); - - return ( - - {title} - - , - , - ]} - /> - - - - {onDiscard && ( - - )} - - - ); -}; - const DigitalContactActivation: React.FC = ({ isTransferring = false }) => { const { t } = useTranslation(['recapiti', 'common']); const navigate = useNavigate(); @@ -233,22 +170,36 @@ const DigitalContactActivation: React.FC = ({ isTransferring = false }) = )} - + onClose={handleConfirmationModalDecline} + onCloseLabel={t('button.do-later', { ns: 'common' })} + > + , + , + ]} + /> + ); }; diff --git a/packages/pn-personafisica-webapp/src/components/Deleghe/ConfirmationModal.tsx b/packages/pn-personafisica-webapp/src/components/Deleghe/ConfirmationModal.tsx deleted file mode 100644 index 814b692d43..0000000000 --- a/packages/pn-personafisica-webapp/src/components/Deleghe/ConfirmationModal.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react'; - -import { Button, DialogTitle } from '@mui/material'; -import { PnDialog, PnDialogActions } from '@pagopa-pn/pn-commons'; - -type Props = { - open: boolean; - title: string; - onConfirm?: React.MouseEventHandler | undefined; - onConfirmLabel?: string; - handleClose: React.MouseEventHandler | undefined; - onCloseLabel?: string; - width?: string; -}; -export default function ConfirmationModal({ - open, - title, - onConfirm, - onConfirmLabel = 'Riprova', - handleClose, - onCloseLabel = 'Annulla', -}: Props) { - return ( - - {title} - - - {onConfirm && ( - - )} - - - ); -} diff --git a/packages/pn-personafisica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx b/packages/pn-personafisica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx deleted file mode 100644 index 406cf06921..0000000000 --- a/packages/pn-personafisica-webapp/src/components/Deleghe/__test__/ConfirmationModal.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { vi } from 'vitest'; - -import { fireEvent, render } from '../../../__test__/test-utils'; -import ConfirmationModal from '../ConfirmationModal'; - -const mockCancelFunction = vi.fn(); -const mockConfirmFunction = vi.fn(); - -describe('ConfirmationModal Component', () => { - it('renders the component', () => { - const { getByRole, getAllByTestId } = render( - - ); - const dialog = getByRole('dialog'); - expect(dialog).toBeInTheDocument(); - expect(dialog).toHaveTextContent(/Test Title/i); - const dialogActions = getAllByTestId('dialogAction'); - expect(dialogActions).toHaveLength(2); - expect(dialogActions[1]).toHaveTextContent(/Confirm/i); - expect(dialogActions[0]).toHaveTextContent(/Cancel/i); - }); - - it('checks that the confirm and cancel functions are executed', () => { - const { getAllByTestId } = render( - - ); - const dialogActions = getAllByTestId('dialogAction'); - const confirm = dialogActions[1]; - const cancel = dialogActions[0]; - fireEvent.click(confirm); - expect(mockConfirmFunction).toBeCalledTimes(1); - fireEvent.click(cancel); - expect(mockCancelFunction).toBeCalledTimes(1); - }); - - it('renders the dialog with default labels', () => { - const { getAllByTestId } = render( - - ); - const dialogActions = getAllByTestId('dialogAction'); - const confirm = dialogActions[1]; - const cancel = dialogActions[0]; - expect(confirm).toHaveTextContent(/Riprova/i); - expect(cancel).toHaveTextContent(/Annulla/i); - }); - - it('renders the dialog with no confirm button', () => { - const { getAllByTestId } = render( - - ); - const dialogActions = getAllByTestId('dialogAction'); - expect(dialogActions).toHaveLength(1); - expect(dialogActions[0]).toHaveTextContent(/Annulla/i); - }); -}); diff --git a/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx b/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx index 336125f97d..81302d1e46 100644 --- a/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/Deleghe.page.tsx @@ -6,12 +6,12 @@ import { AppResponse, AppResponsePublisher, CodeModal, + ConfirmationModal, ErrorMessage, TitleBox, useIsMobile, } from '@pagopa-pn/pn-commons'; -import ConfirmationModal from '../components/Deleghe/ConfirmationModal'; import Delegates from '../components/Deleghe/Delegates'; import Delegators from '../components/Deleghe/Delegators'; import MobileDelegates from '../components/Deleghe/MobileDelegates'; @@ -43,8 +43,9 @@ const Deleghe = () => { name: acceptName, } = useAppSelector((state: RootState) => state.delegationsState.acceptModalState); const [pageReady, setPageReady] = useState(false); - const codeModalRef = - useRef<{ updateError: (error: ErrorMessage, codeNotValid: boolean) => void }>(null); + const codeModalRef = useRef<{ + updateError: (error: ErrorMessage, codeNotValid: boolean) => void; + }>(null); const dispatch = useAppDispatch(); @@ -154,7 +155,7 @@ const Deleghe = () => { : t('deleghe.rejection_question') } onCloseLabel={t('button.annulla', { ns: 'common' })} - handleClose={handleCloseModal} + onClose={handleCloseModal} onConfirm={handleConfirmClick} onConfirmLabel={ type === 'delegates' ? t('deleghe.confirm_revocation') : t('deleghe.confirm_rejection') diff --git a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactActivation.tsx b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactActivation.tsx index cdd49413f2..1e2bf365f7 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactActivation.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Contacts/DigitalContactActivation.tsx @@ -2,14 +2,8 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { Button, DialogContentText, DialogTitle, Typography } from '@mui/material'; -import { - PnDialog, - PnDialogActions, - PnDialogContent, - PnWizard, - PnWizardStep, -} from '@pagopa-pn/pn-commons'; +import { Button, DialogContentText, Typography } from '@mui/material'; +import { ConfirmationModal, PnWizard, PnWizardStep } from '@pagopa-pn/pn-commons'; import { ButtonNaked } from '@pagopa/mui-italia'; import PecContactWizard from '../../components/Contacts/PecContactWizard'; @@ -27,63 +21,6 @@ type ModalType = { exit?: boolean; }; -type DialogProps = { - open: boolean; - title: string; - content: string; - onConfirm: () => void; - confirmAction: string; - onDiscard?: () => void; -}; - -const CourtesyContactConfirmationDialog: React.FC = ({ - open, - title, - content, - onConfirm, - confirmAction, - onDiscard, -}) => { - const { t } = useTranslation(['common', 'recapiti']); - - return ( - - {title} - - , - , - ]} - /> - - - - {onDiscard && ( - - )} - - - ); -}; - const DigitalContactActivation: React.FC = ({ isTransferring = false }) => { const { t } = useTranslation(['recapiti', 'common']); const navigate = useNavigate(); @@ -192,14 +129,28 @@ const DigitalContactActivation: React.FC = ({ isTransferring = false }) = )} - + onConfirmLabel={t(`courtesy-contacts.confirmation-modal-accept`)} + onClose={handleConfirmationModalDecline} + onCloseLabel={t('button.do-later', { ns: 'common' })} + > + , + , + ]} + /> + ); }; diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegationsElements.tsx b/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegationsElements.tsx index 37f165d122..5ca889998d 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegationsElements.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Deleghe/DelegationsElements.tsx @@ -2,12 +2,21 @@ import React, { Dispatch, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import MoreVertIcon from '@mui/icons-material/MoreVert'; -import { Box, Button, IconButton, Menu as MUIMenu, MenuItem, Typography } from '@mui/material'; +import { + Box, + Button, + DialogContentText, + IconButton, + Menu as MUIMenu, + MenuItem, + Typography, +} from '@mui/material'; import { Variant } from '@mui/material/styles/createTypography'; import { AppResponse, AppResponsePublisher, CodeModal, + ConfirmationModal, CustomTagGroup, Row, appStateActions, @@ -27,7 +36,6 @@ import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { RootState } from '../../redux/store'; import { ServerResponseErrorCode } from '../../utility/AppError/types'; import AcceptDelegationModal from './AcceptDelegationModal'; -import ConfirmationModal from './ConfirmationModal'; function handleCustomGenericError( responseError: AppResponse, @@ -235,12 +243,13 @@ export const Menu: React.FC = ({ menuType, id, userLogged, row, onAction + > + {subtitleModal} + {menuType === 'delegators' && row?.status === DelegationStatus.ACTIVE && groups.length > 0 && ( diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegatesByCompany.test.tsx b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegatesByCompany.test.tsx index 5fb62ac4ab..34de1c604c 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegatesByCompany.test.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegatesByCompany.test.tsx @@ -129,9 +129,9 @@ describe('Delegates Component - assuming delegates API works properly', async () fireEvent.click(menuItems[1]); const dialog = await waitFor(() => getByTestId('confirmationDialog')); expect(dialog).toBeInTheDocument(); - const dialogAction = within(dialog).getAllByTestId('dialogAction'); + const confirmButton = within(dialog).getByTestId('confirmButton'); // click on confirm button - fireEvent.click(dialogAction[1]); + fireEvent.click(confirmButton); await waitFor(() => { expect(mock.history.patch.length).toBe(1); expect(mock.history.patch[0].url).toContain( diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsElements.test.tsx b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsElements.test.tsx index 541794c116..8d39598aa5 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsElements.test.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsElements.test.tsx @@ -215,8 +215,8 @@ describe('DelegationElements', async () => { const revoke = menu.querySelectorAll('[role="menuitem"]')[1]; fireEvent.click(revoke); const showDialog = await waitFor(() => screen.getByTestId('confirmationDialog')); - const revokeButton = within(showDialog).getAllByTestId('dialogAction')[1]; - fireEvent.click(revokeButton); + const confirmButton = within(showDialog).getByTestId('confirmButton'); + fireEvent.click(confirmButton); await waitFor(() => { expect(mock.history.patch.length).toBe(1); expect(mock.history.patch[0].url).toContain('/bff/v1/mandate/111/revoke'); @@ -239,7 +239,7 @@ describe('DelegationElements', async () => { const revoke = menu.querySelectorAll('[role="menuitem"]')[1]; fireEvent.click(revoke); const showDialog = await waitFor(() => screen.getByTestId('confirmationDialog')); - const cancelButton = within(showDialog).getAllByTestId('dialogAction')[0]; + const cancelButton = within(showDialog).getByTestId('closeButton'); fireEvent.click(cancelButton); await waitFor(() => { expect(showDialog).not.toBeInTheDocument(); @@ -261,8 +261,8 @@ describe('DelegationElements', async () => { const reject = menu.querySelectorAll('[role="menuitem"]')[0]; fireEvent.click(reject); const showDialog = await waitFor(() => screen.getByTestId('confirmationDialog')); - const rejectButton = within(showDialog).getAllByTestId('dialogAction')[1]; - fireEvent.click(rejectButton); + const confirmButton = within(showDialog).getByTestId('confirmButton'); + fireEvent.click(confirmButton); await waitFor(() => { expect(mock.history.patch.length).toBe(1); expect(mock.history.patch[0].url).toContain('/bff/v1/mandate/111/reject'); diff --git a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsOfTheCompany.test.tsx b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsOfTheCompany.test.tsx index 91b2f8de84..49ec2f9d03 100644 --- a/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsOfTheCompany.test.tsx +++ b/packages/pn-personagiuridica-webapp/src/components/Deleghe/__test__/DelegationsOfTheCompany.test.tsx @@ -351,9 +351,9 @@ describe('DelegationsOfTheCompany Component', async () => { fireEvent.click(menuItems[0]); const dialog = await waitFor(() => getByTestId('confirmationDialog')); expect(dialog).toBeInTheDocument(); - const dialogAction = within(dialog).getAllByTestId('dialogAction'); + const confirmButton = within(dialog).getByTestId('confirmButton'); // click on confirm button - fireEvent.click(dialogAction[1]); + fireEvent.click(confirmButton); await waitFor(() => { expect(mock.history.patch.length).toBe(1); expect(mock.history.patch[0].url).toContain(