Skip to content

Commit

Permalink
farhan/feat: Cashier Completed Transactions (deriv-com#10566)
Browse files Browse the repository at this point in the history
* feat: cashier transactions

* chore: rename components and invalidate query

* chore: move effects down to child components

* refactor: filter logics

* fix: status and filter logics
  • Loading branch information
farhan-nurzi-deriv authored Oct 18, 2023
1 parent 81950f7 commit 3af7eaf
Show file tree
Hide file tree
Showing 22 changed files with 359 additions and 72 deletions.
7 changes: 6 additions & 1 deletion packages/api/src/hooks/useCryptoTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import useSubscription from '../useSubscription';
import { getTruncatedString } from '@deriv/utils';

Expand All @@ -25,6 +25,9 @@ const useCryptoTransactions = () => {
const { subscribe, data, ...rest } = useSubscription('cashier_payments');
const [transactions, setTransactions] = useState<TModifiedTransaction[]>();

// Reset transactions data
const resetData = useCallback(() => setTransactions(undefined), []);

useEffect(() => {
setTransactions(old_transactions => {
const new_transactions = data?.cashier_payments?.crypto as TModifiedTransaction[] | undefined;
Expand Down Expand Up @@ -91,6 +94,8 @@ const useCryptoTransactions = () => {
data: sorted_transactions,
/** Returns the last transaction if exists. */
last_transaction,
/** Reset transactions data */
resetData,
subscribe,
...rest,
};
Expand Down
25 changes: 20 additions & 5 deletions packages/api/src/hooks/useTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import { useMemo } from 'react';

import { useEffect, useMemo, useState } from 'react';
import useInfiniteQuery from '../useInfiniteQuery';

import { TSocketRequestPayload } from '../../types';
import useAuthorize from './useAuthorize';
import useInvalidateQuery from '../useInvalidateQuery';

type TFilter = NonNullable<TSocketRequestPayload<'statement'>['payload']>['action_type'];

/** A custom hook to get the summary of account transactions */
const useTransactions = () => {
const { isSuccess } = useAuthorize();
const { isFetching, isSuccess } = useAuthorize();
const [filter, setFilter] = useState<TFilter>();
const invalidate = useInvalidateQuery();
const { data, fetchNextPage, ...rest } = useInfiniteQuery('statement', {
options: {
enabled: isSuccess,
enabled: !isFetching && isSuccess,
getNextPageParam: (lastPage, pages) => {
if (!lastPage?.statement?.count) return;

return pages.length;
},
},
payload: {
action_type: filter,
},
});

useEffect(() => {
invalidate('statement');
}, [filter, invalidate]);

// Flatten the data array.
const flatten_data = useMemo(() => {
if (!data?.pages?.length) return;

return data?.pages?.flatMap(page => page?.statement?.transactions);
}, [data?.pages]);

// Modify the data.
const modified_data = useMemo(() => {
if (!flatten_data?.length) return;

Expand All @@ -37,6 +50,8 @@ const useTransactions = () => {
data: modified_data,
/** Fetch the next page of transactions */
fetchNextPage,
/** Filter the transactions by type */
setFilter,
...rest,
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.wallets-transactions {
width: 70vw;

@include mobile {
width: 100%;
}

&__header {
display: flex;
gap: 16px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,79 @@
import React, { ComponentProps, useState } from 'react';
import { WalletTransactionsCrypto, WalletTransactionsFilter } from '../../screens/WalletTransactionsScreens';
import React, { ComponentProps, useEffect, useState } from 'react';
import { useActiveWalletAccount } from '@deriv/api';
import {
WalletTransactionsCompleted,
WalletTransactionsFilter,
WalletTransactionsPending,
} from '../../screens/WalletTransactionsScreens';
import './WalletTransactions.scss';

type TWalletTransactionsPendingFilter = ComponentProps<typeof WalletTransactionsPending>['filter'];
type TWalletTransactionCompletedFilter = ComponentProps<typeof WalletTransactionsCompleted>['filter'];
type TFilterValue = TWalletTransactionCompletedFilter | TWalletTransactionsPendingFilter;

const filtersMapper: Record<string, Record<string, TFilterValue>> = {
completed: {
all: undefined,
deposit: 'deposit',
transfer: 'transfer',
withdrawal: 'withdrawal',
},
pending: {
all: 'all',
deposit: 'deposit',
withdrawal: 'withdrawal',
},
};

const WalletTransactions = () => {
const [filterValue, setFilterValue] =
useState<ComponentProps<typeof WalletTransactionsCrypto>['filter']>(undefined);
const [isPendingActive, setIsPendingActive] = useState(true);
const { data } = useActiveWalletAccount();
const [isPendingActive, setIsPendingActive] = useState(false);
const [filterValue, setFilterValue] = useState('all');

useEffect(() => {
if (!data?.currency_config?.is_crypto && isPendingActive) {
setIsPendingActive(false);
}
}, [data?.currency_config?.is_crypto, isPendingActive]);

useEffect(() => {
if (isPendingActive && !Object.keys(filtersMapper.pending).includes(filterValue)) {
setFilterValue('all');
}
if (!isPendingActive && !Object.keys(filtersMapper.completed).includes(filterValue)) {
setFilterValue('all');
}
}, [filterValue, isPendingActive]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<div className='wallets-transactions'>
<div className='wallets-transactions__header'>
<div className='wallets-transactions__toggle'>
<p>Pending Transactions</p>
<input
checked={isPendingActive}
className='wallets-transactions__toggle-switch'
id='toggle-pending'
onChange={() => setIsPendingActive(!isPendingActive)}
type='checkbox'
/>
<label className='wallets-transactions__toggle-switch__label' htmlFor='toggle-pending'>
<span className='wallets-transactions__toggle-switch__button' />
</label>
</div>
{data?.currency_config?.is_crypto && (
<div className='wallets-transactions__toggle'>
<p>Pending Transactions</p>
<input
checked={isPendingActive}
className='wallets-transactions__toggle-switch'
id='toggle-pending'
onChange={() => setIsPendingActive(!isPendingActive)}
type='checkbox'
/>
<label className='wallets-transactions__toggle-switch__label' htmlFor='toggle-pending'>
<span className='wallets-transactions__toggle-switch__button' />
</label>
</div>
)}
<WalletTransactionsFilter isPendingActive={isPendingActive} onSelect={setFilterValue} />
</div>
{isPendingActive ? <WalletTransactionsCrypto filter={filterValue} /> : null}
{isPendingActive ? (
<WalletTransactionsPending
filter={filtersMapper.pending[filterValue] as TWalletTransactionsPendingFilter}
/>
) : (
<WalletTransactionsCompleted
filter={filtersMapper.completed[filterValue] as TWalletTransactionCompletedFilter}
/>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.wallets-transactions-completed {
position: relative;

&__group-title {
width: 74px;
height: 14px;
color: var(--system-light-3-less-prominent-text, #999);
text-align: right;

/* desktop/extra small/XS - regular */
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 14px; /* 140% */
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useCallback, useEffect } from 'react';
import moment from 'moment';
import { useTransactions } from '@deriv/api';
import { TSocketRequestPayload } from '@deriv/api/types';
import { WalletTransactionsCompletedRow } from '../WalletTransactionsCompletedRow';
import { WalletTransactionsNoDataState } from '../WalletTransactionsNoDataState';
import { WalletTransactionsTable } from '../WalletTransactionsTable';
import './WalletTransactionsCompleted.scss';

type TFilter = NonNullable<TSocketRequestPayload<'statement'>['payload']>['action_type'];

type TProps = {
filter?: TFilter;
};

const WalletTransactionsCompleted: React.FC<TProps> = ({ filter }) => {
const { data, fetchNextPage, isFetching, setFilter } = useTransactions();

const fetchMoreOnBottomReached = useCallback(
(containerRefElement?: HTMLDivElement | null) => {
if (containerRefElement) {
const { clientHeight, scrollHeight, scrollTop } = containerRefElement;
//once the user has scrolled within 300px of the bottom of the table, fetch more data if there is any
if (scrollHeight - scrollTop - clientHeight < 300 && !isFetching) {
fetchNextPage();
}
}
},
[fetchNextPage, isFetching]
);

useEffect(() => {
setFilter(filter);
}, [filter]); // eslint-disable-line react-hooks/exhaustive-deps

if (!data) return <WalletTransactionsNoDataState />;

return (
<div>
<WalletTransactionsTable
columns={[
{
accessorFn: row =>
row.transaction_time && moment.unix(row.transaction_time).format('DD MMM YYYY'),
accessorKey: 'date',
header: 'Date',
},
]}
data={data}
fetchMore={fetchMoreOnBottomReached}
groupBy={['date']}
rowGroupRender={transaction => (
<p className='wallets-transactions-completed__group-title'>
{transaction.transaction_time &&
moment.unix(transaction.transaction_time).format('DD MMM YYYY')}
</p>
)}
rowRender={transaction => <WalletTransactionsCompletedRow transaction={transaction} />}
/>
</div>
);
};

export default WalletTransactionsCompleted;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as WalletTransactionsCompleted } from './WalletTransactionsCompleted';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.wallets-transactions-completed-row {
display: flex;
padding: 16px;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--system-light-3-border, #e5e5e5);

&-content {
display: flex;
justify-content: space-between;
}

&__account-details {
display: flex;
width: max-content;
gap: 8px;

&__action-type {
color: var(--system-light-3-less-prominent-text, #999);

/* desktop/small/S - regular */
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
text-transform: capitalize;
}

&__wallet-name {
color: var(--system-light-1-prominent-text, #333);

/* desktop/small/S - bold */
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 18px; /* 150% */
}
}

&__transaction-details {
&__amount {
color: var(--status-light-success, #4bb4b3);
text-align: right;

/* desktop/small/S - bold */
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 18px; /* 150% */

&--negative {
color: var(--status-light-danger, #ec3f3f);
}
}

&__balance {
color: var(--system-light-3-less-prominent-text, #999);
text-align: right;

/* desktop/extra small/XS - regular */
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 14px; /* 140% */
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { useMemo } from 'react';
import { useActiveWalletAccount, useTransactions } from '@deriv/api';
import { WalletCurrencyCard } from '../../../../../components/WalletCurrencyCard';
import './WalletTransactionsCompletedRow.scss';

type TProps = {
transaction: NonNullable<ReturnType<typeof useTransactions>['data']>[number];
};

const WalletTransactionsCompletedRow: React.FC<TProps> = ({ transaction }) => {
const { data } = useActiveWalletAccount();
const displayCode = useMemo(() => data?.currency_config?.display_code || 'USD', [data]);

const formattedAmount = useMemo(() => {
if (!transaction?.amount) return;

if (transaction.amount > 0) {
return `+${transaction.amount}`;
}
return transaction.amount;
}, [transaction]);

return (
<div className='wallets-transactions-completed-row'>
<div className='wallets-transactions-completed-row__account-details'>
<WalletCurrencyCard currency={data?.currency || 'USD'} isDemo={data?.is_virtual} size='md' />
<div>
<p className='wallets-transactions-completed-row__account-details__action-type'>
{transaction.action_type}
</p>
<p className='wallets-transactions-completed-row__account-details__wallet-name'>
{displayCode} Wallet
</p>
</div>
</div>
<div className='wallets-transactions-completed-row__transaction-details'>
<p
className={`wallets-transactions-completed-row__transaction-details__amount ${
transaction?.amount && transaction.amount < 0
? 'wallets-transactions-completed-row__transaction-details__amount--negative'
: ''
}`}
>
{formattedAmount}
</p>
<p className='wallets-transactions-completed-row__transaction-details__balance'>
Balance: {transaction.balance_after}
</p>
</div>
</div>
);
};

export default WalletTransactionsCompletedRow;
Loading

0 comments on commit 3af7eaf

Please sign in to comment.