From 77e74f59c8c92f3aab6f3026a5511aeb01636dac Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Thu, 15 Aug 2024 14:17:38 +0800 Subject: [PATCH 01/19] chore: refactor notification tray while onclick auto scroll to phone field in personal details --- .../Profile/PersonalDetails/verify-button.tsx | 24 ++++++++++++++++--- .../core/src/Stores/notification-store.js | 6 ++--- packages/core/src/Stores/ui-store.js | 7 ++++++ packages/stores/src/mockStore.ts | 2 ++ packages/stores/types.ts | 2 ++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx index d26ab0dd2010..05ed9bbf5004 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import clsx from 'clsx'; import { CaptionText } from '@deriv-com/quill-ui'; import { observer, useStore } from '@deriv/stores'; @@ -18,7 +18,7 @@ type TVerifyButton = { export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) => { const [open_popover, setOpenPopover] = useState(false); const { client, ui } = useStore(); - const { setShouldShowPhoneNumberOTP } = ui; + const { setShouldShowPhoneNumberOTP, is_scroll_to_verify_button, setIsScrollToVerifyButton } = ui; const { account_settings, setVerificationCode } = client; const { phone_number_verification } = account_settings; const phone_number_verified = phone_number_verification?.verified; @@ -27,6 +27,24 @@ export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) const { sendPhoneNumberVerifyEmail, WS } = useVerifyEmail('phone_number_verification'); const { isDesktop } = useDevice(); const { next_otp_request, is_request_button_disabled } = usePhoneNumberVerificationSetTimer(); + const ref = useRef(null); + + useEffect(() => { + if (is_scroll_to_verify_button) { + // To make scrolling work on mobile we need to add a delay. + const timeout = setTimeout(() => { + if (ref.current) { + ref.current.style.scrollMarginTop = isDesktop ? '80px' : '40px'; + ref.current.scrollIntoView({ behavior: 'smooth' }); + } + }, 0); + + return () => { + clearTimeout(timeout); + setIsScrollToVerifyButton(false); + }; + } + }, [is_scroll_to_verify_button, setIsScrollToVerifyButton, isDesktop]); useEffect(() => { if (WS.isSuccess) { @@ -50,7 +68,7 @@ export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) }; return ( -
+
{phone_number_verified ? (
diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index 98409f440b09..bd8e83087491 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -789,7 +789,7 @@ export default class NotificationStore extends BaseStore { setClientNotifications(client_data = {}) { const { ui } = this.root_store; - const { has_enabled_two_fa, setTwoFAChangedStatus, logout, email } = this.root_store.client; + const { has_enabled_two_fa, setTwoFAChangedStatus, logout } = this.root_store.client; const two_fa_status = has_enabled_two_fa ? localize('enabled') : localize('disabled'); const platform_name_trader = getPlatformSettings('trader').name; @@ -1108,9 +1108,9 @@ export default class NotificationStore extends BaseStore { type: 'warning', action: { onClick: () => { - WS.verifyEmail(email, 'phone_number_verification'); + this.root_store.ui.setIsScrollToVerifyButton(true); }, - route: routes.phone_verification, + route: routes.personal_details, text: localize('Get started'), }, should_show_again: true, diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index ac37708d4c24..3336118d7db4 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -87,6 +87,7 @@ export default class UIStore extends BaseStore { // verification modal is_verification_modal_visible = false; + is_scroll_to_verify_button = false; //verification document submitted modal is_verification_submitted = false; @@ -284,6 +285,7 @@ export default class UIStore extends BaseStore { is_real_tab_enabled: observable, is_reports_visible: observable, is_route_modal_on: observable, + is_scroll_to_verify_button: observable, is_set_currency_modal_visible: observable, is_settings_modal_on: observable, is_switch_to_deriv_account_modal_visible: observable, @@ -379,6 +381,7 @@ export default class UIStore extends BaseStore { setReportsTabIndex: action.bound, toggleReadyToDepositModal: action.bound, toggleNeedRealAccountForCashierModal: action.bound, + setIsScrollToVerifyButton: action.bound, toggleShouldShowRealAccountsList: action.bound, shouldNavigateAfterChooseCrypto: action.bound, setIsMT5VerificationFailedModal: action.bound, @@ -901,6 +904,10 @@ export default class UIStore extends BaseStore { this.should_show_risk_warning_modal = value; } + setIsScrollToVerifyButton(is_scroll_to_verify_button) { + this.is_scroll_to_verify_button = is_scroll_to_verify_button; + } + setIsTradingAssessmentForExistingUserEnabled(value) { this.is_trading_assessment_for_existing_user_enabled = value; } diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index e06fe97a4b31..30b9a7af6166 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -384,6 +384,8 @@ const mock = (): TStores & { is_mock: boolean } => { should_show_phone_number_otp: false, is_trading_assessment_for_existing_user_enabled: false, setIsForcedToExitPnv: jest.fn(), + is_scroll_to_verify_button: false, + setIsScrollToVerifyButton: jest.fn(), setRedirectFromEmail: jest.fn(), setShouldShowPhoneNumberOTP: jest.fn(), disableApp: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index e49a844bb4ff..d059409a7e14 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -725,7 +725,9 @@ type TUiStore = { is_reset_email_modal_visible: boolean; is_services_error_visible: boolean; is_trading_assessment_for_existing_user_enabled: boolean; + is_scroll_to_verify_button: boolean; isUrlUnavailableModalVisible: boolean; + setIsScrollToVerifyButton: (value: boolean) => void; onChangeUiStore: ({ name, value }: { name: string; value: unknown }) => void; openPositionsDrawer: () => void; openRealAccountSignup: ( From 96fdca3734a779a869d9c247a62924c9404d7597 Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Mon, 19 Aug 2024 18:44:20 +0800 Subject: [PATCH 02/19] chore: update verify button and change usePhoneVerificationSetTimer function to return only numbers --- .../__tests__/verify-button.spec.tsx | 36 +------ .../PersonalDetails/personal-details-form.tsx | 71 +++++++++++--- .../PersonalDetails/verify-button.scss | 40 +------- .../Profile/PersonalDetails/verify-button.tsx | 95 ++++++------------- .../__test__/confirm-phone-number.spec.tsx | 8 +- .../__test__/resend-code-timer.spec.tsx | 10 +- .../verification-link-expired-modal.spec.tsx | 6 +- .../confirm-phone-number.tsx | 25 ++++- .../PhoneVerification/resend-code-timer.tsx | 42 +++++++- .../verification-link-expired-modal.tsx | 22 ++++- packages/account/src/Styles/account.scss | 7 -- ...sePhoneNumberVerificationSetTimer.spec.tsx | 63 +++++------- .../usePhoneNumberVerificationSetTimer.tsx | 73 ++++---------- 13 files changed, 219 insertions(+), 279 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/verify-button.spec.tsx b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/verify-button.spec.tsx index c8008dd0ea03..aadc664c8e28 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/verify-button.spec.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/verify-button.spec.tsx @@ -36,7 +36,7 @@ describe('VerifyButton', () => { return render( - + ); @@ -47,13 +47,6 @@ describe('VerifyButton', () => { expect(screen.getByText('Verify')).toBeInTheDocument(); }); - it('should render Verify Button with countdown timer return from usePhoneNumberSetTimer and should have disabled class', () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: 'in 60s' }); - renderWithRouter(); - expect(screen.getByText('Verify in 60s')).toBeInTheDocument(); - expect(screen.getByText('Verify in 60s')).toHaveClass('phone-verification-btn--not-verified--disabled'); - }); - it('should redirect user to phone-verification page when clicked on Verify Button', () => { (useVerifyEmail as jest.Mock).mockReturnValue({ sendPhoneNumberVerifyEmail: jest.fn(), @@ -72,32 +65,5 @@ describe('VerifyButton', () => { mock_store.client.account_settings.phone_number_verification.verified = 1; renderWithRouter(); expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getByTestId('dt_phone_verification_popover')).toBeInTheDocument(); - }); - - it('should render popover text when popover is clicked', () => { - if (mock_store.client.account_settings.phone_number_verification) - mock_store.client.account_settings.phone_number_verification.verified = 1; - renderWithRouter(); - const popover = screen.getByTestId('dt_phone_verification_popover'); - userEvent.click(popover); - expect(screen.getByText(/To change your verified phone number, contact us via/)).toBeInTheDocument(); - expect(screen.getByText(/live chat/)).toBeInTheDocument(); - }); - - it('should render live chat window when live chat is clicked', () => { - if (mock_store.client.account_settings.phone_number_verification) - mock_store.client.account_settings.phone_number_verification.verified = 1; - window.LC_API = { - open_chat_window: jest.fn(), - on_chat_ended: jest.fn(), - }; - - renderWithRouter(); - const popover = screen.getByTestId('dt_phone_verification_popover'); - userEvent.click(popover); - const livechat = screen.getByText(/live chat/); - userEvent.click(livechat); - expect(window.LC_API.open_chat_window).toHaveBeenCalled(); }); }); diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx index 021a61f79a1e..8daf134c9e9d 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx @@ -11,6 +11,7 @@ import { HintBox, Input, Loading, + OpenLiveChatLink, SelectNative, Text, } from '@deriv/components'; @@ -31,24 +32,34 @@ import { getPersonalDetailsInitialValues, getPersonalDetailsValidationSchema, ma import FormSelectField from 'Components/forms/form-select-field'; import { VerifyButton } from './verify-button'; import { useInvalidateQuery } from '@deriv/api'; -import { useStatesList, useResidenceList, useGrowthbookGetFeatureValue } from '@deriv/hooks'; +import { + useStatesList, + useResidenceList, + useGrowthbookGetFeatureValue, + usePhoneNumberVerificationSetTimer, +} from '@deriv/hooks'; type TRestState = { show_form: boolean; api_error?: string; }; +type THintMessage = { + is_phone_number_editted: boolean; + is_phone_number_empty: boolean; +}; + const PersonalDetailsForm = observer(() => { const { isDesktop } = useDevice(); const [is_loading, setIsLoading] = useState(false); const [is_btn_loading, setIsBtnLoading] = useState(false); const [is_submit_success, setIsSubmitSuccess] = useState(false); - const [is_phone_field_disable, setIsPhoneFieldDisable] = useState(false); const invalidate = useInvalidateQuery(); const history = useHistory(); const [isPhoneNumberVerificationEnabled] = useGrowthbookGetFeatureValue({ featureFlag: 'phone_number_verification', }); + const { next_request_time, is_request_button_disabled } = usePhoneNumberVerificationSetTimer(); const { client, @@ -109,6 +120,33 @@ const PersonalDetailsForm = observer(() => { } }, [invalidate, is_language_changing]); + const hintMessage = ({ is_phone_number_editted, is_phone_number_empty }: THintMessage) => { + if (account_settings?.phone_number_verification?.verified) { + return ( + ]} + /> + ); + } else if (next_request_time) { + return ( + + ); + } else if (is_phone_number_editted) { + return ; + } else if (is_phone_number_empty) { + return ; + } + }; + const onSubmit = async (values: GetSettings, { setStatus, setSubmitting }: FormikHelpers) => { setStatus({ msg: '' }); const request = makeSettingsRequest({ ...values }, residence_list, states_list, is_virtual); @@ -366,12 +404,12 @@ const PersonalDetailsForm = observer(() => { name='phone' id={'phone'} label={localize('Phone number*')} - className={clsx({ - 'account-form__fieldset--phone': - account_settings?.phone_number_verification?.verified, - })} //@ts-expect-error type of residence should not be null: needs to be updated in GetSettings type value={values.phone} + hint={hintMessage({ + is_phone_number_editted: account_settings.phone !== values.phone, + is_phone_number_empty: !account_settings.phone, + })} onChange={(e: ChangeEvent) => { handleChange(e); setStatus(''); @@ -379,14 +417,23 @@ const PersonalDetailsForm = observer(() => { onBlur={handleBlur} required error={errors.phone} - disabled={isFieldDisabled('phone') || is_phone_field_disable} + disabled={ + isFieldDisabled('phone') || + is_request_button_disabled || + !!next_request_time + } data-testid='dt_phone' /> - {isPhoneNumberVerificationEnabled && - account_settings.phone && - account_settings.phone === values.phone && ( - - )} + {isPhoneNumberVerificationEnabled && ( + + )} )} diff --git a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss index b9fc2f1dd105..94319ec6f2c4 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss +++ b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss @@ -1,41 +1,5 @@ -.phone-verification-btn { +.phone-verification-button { position: absolute; inset-inline-end: 0; - top: 0.4rem; - - &--not-verified { - display: flex; - padding-top: 0.6rem; - padding-inline-end: 1.2rem; - cursor: pointer; - - &--disabled { - cursor: not-allowed; - color: rgba(#ff444f, 0.5); - } - } - - &--verified { - display: flex; - padding-top: 0.6rem; - padding-inline-end: 1.2rem; - color: var(--text-profit-success); - - @include mobile-or-tablet-screen { - padding-inline-end: 4rem; - } - - > :first-child { - padding-inline-end: 0.4rem; - } - - .phone-verification__popover { - position: absolute; - inset-inline-end: -3rem; - - @include mobile-or-tablet-screen { - inset-inline-end: 0rem; - } - } - } + top: 0; } diff --git a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx index 05ed9bbf5004..2606246b36de 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx @@ -1,32 +1,27 @@ -import { useEffect, useRef, useState } from 'react'; -import clsx from 'clsx'; -import { CaptionText } from '@deriv-com/quill-ui'; +import React, { useEffect, useRef } from 'react'; import { observer, useStore } from '@deriv/stores'; -import { LegacyWonIcon } from '@deriv/quill-icons'; import { useHistory } from 'react-router'; import { routes } from '@deriv/shared'; -import { OpenLiveChatLink, Popover, Text } from '@deriv/components'; +import { Button } from '@deriv/components'; import { Localize } from '@deriv/translations'; -import { usePhoneNumberVerificationSetTimer, useVerifyEmail } from '@deriv/hooks'; +import { useVerifyEmail } from '@deriv/hooks'; import { useDevice } from '@deriv-com/ui'; import './verify-button.scss'; type TVerifyButton = { - setIsPhoneFieldDisable: (value: boolean) => void; + is_verify_button_disabled: boolean; }; -export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) => { - const [open_popover, setOpenPopover] = useState(false); +export const VerifyButton = observer(({ is_verify_button_disabled }: TVerifyButton) => { const { client, ui } = useStore(); const { setShouldShowPhoneNumberOTP, is_scroll_to_verify_button, setIsScrollToVerifyButton } = ui; const { account_settings, setVerificationCode } = client; const { phone_number_verification } = account_settings; - const phone_number_verified = phone_number_verification?.verified; + const phone_number_verified = !!phone_number_verification?.verified; const history = useHistory(); - //@ts-expect-error remove this comment when types are added in GetSettings api types - const { sendPhoneNumberVerifyEmail, WS } = useVerifyEmail('phone_number_verification'); + //@ts-expect-error remove this when phone_number_verification is added to api calls + const { sendPhoneNumberVerifyEmail, WS, is_loading } = useVerifyEmail('phone_number_verification'); const { isDesktop } = useDevice(); - const { next_otp_request, is_request_button_disabled } = usePhoneNumberVerificationSetTimer(); const ref = useRef(null); useEffect(() => { @@ -34,7 +29,7 @@ export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) // To make scrolling work on mobile we need to add a delay. const timeout = setTimeout(() => { if (ref.current) { - ref.current.style.scrollMarginTop = isDesktop ? '80px' : '40px'; + ref.current.style.scrollMarginTop = isDesktop ? '120px' : '80px'; ref.current.scrollIntoView({ behavior: 'smooth' }); } }, 0); @@ -52,66 +47,30 @@ export const VerifyButton = observer(({ setIsPhoneFieldDisable }: TVerifyButton) } }, [WS.isSuccess, history]); - useEffect(() => { - if (next_otp_request || is_request_button_disabled) { - setIsPhoneFieldDisable(true); - } else { - setIsPhoneFieldDisable(false); - } - }, [next_otp_request, is_request_button_disabled]); - - const redirectToPhoneVerification = () => { - if (next_otp_request || is_request_button_disabled) return; + const redirectToPhoneVerification = (e: React.MouseEvent) => { + e.preventDefault(); setVerificationCode('', 'phone_number_verification'); setShouldShowPhoneNumberOTP(false); sendPhoneNumberVerifyEmail(); }; return ( -
- {phone_number_verified ? ( -
- - - - - setOpenPopover(prev => !prev)} - message={ - - ]} - /> - - } - zIndex='9999' - /> -
- ) : ( - - - - )} +
+
); }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/confirm-phone-number.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/confirm-phone-number.spec.tsx index 720ec9535a32..291ff02feab9 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/confirm-phone-number.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/confirm-phone-number.spec.tsx @@ -19,7 +19,7 @@ jest.mock('@deriv/hooks', () => ({ invalidate: jest.fn(), })), usePhoneNumberVerificationSetTimer: jest.fn(() => ({ - next_otp_request: undefined, + next_request_time: undefined, })), })); @@ -141,7 +141,7 @@ describe('ConfirmPhoneNumber', () => { }); it('should make both buttons disabled if next_otp_request text is provided', async () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: '60 seconds' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: 60 }); render( @@ -154,12 +154,12 @@ describe('ConfirmPhoneNumber', () => { }); it('should get snackbar text when next_otp_request text is provided', async () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: '60 seconds' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: 60 }); render( ); - expect(screen.getByText(/An error occurred. Request a new OTP in 60 seconds./)); + expect(screen.getByText(/An error occurred. Request a new OTP in 1 minutes./)); }); }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx index 3c6563e074d2..f0de7265f87f 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx @@ -22,7 +22,7 @@ jest.mock('@deriv/hooks', () => ({ }, })), usePhoneNumberVerificationSetTimer: jest.fn(() => ({ - next_otp_request: '', + next_request_time: '', })), })); @@ -36,7 +36,7 @@ describe('ConfirmPhoneNumber', () => { afterEach(() => { jest.clearAllMocks(); - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: '' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: '' }); }); const mockSetShouldShowDidntGetTheCodeModal = jest.fn(); @@ -67,7 +67,7 @@ describe('ConfirmPhoneNumber', () => { }); it('should disable button if usePhoneNumberVerificationSetTimer returns next_otp_request', async () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: 'in 59s' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: 59 }); renderComponent(); expect(screen.queryByRole('button', { name: 'Resend code in 59s' })).toBeDisabled; @@ -94,9 +94,9 @@ describe('ConfirmPhoneNumber', () => { }); it('should display Didn’t get the code? (60s) when usePhoneNumberSetTimer returns (60s)', () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: ' (60s)' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: 60 }); renderComponent(false, false); - const resend_button = screen.getByRole('button', { name: "Didn't get the code? (60s)" }); + const resend_button = screen.getByRole('button', { name: "Didn't get the code? (1m)" }); expect(resend_button).toBeInTheDocument(); }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx index 6e3e547da644..ad594e47c44d 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx @@ -18,7 +18,7 @@ jest.mock('react-router', () => ({ jest.mock('@deriv/hooks', () => ({ ...jest.requireActual('@deriv/hooks'), usePhoneNumberVerificationSetTimer: jest.fn(() => ({ - next_otp_request: '', + next_request_time: '', })), })); @@ -76,8 +76,8 @@ describe('VerificationLinkExpiredModal', () => { }); it('should show in 60s which is coming from usePhoneNumberVerificationSetTimer', () => { - (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_otp_request: ' in 60s' }); + (usePhoneNumberVerificationSetTimer as jest.Mock).mockReturnValue({ next_request_time: 60 }); renderComponent(); - expect(screen.getByText(/in 60s/)).toBeInTheDocument(); + expect(screen.getByText(/in 1m/)).toBeInTheDocument(); }); }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx index 048b88006ddb..613210cc40ad 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx @@ -33,7 +33,7 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica const { data: account_settings, invalidate } = useSettings(); const { ui } = useStore(); const { setShouldShowPhoneNumberOTP } = ui; - const { next_otp_request } = usePhoneNumberVerificationSetTimer(true); + const { next_request_time } = usePhoneNumberVerificationSetTimer(true); const { trackPhoneVerificationEvents } = usePhoneVerificationAnalytics(); useEffect(() => { @@ -81,6 +81,21 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica } }; + const resendPhoneOtpTimer = () => { + let resendPhoneOtpTimer = ''; + if (next_request_time) { + next_request_time < 60 + ? (resendPhoneOtpTimer = localize(`${next_request_time} seconds`)) + : (resendPhoneOtpTimer = localize( + `${next_request_time && Math.round(next_request_time / 60)} minutes` + )); + } else { + resendPhoneOtpTimer = ''; + } + + return resendPhoneOtpTimer; + }; + return ( @@ -103,7 +118,7 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica fullWidth size='lg' onClick={() => handleSubmit(VERIFICATION_SERVICES.SMS)} - disabled={is_button_loading || !!next_otp_request} + disabled={is_button_loading || !!next_request_time} > @@ -114,7 +129,7 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica fullWidth size='lg' onClick={() => handleSubmit(VERIFICATION_SERVICES.WHATSAPP)} - disabled={is_button_loading || !!next_otp_request} + disabled={is_button_loading || !!next_request_time} > @@ -126,10 +141,10 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica message={ } - isVisible={!!next_otp_request} + isVisible={!!next_request_time} /> ); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx index fd52b3ca5668..250fb12131fb 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Button, CaptionText } from '@deriv-com/quill-ui'; import { Localize } from '@deriv/translations'; import { usePhoneNumberVerificationSetTimer, useVerifyEmail } from '@deriv/hooks'; +import { localize } from '@deriv-com/translations'; type TResendCodeTimer = { is_button_disabled: boolean; @@ -21,7 +22,7 @@ const ResendCodeTimer = ({ }: TResendCodeTimer) => { // @ts-expect-error this for now const { sendPhoneNumberVerifyEmail, WS, error } = useVerifyEmail('phone_number_verification'); - const { next_otp_request, is_request_button_disabled } = usePhoneNumberVerificationSetTimer(); + const { is_request_button_disabled, next_request_time } = usePhoneNumberVerificationSetTimer(); React.useEffect(() => { if (WS.isSuccess || error) reInitializeGetSettings(); @@ -37,20 +38,53 @@ const ResendCodeTimer = ({ } }; + const resendCodeTimer = () => { + let resendCodeTimer = ''; + if (next_request_time) { + next_request_time < 60 + ? (resendCodeTimer = localize(` in ${next_request_time}s`)) + : (resendCodeTimer = localize(` in ${next_request_time && Math.round(next_request_time / 60)}m`)); + } else { + resendCodeTimer = ''; + } + + return resendCodeTimer; + }; + + const didntGetACodeTimer = () => { + let didntGetACodeTimer = ''; + if (next_request_time) { + next_request_time < 60 + ? (didntGetACodeTimer = localize(` (${next_request_time}s)`)) + : (didntGetACodeTimer = localize(` (${next_request_time && Math.round(next_request_time / 60)}m)`)); + } else { + didntGetACodeTimer = ''; + } + + return didntGetACodeTimer; + }; + return (
diff --git a/packages/core/src/sass/app/_common/components/account-common.scss b/packages/core/src/sass/app/_common/components/account-common.scss index 941f6ee9449c..2eea8b4772d7 100644 --- a/packages/core/src/sass/app/_common/components/account-common.scss +++ b/packages/core/src/sass/app/_common/components/account-common.scss @@ -357,14 +357,16 @@ color: var(--text-prominent); font-size: var(--text-size-xxs); line-height: 1.5; - text-align: right; + display: flex; + justify-content: end; + align-items: center; min-width: 27.6rem; max-width: 36.6rem; height: 3.6rem; @include mobile-or-tablet-screen { - width: auto; - text-align: center; + width: 100%; + text-align: start; align-self: center; &--dashboard { From 312e5e31aef3c02f9db02e47829380b03e762a8d Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Thu, 22 Aug 2024 18:41:16 +0800 Subject: [PATCH 07/19] chore: change content for cancel-modal, didnt-get-a-code, otp-verification and link expired modal --- .../cancel-phone-verification-modal.spec.tsx | 8 +- .../didnt-get-the-code-modal.spec.tsx | 12 +-- .../__test__/otp-verification.spec.tsx | 9 +- .../verification-link-expired-modal.spec.tsx | 4 +- .../cancel-phone-verification-modal.tsx | 6 +- .../didnt-get-the-code-modal.tsx | 86 +++++++------------ .../PhoneVerification/otp-verification.tsx | 20 ++++- .../verification-link-expired-modal.tsx | 11 +-- packages/core/src/Stores/ui-store.js | 2 +- 9 files changed, 66 insertions(+), 92 deletions(-) diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/cancel-phone-verification-modal.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/cancel-phone-verification-modal.spec.tsx index 7382cec38280..d09e06cf7739 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/cancel-phone-verification-modal.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/cancel-phone-verification-modal.spec.tsx @@ -35,7 +35,7 @@ describe('CancelPhoneVerificationModal', () => { const mock_store = mockStore({}); - const buttons = [/Go back/, /Yes, cancel/]; + const buttons = [/Continue verification/, /Cancel/]; const renderComponent = () => { render( @@ -51,17 +51,17 @@ describe('CancelPhoneVerificationModal', () => { expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); expect(screen.getByText(/Cancel phone number verification?/)).toBeInTheDocument(); - expect(screen.getByText(/All details entered will be lost./)).toBeInTheDocument(); + expect(screen.getByText(/If you cancel, you'll lose all progress./)).toBeInTheDocument(); }); - it('it should render only mockSetShowCancelModal when Go back is clicked', () => { + it('it should render only mockSetShowCancelModal when Continue verification is clicked', () => { renderComponent(); const cancelButton = screen.getByRole('button', { name: buttons[0] }); userEvent.click(cancelButton); expect(mock_push).not.toBeCalled(); }); - it('it should render mockSetShowCancelModal and mock_back_router when Yes, cancel is clicked', () => { + it('it should render mockSetShowCancelModal and mock_back_router when Cancel is clicked', () => { renderComponent(); const cancelButton = screen.getByRole('button', { name: buttons[1] }); userEvent.click(cancelButton); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx index d75d37fa0953..e00213ede6dd 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx @@ -41,11 +41,9 @@ describe('DidntGetTheCodeModal', () => { it('should render DidntGetTheCodeModal', () => { renderComponent(VERIFICATION_SERVICES.SMS); - expect(screen.getByText(/Get a new code/)).toBeInTheDocument(); + expect(screen.getByText(/Didn't receive a code/)).toBeInTheDocument(); expect(screen.getByRole('button', { name: resend_code_text })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /Send code via WhatsApp/ })).toBeInTheDocument(); - expect(screen.getByText(/or/)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Change phone number/ })).toBeInTheDocument(); }); it('should show Send code via SMS if phone_verification_type is whatsapp', () => { @@ -53,14 +51,6 @@ describe('DidntGetTheCodeModal', () => { expect(screen.getByRole('button', { name: /Send code via SMS/ })).toBeInTheDocument(); }); - it('should render setOtpVerification and setShouldShowDidintGetTheCodeModal when Change phone number is clicked, should not render mockSetTimer', () => { - renderComponent(VERIFICATION_SERVICES.SMS); - const change_phone_number_button = screen.getByRole('button', { name: /Change phone number/ }); - userEvent.click(change_phone_number_button); - expect(mockSetShouldShowDidntGetTheCodeModal).toHaveBeenCalledTimes(1); - expect(mockSetOtpVerification).toHaveBeenCalledTimes(1); - }); - it('should render setShouldShowDidintGetTheCodeModal when Resend code is clicked', () => { renderComponent(VERIFICATION_SERVICES.SMS); const resend_code_button = screen.getByRole('button', { name: resend_code_text }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx index c9a0f2b58ef6..04f64bfc8ced 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx @@ -53,12 +53,10 @@ describe('OTPVerification', () => { it('should render ConfirmYourEmail in OTP Verification', () => { renderComponent(); - expect(screen.getByText(/Confirm it's you/)).toBeInTheDocument(); + expect(screen.getByText(/Verify access/)).toBeInTheDocument(); expect(screen.getByText(/We've sent a verification code to/)).toBeInTheDocument(); expect(screen.getByText('johndoe@regentmarkets.com')).toBeInTheDocument(); - expect( - screen.getByText(/Enter the code or click the link in the email to verify that the account belongs to you./) - ).toBeInTheDocument(); + expect(screen.getByText(/Enter the code below so we know the request has come from you./)).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: /OTP code/ })).toBeInTheDocument(); expect(screen.getByText(/Resend Code Timer/)).toBeInTheDocument(); }); @@ -67,7 +65,8 @@ describe('OTPVerification', () => { store.ui.should_show_phone_number_otp = true; renderComponent(); expect(screen.getByText(/Verify your number/)).toBeInTheDocument(); - expect(screen.getByText(/Enter the 6-digit code sent to you via SMS at :/)).toBeInTheDocument(); + expect(screen.getByText(/Enter the 6-digit code sent to you via SMS at ./)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Change/ })).toBeInTheDocument(); }); it('should render whatsapp when phone_verification_type is whatsapp', () => { diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx index ad594e47c44d..d460239cc88c 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/verification-link-expired-modal.spec.tsx @@ -63,8 +63,8 @@ describe('VerificationLinkExpiredModal', () => { buttons.forEach(value => { expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); - expect(screen.getByText(/Verification link expired/)).toBeInTheDocument(); - expect(screen.getByText(/Get another link to verify your number./)).toBeInTheDocument(); + expect(screen.getByText(/Link expired/)).toBeInTheDocument(); + expect(screen.getByText(/Request a new link to verify your phone number./)).toBeInTheDocument(); }); it('should render mockSetShowVerificationLinkExpiredModal and mock_back_router when Cancel is clicked', () => { diff --git a/packages/account/src/Sections/Profile/PhoneVerification/cancel-phone-verification-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/cancel-phone-verification-modal.tsx index 4e72f94720fb..1cea6dfec35b 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/cancel-phone-verification-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/cancel-phone-verification-modal.tsx @@ -58,18 +58,18 @@ const CancelPhoneVerificationModal = observer(() => { showHandleBar isOpened={show_modal} shouldCloseOnPrimaryButtonClick - primaryButtonLabel={} + primaryButtonLabel={} showSecondaryButton showCrossIcon toggleModal={handleStayAtPhoneVerificationPage} - secondaryButtonLabel={} + secondaryButtonLabel={} secondaryButtonCallback={handleLeavePhoneVerificationPage} > } />
- +
diff --git a/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx index a8cd5c62ba59..32a0704a921f 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx @@ -1,4 +1,4 @@ -import { Button, Modal, Text } from '@deriv-com/quill-ui'; +import { Modal, Text } from '@deriv-com/quill-ui'; import { Localize, localize } from '@deriv/translations'; import { VERIFICATION_SERVICES } from '@deriv/shared'; import { convertPhoneTypeDisplay } from '../../../Helpers/utils'; @@ -68,70 +68,50 @@ const DidntGetTheCodeModal = ({ setShouldShowDidntGetTheCodeModal(false); }; - const handleChangePhoneNumber = () => { - clearOtpValue(); - setShouldShowDidntGetTheCodeModal(false); - setOtpVerification({ show_otp_verification: false, phone_verification_type }); - }; - return ( } + secondaryButtonLabel={ + + } disableCloseOnOverlay showCrossIcon toggleModal={() => setShouldShowDidntGetTheCodeModal(false)} - hasFooter={false} > + } />
- - + + -
- - - - - - -
diff --git a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx index 316495fd4009..7cd3fd81df32 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback, Fragment } from 'react'; import PhoneVerificationCard from './phone-verification-card'; -import { Text, InputGroupButton } from '@deriv-com/quill-ui'; +import { Text, InputGroupButton, Button } from '@deriv-com/quill-ui'; import { Localize, localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; import { usePhoneVerificationAnalytics, useSendOTPVerificationCode, useSettings } from '@deriv/hooks'; @@ -134,18 +134,30 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification {should_show_phone_number_otp ? ( ) : ( - + )}
{should_show_phone_number_otp ? ( + setOtpVerification({ show_otp_verification: false, phone_verification_type }) + } + />, + ]} /> ) : ( @@ -158,7 +170,7 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification /> - + )} diff --git a/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx index e19f6fe485c4..77440b70c672 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx @@ -1,7 +1,6 @@ import { Modal, Text } from '@deriv-com/quill-ui'; import { Localize } from '@deriv/translations'; import { useHistory } from 'react-router'; -import { LabelPairedCircleXmarkLgRegularIcon } from '@deriv/quill-icons'; import { usePhoneNumberVerificationSetTimer, useSettings, useVerifyEmail } from '@deriv/hooks'; import { routes } from '@deriv/shared'; import { useDevice } from '@deriv-com/ui'; @@ -68,17 +67,11 @@ const VerificationLinkExpiredModal = ({ secondaryButtonLabel={} secondaryButtonCallback={handleCancelButton} > - } - /> + } />
- - - - +
diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index 3336118d7db4..c382cf11f74d 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -132,7 +132,7 @@ export default class UIStore extends BaseStore { promptFn = () => {}; //phone number verification - should_show_phone_number_otp = false; + should_show_phone_number_otp = true; is_forced_to_exit_pnv = false; //warn user if they want to close create real account modal From a266dc79464c7b6d1ffe0cab371c7b0036e0fa98 Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Mon, 26 Aug 2024 10:47:49 +0800 Subject: [PATCH 08/19] chore: update content for pnv notification --- packages/core/src/Stores/notification-store.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index bd8e83087491..10355fed7178 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -1103,15 +1103,15 @@ export default class NotificationStore extends BaseStore { }, phone_number_verification: { key: 'phone_number_verification', - header: localize('Verify your phone number'), - message: , + header: localize('Complete verification'), + message: , type: 'warning', action: { onClick: () => { this.root_store.ui.setIsScrollToVerifyButton(true); }, route: routes.personal_details, - text: localize('Get started'), + text: localize('Verify now'), }, should_show_again: true, }, From 454f5d72e92a832ec369ed45dad930b7fde54cc8 Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Mon, 26 Aug 2024 11:06:22 +0800 Subject: [PATCH 09/19] chore: change livechat color --- .../Profile/PersonalDetails/personal-details-form.tsx | 10 ++++++++-- packages/account/src/Styles/account.scss | 4 ++++ .../open-livechat-link/open-livechat-link.tsx | 10 ++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx index 3f522e246779..bffe3aa0109c 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx @@ -124,8 +124,14 @@ const PersonalDetailsForm = observer(() => { if (account_settings?.phone_number_verification?.verified) { return ( ]} + i18n_default_text='To change your verified phone number, contact us via <0>.' + components={[ + , + ]} /> ); } else if (is_phone_number_editted || is_phone_number_empty) { diff --git a/packages/account/src/Styles/account.scss b/packages/account/src/Styles/account.scss index 3a593971ccef..30c472005ffc 100644 --- a/packages/account/src/Styles/account.scss +++ b/packages/account/src/Styles/account.scss @@ -330,6 +330,10 @@ $MIN_HEIGHT_FLOATING: calc( max-width: unset; } + &--phone-verification-livechat-link { + color: $color-black-1; + } + &--2-cols { display: grid; grid-template-columns: 1fr 1fr; diff --git a/packages/components/src/components/open-livechat-link/open-livechat-link.tsx b/packages/components/src/components/open-livechat-link/open-livechat-link.tsx index e845b32247c7..22fa92aca117 100644 --- a/packages/components/src/components/open-livechat-link/open-livechat-link.tsx +++ b/packages/components/src/components/open-livechat-link/open-livechat-link.tsx @@ -2,13 +2,19 @@ import React from 'react'; import { Localize } from '@deriv/translations'; import Text from '../text'; import './open-livechat-link.scss'; +import clsx from 'clsx'; type TOpenLiveChatLink = { text_size?: React.ComponentProps['size']; + className?: string; }; -const OpenLiveChatLink = ({ children, text_size }: React.PropsWithChildren) => ( -