Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.development.local
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ REACT_APP_ONETRUST_DOMAIN_ID=a8f58d7a-7f6a-4fe6-ac02-f95bac3876d4-test
REACT_APP_PAGOPA_HELP_EMAIL=assistenza@selfcare.it
REACT_APP_OPERATOR_EMAIL_ADDRESSES='stefano.bafaro@pagopa.it;'

REACT_APP_JWT=DUMMY
REACT_APP_JWT=DUMMY
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ const SecuredRoutes = withLogin(
<ProtectedRoute
permission="settings"
flagValue="settings-section"
orgCheckCondition={(orgInfo) => orgInfo.types.isEcIPA}
>
<SettingsPage />
</ProtectedRoute>
Expand Down
20 changes: 12 additions & 8 deletions src/components/ProtectedRoute/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import {Redirect} from 'react-router-dom';
import { Redirect } from 'react-router-dom';
import ROUTES from '../../routes';
import {usePermissions} from '../../hooks/usePermissions';
import {PermissionName} from '../../model/RolePermission';
import {useFlagValue} from '../../hooks/useFeatureFlags';
import { usePermissions } from '../../hooks/usePermissions';
import { PermissionName } from '../../model/RolePermission';
import { useFlagValue } from '../../hooks/useFeatureFlags';
import { OrgInfo, useOrganizationType } from '../../hooks/useOrganizationType';

type ProtectedRouteProps = {
permission: PermissionName;
flagValue?: string;
orgCheckCondition?: (orgInfo: OrgInfo) => boolean;
children: JSX.Element;
};
export const ProtectedRoute = ({permission, flagValue = "", children}: ProtectedRouteProps) => {
const {userHasPermission} = usePermissions();
export const ProtectedRoute = ({ permission, flagValue = "", orgCheckCondition = (_) => true, children }: ProtectedRouteProps) => {
const { userHasPermission } = usePermissions();
if (!userHasPermission(permission)) {
console.error(
'Permission error - You do not have permission to perform this action -',
permission
);
}

const { orgInfo } = useOrganizationType();
const featureIsEnabled = useFlagValue(flagValue) || flagValue === "";
return featureIsEnabled && userHasPermission(permission) ? (
const orgCheck = orgCheckCondition(orgInfo);
return featureIsEnabled && userHasPermission(permission) && orgCheck ? (
children
) : (
<Redirect to={ROUTES.HOME}/>
<Redirect to={ROUTES.HOME} />
);
};
24 changes: 24 additions & 0 deletions src/components/ProtectedRoute/__tests__/ProtectedRoute.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ describe('ProtectedRoute component', () => {
jest.spyOn(useFlagValue, 'useFlagValue').mockReturnValue(true);

const {getByText} = render(
<Provider store={store}>
<MemoryRouter initialEntries={['/some-route']}>
<ProtectedRoute permission="node-signin">
<div>Child Component</div>
</ProtectedRoute>
</MemoryRouter>
</Provider>
);

const childComponent = screen.queryByText('Child Component');
Expand All @@ -61,11 +63,33 @@ describe('ProtectedRoute component', () => {
jest.spyOn(useFlagValue, 'useFlagValue').mockReturnValue(false);

const {getByText} = render(
<Provider store={store}>
<MemoryRouter initialEntries={['/some-route']}>
<ProtectedRoute permission="node-signin" flagValue={"node-signin"}>
<div>Child Component</div>
</ProtectedRoute>
</MemoryRouter>
</Provider>
);

const childComponent = screen.queryByText('Child Component');
expect(childComponent).toBeNull();
});

test("should not render children when org check fails", () => {
jest.spyOn(usePermissions, 'usePermissions').mockReturnValue({
userHasPermission: (_) => true,
});
jest.spyOn(useFlagValue, 'useFlagValue').mockReturnValue(true);

render(
<Provider store={store}>
<MemoryRouter initialEntries={['/some-route']}>
<ProtectedRoute permission="node-signin" flagValue={"node-signin"} orgCheckCondition={(_)=>false}>
<div>Child Component</div>
</ProtectedRoute>
</MemoryRouter>
</Provider>
);

const childComponent = screen.queryByText('Child Component');
Expand Down
3 changes: 2 additions & 1 deletion src/components/SideMenu/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { usePermissions } from '../../hooks/usePermissions';
import { userIsPagopaOperator } from '../../hooks/useUserRole';
import { useFlagValue } from '../../hooks/useFeatureFlags';
import { useOrganizationType } from '../../hooks/useOrganizationType';
import { ShowSettingsSection } from '../../pages/settings/utils';
import SidenavItem from './SidenavItem';


Expand Down Expand Up @@ -225,7 +226,7 @@ export default function SideMenu({
/>
)}

{ useFlagValue('settings-section') && orgInfo.types.isEcIPA &&(
{ShowSettingsSection(useFlagValue, userHasPermission, orgInfo) && (
<SidenavItem
collapsed={collapsed}
title={t('sideMenu.settings.title')}
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ export const useFeatureFlags = (): (() => Promise<FeatureFlags>) => {

export const useFlagValue = (name: string): boolean => {
const featureFlags = useAppSelector(featureFlagsSelectors.selectFeatureFlags);
return featureFlags ? featureFlags![name] : false;
return featureFlags ? featureFlags[name] : false;
};
2 changes: 0 additions & 2 deletions src/model/RolePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,7 @@ export const permissions = {
ROLE.EC_OPERATOR,
ROLE.EC_DIRECT_ADMIN,
ROLE.EC_DIRECT_OPERATOR,
ROLE.PT_UNSIGNED,
ROLE.PT_EC_OPERATOR,
ROLE.PT_PSP_OPERATOR,
ROLE.PT_PSPEC_OPERATOR,
ROLE.PAGOPA_OPERATOR,
]
Expand Down
53 changes: 29 additions & 24 deletions src/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {Alert, AlertTitle, Box, Card, Grid, Link, Typography} from '@mui/material';
import {TitleBox} from '@pagopa/selfcare-common-frontend';
import {Trans, useTranslation} from 'react-i18next';
import { useHistory} from 'react-router-dom';
import { Alert, AlertTitle, Box, Card, Grid, Link, Typography } from '@mui/material';
import { TitleBox } from '@pagopa/selfcare-common-frontend';
import { Trans, useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import SideMenuLayout from '../../components/SideMenu/SideMenuLayout';
import {usePermissions} from '../../hooks/usePermissions';
import {useAppSelector} from '../../redux/hooks';
import {partiesSelectors} from '../../redux/slices/partiesSlice';
import { usePermissions } from '../../hooks/usePermissions';
import { useAppSelector } from '../../redux/hooks';
import { partiesSelectors } from '../../redux/slices/partiesSlice';
import ROUTES from '../../routes';
import { ShowSettingsSection } from '../settings/utils';
import { useFlagValue } from '../../hooks/useFeatureFlags';
import { useOrganizationType } from '../../hooks/useOrganizationType';
import DownloadSection from './components/DownloadSection';
import ECRegistrationData from './components/ECRegistrationData';
import NextSteps from './components/NextSteps';
Expand All @@ -15,11 +18,12 @@ import PSPRegistrationData from './components/PSPRegistrationData';
import PTRegistrationData from './components/PTRegistrationData';

const DashboardPage = () => {
const {t} = useTranslation();
const { t } = useTranslation();
const history = useHistory();
const selectedParty = useAppSelector(partiesSelectors.selectPartySelected);
const signinData = useAppSelector(partiesSelectors.selectSigninData);
const {userHasPermission} = usePermissions();
const { orgInfo, orgIsBrokerSigned } = useOrganizationType();
const { userHasPermission } = usePermissions();
return (
<SideMenuLayout>
<TitleBox
Expand All @@ -30,37 +34,38 @@ const DashboardPage = () => {
variantSubTitle="body1"
/>
{history.location.state && (history.location.state as any).alertSuccessMessage && (
<Alert severity="success" variant="outlined" sx={{mb: 4}}>
<Alert severity="success" variant="outlined" sx={{ mb: 4 }}>
{(history.location.state as any).alertSuccessMessage}
</Alert>
)}

<Alert severity="info" sx={{mb: 4}}>
<AlertTitle>{t('dashboardPage.newServiceAlerts.RTP.title')}</AlertTitle>
{t('dashboardPage.newServiceAlerts.RTP.subtitle')}
<Trans
{ShowSettingsSection(useFlagValue, userHasPermission, orgInfo) && (
<Alert severity="info" sx={{ mb: 4 }}>
<AlertTitle>{t('dashboardPage.newServiceAlerts.RTP.title')}</AlertTitle>
{t('dashboardPage.newServiceAlerts.RTP.subtitle')}
<Trans
i18nKey="dashboardPage.newServiceAlerts.RTP.message"
components={{
service_link: (<Link href={(`${ROUTES.SETTINGS}`)} underline="hover" fontWeight="bold"
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
</Link>),
service_link: (<Link href={(`${ROUTES.SETTINGS}`)} underline="hover" fontWeight="bold"
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
</Link>),
}}
/>
</Alert>
</Alert>
)}

<Grid container spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" sx={{border: 0, borderRadius: 0, p: 3, mb: 1}}>
<Card variant="outlined" sx={{ border: 0, borderRadius: 0, p: 3, mb: 1 }}>
<Box mb={3}>
<Typography variant="h6">{t('dashboardPage.registrationData.title')}</Typography>
</Box>
<Grid container spacing={3} pb={4}>
{selectedParty?.institutionType === 'PSP' ? (
<PSPRegistrationData/>
<PSPRegistrationData />
) : selectedParty?.institutionType === 'PT' ? (
<PTRegistrationData/>
<PTRegistrationData />
) : (
<ECRegistrationData/>
<ECRegistrationData />
)}
</Grid>
</Card>
Expand All @@ -72,7 +77,7 @@ const DashboardPage = () => {

{selectedParty &&
userHasPermission('operation-table-read-write') && (
<OperationTable ecCode={selectedParty.fiscalCode}/>
<OperationTable ecCode={selectedParty.fiscalCode} />
)}
</Grid>
</SideMenuLayout>
Expand Down
9 changes: 8 additions & 1 deletion src/pages/settings/components/ServiceSettingsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import { useAppSelector } from '../../../redux/hooks';
import { partiesSelectors } from '../../../redux/slices/partiesSlice';
import { ServiceConsentResponse } from '../../../api/generated/portal/ServiceConsentResponse';
import { ServiceIdEnum } from '../../../api/generated/portal/ServiceConsentInfo';
import { rtpServiceStartingTimestamp, URLS } from './utils';
import { rtpServiceStartingTimestamp, URLS } from '../utils';
import { useUserRole } from '../../../hooks/useUserRole';

export type ServiceInfo = {
serviceId: ServiceIdEnum;
Expand Down Expand Up @@ -88,13 +89,15 @@ const StatusChip = ({ serviceInfo }: ({ serviceInfo: ServiceInfo })) => {

const ServiceButton = ({ serviceInfo, onClick }: ({ serviceInfo: ServiceInfo; onClick: () => void })) => {
const { t } = useTranslation();
const userRole = useUserRole();
if (serviceInfo.consent === ConsentEnum.OPT_IN) {
return (<Button
data-testid={`settingCard-${serviceInfo.serviceId}-disableButton`}
variant="outlined"
startIcon={<DoDisturbAltIcon />}
color="error"
onClick={onClick}
disabled={!userRole.userIsAdmin}
>
{t(`serviceConsent.${serviceInfo.serviceId}.disableButtonText`)}
</Button>);
Expand All @@ -104,6 +107,7 @@ const ServiceButton = ({ serviceInfo, onClick }: ({ serviceInfo: ServiceInfo; on
variant="contained"
endIcon={<ArrowForwardIcon />}
onClick={onClick}
disabled={!userRole.userIsAdmin}
>
{t(`serviceConsent.${serviceInfo.serviceId}.enableButtonText`)}
</Button>);
Expand All @@ -118,6 +122,7 @@ const ServiceStatusChangeModal = ({ serviceInfo, modalOpenFlag, onModalStateChan
const translationRootKey = `serviceConsent.${serviceId}.popups.${isServiceEnabled ? "disableService" : "enableService"}`;
const setLoading = useLoading('PUT_CONSENT');
const selectedParty = useAppSelector(partiesSelectors.selectPartySelected);
const userRole = useUserRole();
const addError = useErrorDispatcher();
return (
<Dialog
Expand Down Expand Up @@ -161,6 +166,7 @@ const ServiceStatusChangeModal = ({ serviceInfo, modalOpenFlag, onModalStateChan
variant="outlined"
color="error"
startIcon={<DoDisturbAltIcon />}
disabled={!userRole.userIsAdmin}
onClick={() => {
setLoading(true);
saveServiceConsent(selectedParty?.partyId || '', serviceId, ConsentEnum.OPT_OUT)
Expand All @@ -179,6 +185,7 @@ const ServiceStatusChangeModal = ({ serviceInfo, modalOpenFlag, onModalStateChan
<Button
data-testid={`settingCard-${serviceId}-dialog-enableButton`}
variant="contained"
disabled={!userRole.userIsAdmin}
onClick={() => {
setLoading(true);
saveServiceConsent(selectedParty?.partyId || '', serviceId, ConsentEnum.OPT_IN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ThemeProvider } from "@mui/system";
import { theme } from "@pagopa/mui-italia";
import { ConsentEnum, ServiceIdEnum } from "../../../../api/generated/portal/ServiceConsentInfo";
import { partiesActions } from "../../../../redux/slices/partiesSlice";
import { ecAdminSignedDirect } from "../../../../services/__mocks__/partyService";
import { ecAdminSignedDirect, ecOperatorSignedDirect } from "../../../../services/__mocks__/partyService";
import i18n, { configureI18n } from "@pagopa/selfcare-common-frontend/locale/locale-utils";
import ita from '../../../../locale/it.json';
import ServiceSettingsCard, { ChipStatus, rtpServiceChipStatusConf, ServiceInfo } from "../ServiceSettingsCard";
Expand All @@ -23,7 +23,7 @@ afterEach(() => {
});

const saveServiceConsentSpy = jest.spyOn(require('../../../../services/institutionService'), 'saveServiceConsent');
const rtpServiceStartingDate = jest.spyOn(require('../utils'), 'rtpServiceStartingTimestamp');
const rtpServiceStartingDate = jest.spyOn(require('../../utils'), 'rtpServiceStartingTimestamp');

const checkElementToBeVisibleWithText = async (dataTestId: string, elementContent: string) => {
const element = await screen.findByTestId(dataTestId);
Expand Down Expand Up @@ -255,4 +255,51 @@ describe('Service setting page card rendering', () => {
jest.useRealTimers();
}
});


it.each([
[ConsentEnum.OPT_IN, true],
[ConsentEnum.OPT_OUT, false]
])('should disable action button for not admin user with consent %s', async (consent: ConsentEnum, serviceEnabled: boolean) => {
// pre-conditions
const consentDate = new Date();
// set consent date to yesterday so that it's evalued as consolidated
consentDate.setHours(-24, 0, 0, 0);
store.dispatch(partiesActions.setPartySelected(ecOperatorSignedDirect));
const serviceInfo: ServiceInfo = {
serviceId: ServiceIdEnum.RTP,
consent: consent,
consentDate: consentDate
}
rtpServiceStartingDate.mockReturnValue(0);
saveServiceConsentSpy.mockReturnValue(getSaveConsentResponseMock(consent));
// render page
render(
<Provider store={store}>
<MemoryRouter initialEntries={["/ui/settings"]}>
<ThemeProvider theme={theme}>
<ServiceSettingsCard
serviceId={serviceInfo.serviceId}
consent={serviceInfo.consent}
consentDate={serviceInfo.consentDate}
/>
</ThemeProvider>
</MemoryRouter>
</Provider>
)
// assertions

await act(async () => {
let serviceActionButton;
// search for service enable/disable button
if (serviceEnabled) {
serviceActionButton = await screen.findByTestId(`settingCard-${serviceInfo.serviceId}-disableButton`);
} else {
serviceActionButton = await screen.findByTestId(`settingCard-${serviceInfo.serviceId}-enableButton`);
}
// check that service enable/disable button is visible but disabled
expect(serviceActionButton).toBeVisible();
expect(serviceActionButton).toBeDisabled();
});
});
});
7 changes: 0 additions & 7 deletions src/pages/settings/components/utils.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/pages/settings/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { OrgInfo } from "../../hooks/useOrganizationType";
import { PermissionName } from "../../model/RolePermission";
import { ENV } from "../../utils/env";

export const rtpServiceStartingTimestamp = (): number => ENV.SETTINGS.SERVICES.RTP.SERVICE_STARTING_DATE.getTime();
export const URLS = {
SANP_URL: ENV.SETTINGS.SERVICES.SANP_URL,
RTP_OVERVIEW_URL: ENV.SETTINGS.SERVICES.RTP_OVERVIEW_URL,
};

export const ShowSettingsSection = (useFlagValue:((name: string) => boolean), userHasPermission:((permissionName: PermissionName)=>boolean) , orgInfo:OrgInfo): boolean => useFlagValue('settings-section') && userHasPermission("settings") && orgInfo.types.isEcIPA;
Loading
Loading