Skip to content

Commit

Permalink
[Cashier-V2] george / FEQ-19 / Wrap the components into Payment Agent…
Browse files Browse the repository at this point in the history
… Withdrawal module (binary-com#14376)

* feat(cashier-v2): ⚡ add payment agent withdrawal module

* perf(cashier-v2): 🎨 replace successfull state with status

* refactor(cashier-v2): restructure payment agent modules folder
  • Loading branch information
heorhi-deriv committed Apr 2, 2024
1 parent c4d7009 commit f095005
Show file tree
Hide file tree
Showing 48 changed files with 725 additions and 152 deletions.
1 change: 1 addition & 0 deletions packages/api-v2/src/hooks/index.ts
Expand Up @@ -53,6 +53,7 @@ export { default as useOnfidoNotificationEvent } from './useOnfidoNotificationEv
export { default as useOnfidoServiceToken } from './useOnfidoServiceToken';
export { default as useOtherCFDPlatformsDeposit } from './useOtherCFDPlatformsDeposit';
export { default as usePaymentAgentList } from './usePaymentAgentList';
export { default as usePaymentAgentWithdrawal } from './usePaymentAgentWithdrawal';
export { default as usePOA } from './usePOA';
export { default as usePOI } from './usePOI';
export { default as useResidenceList } from './useResidenceList';
Expand Down
14 changes: 14 additions & 0 deletions packages/api-v2/src/hooks/usePaymentAgentWithdrawal.ts
@@ -0,0 +1,14 @@
import useMutation from '../useMutation';

/** A custom hook to request paymentagent withdrawal */
const usePaymentAgentWithdrawal = () => {
const { data, ...rest } = useMutation('paymentagent_withdraw');

/** Paymentagent withdrawal response */
return {
data,
...rest,
};
};

export default usePaymentAgentWithdrawal;
Expand Up @@ -17,7 +17,6 @@ type TTransferConfirmProps = {
onClickBack: VoidFunction;
onClickConfirm: VoidFunction;
title: string;
warningMessages: { key: number | string; text: JSX.Element | string }[];
};

const Row: React.FC<TRowProps> = ({ itemKey, label, value }) => (
Expand Down Expand Up @@ -63,7 +62,6 @@ const TransferConfirm: React.FC<TTransferConfirmProps> = ({
onClickBack,
onClickConfirm,
title,
warningMessages,
}) => {
const [isTransferConsentChecked, setIsTransferConsentChecked] = useState(false);

Expand All @@ -79,13 +77,12 @@ const TransferConfirm: React.FC<TTransferConfirmProps> = ({
))}
</div>
<ul className={styles['warnings-container']}>
{warningMessages.map(({ key, text }) => {
return (
<Text as='li' color='red' key={key} size='xs'>
{text}
</Text>
);
})}
<Text as='li' color='red' size='xs'>
Please ensure <strong>all details</strong> are <strong>correct</strong> before making your transfer.
</Text>
<Text as='li' color='red' size='xs'>
We <strong>do not</strong> guarantee a refund if you make a wrong transfer.
</Text>
</ul>
<Checkbox
checked={isTransferConsentChecked}
Expand Down
Expand Up @@ -13,7 +13,6 @@ describe('<TransferConfirmScreenScreen />', () => {
onClickBack: jest.fn(),
onClickConfirm: jest.fn(),
title: 'Transfer confirm title',
warningMessages: [],
};
});

Expand Down Expand Up @@ -63,18 +62,6 @@ describe('<TransferConfirmScreenScreen />', () => {
expect(screen.getByText('Lorem ipsum')).toBeInTheDocument();
});

it('should render proper warning messages', () => {
mockedProps.warningMessages = [
{ key: 'Message 1', text: 'Warning message 1' },
{ key: 'Message 2', text: 'Warning message 2' },
];

render(<TransferConfirmScreen {...mockedProps} />);

expect(screen.getByText('Warning message 1')).toBeInTheDocument();
expect(screen.getByText('Warning message 2')).toBeInTheDocument();
});

it('should render proper checkbox label', () => {
render(<TransferConfirmScreen {...mockedProps} />);

Expand Down
39 changes: 39 additions & 0 deletions packages/cashier-v2/src/flows/PaymentAgent/PaymentAgent.tsx
@@ -0,0 +1,39 @@
import React, { useEffect, useState } from 'react';
import { PageContainer } from '../../components';
import { PaymentAgentWithdrawalModule, WithdrawalVerificationModule } from '../../lib';

const PaymentAgent = () => {
const [verificationCode, setVerificationCode] = useState('1');

useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const verificationQueryParam = queryParams.get('verification');

if (verificationQueryParam) {
setVerificationCode(verificationQueryParam);

const url = new URL(window.location.href);
url.searchParams.delete('verification'); // Remove the 'verification_code' query parameter
window.history.replaceState({}, document.title, url.toString());
}
}, []);

if (verificationCode) {
return (
<PageContainer>
<PaymentAgentWithdrawalModule
setVerificationCode={setVerificationCode}
verificationCode={verificationCode}
/>
</PageContainer>
);
}

return (
<PageContainer>
<WithdrawalVerificationModule withdrawalType='paymentagent_withdraw' />
</PageContainer>
);
};

export default PaymentAgent;
1 change: 1 addition & 0 deletions packages/cashier-v2/src/flows/PaymentAgent/index.ts
@@ -0,0 +1 @@
export { default as PaymentAgent } from './PaymentAgent';
Empty file.
2 changes: 1 addition & 1 deletion packages/cashier-v2/src/flows/Withdrawal/Withdrawal.tsx
Expand Up @@ -48,7 +48,7 @@ const Withdrawal = () => {
}
return (
<PageContainer>
<WithdrawalVerificationModule />
<WithdrawalVerificationModule withdrawalType='payment_withdraw' />
</PageContainer>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/cashier-v2/src/flows/index.ts
@@ -1,4 +1,5 @@
export { AccountTransfer } from './AccountTransfer';
export { Deposit } from './Deposit';
export { PaymentAgent } from './PaymentAgent';
export { PaymentAgentTransfer } from './PaymentAgentTransfer';
export { Withdrawal } from './Withdrawal';
@@ -1,7 +1,7 @@
import React from 'react';
import { Loader, Text } from '@deriv-com/ui';
import { PaymentAgentList, PaymentAgentSearchContainer } from '../../components';
import { PaymentAgentProvider, usePaymentAgentContext } from '../../provider';
import { PaymentAgentList, PaymentAgentSearchContainer } from '../components';
import { PaymentAgentProvider, usePaymentAgentContext } from '../provider';
import styles from './PaymentAgentDeposit.module.scss';

const PaymentAgentDeposit = () => {
Expand Down
@@ -1,7 +1,7 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import PaymentAgentDeposit from '../PaymentAgentDeposit';
import { usePaymentAgentContext } from '../../../provider';
import { usePaymentAgentContext } from '../../provider';

const mockUsePaymentAgentContext = usePaymentAgentContext as jest.MockedFunction<typeof usePaymentAgentContext>;

Expand All @@ -10,14 +10,14 @@ jest.mock('@deriv-com/ui', () => ({
Loader: () => <div>Loader</div>,
}));

jest.mock('../../../provider', () => ({
jest.mock('../../provider', () => ({
PaymentAgentProvider: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
usePaymentAgentContext: jest.fn(() => ({
isPaymentAgentListLoading: false,
})),
}));

jest.mock('../../../components', () => ({
jest.mock('../../components', () => ({
PaymentAgentList: () => <div>PaymentAgentList</div>,
PaymentAgentSearchContainer: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
Expand Down
@@ -0,0 +1,13 @@
.container {
display: flex;
flex-direction: column;
gap: 1.6rem;
margin: 0 auto;
}

.unlisted-withdrawal-link {
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
@@ -0,0 +1,69 @@
import React, { useCallback } from 'react';
import { Loader, Text } from '@deriv-com/ui';
import { clickAndKeyEventHandler } from '../../../utils';
import { PaymentAgentList, PaymentAgentSearchContainer } from '../components';
import { PaymentAgentProvider, usePaymentAgentContext } from '../provider';
import {
PaymentAgentUnlistedWithdrawalForm,
PaymentAgentWithdrawalConfirm,
PaymentAgentWithdrawalReceipt,
} from './components';
import { PaymentAgentWithdrawalProvider, usePaymentAgentWithdrawalContext } from './provider';
import type { TPaymentAgentWithdrawalModuleProps } from './types';
import styles from './PaymentAgentWithdrawal.module.scss';

const PaymentAgentWithdrawal = () => {
const { isPaymentAgentListLoading } = usePaymentAgentContext();
const { isUnlistedWithdrawal, setIsUnlistedWithdrawal, withdrawalStatus } = usePaymentAgentWithdrawalContext();

const showUnlistedWithdrawalForm = useCallback(
(e?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>) => {
clickAndKeyEventHandler(() => setIsUnlistedWithdrawal(true), e);
},
[setIsUnlistedWithdrawal]
);

if (isPaymentAgentListLoading) return <Loader />;

if (withdrawalStatus === 'try_successful') return <PaymentAgentWithdrawalConfirm />;

if (withdrawalStatus === 'successful') return <PaymentAgentWithdrawalReceipt />;

if (isUnlistedWithdrawal) return <PaymentAgentUnlistedWithdrawalForm />;

return (
<div className={styles.container}>
<Text size='sm'>
Choose your preferred payment agent and enter your withdrawal amount. If your payment agent is not
listed,{' '}
<Text className={styles['unlisted-withdrawal-link']} color='red' size='sm' weight='bold'>
<span onClick={showUnlistedWithdrawalForm} onKeyDown={showUnlistedWithdrawalForm}>
search for them using their account number
</span>
</Text>
.
</Text>
<PaymentAgentSearchContainer>
<PaymentAgentList />
</PaymentAgentSearchContainer>
</div>
);
};

const PaymentAgentWithdrawalModule: React.FC<TPaymentAgentWithdrawalModuleProps> = ({
setVerificationCode,
verificationCode,
}) => {
return (
<PaymentAgentProvider>
<PaymentAgentWithdrawalProvider
setVerificationCode={setVerificationCode}
verificationCode={verificationCode}
>
<PaymentAgentWithdrawal />
</PaymentAgentWithdrawalProvider>
</PaymentAgentProvider>
);
};

export default PaymentAgentWithdrawalModule;
Expand Up @@ -12,6 +12,10 @@
align-items: center;
}

.back-arrow-icon {
cursor: pointer;
}

.form {
display: flex;
flex-direction: column;
Expand Down
@@ -1,18 +1,41 @@
import React from 'react';
import React, { useCallback } from 'react';
import { Field, FieldProps, Form, Formik } from 'formik';
import { useActiveAccount } from '@deriv/api-v2';
import { LabelPairedCircleXmarkMdFillIcon, StandaloneArrowLeftBoldIcon } from '@deriv/quill-icons';
import { Button, Input, Text } from '@deriv-com/ui';
import { usePaymentAgentWithdrawalContext } from '../../provider';
import styles from './PaymentAgentUnlistedWithdrawalForm.module.scss';

const PaymentAgentUnlistedWithdrawalForm = () => {
const { data: activeAccount } = useActiveAccount();
const onSubmitHandler = () => undefined;

const { getPaymentAgentWithdrawalValidationSchema, requestTryPaymentAgentWithdrawal, setIsUnlistedWithdrawal } =
usePaymentAgentWithdrawalContext();

const onSubmitHandler = useCallback(
({ accountNumber, amount }: { accountNumber: string; amount: string }) => {
requestTryPaymentAgentWithdrawal({
amount: Number(amount),
paymentagent_loginid: accountNumber,
});
},
[requestTryPaymentAgentWithdrawal]
);

const getAccountNumberInputMessage = useCallback((isTouched?: boolean, errorMessage?: string) => {
if (isTouched && errorMessage) return errorMessage;
return 'Example: CR123456789';
}, []);

return (
<div className={styles.container}>
<div className={styles['back-section']}>
<StandaloneArrowLeftBoldIcon data-testid='dt-back-arrow-icon' iconSize='md' />
<StandaloneArrowLeftBoldIcon
className={styles['back-arrow-icon']}
data-testid='dt-back-arrow-icon'
iconSize='md'
onClick={() => setIsUnlistedWithdrawal(false)}
/>
<Text size='sm' weight='bold'>
Back to list
</Text>
Expand All @@ -23,13 +46,14 @@ const PaymentAgentUnlistedWithdrawalForm = () => {
amount: '',
}}
onSubmit={onSubmitHandler}
validationSchema={getPaymentAgentWithdrawalValidationSchema()}
>
{({ errors, isSubmitting, isValid, setFieldValue, touched, values }) => {
const isFormEmpty = !Object.values(values).some(Boolean);

return (
<Form className={styles.form}>
<Field name='account_number'>
<Field name='accountNumber'>
{({ field }: FieldProps) => (
<Input
{...field}
Expand All @@ -38,10 +62,13 @@ const PaymentAgentUnlistedWithdrawalForm = () => {
isFullWidth
label='Enter the payment agent account number'
maxLength={30}
message='Example: CR123456789'
message={getAccountNumberInputMessage(
touched.accountNumber,
errors.accountNumber
)}
required
rightPlaceholder={
errors.accountNumber ? (
field.value ?? errors.accountNumber ? (
<LabelPairedCircleXmarkMdFillIcon
onClick={() => setFieldValue('accountNumber', '')}
/>
Expand All @@ -61,6 +88,7 @@ const PaymentAgentUnlistedWithdrawalForm = () => {
isFullWidth
label='Enter amount'
maxLength={30}
message={touched.amount && errors.amount}
required
rightPlaceholder={
<Text as='span' size='sm'>
Expand All @@ -73,6 +101,7 @@ const PaymentAgentUnlistedWithdrawalForm = () => {
</Field>
<Button
disabled={!isValid || isSubmitting || isFormEmpty}
isLoading={isSubmitting}
size='lg'
textSize='sm'
type='submit'
Expand Down

0 comments on commit f095005

Please sign in to comment.