Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Akmal / feat: add useContractsForCompanyHook #76

Merged
merged 9 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
17 changes: 15 additions & 2 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,7 +159,7 @@ 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[]) => {
Expand All @@ -178,14 +179,26 @@ 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.length > 0 &&
saved_other_trade_types[0].items
akmal-deriv marked this conversation as resolved.
Show resolved Hide resolved
.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 show_guide_for_selected_contract />}
akmal-deriv marked this conversation as resolved.
Show resolved Hide resolved
/>
<ActionSheet.Content className='mock-action-sheet--content'>
{is_editing ? (
<DraggableList
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;
akmal-deriv marked this conversation as resolved.
Show resolved Hide resolved
}

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