Untitled

mail@pastecode.io avatar
unknown
plain_text
2 years ago
9.0 kB
3
Indexable
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('  '),
});