From 364c641695339591da3f3d8d788568303c30cb6f Mon Sep 17 00:00:00 2001 From: Omar ALKABOUSS MOUSSANA Date: Wed, 9 Oct 2024 11:31:20 +0200 Subject: [PATCH 1/2] feat(procedures): implemented gdpr form ref:MANAGER-15317 Signed-off-by: Omar ALKABOUSS MOUSSANA --- .../manager/apps/container/tailwind.config.js | 3 + .../components/Select/Select.component.tsx | 83 +++++++++ .../TextArea/TextArea.component.tsx | 62 +++++++ .../TextInput/TextInput.component.tsx | 67 +++++++ .../src/pages/rgdp/RGDP.page.page.test.tsx | 7 + .../procedures/src/pages/rgdp/RGDP.page.tsx | 2 + .../rgdp/rgdpForm/RGDPForm.component.tsx | 147 +++++++++++++++ .../pages/rgdp/rgdpForm/RGDPForm.constants.ts | 13 ++ .../src/pages/rgdp/rgdpForm/RGDPForm.test.tsx | 169 ++++++++++++++++++ .../SelectField/SelectField.component.tsx | 60 +++++++ .../TextAreaField/TextAreaField.component.tsx | 54 ++++++ .../TextField/TextField.component.tsx | 56 ++++++ .../translations/rgdp/Messages_fr_FR.json | 25 ++- .../apps/procedures/src/types/gdpr.type.ts | 10 ++ .../apps/procedures/tailwind.config.js | 3 + 15 files changed, 760 insertions(+), 1 deletion(-) create mode 100644 packages/manager/apps/procedures/src/components/Select/Select.component.tsx create mode 100644 packages/manager/apps/procedures/src/components/TextArea/TextArea.component.tsx create mode 100644 packages/manager/apps/procedures/src/components/TextInput/TextInput.component.tsx create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.constants.ts create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/SelectField/SelectField.component.tsx create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/TextAreaField/TextAreaField.component.tsx create mode 100644 packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/TextField/TextField.component.tsx create mode 100644 packages/manager/apps/procedures/src/types/gdpr.type.ts diff --git a/packages/manager/apps/container/tailwind.config.js b/packages/manager/apps/container/tailwind.config.js index d54ec991a400..14998255815f 100644 --- a/packages/manager/apps/container/tailwind.config.js +++ b/packages/manager/apps/container/tailwind.config.js @@ -7,4 +7,7 @@ module.exports = { './src/**/*.{js,jsx,ts,tsx}', '../../../manager-react-components/src/**/*.{js,jsx,ts,tsx}', ], + corePlugins: { + preflight: false, + }, }; diff --git a/packages/manager/apps/procedures/src/components/Select/Select.component.tsx b/packages/manager/apps/procedures/src/components/Select/Select.component.tsx new file mode 100644 index 000000000000..297587119b3c --- /dev/null +++ b/packages/manager/apps/procedures/src/components/Select/Select.component.tsx @@ -0,0 +1,83 @@ +import { + ODS_INPUT_TYPE, + OdsInputValueChangeEventDetail, + OdsSelectValueChangeEventDetail, + OsdsInputCustomEvent, + OsdsSelectCustomEvent, +} from '@ovhcloud/ods-components'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, +} from '@ovhcloud/ods-common-theming'; +import { + OsdsFormField, + OsdsSelect, + OsdsSelectOption, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import React, { FunctionComponent } from 'react'; + +export type SelectOption = { + label: string; + value: string; +}; + +type Props = { + label?: string; + value?: string; + onChange?: ( + event: OsdsSelectCustomEvent, + ) => void; + onBlur?: React.FocusEventHandler; + name: string; + id: string; + error?: string; + required?: boolean; + placeholder?: string; + options: SelectOption[]; +}; + +export const Select: FunctionComponent = ({ + label, + error, + id, + onChange, + name, + value, + onBlur, + required, + placeholder, + options, +}) => { + return ( + + {label && ( + + )} + + + {placeholder && {placeholder}} + {options.map((option, index) => ( + + {option.label} + + ))} + + + ); +}; diff --git a/packages/manager/apps/procedures/src/components/TextArea/TextArea.component.tsx b/packages/manager/apps/procedures/src/components/TextArea/TextArea.component.tsx new file mode 100644 index 000000000000..bca37a68888b --- /dev/null +++ b/packages/manager/apps/procedures/src/components/TextArea/TextArea.component.tsx @@ -0,0 +1,62 @@ +import { + OdsTextAreaValueChangeEvent, + OsdsTextareaCustomEvent, +} from '@ovhcloud/ods-components'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, +} from '@ovhcloud/ods-common-theming'; +import { + OsdsFormField, + OsdsText, + OsdsTextarea, +} from '@ovhcloud/ods-components/react'; +import React, { FunctionComponent } from 'react'; + +type Props = { + label?: string; + value?: string; + onChange?: ( + event: OsdsTextareaCustomEvent, + ) => void; + onBlur?: (event: OsdsTextareaCustomEvent) => void; + name: string; + id: string; + error?: string; + required?: boolean; +}; + +export const TextArea: FunctionComponent = ({ + label, + error, + id, + onChange, + name, + value, + onBlur, + required, +}) => { + return ( + + {label && ( + + )} + + + ); +}; diff --git a/packages/manager/apps/procedures/src/components/TextInput/TextInput.component.tsx b/packages/manager/apps/procedures/src/components/TextInput/TextInput.component.tsx new file mode 100644 index 000000000000..dd039f206924 --- /dev/null +++ b/packages/manager/apps/procedures/src/components/TextInput/TextInput.component.tsx @@ -0,0 +1,67 @@ +import { + ODS_INPUT_TYPE, + OdsInputValueChangeEventDetail, + OsdsInputCustomEvent, +} from '@ovhcloud/ods-components'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, +} from '@ovhcloud/ods-common-theming'; +import { + OsdsFormField, + OsdsInput, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import React, { FunctionComponent } from 'react'; + +type Props = { + label?: string; + value?: string; + onChange?: ( + event: OsdsInputCustomEvent, + ) => void; + onBlur?: (event: OsdsInputCustomEvent) => void; + name: string; + id: string; + error?: string; + required?: boolean; + helper?: string; +}; + +export const TextInput: FunctionComponent = ({ + label, + error, + id, + onChange, + name, + value, + onBlur, + required, + helper, +}) => { + return ( + + {label && ( + + )} + + {helper && {helper}} + + ); +}; diff --git a/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.page.test.tsx b/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.page.test.tsx index 9a120813fb60..ae484b8d8abf 100644 --- a/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.page.test.tsx +++ b/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.page.test.tsx @@ -7,11 +7,18 @@ vi.mock('./rgdpIntroduction/RGDPIntroduction.component', () => ({ RGDPIntroduction: () =>
RGDPIntroduction
, })); +vi.mock('./rgdpForm/RGDPForm.component', () => ({ + RGDPForm: () =>
RGDPForm
, +})); + describe('RGDP Component', () => { it('renders the component correctly', () => { render(); const introductionElement = screen.getByText('RGDPIntroduction'); + const formElement = screen.getByText('RGDPForm'); + expect(introductionElement).toBeInTheDocument(); + expect(formElement).toBeInTheDocument(); }); }); diff --git a/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.tsx b/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.tsx index 8347853e02fc..8fbee22e59c9 100644 --- a/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.tsx +++ b/packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { PageLayout } from '@/components/PageLayout/PageLayout.component'; import { RGDPIntroduction } from './rgdpIntroduction/RGDPIntroduction.component'; +import { RGDPForm } from './rgdpForm/RGDPForm.component'; export default function RGDP() { return ( + ); } 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 new file mode 100644 index 000000000000..504c04b29ea6 --- /dev/null +++ b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx @@ -0,0 +1,147 @@ +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, +} from '@ovhcloud/ods-common-theming'; +import { ODS_BUTTON_TYPE } from '@ovhcloud/ods-components'; +import { OsdsButton, OsdsText } from '@ovhcloud/ods-components/react'; +import React, { FunctionComponent, useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { GDPRFormValues } from '@/types/gdpr.type'; +import { TextField } from './TextField/TextField.component'; +import { SelectField } from './SelectField/SelectField.component'; +import { TextAreaField } from './TextAreaField/TextAreaField.component'; +import { + EmailRegex, + GDPRSubjectValues, + TextInputRegex, +} from './RGDPForm.constants'; + +export const RGDPForm: FunctionComponent = () => { + const { t } = useTranslation('rgdp'); + const { + handleSubmit, + watch, + control, + formState: { isSubmitting, isValid, touchedFields }, + trigger, + } = useForm({ mode: 'onBlur' }); + + const onSubmit = (data: GDPRFormValues) => { + // TODO: Handle API call & ConfirmModal + console.log(data); + }; + + const email = watch('email'); + + useEffect(() => { + if (touchedFields.confirmEmail) { + trigger('confirmEmail'); + } + }, [email]); + + console.log(isSubmitting); + return ( +
+
+ + + + + + + + value === email || t('rgdp_form_validation_message_email_match') + } + control={control} + /> + + + +
+ + {t('rgdp_form_field_label_subject')} + + + ({ + value, + label: t(`rgdp_form_subject_${value}`), + }))} + /> + + + {t('rgdp_form_field_label_subject_detail')} + +
+ + +
+ + + {t('rgdp_form_submit')} + +
+ ); +}; diff --git a/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.constants.ts b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.constants.ts new file mode 100644 index 000000000000..48da92cadb7e --- /dev/null +++ b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.constants.ts @@ -0,0 +1,13 @@ +export const GDPRSubjectValues = [ + 'opposition_right', + 'rectification_right', + 'access_right', + 'erasure_right', + 'limitation_right', + 'portability_right', + 'payment_method_remove', + 'other_request', +]; + +export const EmailRegex = /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9]{2}(?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/; +export const TextInputRegex = /^[^<>]*$/; 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 new file mode 100644 index 000000000000..ff587c597254 --- /dev/null +++ b/packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx @@ -0,0 +1,169 @@ +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 { RGDPForm } from './RGDPForm.component'; +import { GDPRFormValues } from '@/types/gdpr.type'; + +const getOsdsElementByFormName = (fieldName: keyof GDPRFormValues) => + (screen.queryByTestId(`field_id_${fieldName}`) as unknown) as T; + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +vi.mock('@ovhcloud/ods-components/react', async (importOriginal) => { + const module: typeof OdsComponentModule = await importOriginal(); + return { + ...module, + OsdsFormField: ({ children, error }: any) => ( +
+ {children} +

{error}

+
+ ), + OsdsInput: ({ ...props }: any) => ( + + ), + OsdsButton: ({ ...props }: any) =>