From db9e8de2b129d89f2898976142c53fe41a8db811 Mon Sep 17 00:00:00 2001 From: Omar ALKABOUSS MOUSSANA Date: Thu, 17 Oct 2024 09:52:25 +0200 Subject: [PATCH] feat(procedures): implement gdpr document upload and confirmation ref: MANAGER-15368 Signed-off-by: Omar ALKABOUSS MOUSSANA --- .../confirmModal/ConfirmModal.component.tsx} | 32 +++---- .../successModal/SuccessModal.component.tsx} | 23 ++--- .../procedures/src/data/api/rgdp/rgdpApi.ts | 47 +++++++++ .../src/data/hooks/rgdp/useRGDP.tsx | 51 ++++++++++ .../disableMFA/create/form/Form.page.tsx | 30 +++++- .../create/form/constants/form.constants.tsx | 1 - .../rgdp/rgdpForm/RGDPForm.component.tsx | 96 +++++++++++++++++-- .../src/pages/rgdp/rgdpForm/RGDPForm.test.tsx | 80 ++++++++++++++-- .../translations/rgdp/Messages_fr_FR.json | 14 ++- 9 files changed, 328 insertions(+), 46 deletions(-) rename packages/manager/apps/procedures/src/{pages/disableMFA/create/form/Modal/ConfirmModal.tsx => components/modals/confirmModal/ConfirmModal.component.tsx} (78%) rename packages/manager/apps/procedures/src/{pages/disableMFA/create/form/Modal/SuccessModal.tsx => components/modals/successModal/SuccessModal.component.tsx} (76%) create mode 100644 packages/manager/apps/procedures/src/data/api/rgdp/rgdpApi.ts create mode 100644 packages/manager/apps/procedures/src/data/hooks/rgdp/useRGDP.tsx delete mode 100644 packages/manager/apps/procedures/src/pages/disableMFA/create/form/constants/form.constants.tsx diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/ConfirmModal.tsx b/packages/manager/apps/procedures/src/components/modals/confirmModal/ConfirmModal.component.tsx similarity index 78% rename from packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/ConfirmModal.tsx rename to packages/manager/apps/procedures/src/components/modals/confirmModal/ConfirmModal.component.tsx index 7b879322f4fa..34da8325cd4d 100644 --- a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/ConfirmModal.tsx +++ b/packages/manager/apps/procedures/src/components/modals/confirmModal/ConfirmModal.component.tsx @@ -22,23 +22,25 @@ type Props = { isPending: boolean; onClose: () => void; onValidate: () => void; + title: string; + descriptionInsure: string; + descriptionConfirm: string; + noButtonLabel: string; + yesButtonLabel: string; }; export const ConfirmModal: FunctionComponent = ({ onClose, onValidate, isPending, + title, + descriptionInsure, + descriptionConfirm, + noButtonLabel, + yesButtonLabel, }) => { - const { t } = useTranslation('account-disable-2fa'); - return ( - +
= ({ className="block" hue={ODS_TEXT_COLOR_HUE._500} > - {t( - 'account-disable-2fa-create-form-confirm-modal-send-document-description-insure', - )} + {descriptionInsure} = ({ className="block mt-2" hue={ODS_TEXT_COLOR_HUE._500} > - {t( - 'account-disable-2fa-create-form-confirm-modal-send-document-description-confirm', - )} + {descriptionConfirm}
@@ -84,7 +82,7 @@ export const ConfirmModal: FunctionComponent = ({ color={ODS_THEME_COLOR_INTENT.primary} onClick={onClose} > - {t('account-disable-2fa-confirm-modal-no')} + {noButtonLabel} = ({ variant={ODS_BUTTON_VARIANT.flat} color={ODS_THEME_COLOR_INTENT.primary} > - {t('account-disable-2fa-confirm-modal-yes')} + {yesButtonLabel} )} diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/SuccessModal.tsx b/packages/manager/apps/procedures/src/components/modals/successModal/SuccessModal.component.tsx similarity index 76% rename from packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/SuccessModal.tsx rename to packages/manager/apps/procedures/src/components/modals/successModal/SuccessModal.component.tsx index 12c3e5f7b03e..208108c18194 100644 --- a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Modal/SuccessModal.tsx +++ b/packages/manager/apps/procedures/src/components/modals/successModal/SuccessModal.component.tsx @@ -13,16 +13,21 @@ import { OsdsModal, OsdsText, } from '@ovhcloud/ods-components/react'; -import { useTranslation } from 'react-i18next'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; type Props = { + title: string; + description: string; ovhHomePageHref: string; + ovhHomePageLabel: string; }; -export const SuccessModal: FunctionComponent = ({ ovhHomePageHref }) => { - const { t } = useTranslation('account-disable-2fa'); - +export const SuccessModal: FunctionComponent = ({ + ovhHomePageHref, + title, + description, + ovhHomePageLabel, +}) => { const gotToHomePage = () => { window.location.href = ovhHomePageHref; }; @@ -43,9 +48,7 @@ export const SuccessModal: FunctionComponent = ({ ovhHomePageHref }) => { className="block font-bold" hue={ODS_TEXT_COLOR_HUE._700} > - {t( - 'account-disable-2fa-create-form-success-modal-send-document-title', - )} + {title} = ({ ovhHomePageHref }) => { className="block mt-2" hue={ODS_TEXT_COLOR_HUE._500} > - {t( - 'account-disable-2fa-create-form-success-modal-send-document-description', - )} + {description} = ({ ovhHomePageHref }) => { color={ODS_THEME_COLOR_INTENT.primary} onClick={gotToHomePage} > - {t('account-disable-2fa-success-modal-back-home')} + {ovhHomePageLabel}
); diff --git a/packages/manager/apps/procedures/src/data/api/rgdp/rgdpApi.ts b/packages/manager/apps/procedures/src/data/api/rgdp/rgdpApi.ts new file mode 100644 index 000000000000..6ab99402e9c0 --- /dev/null +++ b/packages/manager/apps/procedures/src/data/api/rgdp/rgdpApi.ts @@ -0,0 +1,47 @@ +import { FileWithError } from '@/components/FileInput/FileInputContainer'; +import { GDPRFormValues } from '@/types/gdpr.type'; + +export type UploadLink = { + link: string; + method: string; + headers: any; +}; + +export type GetUploadDocumentsLinks = { + numberOfDocuments: number; + formData: Omit< + GDPRFormValues, + 'idDocumentFront' | 'idDocumentBack' | 'otherDocuments' + >; +}; + +export type UploadDocuments = { + fileLinks: UploadLink[]; + files: FileWithError[]; +}; + +export const getUploadDocumentsLinks = ({ + formData, + numberOfDocuments, +}: GetUploadDocumentsLinks): Promise => { + return new Promise((res) => { + setTimeout(() => { + res([]); + console.log('value', numberOfDocuments, formData); + }, 500); + }); +}; + +export const uploadDocuments = (documents: UploadDocuments): Promise => + new Promise((res) => { + setTimeout(() => { + res(); + }, 500); + }); + +export const finalize = (): Promise => + new Promise((res) => { + setTimeout(() => { + res(); + }, 500); + }); diff --git a/packages/manager/apps/procedures/src/data/hooks/rgdp/useRGDP.tsx b/packages/manager/apps/procedures/src/data/hooks/rgdp/useRGDP.tsx new file mode 100644 index 000000000000..d60a91775e59 --- /dev/null +++ b/packages/manager/apps/procedures/src/data/hooks/rgdp/useRGDP.tsx @@ -0,0 +1,51 @@ +import { useMutation } from '@tanstack/react-query'; +import { + UploadLink, + getUploadDocumentsLinks, + GetUploadDocumentsLinks, + UploadDocuments, + uploadDocuments, + finalize, +} from '@/data/api/rgdp/rgdpApi'; + +export const useRGDPSendForm = ({ + onSuccess, + onError, +}: { + onSuccess: (links: UploadLink[]) => void; + onError: () => void; +}) => + useMutation({ + mutationFn: (data: GetUploadDocumentsLinks) => + getUploadDocumentsLinks(data), + onSuccess: (links) => { + onSuccess?.(links); + }, + onError: () => { + onError?.(); + }, + }); + +export const useRGDPUploadDocuments = ({ + onSuccess, + onError, +}: { + onSuccess: () => void; + onError: () => void; +}) => + useMutation({ + mutationFn: async (documents: UploadDocuments) => { + if (documents.fileLinks.length > 0) { + await uploadDocuments(documents); + } + await finalize(); + }, + onSuccess: () => { + onSuccess?.(); + }, + onError: () => { + onError?.(); + }, + retry: 1, + retryDelay: 3000, + }); diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Form.page.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Form.page.tsx index 27c4b5794639..eafef16f7243 100644 --- a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Form.page.tsx +++ b/packages/manager/apps/procedures/src/pages/disableMFA/create/form/Form.page.tsx @@ -22,9 +22,9 @@ import { FormDocumentFieldList } from './FormDocumentFields/FormDocumentFieldLis import { LegalFrom } from '@/types/user.type'; import useUser from '@/context/User/useUser'; import { useUploadDocuments } from '@/data/hooks/useDocuments'; -import { ConfirmModal } from './Modal/ConfirmModal'; -import { SuccessModal } from './Modal/SuccessModal'; -import { ovhHomePageHref } from './constants/form.constants'; +import { getWebSiteRedirectUrl } from '@/utils/url-builder'; +import { ConfirmModal } from '@/components/modals/confirmModal/ConfirmModal.component'; +import { SuccessModal } from '@/components/modals/successModal/SuccessModal.component'; const flatFiles = (files: FieldValues) => Object.values(files) @@ -132,6 +132,17 @@ const FormCreateRequest = () => { {showConfirmModal && ( setShowConfirmModal(false)} onValidate={() => { @@ -139,7 +150,18 @@ const FormCreateRequest = () => { }} /> )} - {showSuccessModal && } + {showSuccessModal && ( + + )} ); }; diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/constants/form.constants.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/create/form/constants/form.constants.tsx deleted file mode 100644 index f132261c5f06..000000000000 --- a/packages/manager/apps/procedures/src/pages/disableMFA/create/form/constants/form.constants.tsx +++ /dev/null @@ -1 +0,0 @@ -export const ovhHomePageHref = 'https://ovhcloud.com'; diff --git a/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx index 639e781e8854..15b9e4e8abdd 100644 --- a/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx +++ b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx @@ -3,8 +3,12 @@ import { ODS_THEME_TYPOGRAPHY_LEVEL, } from '@ovhcloud/ods-common-theming'; import { ODS_BUTTON_TYPE, ODS_TEXT_SIZE } from '@ovhcloud/ods-components'; -import { OsdsButton, OsdsText } from '@ovhcloud/ods-components/react'; -import React, { FunctionComponent, useEffect } from 'react'; +import { + OsdsButton, + OsdsMessage, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import React, { FunctionComponent, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { GDPRFormValues } from '@/types/gdpr.type'; @@ -21,6 +25,22 @@ import { } from './RGDPForm.constants'; import './RGDPForm.style.css'; import { FileField } from './FileField/FileField.component'; +import { + useRGDPSendForm, + useRGDPUploadDocuments, +} from '@/data/hooks/rgdp/useRGDP'; +import { ConfirmModal } from '@/components/modals/confirmModal/ConfirmModal.component'; +import { SuccessModal } from '@/components/modals/successModal/SuccessModal.component'; +import { getWebSiteRedirectUrl } from '@/utils/url-builder'; +import { FileWithError } from '@/components/FileInput/FileInputContainer'; + +const extractFiles = (formValue: GDPRFormValues): FileWithError[] => { + return [ + formValue.idDocumentBack, + formValue.idDocumentFront, + formValue.otherDocuments, + ].flatMap((file) => (file ? [file] : [])) as any[]; +}; export const RGDPForm: FunctionComponent = () => { const { t } = useTranslation('rgdp'); @@ -32,9 +52,47 @@ export const RGDPForm: FunctionComponent = () => { trigger, } = useForm({ mode: 'onBlur' }); - const onSubmit = (data: GDPRFormValues) => { - // TODO: Handle API call & ConfirmModal - console.log(data); + const [showConfirmModal, setShowConfirmModal] = useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); + + const { + mutate: uploadDocuments, + isPending: isPendingUpload, + isError: isErrorUpload, + } = useRGDPUploadDocuments({ + onSuccess: () => { + setShowSuccessModal(true); + setShowConfirmModal(false); + }, + onError: () => { + setShowConfirmModal(false); + }, + }); + + const { + mutate: sendForm, + isPending: isPendingSendForm, + isError: isErrorSendForm, + } = useRGDPSendForm({ + onSuccess: (links) => { + const data = watch(); + const files = extractFiles(data); + uploadDocuments({ fileLinks: links, files }); + }, + onError: () => { + setShowConfirmModal(false); + }, + }); + + const isPending = isPendingUpload || isPendingSendForm; + const isError = isErrorUpload || isErrorSendForm; + + const onSubmitForm = () => { + const data = watch(); + const files = extractFiles(data); + const filesCount = files.length; + + sendForm({ formData: data, numberOfDocuments: filesCount }); }; const email = watch('email'); @@ -46,7 +104,7 @@ export const RGDPForm: FunctionComponent = () => { }, [email]); return ( -
+ setShowConfirmModal(true))} noValidate>
{ helper={t('rgdp_form_field_helper_other_documents', { maxFiles: 8 })} />
+ {isError && ( + + {t('rgdp_form_error_message_submit')} + + )} { > {t('rgdp_form_submit')} + + {showConfirmModal && ( + setShowConfirmModal(false)} + onValidate={onSubmitForm} + /> + )} + {showSuccessModal && ( + + )} ); }; diff --git a/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx index 692f4b03f905..b678fafb81da 100644 --- a/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx +++ b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx @@ -1,8 +1,15 @@ -import { render, screen, act, waitFor } from '@testing-library/react'; +import { + render, + screen, + act, + waitFor, + fireEvent, +} from '@testing-library/react'; import React from 'react'; import { describe, it, expect, vi } from 'vitest'; import * as OdsComponentModule from '@ovhcloud/ods-components/react'; -import { OsdsInput, OsdsTextArea } from '@ovhcloud/ods-components'; +import { OsdsInput, OsdsSelect, OsdsTextArea } from '@ovhcloud/ods-components'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { RGDPForm } from './RGDPForm.component'; import { GDPRFormValues } from '@/types/gdpr.type'; @@ -22,7 +29,13 @@ vi.mock('@ovhcloud/ods-components/react', async (importOriginal) => { OsdsInput: ({ ...props }: any) => ( ), - OsdsButton: ({ ...props }: any) =>