From 0736c4e2632f6e22c95754142277d9add1027b76 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari Date: Tue, 13 Feb 2024 12:37:44 +0400 Subject: [PATCH 1/4] fix: icon color and component --- packages/account-v2/src/App.tsx | 2 +- .../DocumentSubmission/DocumentSubmission.tsx | 2 +- .../selfie-document-upload.tsx | 46 +++++++++++-------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/account-v2/src/App.tsx b/packages/account-v2/src/App.tsx index 750ad5055014..d5ec0045e8fe 100644 --- a/packages/account-v2/src/App.tsx +++ b/packages/account-v2/src/App.tsx @@ -25,7 +25,7 @@ const App: React.FC = () => { {/* [TODO]:Mock - Remove Mock values */} {}} validationSchema={getValidationSchema}> {/* */} - + {/* [TODO]:Mock - Remove Mock values */} diff --git a/packages/account-v2/src/containers/POAForm/DocumentSubmission/DocumentSubmission.tsx b/packages/account-v2/src/containers/POAForm/DocumentSubmission/DocumentSubmission.tsx index ee9c009c6620..04de85b60f59 100644 --- a/packages/account-v2/src/containers/POAForm/DocumentSubmission/DocumentSubmission.tsx +++ b/packages/account-v2/src/containers/POAForm/DocumentSubmission/DocumentSubmission.tsx @@ -63,7 +63,7 @@ const DocumentSubmission: React.FC = () => { descriptionSize='2xs' fileFormats={['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'application/pdf']} hoverMessage='Upload your file here' - icon={} + icon={} maxSize={8388608} name='document' title='Drag and drop a file or click to browse your files.' diff --git a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx index c8d4ff1016e4..8f9b9877102b 100644 --- a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx +++ b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx @@ -1,28 +1,38 @@ import React from 'react'; -import { useFormikContext } from 'formik'; +import { Field, FieldProps } from 'formik'; +import * as Yup from 'yup'; import { Text, useDevice } from '@deriv-com/ui'; import SelfieIcon from '../../assets/manual-upload/selfie-icon.svg'; import { Dropzone } from '../../components/Dropzone'; +import { validateField } from '../../utils/validation'; -export const SelfieDocumentUpload = () => { +type TSelfieDocumentUpload = { + name: string; + validationSchema?: Yup.AnySchema; +}; + +export const SelfieDocumentUpload = ({ name, validationSchema }: TSelfieDocumentUpload) => { const { isMobile } = useDevice(); - const { setFieldValue } = useFormikContext(); return ( -
- Upload your selfie - } - onFileChange={(file: File) => setFieldValue('selfie', file)} - /> - - Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and your face - is within the frame. - -
+ + {({ form }: FieldProps) => ( +
+ Upload your selfie + } + onFileChange={(file: File) => form.setFieldValue(name, file)} + /> + + Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and + your face is within the frame. + +
+ )} +
); }; From 042bb8dbae758bd565c106b5ce054c60b0f14835 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari Date: Wed, 14 Feb 2024 14:47:17 +0400 Subject: [PATCH 2/4] chore: added selfieupload form --- .../src/components/Dropzone/Dropzone.scss | 60 +++++------- .../src/components/Dropzone/Dropzone.tsx | 10 +- .../components/IconButton/icon-button.scss | 94 ------------------- .../src/components/IconButton/icon-button.tsx | 1 - .../src/constants/manualFormConstants.tsx | 1 + .../selfie-document-upload.tsx | 94 +++++++++++++------ .../account-v2/src/modules/IDVForm/index.tsx | 10 +- .../modules/ManualUpload/manual-upload.tsx | 14 +-- .../manual-form-container.tsx | 33 +++++++ .../account-v2/src/utils/manualFormUtils.ts | 18 +++- packages/account-v2/tailwind.config.ts | 1 + .../card-details/selfie-upload.tsx | 3 +- 12 files changed, 161 insertions(+), 178 deletions(-) delete mode 100644 packages/account-v2/src/components/IconButton/icon-button.scss create mode 100644 packages/account-v2/src/pages/ManualFormContainer/manual-form-container.tsx diff --git a/packages/account-v2/src/components/Dropzone/Dropzone.scss b/packages/account-v2/src/components/Dropzone/Dropzone.scss index 8f3092a9da83..5c5e87a7e7b0 100644 --- a/packages/account-v2/src/components/Dropzone/Dropzone.scss +++ b/packages/account-v2/src/components/Dropzone/Dropzone.scss @@ -23,16 +23,6 @@ border: 0.1rem solid var(--status-success, #4bb4b3); } - &__content { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2.4rem; - } - &__thumb { position: relative; width: 100%; @@ -56,33 +46,33 @@ } } - &__placeholder { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.4rem; - } + // &__placeholder { + // display: flex; + // flex-direction: column; + // align-items: center; + // gap: 1.4rem; + // } - &__placeholder-icon { - flex-shrink: 0; - } + // &__placeholder-icon { + // flex-shrink: 0; + // } - &__placeholder-text { - display: flex; - flex-direction: column; - align-items: center; - gap: 3.2rem; - } + // &__placeholder-text { + // display: flex; + // flex-direction: column; + // align-items: center; + // gap: 3.2rem; + // } - &__remove-file { - position: absolute; - top: 0.8rem; - right: 0.8rem; - border-radius: 50%; - background-color: var(--system-light-5-active-background, #d6dadb); + // &__remove-file { + // position: absolute; + // top: 0.8rem; + // right: 0.8rem; + // border-radius: 50%; + // background-color: var(--system-light-5-active-background, #d6dadb); - &:hover { - background-color: var(--system-light-5-hover-background, #e9ecef); - } - } + // &:hover { + // background-color: var(--system-light-5-hover-background, #e9ecef); + // } + // } } diff --git a/packages/account-v2/src/components/Dropzone/Dropzone.tsx b/packages/account-v2/src/components/Dropzone/Dropzone.tsx index 9213ab6b81f5..a5246ee77f94 100644 --- a/packages/account-v2/src/components/Dropzone/Dropzone.tsx +++ b/packages/account-v2/src/components/Dropzone/Dropzone.tsx @@ -112,11 +112,11 @@ const Dropzone: React.FC = ({ { 'wallets-dropzone--active': file } )} > -
+
{showHoverMessage && {hoverMessage}} {!file && ( -
-
{icon}
+
+
{icon}
{title && ( {title} @@ -126,7 +126,7 @@ const Dropzone: React.FC = ({ {description} {buttonText && ( -
+
@@ -150,7 +150,7 @@ const Dropzone: React.FC = ({ > {hasFrame && } } diff --git a/packages/account-v2/src/components/IconButton/icon-button.scss b/packages/account-v2/src/components/IconButton/icon-button.scss deleted file mode 100644 index 8a4a361cc35d..000000000000 --- a/packages/account-v2/src/components/IconButton/icon-button.scss +++ /dev/null @@ -1,94 +0,0 @@ -/* stylelint-disable color-no-hex */ - -$color-map: ( - 'primary': ( - backgroundcolor: #ff444f, - hover: #eb3e48, - ), - 'white': ( - backgroundcolor: #fff, - hover: #d6dadb, - ), - 'black': ( - backgroundcolor: #0e0e0e, - hover: #323738, - ), - 'transparent': ( - backgroundcolor: transparent, - hover: #d6dadb, - ), -); - -$size-map: ( - sm: ( - padding: 0.4rem, - height: 2.4rem, - ), - md: ( - padding: 0.8rem, - height: 3.2rem, - ), - lg: ( - padding: 1.2rem, - height: 4rem, - ), -); - -$border-radius-map: ( - sm: 0.4rem, - md: 6.4rem, - lg: 50%, -); - -.icon-button { - // display: grid; - // place-content: center; - // border: none; - - // &:hover { - // cursor: pointer; - // } - - // &:disabled { - // display: none; - // } - - &__border-radius { - &--round { - border-radius: 50%; - } - - &--default { - border-radius: 0.4rem; - } - } - - @each $color, $values in $color-map { - &__color--#{$color} { - background-color: map-get($values, backgroundcolor); - &:hover { - background-color: map-get($values, hover); - } - } - } - - @each $size, $values in $size-map { - &__size--#{$size} { - padding: map-get($values, padding); - height: map-get($values, height); - } - } - - @each $size, $values in $border-radius-map { - &__border-radius--#{$size} { - border-radius: ($values); - } - } - - // &__icon { - // display: grid; - // place-content: center; - // width: 1.6rem; - // height: 1.6rem; - // } -} diff --git a/packages/account-v2/src/components/IconButton/icon-button.tsx b/packages/account-v2/src/components/IconButton/icon-button.tsx index 6be1fc1c13a4..94fc57bf4e48 100644 --- a/packages/account-v2/src/components/IconButton/icon-button.tsx +++ b/packages/account-v2/src/components/IconButton/icon-button.tsx @@ -2,7 +2,6 @@ import React, { ButtonHTMLAttributes, ComponentProps, forwardRef, Ref } from 're import clsx from 'clsx'; import { TGenericSizes } from '../types'; import { iconButtonVariants } from './icon-button.classnames'; -import './icon-button.scss'; interface IconButtonProps extends ButtonHTMLAttributes { color?: 'black' | 'primary' | 'transparent' | 'white'; diff --git a/packages/account-v2/src/constants/manualFormConstants.tsx b/packages/account-v2/src/constants/manualFormConstants.tsx index 1bbbbd4b7f9d..da057f8aeaa9 100644 --- a/packages/account-v2/src/constants/manualFormConstants.tsx +++ b/packages/account-v2/src/constants/manualFormConstants.tsx @@ -13,6 +13,7 @@ export const MANUAL_DOCUMENT_TYPES = Object.freeze({ NATIONAL_IDENTITY_CARD: 'national_identity_card', NIMC_SLIP: 'nimc_slip', PASSPORT: 'passport', + SELFIE: 'selfie_with_id', }); export const MANUAL_FORM_INITIAL_VALUES = Object.freeze({ diff --git a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx index 8f9b9877102b..4607b50f8f26 100644 --- a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx +++ b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx @@ -1,38 +1,76 @@ import React from 'react'; -import { Field, FieldProps } from 'formik'; -import * as Yup from 'yup'; -import { Text, useDevice } from '@deriv-com/ui'; +import { Formik } from 'formik'; +import { Button, Text, useDevice } from '@deriv-com/ui'; import SelfieIcon from '../../assets/manual-upload/selfie-icon.svg'; import { Dropzone } from '../../components/Dropzone'; -import { validateField } from '../../utils/validation'; +import { MANUAL_DOCUMENT_TYPES } from '../../constants/manualFormConstants'; +import { getSelfieValidationSchema } from '../../utils/manualFormUtils'; -type TSelfieDocumentUpload = { - name: string; - validationSchema?: Yup.AnySchema; -}; +// type TSelfieDocumentUpload = { +// validationSchema?: Yup.AnySchema; +// }; -export const SelfieDocumentUpload = ({ name, validationSchema }: TSelfieDocumentUpload) => { +export const SelfieDocumentUpload = () => { const { isMobile } = useDevice(); + const validationSchema = getSelfieValidationSchema(); + return ( - - {({ form }: FieldProps) => ( -
- Upload your selfie - } - onFileChange={(file: File) => form.setFieldValue(name, file)} - /> - - Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and - your face is within the frame. - -
- )} -
+ console.log('Submission called')} + validationSchema={validationSchema} + > + {({ errors, initialValues, isValid, setFieldValue, values }) => { + console.log('Values: ', { initialValues, errors, values }); + return ( +
+ Upload your selfie + } + onFileChange={(file: File) => setFieldValue(MANUAL_DOCUMENT_TYPES.SELFIE, file)} + /> + + Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible + and your face is within the frame. + +
+ + +
+
+ ); + }} +
); + + // return ( + // + // {({ form }: FieldProps) => ( + //
+ // Upload your selfie + // } + // onFileChange={(file: File) => form.setFieldValue(name, file)} + // /> + // + // Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and + // your face is within the frame. + // + //
+ // )} + //
+ // ); }; diff --git a/packages/account-v2/src/modules/IDVForm/index.tsx b/packages/account-v2/src/modules/IDVForm/index.tsx index c108a7a3c253..3648ac86bb3c 100644 --- a/packages/account-v2/src/modules/IDVForm/index.tsx +++ b/packages/account-v2/src/modules/IDVForm/index.tsx @@ -28,7 +28,8 @@ type TDropDownList = { }; export const IDVForm = ({ allowIDVSkip, selectedCountry }: TIDVFormProps) => { - const { setFieldValue, values }: FormikProps = useFormikContext(); + const formik: FormikProps = useFormikContext(); + const [documentList, setDocumentList] = useState([]); const [selectedDocument, setSelectedDocument] = useState(); @@ -46,6 +47,8 @@ export const IDVForm = ({ allowIDVSkip, selectedCountry }: TIDVFormProps) => { value: '', }; + const { setFieldValue, values } = formik; + const bindDocumentData = (item: string) => { setFieldValue('document_type', item, true); setSelectedDocument(getSelectedDocumentConfigData(item, DOCUMENT_LIST)); @@ -75,6 +78,11 @@ export const IDVForm = ({ allowIDVSkip, selectedCountry }: TIDVFormProps) => { } }, [documents_supported]); + if (!formik) { + console.error('Subform must be wrapped with a Formik provider'); + return null; // or render an error message, redirect, etc. + } + return (
diff --git a/packages/account-v2/src/modules/ManualUpload/manual-upload.tsx b/packages/account-v2/src/modules/ManualUpload/manual-upload.tsx index bc7e16185700..1cfe1e69473a 100644 --- a/packages/account-v2/src/modules/ManualUpload/manual-upload.tsx +++ b/packages/account-v2/src/modules/ManualUpload/manual-upload.tsx @@ -1,8 +1,7 @@ /* eslint-disable no-console */ import React, { useState } from 'react'; -import { TManualDocumentTypes } from '../../constants/manualFormConstants'; import { DocumentSelection } from '../../containers/DocumentSelection'; -import { ManualForm } from '../../containers/ManualForm'; +import { ManualUploadContainer } from '../../pages/ManualFormContainer/manual-form-container'; type TManualUploadProps = { countryCode: string }; @@ -10,16 +9,7 @@ export const ManualUpload = ({ countryCode }: TManualUploadProps) => { const [selectedDocument, setSelectedDocument] = useState(null); if (selectedDocument) { - return ( - { - console.log('Called on Cancel'); - setSelectedDocument(null); - }} - onSubmit={() => console.log('Called submit')} - selectedDocument={selectedDocument as TManualDocumentTypes} - /> - ); + return ; } return ; }; diff --git a/packages/account-v2/src/pages/ManualFormContainer/manual-form-container.tsx b/packages/account-v2/src/pages/ManualFormContainer/manual-form-container.tsx new file mode 100644 index 000000000000..95aee675a218 --- /dev/null +++ b/packages/account-v2/src/pages/ManualFormContainer/manual-form-container.tsx @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ +import React, { useState } from 'react'; +import { TManualDocumentTypes } from '../../constants/manualFormConstants'; +import { ManualForm } from '../../containers/ManualForm'; +import { SelfieDocumentUpload } from '../../containers/SelfieDocumentUpload'; + +type TManualUploadContainerProps = { + selectedDocument: string | null; + setSelectedDocument: (value: string | null) => void; +}; + +export const ManualUploadContainer = ({ selectedDocument, setSelectedDocument }: TManualUploadContainerProps) => { + const [formData, setFormData] = useState({}); + const [shouldUploadSelfie, setShouldUploadSelfie] = useState(false); + + if (shouldUploadSelfie) { + return ; + } + return ( + { + console.log('Called on Cancel'); + setSelectedDocument(null); + setShouldUploadSelfie(false); + }} + onSubmit={() => { + console.log('Called submit'); + setShouldUploadSelfie(true); + }} + selectedDocument={selectedDocument as TManualDocumentTypes} + /> + ); +}; diff --git a/packages/account-v2/src/utils/manualFormUtils.ts b/packages/account-v2/src/utils/manualFormUtils.ts index 0800d7962adb..c520ec73a348 100644 --- a/packages/account-v2/src/utils/manualFormUtils.ts +++ b/packages/account-v2/src/utils/manualFormUtils.ts @@ -1,5 +1,9 @@ import * as Yup from 'yup'; -import { MANUAL_DOCUMENT_TYPES_DATA, TManualDocumentTypes } from '../constants/manualFormConstants'; +import { + MANUAL_DOCUMENT_TYPES, + MANUAL_DOCUMENT_TYPES_DATA, + TManualDocumentTypes, +} from '../constants/manualFormConstants'; export const getTitleForFormInputs = (selectedDocument: TManualDocumentTypes) => MANUAL_DOCUMENT_TYPES_DATA[selectedDocument].inputSectionHeader; @@ -20,3 +24,15 @@ export const getManualFormValidationSchema = (selectedDocument: TManualDocumentT document_number: Yup.string().required(fieldsData.documentNumber.errorMessage), }); }; + +export const getSelfieValidationSchema = () => { + return Yup.object({ + [MANUAL_DOCUMENT_TYPES.SELFIE]: Yup.mixed().test({ + message: 'File is required', + name: 'file', + test: value => { + return value && value instanceof File; + }, + }), + }); +}; diff --git a/packages/account-v2/tailwind.config.ts b/packages/account-v2/tailwind.config.ts index 6aa418433aa7..d1ea6adc86f3 100644 --- a/packages/account-v2/tailwind.config.ts +++ b/packages/account-v2/tailwind.config.ts @@ -13,6 +13,7 @@ export default { '2': '#f2f3f4', '5': '#d6dadb', '6': '#d6d6d6', + '14': '#e9ecef', }, black: { '0': '#0e0e0e', diff --git a/packages/account/src/Components/poi/status/unsupported/card-details/selfie-upload.tsx b/packages/account/src/Components/poi/status/unsupported/card-details/selfie-upload.tsx index 15726249d263..586e886a4cb0 100644 --- a/packages/account/src/Components/poi/status/unsupported/card-details/selfie-upload.tsx +++ b/packages/account/src/Components/poi/status/unsupported/card-details/selfie-upload.tsx @@ -29,7 +29,8 @@ const SelfieUpload = ({ initial_values, goBack, onConfirm, onFileDrop }: TSelfie validate={values => validateFields(values, undefined, [SELFIE_DOCUMENT])} onSubmit={onConfirm} > - {({ values, isValid, isSubmitting, touched }: Partial>) => { + {({ values, isValid, isSubmitting, touched, initialValues }: Partial>) => { + console.log('Values: ', values, initialValues); let is_form_touched, is_form_empty; if (touched) { is_form_touched = Object.keys(touched).length > 0; From 9f571ec3f0adb0038578894edff58c89d6116255 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari Date: Thu, 15 Feb 2024 15:27:28 +0400 Subject: [PATCH 3/4] fix: included manual-form changes --- packages/account-v2/src/App.tsx | 22 ++---- .../src/constants/manualFormConstants.tsx | 5 -- .../src/containers/ManualForm/manualForm.tsx | 74 +++++++++++-------- .../__tests__/selfie-document-upload.spec.tsx | 69 +++++++++++++++++ .../selfie-document-upload.tsx | 50 +++++-------- .../IDVForm/__tests__/idv-form.spec.tsx | 33 +++++++++ .../IDVForm/{index.tsx => idv-form.tsx} | 39 +++++----- .../account-v2/src/modules/IDVForm/index.ts | 1 + .../manual-form-container.tsx | 25 ++++++- .../__tests__/idv-form-utils.spec.ts} | 2 +- .../utils.ts => utils/idv-form-utils.ts} | 0 .../account-v2/src/utils/manualFormUtils.ts | 20 ++--- 12 files changed, 224 insertions(+), 116 deletions(-) create mode 100644 packages/account-v2/src/containers/SelfieDocumentUpload/__tests__/selfie-document-upload.spec.tsx create mode 100644 packages/account-v2/src/modules/IDVForm/__tests__/idv-form.spec.tsx rename packages/account-v2/src/modules/IDVForm/{index.tsx => idv-form.tsx} (94%) create mode 100644 packages/account-v2/src/modules/IDVForm/index.ts rename packages/account-v2/src/{modules/IDVForm/__tests__/utils.spec.ts => utils/__tests__/idv-form-utils.spec.ts} (98%) rename packages/account-v2/src/{modules/IDVForm/utils.ts => utils/idv-form-utils.ts} (100%) diff --git a/packages/account-v2/src/App.tsx b/packages/account-v2/src/App.tsx index d5ec0045e8fe..3c98ee5e0d2c 100644 --- a/packages/account-v2/src/App.tsx +++ b/packages/account-v2/src/App.tsx @@ -2,34 +2,22 @@ /* eslint-disable no-console */ /* eslint-disable @typescript-eslint/no-empty-function */ import React from 'react'; -import { Formik } from 'formik'; import { APIProvider } from '@deriv/api'; import { BreakpointProvider } from '@deriv/quill-design'; -import { SelfieDocumentUpload } from './containers/SelfieDocumentUpload'; -import { DOCUMENT_LIST, INITIAL_VALUES, SELECTED_COUNTRY } from './mocks/idv-form.mock'; -import { IDVForm } from './modules/IDVForm'; -import { getIDVFormValidationSchema } from './modules/IDVForm/utils'; +import { ManualUploadContainer } from './pages/ManualFormContainer/manual-form-container'; import RouteLinks from './router/components/route-links/route-links'; import './index.scss'; const App: React.FC = () => { - // TODO - Remove this once the IDV form is moved out - const getValidationSchema = getIDVFormValidationSchema(DOCUMENT_LIST); - return (
Account V2
- {/* TODO - Remove this ManualForm later, only rendered here for testing purposes. */} - {/* {}} selectedDocument='driving_licence' /> */} - {/* [TODO]:Mock - Remove Mock values */} - {}} validationSchema={getValidationSchema}> - {/* */} - - + console.log('Called with', val)} + /> - {/* [TODO]:Mock - Remove Mock values */} - {/* console.log(val)} /> */}
); diff --git a/packages/account-v2/src/constants/manualFormConstants.tsx b/packages/account-v2/src/constants/manualFormConstants.tsx index da057f8aeaa9..6c5605ed7f4f 100644 --- a/packages/account-v2/src/constants/manualFormConstants.tsx +++ b/packages/account-v2/src/constants/manualFormConstants.tsx @@ -16,11 +16,6 @@ export const MANUAL_DOCUMENT_TYPES = Object.freeze({ SELFIE: 'selfie_with_id', }); -export const MANUAL_FORM_INITIAL_VALUES = Object.freeze({ - document_expiry: '', - document_number: '', -}); - const documentExpiry = { errorMessage: 'Expiry date is required.', label: 'Expiry date', diff --git a/packages/account-v2/src/containers/ManualForm/manualForm.tsx b/packages/account-v2/src/containers/ManualForm/manualForm.tsx index 735bc33dd5a3..9c1489f35039 100644 --- a/packages/account-v2/src/containers/ManualForm/manualForm.tsx +++ b/packages/account-v2/src/containers/ManualForm/manualForm.tsx @@ -1,40 +1,56 @@ import React from 'react'; -import { Form, Formik } from 'formik'; +import { Form, Formik, FormikValues } from 'formik'; +import { InferType } from 'yup'; import { Button } from '@deriv-com/ui'; -import { MANUAL_FORM_INITIAL_VALUES, TManualDocumentTypes } from '../../constants/manualFormConstants'; +import { TManualDocumentTypes } from '../../constants/manualFormConstants'; import { getManualFormValidationSchema } from '../../utils/manualFormUtils'; import { ManualFormDocumentUpload } from './manualFormDocumentUpload'; import { ManualFormFooter } from './manualFormFooter'; import { ManualFormInputs } from './manualFormInputs'; +type TManualDocumentDetailsForm = InferType>; + type TManualFormProps = { - onSubmit: (values: typeof MANUAL_FORM_INITIAL_VALUES) => void; - selectedDocument: TManualDocumentTypes; + formData: FormikValues; onCancel: () => void; + onSubmit: (values: TManualDocumentDetailsForm) => void; + selectedDocument: TManualDocumentTypes; }; -export const ManualForm = ({ onSubmit, selectedDocument, onCancel }: TManualFormProps) => ( -
- getManualFormValidationSchema(selectedDocument)} - > - {({ isValid }) => ( -
-
- - - -
- - -
-
-
- )} -
-
-); +export const ManualForm = ({ formData, onCancel, onSubmit, selectedDocument }: TManualFormProps) => { + const validationSchema = getManualFormValidationSchema(selectedDocument); + + const initialValues = validationSchema.cast({ + document_expiry: formData.document_expiry ?? validationSchema.getDefault().document_expiry, + document_number: formData.document_number ?? validationSchema.getDefault().document_number, + }); + + return ( +
+ + {({ isValid, values }) => { + console.log('Form values: ', values); + return ( +
+
+ + + +
+ + +
+
+
+ ); + }} +
+
+ ); +}; diff --git a/packages/account-v2/src/containers/SelfieDocumentUpload/__tests__/selfie-document-upload.spec.tsx b/packages/account-v2/src/containers/SelfieDocumentUpload/__tests__/selfie-document-upload.spec.tsx new file mode 100644 index 000000000000..8f7f000ff7d8 --- /dev/null +++ b/packages/account-v2/src/containers/SelfieDocumentUpload/__tests__/selfie-document-upload.spec.tsx @@ -0,0 +1,69 @@ +import React, { ComponentProps } from 'react'; +import { useDevice } from '@deriv-com/ui'; +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SelfieDocumentUpload } from '../selfie-document-upload'; + +jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn().mockReturnValue({ isMobile: false }), +})); + +describe('SelfieDocumentUpload', () => { + const mockHandleCancel = jest.fn(); + const mockHandleSubmit = jest.fn(); + + const mockProps: ComponentProps = { + formData: {}, + handleCancel: mockHandleCancel, + handleSubmit: mockHandleSubmit, + }; + + it('should render component', () => { + render(); + + expect(screen.getAllByText(/Upload your selfie/i)).toHaveLength(2); + expect(screen.getAllByRole('button')).toHaveLength(3); + expect(screen.getByRole('button', { name: /Drop file or click here to upload/i })).toBeInTheDocument(); + }); + + it('should render component in mobile mode', () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: true }); + + render(); + expect(screen.getAllByText(/Upload your selfie/i)).toHaveLength(2); + expect(screen.getAllByRole('button')).toHaveLength(3); + expect(screen.getByText(/Tap here to upload/i)).toBeInTheDocument(); + }); + + it('should disable Confirm and upload button when no file is uploaded but enable Back button', () => { + render(); + expect(screen.getByRole('button', { name: /Confirm and upload/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /Back/i })).toBeEnabled(); + }); + + it('should enable Confirm and upload button when file is uploaded via drag and drop', async () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: true }); + const file = new File(['file'], 'ping.jpg', { + type: 'image/jpg', + }); + window.URL.createObjectURL = jest.fn().mockImplementation(() => 'url'); + render(); + const dropZoneEl = screen.getByTestId('dt_dropzone-input'); + userEvent.upload(dropZoneEl, file); + + expect(await screen.findByRole('button', { name: /Confirm and upload/i })).toBeEnabled(); + }); + + it('should enable Confirm and upload button when file is uploaded via file input', async () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: false }); + render(); + const file = new File(['file'], 'ping.jpg', { + type: 'image/jpg', + }); + const dropZoneEl = screen.getByTestId('dt_dropzone-input'); + fireEvent.change(dropZoneEl, { target: { files: [file] } }); + + expect(await screen.findByRole('button', { name: /Confirm and upload/i })).toBeEnabled(); + }); +}); diff --git a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx index 4607b50f8f26..2544222a1c24 100644 --- a/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx +++ b/packages/account-v2/src/containers/SelfieDocumentUpload/selfie-document-upload.tsx @@ -1,28 +1,38 @@ import React from 'react'; -import { Formik } from 'formik'; +import { Formik, FormikValues } from 'formik'; +import { InferType } from 'yup'; import { Button, Text, useDevice } from '@deriv-com/ui'; import SelfieIcon from '../../assets/manual-upload/selfie-icon.svg'; import { Dropzone } from '../../components/Dropzone'; import { MANUAL_DOCUMENT_TYPES } from '../../constants/manualFormConstants'; import { getSelfieValidationSchema } from '../../utils/manualFormUtils'; -// type TSelfieDocumentUpload = { -// validationSchema?: Yup.AnySchema; -// }; +type TSelfieFormValue = InferType>; -export const SelfieDocumentUpload = () => { +type TSelfieDocumentUpload = { + formData: FormikValues; + handleCancel: () => void; + handleSubmit: (value: TSelfieFormValue) => void; +}; + +export const SelfieDocumentUpload = ({ formData, handleCancel, handleSubmit }: TSelfieDocumentUpload) => { const { isMobile } = useDevice(); const validationSchema = getSelfieValidationSchema(); + const initialVal = validationSchema.cast({ + [MANUAL_DOCUMENT_TYPES.SELFIE]: + formData[MANUAL_DOCUMENT_TYPES.SELFIE] ?? validationSchema.getDefault().selfie_with_id, + }); + return ( console.log('Submission called')} + initialValues={initialVal as TSelfieFormValue} + onSubmit={handleSubmit} validationSchema={validationSchema} > {({ errors, initialValues, isValid, setFieldValue, values }) => { - console.log('Values: ', { initialValues, errors, values }); + console.log('Values: ', { initialValues, errors, values, isValid }); return (
Upload your selfie @@ -39,7 +49,7 @@ export const SelfieDocumentUpload = () => { and your face is within the frame.
- - +
)}
-
-); + ); +}; diff --git a/packages/account-v2/src/containers/ManualForm/manualFormDocumentUpload.tsx b/packages/account-v2/src/containers/ManualForm/manualFormDocumentUpload.tsx index 60ec886fc0ab..08de109b2c0e 100644 --- a/packages/account-v2/src/containers/ManualForm/manualFormDocumentUpload.tsx +++ b/packages/account-v2/src/containers/ManualForm/manualFormDocumentUpload.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { WalletText } from '../../components/base/WalletText'; -import { Dropzone } from '../../components/Dropzone'; +import { Text } from '@deriv-com/ui'; +import FormDocumentUploadField from '../../components/FormFields/FormDocumentUploadField'; import { TManualDocumentTypes } from '../../constants/manualFormConstants'; import { getTitleForDocumentUpload, getUploadConfig } from '../../utils/manualFormUtils'; @@ -11,17 +11,18 @@ export const ManualFormDocumentUpload = ({ selectedDocument }: TManualFormDocume return (
- {getTitleForDocumentUpload(selectedDocument)} + {getTitleForDocumentUpload(selectedDocument)}
{uploadConfig.map(upload => ( -
- + console.log(file.name)} + name={upload.pageType} />
))} diff --git a/packages/account-v2/src/containers/ManualForm/manualFormFooter.tsx b/packages/account-v2/src/containers/ManualForm/manualFormFooter.tsx index 48879404c1c3..487b7a16d30a 100644 --- a/packages/account-v2/src/containers/ManualForm/manualFormFooter.tsx +++ b/packages/account-v2/src/containers/ManualForm/manualFormFooter.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { Text } from '@deriv-com/ui'; import IcPoiClearPhoto from '../../assets/manual-upload/ic-poi-clear-photo.svg'; import IcPoiDocExpiry from '../../assets/manual-upload/ic-poi-doc-expiry.svg'; import IcPoiFileFormat from '../../assets/manual-upload/ic-poi-file-format.svg'; import IcPoiFileSize from '../../assets/manual-upload/ic-poi-file-size.svg'; -import { WalletText } from '../../components/base/WalletText'; const FOOTER_ITEMS = [ { icon: , text: 'A clear colour photo or scanned image' }, @@ -13,13 +13,13 @@ const FOOTER_ITEMS = [ ]; export const ManualFormFooter = () => ( -
+
{FOOTER_ITEMS.map(footer => (
{footer.icon}
- + {footer.text} - +
))}
diff --git a/packages/account-v2/src/containers/ManualForm/manualFormInputs.tsx b/packages/account-v2/src/containers/ManualForm/manualFormInputs.tsx index 5a7073e7fe7a..5ec13f858725 100644 --- a/packages/account-v2/src/containers/ManualForm/manualFormInputs.tsx +++ b/packages/account-v2/src/containers/ManualForm/manualFormInputs.tsx @@ -1,8 +1,7 @@ import React, { Fragment } from 'react'; import { Field, FieldProps } from 'formik'; -import { Input } from '@deriv-com/ui'; +import { Input, Text } from '@deriv-com/ui'; import { WalletDatePicker } from '../../components/base/WalletDatePicker'; -import { WalletText } from '../../components/base/WalletText'; import { TManualDocumentTypes } from '../../constants/manualFormConstants'; import { useManualForm } from '../../hooks'; import { getFieldsConfig, getTitleForFormInputs } from '../../utils/manualFormUtils'; @@ -15,7 +14,7 @@ export const ManualFormInputs = ({ selectedDocument }: TManualFormInputsProps) = return ( - {getTitleForFormInputs(selectedDocument)} + {getTitleForFormInputs(selectedDocument)}
{({ field, meta }: FieldProps) => { @@ -25,6 +24,7 @@ export const ManualFormInputs = ({ selectedDocument }: TManualFormInputsProps) = ({ + ...jest.requireActual('@deriv/api'), + useSettings: jest.fn(), +})); + +describe('useManualForm', () => { + it('should return false if citizenship is ng', () => { + (useSettings as jest.Mock).mockReturnValue({ + data: { + citizen: 'ng', + country_code: 'in', + }, + }); + const { result } = renderHook(() => useManualForm()); + const { isExpiryDateRequired } = result.current; + expect(isExpiryDateRequired).toBe(false); + }); + + it('should return true if citizenship and country code is not ng', () => { + (useSettings as jest.Mock).mockReturnValue({ + data: { + citizen: 'au', + country_code: 'au', + }, + }); + const { result } = renderHook(() => useManualForm()); + const { isExpiryDateRequired } = result.current; + expect(isExpiryDateRequired).toBe(true); + }); + + it('should return true if citizenship is not ng but country code is ng', () => { + (useSettings as jest.Mock).mockReturnValue({ + data: { + citizen: 'in', + country_code: 'ng', + }, + }); + const { result } = renderHook(() => useManualForm()); + const { isExpiryDateRequired } = result.current; + expect(isExpiryDateRequired).toBe(true); + }); +}); diff --git a/packages/account-v2/src/hooks/index.ts b/packages/account-v2/src/hooks/index.ts index c73eaaea09f1..6e31bb348d3a 100644 --- a/packages/account-v2/src/hooks/index.ts +++ b/packages/account-v2/src/hooks/index.ts @@ -1 +1 @@ -export { default as useManualForm } from './ManualForm/useManualForm'; +export { default as useManualForm } from './useManualForm'; diff --git a/packages/account-v2/src/hooks/ManualForm/useManualForm.ts b/packages/account-v2/src/hooks/useManualForm.ts similarity index 53% rename from packages/account-v2/src/hooks/ManualForm/useManualForm.ts rename to packages/account-v2/src/hooks/useManualForm.ts index 7e0ea1c81774..dcdba4bc310b 100644 --- a/packages/account-v2/src/hooks/ManualForm/useManualForm.ts +++ b/packages/account-v2/src/hooks/useManualForm.ts @@ -2,12 +2,12 @@ import { useSettings } from '@deriv/api'; /** A custom hook used for manual verification flow */ const useManualForm = () => { - const { data: settings, ...rest } = useSettings(); - const isExpiryDateRequired = settings?.country !== 'ng'; + const { data: settings } = useSettings(); + const countryCode = settings?.citizen ?? settings?.country_code; + const isExpiryDateRequired = !!countryCode && countryCode !== 'ng'; return { isExpiryDateRequired, - ...rest, }; }; diff --git a/packages/account-v2/src/router/components/route-links/route-links.tsx b/packages/account-v2/src/router/components/route-links/route-links.tsx index c78e8fb41665..4c0994ffc1c1 100644 --- a/packages/account-v2/src/router/components/route-links/route-links.tsx +++ b/packages/account-v2/src/router/components/route-links/route-links.tsx @@ -4,7 +4,7 @@ import { defaultRoute, routes } from '../../constants/routesConfig'; const RouteLinks = () => ( -
+
{routes.map(route => ( MANUAL_DOCUMENT_TYPES_DATA[selectedDocument].fields; export const getUploadConfig = (selectedDocument: TManualDocumentTypes) => - MANUAL_DOCUMENT_TYPES_DATA[selectedDocument].upload; + MANUAL_DOCUMENT_TYPES_DATA[selectedDocument].uploads; + +export const getManualFormValidationSchema = ( + selectedDocument: TManualDocumentTypes, + isExpiryDateRequired: boolean +) => { + const fieldsConfig = getFieldsConfig(selectedDocument); + const uploadConfig = getUploadConfig(selectedDocument); + + const documentExpiryValidation = { + document_expiry: isExpiryDateRequired + ? Yup.string().required(fieldsConfig.documentExpiry.errorMessage) + : Yup.string().notRequired(), + }; + + const documentUploadValidation = Object.fromEntries( + uploadConfig.map(item => [item.pageType, Yup.string().required(item.error)]) + ); -export const getManualFormValidationSchema = (selectedDocument: TManualDocumentTypes) => { - const fieldsData = MANUAL_DOCUMENT_TYPES_DATA[selectedDocument].fields; return Yup.object({ - document_expiry: Yup.string().required(fieldsData.documentExpiry.errorMessage), - document_number: Yup.string().required(fieldsData.documentNumber.errorMessage), + document_number: Yup.string().required(fieldsConfig.documentNumber.errorMessage), + ...documentExpiryValidation, + ...documentUploadValidation, }); }; diff --git a/packages/api/src/hooks/useSettings.ts b/packages/api/src/hooks/useSettings.ts index 6b739edf7a8f..48dd5159b114 100644 --- a/packages/api/src/hooks/useSettings.ts +++ b/packages/api/src/hooks/useSettings.ts @@ -1,4 +1,5 @@ import { useCallback, useMemo } from 'react'; +import useAuthorize from './useAuthorize'; import useQuery from '../useQuery'; import useInvalidateQuery from '../useInvalidateQuery'; import useMutation from '../useMutation'; @@ -9,7 +10,8 @@ type TSetSettingsPayload = NonNullable< /** A custom hook to get and update the user settings. */ const useSettings = () => { - const { data, ...rest } = useQuery('get_settings'); + const { isSuccess } = useAuthorize(); + const { data, ...rest } = useQuery('get_settings', { options: { enabled: isSuccess } }); const { mutate, ...mutate_rest } = useMutation('set_settings', { onSuccess: () => invalidate('get_settings') }); const invalidate = useInvalidateQuery();