import { ApolloClient, useQuery } from '@apollo/client';
import * as React from 'react';
import { createSelector } from 'reselect';
import {
EReputationProvider,
WorkflowState,
MainFrameProps,
Permission,
User,
WorkflowProgressBar,
} from '../../../../src/contexts/mainframe/_module_/types';
import callAPI2, { CallAPIResultType } from '../../../server/callAPI2';
import LoadingSpinner from '../../../components/LoadingSpinner';
import { fileAPIConfigURI } from '../../../withFileAPI';
import { LOGOUT_URL } from '../../../withAuth';
import Alert from '../../../components/Alert';
import AsyncCallManager from '../../../asyncHelper';
import { initializeI18n } from '../../../i18n';
import { NNU } from '../../../helpers';
import { AlertParams, MainFrameContext } from './withMainFrameContext';
import SettingsQuery from './Settings.graphql';
interface UserSettingsData {
currentUser: User & { primaryValidatingUser: User | null };
permissions: Permission[];
stakeholderPermissions: Permission[];
eReputationAlertsPermissions: Permission[];
eReputationPermissions: Permission[];
questionnairePermissions: Permission[];
documentsStoragePermissions: Permission[];
folderFilePermissions: Permission[];
parameters: {
languageTag: string;
languageTexts: {
content: string;
};
documentationUrl: string | null;
beneficiaryOwnerThreshold: number;
eReputationProvider: EReputationProvider;
};
features: {
corpMasterDataIntegration: boolean;
personMasterDataIntegration: boolean;
dccpAnalysis: boolean;
dccpFollowUp: boolean;
folderRiskPeriodicUpdate: boolean;
folderStatus: boolean;
eReputationAlerts: boolean;
votingRights: boolean;
standaloneEreputation: boolean;
questionnaires: boolean;
customerSubsidiaries: boolean;
folderFileTag: boolean;
downloadDocsFromKyc: boolean;
documentPortal: boolean;
factivaContentIntegration: boolean;
triggerExternalFolderFileAction: boolean;
recreateCurrentStakeholderRoles: boolean;
countryRiskMap: boolean;
transformAttachmentIntoDocument: boolean;
groupFilesByFamily: boolean;
monitoring: boolean;
beneficialOwners: boolean;
displayKycReviewStatus: boolean;
};
workflow: {
states: WorkflowState[];
progressBars: WorkflowProgressBar[];
};
}
export interface Props {
children: (props: MainFrameProps) => React.ReactElement<MainFrameProps>;
}
interface State {
alertParams: AlertParams | null;
isLoading: number;
documentServiceError: boolean;
documentServiceLoading: boolean;
isInitialized: boolean;
documentServiceConfig: {
maxContentLength: number;
mimeTypes: string;
extensions: string;
} | null;
}
export interface MimeType {
[mimeTypeElement: string]: string[];
}
const MainFrameContainer: React.FC<Props> = (props) => {
const [state, setState] = React.useState<State>({
alertParams: null,
isLoading: 0,
documentServiceError: false,
documentServiceLoading: true,
isInitialized: false,
documentServiceConfig: null,
});
const asyncOperations = React.useMemo(() => new AsyncCallManager(), []);
// Specifies if the document service config is loading.
// This is used to prevent making multiple calls to the document service config endpoint.
// `state.documentServiceLoading` is not sufficient because if we requests were made during one rerender
// then the `state.documentServiceLoading` will be updated only after all calls have been already made.
const isDocumentServiceLoading = React.useRef(false);
const fetchDocumentServiceConfig = React.useCallback(() => {
isDocumentServiceLoading.current = true;
setState((state) => ({ ...state, documentServiceLoading: true }));
asyncOperations.execute({
operation: async () => {
const response = await makeGetDocumentsConfigRequest();
if (response.type === CallAPIResultType.JSON_SUCCESS) {
return mapDocumentConfig(response.body);
}
throw new Error('Unexpected response received from file API');
},
success: (documentServiceConfig) => {
(isDocumentServiceLoading.current = false),
setState((state) => ({
...state,
documentServiceLoading: false,
isInitialized: true,
documentServiceConfig,
})),
() => (isDocumentServiceLoading.current = true);
},
error: () => {
isDocumentServiceLoading.current = false;
setState((state) => ({
...state,
documentServiceError: true,
isInitialized: true,
documentServiceLoading: false,
}));
},
});
}, [asyncOperations]);
const userLogoutRequested = async () => {
// logout from the server
await callAPI2(LOGOUT_URL);
};
const userAuthenticationExpired = async () => {
// reload the current page to re-authenticate
window.location.reload();
};
React.useEffect(() => {
fetchDocumentServiceConfig();
return () => asyncOperations.ignoreResults();
}, [asyncOperations, fetchDocumentServiceConfig]);
const { loading, error, data, client } = useQuery<UserSettingsData>(SettingsQuery, {
nextFetchPolicy: 'cache-only',
});
const getMainframeContext = createSelector(
(userSettingsData: UserSettingsData) => userSettingsData,
(_: UserSettingsData, documentServiceConfig: State['documentServiceConfig']) =>
documentServiceConfig,
(_: UserSettingsData, __: State['documentServiceConfig'], client: ApolloClient<any>) => client,
(userSettingsData, documentServiceConfig, client): MainFrameContext => {
const userSettings = NNU(userSettingsData);
const alert = (alertParams: AlertParams) => setState((state) => ({ ...state, alertParams }));
const updateDocumentsServiceConfig = () => {
if (state.documentServiceLoading || isDocumentServiceLoading.current) return;
fetchDocumentServiceConfig();
};
return {
userSettings: {
...userSettings,
parameters: {
...userSettings.parameters,
documentServiceConfig,
},
},
alert,
updateDocumentsServiceConfig,
isLoading: (status) =>
setState((state) => ({ ...state, isLoading: state.isLoading + (status ? 1 : -1) })),
mutate: (options, errorAlertOptions, updatingCallback?: () => void) =>
asyncOperations.execute({
operation: () => client.mutate(options),
error: () => alert(errorAlertOptions),
finally: () => updatingCallback && updatingCallback(),
}),
};
}
);
if (error) {
return (
<div
data-notification
className='main-frame-container-error bx--inline-notification bx--inline-notification--warning'
role='alert'
>
<div className='bx--inline-notification__details'>
<div className='bx--inline-notification__text-wrapper'>
<p className='bx--inline-notification__title'>
Erreur d'initialisation de l'application
</p>
<p className='bx--inline-notification__subtitle'>Veuillez réessayer</p>
</div>
</div>
</div>
);
}
if (loading || (state.documentServiceLoading && !state.isInitialized)) {
return <LoadingSpinner className='main-frame-container-loading' spinnerSize='fa-2x' />;
}
const userSettings = NNU(data);
initializeI18n(
JSON.parse(userSettings.parameters.languageTexts.content),
userSettings.parameters.languageTag
);
return (
<div className='main-frame-container'>
<MainFrameContext.Provider
value={getMainframeContext(NNU(data), state.documentServiceConfig, client)}
>
{props.children({
userLogoutRequested,
userAuthenticationExpired,
})}
</MainFrameContext.Provider>
{state.alertParams && (
<Alert
{...state.alertParams}
dismissed={() => setState((state) => ({ ...state, alertParams: null }))}
/>
)}
{state.isLoading > 0 && <LoadingSpinner className='main-frame-loading-spinner' />}
</div>
);
};
export default MainFrameContainer;
export const parseMimeType = (mime: MimeType) =>
Object.entries(mime)
.map(([type, extension]) =>
type.startsWith('application/x-tika') ? `.${extension.toString()}` : type
)
.toString();
interface RemoteDocumentsConfig {
maxContentLength: number;
mimeTypes: MimeType;
}
const makeGetDocumentsConfigRequest = () => {
return callAPI2<RemoteDocumentsConfig>(fileAPIConfigURI);
};
const mapDocumentConfig = (remoteConfig: RemoteDocumentsConfig) => ({
maxContentLength: remoteConfig.maxContentLength,
mimeTypes: parseMimeType(remoteConfig.mimeTypes),
extensions: Object.values(remoteConfig.mimeTypes)
.reduce<string[]>((acc, extensions) => [...acc, ...extensions], [])
.map((e) => `.${e}`)
.join(' '),
});