-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(procedures): implemented gdpr form (#13558)
ref:MANAGER-15317 Signed-off-by: Omar ALKABOUSS MOUSSANA <[email protected]>
- Loading branch information
1 parent
9b7d52d
commit 6916a19
Showing
12 changed files
with
642 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
packages/manager/apps/procedures/src/pages/rgdp/RGDP.page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<PageLayout> | ||
<RGDPIntroduction /> | ||
<RGDPForm /> | ||
</PageLayout> | ||
); | ||
} |
147 changes: 147 additions & 0 deletions
147
packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
import './RGDPForm.style.css'; | ||
|
||
export const RGDPForm: FunctionComponent = () => { | ||
const { t } = useTranslation('rgdp'); | ||
const { | ||
handleSubmit, | ||
watch, | ||
control, | ||
formState: { isSubmitting, isValid, touchedFields }, | ||
trigger, | ||
} = useForm<GDPRFormValues>({ 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]); | ||
|
||
return ( | ||
<form onSubmit={handleSubmit(onSubmit)} noValidate> | ||
<div className="my-10"> | ||
<TextField | ||
label={t('rgdp_form_field_label_firstname')} | ||
name="firstName" | ||
required={t('rgdp_form_validation_message_required')} | ||
pattern={{ | ||
value: TextInputRegex, | ||
message: t('rgdp_form_validation_message_invalid_format'), | ||
}} | ||
control={control} | ||
/> | ||
|
||
<TextField | ||
label={t('rgdp_form_field_label_surname')} | ||
name="surname" | ||
required={t('rgdp_form_validation_message_required')} | ||
pattern={{ | ||
value: TextInputRegex, | ||
message: t('rgdp_form_validation_message_invalid_format'), | ||
}} | ||
control={control} | ||
/> | ||
|
||
<TextField | ||
label={t('rgdp_form_field_label_email')} | ||
helper={t('rgdp_form_field_helper_email')} | ||
name="email" | ||
required={t('rgdp_form_validation_message_required')} | ||
pattern={{ | ||
value: EmailRegex, | ||
message: t('rgdp_form_validation_message_invalid_format'), | ||
}} | ||
control={control} | ||
/> | ||
|
||
<TextField | ||
label={t('rgdp_form_field_label_confirm_email')} | ||
name="confirmEmail" | ||
validate={(value) => | ||
value === email || t('rgdp_form_validation_message_email_match') | ||
} | ||
control={control} | ||
/> | ||
|
||
<TextField | ||
label={`${t('rgdp_form_field_label_nic')} (${t( | ||
'rgdp_form_field_optional', | ||
)})`} | ||
name="nicHandle" | ||
pattern={{ | ||
value: TextInputRegex, | ||
message: t('rgdp_form_validation_message_invalid_format'), | ||
}} | ||
control={control} | ||
/> | ||
|
||
<div className="flex flex-col md:flex-row gap-2 flex-wrap md:items-center mb-8"> | ||
<OsdsText | ||
level={ODS_THEME_TYPOGRAPHY_LEVEL.heading} | ||
color={ODS_THEME_COLOR_INTENT.primary} | ||
> | ||
{t('rgdp_form_field_label_subject')} | ||
</OsdsText> | ||
|
||
<SelectField | ||
name="messageSubject" | ||
required={t('rgdp_form_validation_message_required')} | ||
control={control} | ||
placeholder={t('rgdp_form_field_placeholder_subject')} | ||
options={GDPRSubjectValues.map((value) => ({ | ||
value, | ||
label: t(`rgdp_form_subject_${value}`), | ||
}))} | ||
/> | ||
|
||
<OsdsText | ||
level={ODS_THEME_TYPOGRAPHY_LEVEL.heading} | ||
color={ODS_THEME_COLOR_INTENT.primary} | ||
> | ||
{t('rgdp_form_field_label_subject_detail')} | ||
</OsdsText> | ||
</div> | ||
|
||
<TextAreaField | ||
label={t('rgdp_form_field_label_request_description')} | ||
name="requestDescription" | ||
required={t('rgdp_form_validation_message_required')} | ||
control={control} | ||
/> | ||
</div> | ||
|
||
<OsdsButton | ||
type={ODS_BUTTON_TYPE.submit} | ||
color={ODS_THEME_COLOR_INTENT.primary} | ||
disabled={!isValid || isSubmitting || undefined} | ||
inline={true} | ||
> | ||
{t('rgdp_form_submit')} | ||
</OsdsButton> | ||
</form> | ||
); | ||
}; |
13 changes: 13 additions & 0 deletions
13
packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = /^[^<>]*$/; |
26 changes: 26 additions & 0 deletions
26
packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.style.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* TODO: | ||
It is not clean to apply CSS for ODS components. It was added because Tailwind CSS overrides the border of ODS inputs. | ||
The best solution is to configure Tailwind CSS with this option: corePlugins: { | ||
preflight: false, | ||
}, | ||
However, | ||
some components on the MFA page are not in ODS. Consider converting these components to ODS in the future. | ||
*/ | ||
|
||
osds-input[size='md'] { | ||
border-width: var(--ods-size-input-md-border-width); | ||
} | ||
|
||
osds-input[color='primary'] { | ||
border-color: var(--ods-color-primary-200); | ||
} | ||
|
||
osds-input[color='error'] { | ||
border-color: var(--ods-color-error-500); | ||
} | ||
|
||
.ods-error { | ||
border-color: var(--ods-color-error-500) !important; | ||
} |
163 changes: 163 additions & 0 deletions
163
packages/manager/apps/procedures/src/pages/rgdp/rgdpForm/RGDPForm.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
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 = <T,>(fieldName: keyof GDPRFormValues) => | ||
(screen.queryByTestId(`field_id_${fieldName}`) as unknown) as T; | ||
|
||
vi.mock('@ovhcloud/ods-components/react', async (importOriginal) => { | ||
const module: typeof OdsComponentModule = await importOriginal(); | ||
return { | ||
...module, | ||
OsdsFormField: ({ children, error }: any) => ( | ||
<div> | ||
{children} | ||
<p>{error}</p> | ||
</div> | ||
), | ||
OsdsInput: ({ ...props }: any) => ( | ||
<module.OsdsInput {...props} data-testid={props.id} /> | ||
), | ||
OsdsButton: ({ ...props }: any) => <button {...props} />, | ||
OsdsSelect: ({ ...props }: any) => ( | ||
<module.OsdsSelect {...props} data-testid={props.id} /> | ||
), | ||
OsdsTextarea: ({ ...props }: any) => ( | ||
<module.OsdsTextarea {...props} data-testid={props.id} /> | ||
), | ||
}; | ||
}); | ||
|
||
describe('RGDPForm', () => { | ||
const renderForm = () => render(<RGDPForm />); | ||
|
||
it('Should render the form fields correctly when the form is displayed', async () => { | ||
const { getByText } = renderForm(); | ||
|
||
await waitFor(() => { | ||
expect(getByText('rgdp_form_field_label_firstname:')).toBeInTheDocument(); | ||
expect(getByText('rgdp_form_field_label_surname:')).toBeInTheDocument(); | ||
expect(getByText('rgdp_form_field_label_email:')).toBeInTheDocument(); | ||
expect( | ||
getByText('rgdp_form_field_label_confirm_email:'), | ||
).toBeInTheDocument(); | ||
expect(getByText('rgdp_form_field_label_subject')).toBeInTheDocument(); | ||
expect( | ||
getByText('rgdp_form_field_label_subject_detail'), | ||
).toBeInTheDocument(); | ||
expect( | ||
getByText('rgdp_form_field_label_request_description:'), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('Should disable the submit button when required fields are not filled', async () => { | ||
const { getByRole } = renderForm(); | ||
const submitBtn = getByRole('button', { name: 'rgdp_form_submit' }); | ||
expect(submitBtn).toBeDisabled(); | ||
}); | ||
|
||
it('Should show errors on multiple required fields when blurred with empty values', async () => { | ||
const { queryAllByText } = renderForm(); | ||
|
||
const surnameInput = getOsdsElementByFormName<OsdsInput>('surname'); | ||
const firstNameInput = getOsdsElementByFormName<OsdsInput>('firstName'); | ||
const requestDescriptionText = getOsdsElementByFormName<OsdsTextArea>( | ||
'requestDescription', | ||
); | ||
|
||
await act(async () => { | ||
surnameInput.value = ''; | ||
firstNameInput.value = ''; | ||
requestDescriptionText.value = ''; | ||
|
||
surnameInput.odsInputBlur.emit(); | ||
firstNameInput.odsInputBlur.emit(); | ||
requestDescriptionText.odsBlur.emit(); | ||
}); | ||
|
||
await waitFor(() => { | ||
const errorMessages = queryAllByText( | ||
'rgdp_form_validation_message_required', | ||
); | ||
expect(errorMessages).toHaveLength(3); | ||
}); | ||
}); | ||
|
||
it('Should show pattern validation errors on multiple fields when invalid characters are entered', async () => { | ||
const { queryAllByText } = renderForm(); | ||
|
||
const surnameInput = getOsdsElementByFormName<OsdsInput>('surname'); | ||
const firstNameInput = getOsdsElementByFormName<OsdsInput>('firstName'); | ||
const nicInput = getOsdsElementByFormName<OsdsInput>('nicHandle'); | ||
|
||
await act(async () => { | ||
surnameInput.value = '<'; | ||
firstNameInput.value = '>'; | ||
nicInput.value = '<'; | ||
|
||
surnameInput.odsValueChange.emit(); | ||
firstNameInput.odsValueChange.emit(); | ||
nicInput.odsValueChange.emit(); | ||
|
||
surnameInput.odsInputBlur.emit(); | ||
firstNameInput.odsInputBlur.emit(); | ||
nicInput.odsInputBlur.emit(); | ||
}); | ||
|
||
await waitFor(() => { | ||
const errorMessages = queryAllByText( | ||
'rgdp_form_validation_message_invalid_format', | ||
); | ||
expect(errorMessages).toHaveLength(3); | ||
}); | ||
}); | ||
|
||
it('Should show an error when an invalid email is entered', async () => { | ||
const { getByText } = renderForm(); | ||
|
||
const emailInput = getOsdsElementByFormName<OsdsInput>('email'); | ||
|
||
await act(async () => { | ||
emailInput.value = 'invalidEmail'; | ||
emailInput.odsValueChange.emit(); | ||
emailInput.odsInputBlur.emit(); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect( | ||
getByText('rgdp_form_validation_message_invalid_format'), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('Should validate email fields and show an error if they don t match', async () => { | ||
const { getByText } = renderForm(); | ||
|
||
const emailInput = getOsdsElementByFormName<OsdsInput>('email'); | ||
const confirmEmailInput = getOsdsElementByFormName<OsdsInput>( | ||
'confirmEmail', | ||
); | ||
|
||
await act(async () => { | ||
emailInput.value = '[email protected]'; | ||
confirmEmailInput.value = '[email protected]'; | ||
|
||
emailInput.odsValueChange.emit(); | ||
confirmEmailInput.odsValueChange.emit(); | ||
|
||
emailInput.odsInputBlur.emit(); | ||
confirmEmailInput.odsInputBlur.emit(); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect( | ||
getByText('rgdp_form_validation_message_email_match'), | ||
).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.