Skip to content

Commit

Permalink
Merge pull request #76 from akmal-deriv/f-apply-new-api
Browse files Browse the repository at this point in the history
Akmal / feat: add useContractsForCompanyHook
  • Loading branch information
akmal-deriv committed Aug 22, 2024
2 parents c6bfab0 + 0fd1ff2 commit 4888f4b
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 9 deletions.
7 changes: 7 additions & 0 deletions packages/core/src/_common/base/socket_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@ const BinarySocketBase = (() => {

const activeSymbols = (mode = 'brief') => deriv_api.activeSymbols(mode);

const contractsForCompany = ({ landing_company }) =>
deriv_api.send({
landing_company,
contracts_for_company: 1,
});

const transferBetweenAccounts = (account_from, account_to, currency, amount) =>
deriv_api.send({
transfer_between_accounts: 1,
Expand Down Expand Up @@ -482,6 +488,7 @@ const BinarySocketBase = (() => {
tradingPlatformInvestorPasswordChange,
tradingPlatformInvestorPasswordReset,
activeSymbols,
contractsForCompany,
paymentAgentList,
allPaymentAgentList,
paymentAgentDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TradeTypeList from '../trade-type-list';

jest.mock('../trade-type-list-item', () => ({ title, onRightIconClick }) => (
<div onClick={onRightIconClick}>{title}</div>
));
const MockTradeTypeListItem = ({ title, onRightIconClick }) => <div onClick={onRightIconClick}>{title}</div>;
MockTradeTypeListItem.displayName = 'MockTradeTypeListItem';

jest.mock('../trade-type-list-item', () => MockTradeTypeListItem);

describe('TradeTypeList', () => {
const categories = [
Expand Down
25 changes: 21 additions & 4 deletions packages/trader/src/AppV2/Containers/Trade/trade-types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DraggableList } from 'AppV2/Components/DraggableList';
import { TradeTypeList } from 'AppV2/Components/TradeTypeList';
import { getTradeTypesList } from 'AppV2/Utils/trade-types-utils';
import { Localize, localize } from '@deriv/translations';
import Guide from '../../Components/Guide';

type TTradeTypesProps = {
onTradeTypeSelect: (e: React.MouseEvent<HTMLButtonElement>) => void;
Expand Down Expand Up @@ -158,13 +159,18 @@ const TradeTypes = ({ contract_type, onTradeTypeSelect, trade_types }: TTradeTyp
const savePinnedToLocalStorage = () => {
localStorage.setItem('pinned_trade_types', JSON.stringify(pinned_trade_types));
localStorage.setItem('other_trade_types', JSON.stringify(other_trade_types));
setIsOpen(false);
setIsEditing(false);
};

const handleOnDrag = (categories: TResultItem[]) => {
setPinnedTradeTypes(categories);
};

const handleOnTradeTypeSelect = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
onTradeTypeSelect(e);
setIsOpen(false);
}

const isTradeTypeSelected = (value: string) =>
[contract_type, value].every(type => type.startsWith('vanilla')) ||
[contract_type, value].every(type => type.startsWith('turbos')) ||
Expand All @@ -178,14 +184,25 @@ const TradeTypes = ({ contract_type, onTradeTypeSelect, trade_types }: TTradeTyp
<Text size='sm'>{title}</Text>
</Chip.Selectable>
))}
{!saved_pinned_trade_types[0]?.items.some((item: TItem) => item.id === contract_type) &&
saved_other_trade_types[0]?.items
.filter((item: TItem) => item.id === contract_type)
.map(({ title, id }: TItem) => (
<Chip.Selectable key={id} onChipSelect={onTradeTypeSelect} selected={isTradeTypeSelected(id)}>
<Text size='sm'>{title}</Text>
</Chip.Selectable>
))}
<a key='trade-types-all' onClick={() => setIsOpen(true)} className='trade__trade-types-header'>
<Text size='sm' bold underlined>
{<Localize i18n_default_text='View all' />}
</Text>
</a>
<ActionSheet.Root isOpen={is_open} expandable={false} onClose={handleCloseTradeTypes}>
<ActionSheet.Portal>
<ActionSheet.Header title={<Localize i18n_default_text='Trade types' />} />
<ActionSheet.Header
title={<Localize i18n_default_text='Trade types' />}
icon={!is_editing && <Guide />}
/>
<ActionSheet.Content className='mock-action-sheet--content'>
{is_editing ? (
<DraggableList
Expand All @@ -198,7 +215,7 @@ const TradeTypes = ({ contract_type, onTradeTypeSelect, trade_types }: TTradeTyp
<TradeTypeList
categories={saved_pinned_trade_types}
onAction={() => setIsEditing(true)}
onTradeTypeClick={onTradeTypeSelect}
onTradeTypeClick={handleOnTradeTypeSelect}
selected_item={contract_type}
should_show_title={false}
selectable
Expand All @@ -207,7 +224,7 @@ const TradeTypes = ({ contract_type, onTradeTypeSelect, trade_types }: TTradeTyp
<TradeTypeList
categories={is_editing ? other_trade_types : saved_other_trade_types}
onRightIconClick={is_editing ? handleAddPinnedClick : undefined}
onTradeTypeClick={!is_editing ? onTradeTypeSelect : undefined}
onTradeTypeClick={!is_editing ? handleOnTradeTypeSelect : undefined}
selected_item={contract_type}
selectable={!is_editing}
/>
Expand Down
11 changes: 9 additions & 2 deletions packages/trader/src/AppV2/Containers/Trade/trade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ import { TradeChart } from '../Chart';
import { isDigitTradeType } from 'Modules/Trading/Helpers/digits';
import TradeTypes from './trade-types';
import MarketSelector from 'AppV2/Components/MarketSelector';
import useContractsForCompany, { TContractTypesList } from 'AppV2/Hooks/useContractsForCompany';
import AccumulatorStats from 'AppV2/Components/AccumulatorStats';
import { isAccumulatorContract } from '@deriv/shared';

const Trade = observer(() => {
const [is_minimized_params_visible, setIsMinimizedParamsVisible] = React.useState(false);
const chart_ref = React.useRef<HTMLDivElement>(null);

const { active_symbols, contract_type, contract_types_list, onMount, onChange, onUnmount } = useTraderStore();
const { active_symbols, contract_type, onMount, onChange, onUnmount } = useTraderStore();
const { contract_types_list } = useContractsForCompany();

const trade_types = React.useMemo(() => {
return Array.isArray(contract_types_list) && contract_types_list.length === 0
? []
: getTradeTypesList(contract_types_list as TContractTypesList);
}, [contract_types_list]);

const trade_types = React.useMemo(() => getTradeTypesList(contract_types_list), [contract_types_list]);
const symbols = React.useMemo(
() =>
active_symbols.map(({ display_name, symbol: underlying }) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { mockStore } from '@deriv/stores';
import TraderProviders from '../../../trader-providers';
import { WS, getContractCategoriesConfig, getContractTypesConfig } from '@deriv/shared';
import useContractsForCompany from '../useContractsForCompany';
import { waitFor } from '@testing-library/react';

jest.mock('@deriv/shared', () => ({
...jest.requireActual('@deriv/shared'),
getContractCategoriesConfig: jest.fn(),
getContractTypesConfig: jest.fn(),
WS: {
contractsForCompany: jest.fn(),
},
}));

describe('useContractsForCompany', () => {
let mocked_store: ReturnType<typeof mockStore>;

const wrapper = ({ children }: { children: JSX.Element }) => (
<TraderProviders store={mocked_store}>{children}</TraderProviders>
);

beforeEach(() => {
mocked_store = {
...mockStore({}),
client: {
...mockStore({}).client,
landing_company_shortcode: 'maltainvest',
},
modules: {
trade: {
setContractTypesListV2: jest.fn(),
},
},
};

(getContractCategoriesConfig as jest.Mock).mockReturnValue({
category_1: { categories: ['type_1'] },
category_2: { categories: ['type_2'] },
});

(getContractTypesConfig as jest.Mock).mockReturnValue({
type_1: { trade_types: ['type_1'], title: 'Type 1', barrier_count: 0 },
type_2: { trade_types: ['type_2'], title: 'Type 2', barrier_count: 1 },
});

jest.clearAllMocks();
});

it('should fetch and set contract types for the company successfully', async () => {
WS.contractsForCompany.mockResolvedValue({
contracts_for_company: {
available: [{ contract_type: 'type_1' }, { contract_type: 'type_2' }],
},
});

const { result } = renderHook(() => useContractsForCompany(), { wrapper });

await waitFor(() => {
expect(result.current.contract_types_list).toEqual({
category_1: { categories: [{ value: 'type_1', text: 'Type 1' }] },
category_2: { categories: [{ value: 'type_2', text: 'Type 2' }] },
});
expect(mocked_store.modules.trade.setContractTypesListV2).toHaveBeenCalledWith({
category_1: { categories: [{ value: 'type_1', text: 'Type 1' }] },
category_2: { categories: [{ value: 'type_2', text: 'Type 2' }] },
});
});
});

it('should handle API errors gracefully', async () => {
WS.contractsForCompany.mockResolvedValue({
error: { message: 'Some error' },
});

const { result } = renderHook(() => useContractsForCompany(), { wrapper });

await waitFor(() => {
expect(result.current.contract_types_list).toEqual([]);
expect(mocked_store.modules.trade.setContractTypesListV2).not.toHaveBeenCalled();
});
});

it('should not set unsupported contract types', async () => {
WS.contractsForCompany.mockResolvedValue({
contracts_for_company: {
available: [{ contract_type: 'unsupported_type' }],
},
});

const { result } = renderHook(() => useContractsForCompany(), { wrapper });

await waitFor(() => {
expect(result.current.contract_types_list).toEqual([]);
expect(mocked_store.modules.trade.setContractTypesListV2).not.toHaveBeenCalled();
});
});
});
87 changes: 87 additions & 0 deletions packages/trader/src/AppV2/Hooks/useContractsForCompany.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import { useTraderStore } from 'Stores/useTraderStores';
import { useStore } from '@deriv/stores';
import { cloneObject, getContractCategoriesConfig, getContractTypesConfig, WS } from '@deriv/shared';

type TContractType = {
text?: string;
value: string;
};

export type TContractTypesList = {
[key: string]: {
name: string;
categories: DeepRequired<TContractType[]>;
};
};

const useContractsForCompany = () => {
const [contract_types_list, setContractTypesList] = React.useState<TContractTypesList | []>([]);
const { setContractTypesListV2 } = useTraderStore();
const { client } = useStore();
const { landing_company_shortcode } = client;
const contract_categories = getContractCategoriesConfig();
const available_categories = cloneObject(contract_categories);
const contract_types = getContractTypesConfig();
let available_contract_types: ReturnType<typeof getContractTypesConfig> = {};

const fetchContractForCompany = React.useCallback(async () => {
let response;

const request = {
landing_company: landing_company_shortcode,
};

try {
response = await WS.contractsForCompany(request);
const { contracts_for_company = [], error } = response;
available_contract_types = {};

if (error) {
console.error(error);
} else if (contracts_for_company?.available.length) {
contracts_for_company.available.forEach((contract: any) => {
const type = Object.keys(contract_types).find(
key =>
contract_types[key].trade_types.indexOf(contract.contract_type) !== -1 &&
(contract.contract_type !== 'PUT' || contract_types[key].barrier_count === 1) // To distinguish betweeen Rise/Fall & Higher/Lower
);

if (!type) return; // ignore unsupported contract types

if (!available_contract_types[type]) {
// extend contract_categories to include what is needed to create the contract list
const sub_cats =
available_categories[
Object.keys(available_categories).find(
key => available_categories[key].categories.indexOf(type) !== -1
) ?? ''
].categories;

if (!sub_cats) return;

sub_cats[(sub_cats as string[]).indexOf(type)] = {
value: type,
text: contract_types[type].title,
};

available_contract_types[type] = cloneObject(contract_types[type]);
}
});

setContractTypesListV2(available_categories);
setContractTypesList(available_categories);
}
} catch (err) {
console.error(err);
}
}, [setContractTypesListV2]);

React.useEffect(() => {
fetchContractForCompany();
}, [fetchContractForCompany]);

return { contract_types_list };
};

export default useContractsForCompany;
6 changes: 6 additions & 0 deletions packages/trader/src/Stores/Modules/Trading/trade-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export default class TradeStore extends BaseStore {
non_available_contract_types_list: TContractTypesList = {};
trade_type_tab = '';
trade_types: { [key: string]: string } = {};
contract_types_list_v2: TContractTypesList = {};

// Amount
amount = 10;
Expand Down Expand Up @@ -521,6 +522,7 @@ export default class TradeStore extends BaseStore {
setChartModeFromURL: action.bound,
setChartStatus: action.bound,
setContractTypes: action.bound,
setContractTypesListV2: action.bound,
setDefaultSymbol: action.bound,
setIsTradeParamsExpanded: action.bound,
setIsDigitsWidgetActive: action.bound,
Expand Down Expand Up @@ -1952,6 +1954,10 @@ export default class TradeStore extends BaseStore {
this.has_symbols_for_v2 = !!active_symbols.length;
}

setContractTypesListV2(contract_types_list: TContractTypesList) {
this.contract_types_list_v2 = contract_types_list;
}

setBarrierChoices(barrier_choices: string[]) {
this.barrier_choices = barrier_choices ?? [];
if (this.is_turbos) {
Expand Down

0 comments on commit 4888f4b

Please sign in to comment.