diff --git a/packages/account/src/Components/hooks/useToggleValidation.tsx b/packages/account/src/Components/hooks/useToggleValidation.tsx deleted file mode 100644 index 338f152543c2..000000000000 --- a/packages/account/src/Components/hooks/useToggleValidation.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { getPlatformFromUrl } from '@deriv/shared'; -import React from 'react'; - -export const useToggleValidation = (hash: string) => { - const [is_validation_enabled, setIsValidationEnabled] = React.useState(false); - const { is_staging_deriv_app } = getPlatformFromUrl(); - - React.useEffect(() => { - // This effect allows to toggle IDV validation - // for repetitive and sequential numbers - if (!is_staging_deriv_app || (hash && hash === '#toggle_id_validation')) { - setIsValidationEnabled(true); - } else { - setIsValidationEnabled(false); - } - }, [hash]); - - return is_validation_enabled; -}; diff --git a/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.js b/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.js index 760be0ae6890..40b62b8829a0 100644 --- a/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.js +++ b/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.js @@ -6,9 +6,6 @@ import { isSequentialNumber, isRecurringNumberRegex } from '../utils'; jest.mock('react-router'); jest.mock('Assets/ic-document-submit-icon.svg', () => jest.fn(() => 'DocumentSubmitLogo')); -jest.mock('../../../hooks/useToggleValidation', () => ({ - useToggleValidation: jest.fn(() => '#toggle_id_validation'), -})); jest.mock('../utils.js', () => ({ getDocumentData: function (country_code, key) { const data = { @@ -30,6 +27,7 @@ jest.mock('../utils.js', () => ({ getRegex: jest.fn(() => /5436454364243/i), isSequentialNumber: jest.fn(() => false), isRecurringNumberRegex: jest.fn(() => false), + isIDVWhitelistDocumentNumber: jest.fn(() => false), })); jest.mock('@deriv/shared', () => ({ diff --git a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx index 2b1ed17d7462..ae42fd1d24c9 100644 --- a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx +++ b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useLocation } from 'react-router'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Autocomplete, Button, DesktopWrapper, Input, MobileWrapper, Text, SelectNative } from '@deriv/components'; @@ -13,15 +12,13 @@ import { isRecurringNumberRegex, isSequentialNumber, preventEmptyClipboardPaste, + isIDVWhitelistDocumentNumber, } from './utils'; -import { useToggleValidation } from '../../hooks/useToggleValidation'; import FormFooter from 'Components/form-footer'; import BackButtonIcon from 'Assets/ic-poi-back-btn.svg'; import DocumentSubmitLogo from 'Assets/ic-document-submit-icon.svg'; const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, is_from_external }) => { - const location = useLocation(); - const validation_is_enabled = useToggleValidation(location?.hash); const [document_list, setDocumentList] = React.useState([]); const [document_image, setDocumentImage] = React.useState(null); const [is_input_disable, setInputDisable] = React.useState(true); @@ -118,6 +115,14 @@ const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, i const is_sequential_number = isSequentialNumber(document_number); const is_recurring_number = isRecurringNumberRegex(document_number); const needs_additional_document = !!document_type.additional; + const is_idv_whitelist_document_number = isIDVWhitelistDocumentNumber( + country_code, + document_type.id, + document_number + ); + const is_document_number_invalid = + (!is_idv_whitelist_document_number && (is_recurring_number || is_sequential_number)) || + document_number === document_type.example_format; if (!document_type || !document_type.text || !document_type.value) { errors.document_type = localize('Please select a document type.'); @@ -135,10 +140,7 @@ const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, i if (!document_number) { errors.document_number = localize('Please enter your document number. ') + getExampleFormat(document_type.example_format); - } else if ( - (validation_is_enabled && (is_recurring_number || is_sequential_number)) || - document_number === document_type.example_format - ) { + } else if (is_document_number_invalid) { errors.document_number = localize('Please enter a valid ID number.'); } else { const format_regex = getRegex(document_type.value); diff --git a/packages/account/src/Components/poi/idv-document-submit/utils.js b/packages/account/src/Components/poi/idv-document-submit/utils.js index 44abda08b293..719f7a729ace 100644 --- a/packages/account/src/Components/poi/idv-document-submit/utils.js +++ b/packages/account/src/Components/poi/idv-document-submit/utils.js @@ -1,4 +1,4 @@ -import { getUrlBase } from '@deriv/shared'; +import { getPlatformFromUrl, getUrlBase } from '@deriv/shared'; const PATTERN_SIZE = 5; @@ -48,6 +48,15 @@ export const isSequentialNumber = document_number => { return pattern_results.includes(true); }; +// function for skipping validation of exact document numbers for QA smileidentity sandbox testing +export const isIDVWhitelistDocumentNumber = (country, document_type, document_number) => { + const is_whitelisted_number = + idv_test_document_whitelist.has(country) && + idv_test_document_whitelist.get(country)[document_type] === document_number; + + return is_whitelisted_number && (getPlatformFromUrl().is_test_link || getPlatformFromUrl().is_staging); +}; + export const getRegex = target_regex => { const output_regex = regex.find(r => r.regex_string === target_regex); if (output_regex) { @@ -206,3 +215,10 @@ const idv_document_data = { }, }, }; + +export const idv_test_document_whitelist = new Map([ + ['gh', { drivers_license: 'B0000000', passport: 'G0000000', ssnit: 'C000000000000', voter_id: '0000000000' }], + ['ke', { alien_card: '000000', passport: 'A00000000', national_id: '00000000' }], + ['ng', { drivers_license: 'ABC000000000', nin_slip: '00000000000', voter_id: '0000000000000000000' }], + ['za', { national_id: '0000000000000', national_id_no_photo: '0000000000000' }], +]); diff --git a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.jsx b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.jsx index a30eb074437a..627224a57346 100644 --- a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.jsx +++ b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useLocation } from 'react-router'; import { Formik, Field } from 'formik'; import { localize, Localize } from '@deriv/translations'; import { @@ -22,13 +21,11 @@ import { isRecurringNumberRegex, isSequentialNumber, preventEmptyClipboardPaste, + isIDVWhitelistDocumentNumber, } from '../../idv-document-submit/utils'; -import { useToggleValidation } from '../../../hooks/useToggleValidation'; import DocumentSubmitLogo from 'Assets/ic-document-submit-icon.svg'; export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, onNext, value, has_idv_error }) => { - const location = useLocation(); - const validation_is_enabled = useToggleValidation(location?.hash); const [document_list, setDocumentList] = React.useState([]); const [document_image, setDocumentImage] = React.useState(null); const [is_input_disable, setInputDisable] = React.useState(true); @@ -100,6 +97,14 @@ export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, o const is_sequential_number = isSequentialNumber(document_number); const is_recurring_number = isRecurringNumberRegex(document_number); const needs_additional_document = !!document_type.additional; + const is_idv_whitelist_document_number = isIDVWhitelistDocumentNumber( + country_code, + document_type.id, + document_number + ); + const is_document_number_invalid = + (!is_idv_whitelist_document_number && (is_recurring_number || is_sequential_number)) || + document_number === document_type.example_format; if (!document_type || !document_type.text || !document_type.value) { errors.document_type = localize('Please select a document type.'); @@ -117,10 +122,7 @@ export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, o if (!document_number) { errors.document_number = localize('Please enter your document number. ') + getExampleFormat(document_type.example_format); - } else if ( - (validation_is_enabled && (is_recurring_number || is_sequential_number)) || - document_number === document_type.example_format - ) { + } else if (is_document_number_invalid) { errors.document_number = localize('Please enter a valid ID number.'); } else { const format_regex = getRegex(document_type.value); @@ -177,7 +179,17 @@ export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, o validateOnChange validateOnBlur > - {({ errors, handleBlur, handleChange, handleSubmit, isValid, setFieldValue, touched, values }) => ( + {({ + errors, + handleBlur, + handleChange, + handleSubmit, + isSubmitting, + isValid, + setFieldValue, + touched, + values, + }) => ( {({ setRef }) => (
@@ -332,8 +344,9 @@ export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, o } disabled={is_input_disable} error={ - touched.document_number && - errors.document_number + (touched.document_number && + errors.document_number) || + errors.error_message } autoComplete='off' placeholder='Enter your document number' @@ -421,7 +434,9 @@ export const IdvDocSubmitOnSignup = ({ citizen_data, has_previous, onPrevious, o val, required_fields, localize('This field is required')); - const only_alphabet_fields = ['first_name', 'last_name']; - validateValues(validLetterSymbol, only_alphabet_fields, localize('Only alphabet is allowed')); const residence_fields = ['citizen']; const validateResidence = val => getLocation(residence_list, val, 'value'); @@ -351,12 +350,19 @@ export const PersonalDetailsForm = ({ const min_name = 2; const max_name = 50; - if (values.first_name && !validLength(values.first_name.trim(), { min: min_name, max: max_name })) { - errors.first_name = localize('You should enter 2-50 characters.'); - } - if (values.last_name && !validLength(values.last_name.trim(), { min: min_name, max: max_name })) { - errors.last_name = localize('You should enter 2-50 characters.'); - } + const validateName = (name, field) => { + if (name) { + if (!validLength(name.trim(), { min: min_name, max: max_name })) { + errors[field] = localize('You should enter 2-50 characters.'); + } else if (!validName(name)) { + // validName() has the exact regex used at the backend for allowing non digit characters including accented unicode characters. + // two or more space between name not allowed. + errors[field] = localize('Letters, spaces, periods, hyphens, apostrophes only.'); + } + } + }; + validateName(values.first_name, 'first_name'); + validateName(values.last_name, 'last_name'); if (values.phone) { // minimum characters required is 9 numbers (excluding +- signs or space) diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx index 1fcbd1dc56f9..bbd331f78617 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx @@ -50,7 +50,8 @@ const POISubmissionForMT5 = ({ refreshNotifications(); }); }; - const handleIdvSubmit = values => { + const handleIdvSubmit = (values, { setSubmitting, setErrors }) => { + setSubmitting(true); const { document_number, document_type } = values; const submit_data = { identity_verification_document_add: 1, @@ -59,7 +60,12 @@ const POISubmissionForMT5 = ({ issuing_country: citizen_data.value, }; - WS.send(submit_data).then(() => { + WS.send(submit_data).then(response => { + setSubmitting(false); + if (response.error) { + setErrors({ error_message: response.error.message }); + return; + } handlePOIComplete(); }); }; diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index 1e5c630a5344..ee84ecccefdb 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -1145,6 +1145,10 @@ &-row-demo { grid-template-columns: 1.5fr 1fr 1fr 1fr; + + &--restricted { + grid-template-columns: 1fr 2fr 1fr; + } @include mobile { width: 116vw; height: unset; @@ -1177,6 +1181,10 @@ &__pre-appstore { grid-template-columns: 1fr 1fr; } + } @else if $synthetic_accounts_count > 0 and $financial_accounts_count == 0 { + &__pre-appstore { + grid-template-columns: 2fr 1fr; + } } @else if $synthetic_accounts_count == 0 { grid-template-columns: 1fr ($financial_accounts_count + fr); &__pre-appstore { @@ -1189,6 +1197,11 @@ } } } + .cfd-accounts-compare-modal__table-header-for-synthetic-#{$synthetic_accounts_count} { + &__pre-appstore { + grid-template-columns: 1fr 2fr 1fr; + } + } } } &__table-header-item { @@ -1209,6 +1222,10 @@ &--platform { &__pre-appstore { + grid-template-columns: 1fr 4.25fr 0.9fr; + &--restricted { + grid-template-columns: 1fr 2fr 1fr; + } grid-template-columns: 11rem 82rem 13.5rem; } } diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index c7b74a5819e2..5f73af706f52 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -174,6 +174,7 @@ const CFDsListing = () => { setSelectedAccount(existing_account); } else if (button_name === 'topup-btn') { showTopUpModal(existing_account); + setAppstorePlatform(existing_account.platform); } else { startTrade(existing_account.platform, existing_account); } @@ -241,6 +242,7 @@ const CFDsListing = () => { setSelectedAccount(existing_account); } else if (button_name === 'topup-btn') { showTopUpModal(existing_account); + setAppstorePlatform(account.platform); } else { startTrade(account.platform, existing_account); } diff --git a/packages/appstore/src/components/onboarding-new/static-dashboard.scss b/packages/appstore/src/components/onboarding-new/static-dashboard.scss index fcc0799ccf1d..27dba57ca43e 100644 --- a/packages/appstore/src/components/onboarding-new/static-dashboard.scss +++ b/packages/appstore/src/components/onboarding-new/static-dashboard.scss @@ -9,18 +9,17 @@ position: relative; bottom: 2rem; } - - &--deposit-button-blurry { - opacity: 0.2; + &--deposit-button { cursor: not-allowed; + &-blurry { + opacity: 0.2; + } + &-animated { + width: fit-content; + box-shadow: 0 2rem 1.5rem -0.1rem var(--general-section-1); + animation: triggerAnimation 0.5s linear infinite alternate; + } } - - &--deposit-button-animated { - width: fit-content; - box-shadow: 0 2rem 1.5rem -0.1rem var(--general-section-1); - animation: triggerAnimation 0.5s linear infinite alternate; - } - &--eu { padding-bottom: 7rem; } diff --git a/packages/appstore/src/components/onboarding-new/static-dashboard.tsx b/packages/appstore/src/components/onboarding-new/static-dashboard.tsx index 3b370a135125..6d98895d07fe 100644 --- a/packages/appstore/src/components/onboarding-new/static-dashboard.tsx +++ b/packages/appstore/src/components/onboarding-new/static-dashboard.tsx @@ -219,7 +219,7 @@ const StaticDashboard = ({ actions={