From f07ed08f879b464b10262b4131dd54eda0502787 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 | 34 +++++++ .../src/data/hooks/rgdp/useRGDP.tsx | 46 +++++++++ .../disableMFA/create/form/Form.page.tsx | 30 +++++- .../create/form/constants/form.constants.tsx | 1 - .../rgdp/rgdpForm/RGDPForm.component.tsx | 95 +++++++++++++++++-- .../src/pages/rgdp/rgdpForm/RGDPForm.test.tsx | 75 ++++++++++++++- .../translations/rgdp/Messages_fr_FR.json | 14 ++- 9 files changed, 308 insertions(+), 42 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..bf04a7e354fd --- /dev/null +++ b/packages/manager/apps/procedures/src/data/api/rgdp/rgdpApi.ts @@ -0,0 +1,34 @@ +import { FileWithError } from '@/components/FileInput/FileInputContainer'; +import { GDPRFormValues } from '@/types/gdpr.type'; + +export type UploadLink = { + link: string; + method: string; + headers: any; +}; + +export type FormValue = { + fileLinks: UploadLink[]; + formData: Omit< + GDPRFormValues, + 'idDocumentFront' | 'idDocumentBack' | 'otherDocuments' + >; + files: FileWithError[]; +}; +export const sendForm = (value: FormValue): Promise => + new Promise((res) => { + setTimeout(() => { + res(); + }, 500); + }); + +export const getUploadDocumentsLinks = ( + numberOfDocuments: number, +): Promise => { + return new Promise((res) => { + setTimeout(() => { + res([]); + console.log('value', numberOfDocuments); + }, 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..2820498df33b --- /dev/null +++ b/packages/manager/apps/procedures/src/data/hooks/rgdp/useRGDP.tsx @@ -0,0 +1,46 @@ +import { useMutation } from '@tanstack/react-query'; +import { + sendForm, + UploadLink, + getUploadDocumentsLinks, + FormValue, +} from '@/data/api/rgdp/rgdpApi'; + +export const useRGDPUploadLinks = ({ + onSuccess, + onError, +}: { + onSuccess: (links: UploadLink[]) => void; + onError: () => void; +}) => + useMutation({ + mutationFn: (numberOfDocuments: number) => + getUploadDocumentsLinks(numberOfDocuments), + onSuccess: (links) => { + onSuccess?.(links); + }, + onError: () => { + onError?.(); + }, + }); + +export const useRGDPSendForm = ({ + onSuccess, + onError, +}: { + onSuccess: () => void; + onError: () => void; +}) => + useMutation({ + mutationFn: (value: FormValue) => { + return sendForm(value); + }, + 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..c8b71b4ad581 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,19 @@ import { } from './RGDPForm.constants'; import './RGDPForm.style.css'; import { FileField } from './FileField/FileField.component'; +import { useRGDPSendForm, useRGDPUploadLinks } 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 +49,49 @@ 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: sendForm, + isPending: isPendingSendForm, + isError: isErrorSendForm, + } = useRGDPSendForm({ + onSuccess: () => { + setShowSuccessModal(true); + setShowConfirmModal(false); + }, + onError: () => { + setShowConfirmModal(false); + }, + }); + const { + mutate: uploadLink, + isPending: isPendingUploadLink, + isError: isErrorUploadLink, + } = useRGDPUploadLinks({ + onSuccess: (links) => { + const data = watch(); + sendForm({ formData: data, fileLinks: links, files: extractFiles(data) }); + }, + onError: () => { + setShowConfirmModal(false); + }, + }); + + const isPending = isPendingUploadLink || isPendingSendForm; + const isError = isErrorUploadLink || isErrorSendForm; + + const onSubmitForm = () => { + const data = watch(); + const files = extractFiles(data); + const filesCount = files.length; + + if (filesCount > 0) { + uploadLink(filesCount); + } else { + sendForm({ formData: data, fileLinks: [], files }); + } }; const email = watch('email'); @@ -46,7 +103,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..994a98a76919 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 @@ -2,7 +2,9 @@ import { render, screen, act, waitFor } 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 * as ConfirmModalModule from '@/components/modals/confirmModal/ConfirmModal.component'; import { RGDPForm } from './RGDPForm.component'; import { GDPRFormValues } from '@/types/gdpr.type'; @@ -32,8 +34,27 @@ vi.mock('@ovhcloud/ods-components/react', async (importOriginal) => { }; }); +vi.mock( + '@/components/modals/confirmModal/ConfirmModal.component', + async (importOriginal) => { + const module: typeof ConfirmModalModule = await importOriginal(); + return { + ConfirmModal: ({ ...props }: any) => ( + + ), + }; + }, +); + describe('RGDPForm', () => { - const renderForm = () => render(); + const renderForm = () => { + const queryClient = new QueryClient(); + return render( + + + , + ); + }; it('Should render the form fields correctly when the form is displayed', async () => { const { getByText } = renderForm(); @@ -166,4 +187,54 @@ describe('RGDPForm', () => { ).toBeInTheDocument(); }); }); + + it('Should show confirmModal when...', async () => { + const { getByRole } = renderForm(); + + const surnameInput = getOsdsElementByFormName('surname'); + const firstNameInput = getOsdsElementByFormName('firstName'); + const requestDescriptionText = getOsdsElementByFormName( + 'requestDescription', + ); + const emailInput = getOsdsElementByFormName('email'); + const confirmEmailInput = getOsdsElementByFormName( + 'confirmEmail', + ); + const objectSelect = getOsdsElementByFormName('messageSubject'); + + const submitBtn = getByRole('button', { name: 'rgdp_form_submit' }); + + act(() => { + surnameInput.value = 'name'; + firstNameInput.value = 'firstNameInput'; + requestDescriptionText.value = 'des'; + emailInput.value = 'ovh@internet.com'; + confirmEmailInput.value = 'ovh@internet.com'; + objectSelect.value = 'other_request'; + + surnameInput.odsValueChange.emit(); + firstNameInput.odsValueChange.emit(); + requestDescriptionText.odsValueChange.emit(); + emailInput.odsValueChange.emit(); + confirmEmailInput.odsValueChange.emit(); + objectSelect.odsValueChange.emit(); + + surnameInput.odsInputBlur.emit(); + firstNameInput.odsInputBlur.emit(); + requestDescriptionText.odsBlur.emit(); + emailInput.odsInputBlur.emit(); + confirmEmailInput.odsInputBlur.emit(); + objectSelect.odsBlur.emit(); + }); + + act(() => { + submitBtn.click(); + }); + + await waitFor(() => { + const confirmModal = screen.queryByTestId('confirmModal'); + expect(confirmModal).toBeInTheDocument(); + expect(submitBtn).not.toBeDisabled(); + }); + }); }); diff --git a/packages/manager/apps/procedures/src/public/translations/rgdp/Messages_fr_FR.json b/packages/manager/apps/procedures/src/public/translations/rgdp/Messages_fr_FR.json index 8710fecd4d1c..31d58a8e60e2 100644 --- a/packages/manager/apps/procedures/src/public/translations/rgdp/Messages_fr_FR.json +++ b/packages/manager/apps/procedures/src/public/translations/rgdp/Messages_fr_FR.json @@ -39,5 +39,17 @@ "rgdp_form_field_label_id_back": "Pièce d'identité (verso)", "rgdp_form_field_label_other_documents": "Autre document nécessaire pour la demande", "rgdp_form_field_helper_id": "Carte nationale d'identité, passeport, carte de séjour ou permis de conduire.", - "rgdp_form_field_helper_other_documents": "Le nombre maximal de documents supporté est de {{maxFiles}}." + "rgdp_form_field_helper_other_documents": "Le nombre maximal de documents supporté est de {{maxFiles}}.", + + "rgpd_confirm_modal_yes": "Oui", + "rgpd_confirm_modal_no": "Non", + "rgpd_confirm_modal_title": "Envoyer mes documents", + "rgpd_confirm_modal_description_insure": "Veuillez vous assurer que tous vos documents sont corrects et lisibles avant envoi. En cas de documents non valides, cette procédure sera annulée et vous devrez en effectuer une nouvelle.", + "rgpd_confirm_modal_description_confirm": "Confirmez-vous avoir ajouté l'ensemble des éléments nécessaires ?", + + "rgpd_confirm_success_modal_title": "Merci, nous avons bien reçu tous vos documents !", + "rgpd_confirm_success_modal_description": "Ils vont être soumis à notre équipe pour validation. Notre équipe support vous répond dans les 72 heures ! Vous serez notifié de leur validation par email, ou vous serez contacté si nous avons besoin de plus d'informations.", + "rgpd_confirm_success_modal_back_home": "Retour à la page d'accueil", + + "rgdp_form_error_message_submit": "Vos documents n'ont pas été correctement envoyés. Veuillez relancer l'envoi de vos documents." }