Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ cline_instructions.md
.ai-instructions.md
.chatgpt-instructions.md
.claude-instructions.md

# AI Tool Configuration Files
.roo-config.json
.ai-rules.md
23 changes: 0 additions & 23 deletions .husky/pre-push

This file was deleted.

428 changes: 408 additions & 20 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7",
"@deriv-com/eslint-config-deriv": "2.2.0",
"@deriv-com/shiftai-cli": "^1.0.7",
"@deriv-com/shiftai-cli": "^1.0.12",
"@jest/globals": "^29.7.0",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^13.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const default_mocked_props: React.ComponentProps<typeof TogglePositionsMobile> =
is_empty: true,
onClickSell: jest.fn(),
onClickCancel: jest.fn(),
removePositionById: jest.fn(),
};

const default_mock_store = {
Expand Down Expand Up @@ -142,6 +143,7 @@ describe('TogglePositionsMobile component', () => {
is_empty: false,
onClickSell: jest.fn(),
onClickCancel: jest.fn(),
removePositionById: jest.fn(),
};
const mock_root_store = mockStore({
...default_mock_store,
Expand Down Expand Up @@ -193,6 +195,7 @@ describe('TogglePositionsMobile component', () => {
is_empty: false,
onClickSell: jest.fn(),
onClickCancel: jest.fn(),
removePositionById: jest.fn(),
};
const mock_root_store = mockStore({
...default_mock_store,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ import TogglePositions from './toggle-positions';

type TTogglePositionsMobile = Pick<
ReturnType<typeof useStore>['portfolio'],
'active_positions_count' | 'error' | 'onClickSell' | 'onClickCancel'
'active_positions_count' | 'error' | 'onClickSell' | 'onClickCancel' | 'removePositionById'
> & {
currency: ReturnType<typeof useStore>['client']['currency'];
filtered_positions: ReturnType<typeof useStore>['portfolio']['all_positions'];
is_empty: boolean;
};

type THiddenPositionsId = TTogglePositionsMobile['filtered_positions'][0]['id'];

const TogglePositionsMobile = observer(
({
active_positions_count,
Expand All @@ -35,9 +33,9 @@ const TogglePositionsMobile = observer(
is_empty,
onClickSell,
onClickCancel,
removePositionById: onClickRemove,
}: TTogglePositionsMobile) => {
const { togglePositionsDrawer, is_positions_drawer_on } = useStore().ui;
const [hidden_positions_ids, setHiddenPositionsIds] = React.useState<THiddenPositionsId[]>([]);
const { isMobile, isTablet } = useDevice();

const location = useLocation();
Expand All @@ -56,19 +54,26 @@ const TogglePositionsMobile = observer(
!show_blocker_dtrader_mobile_landscape_view &&
(is_hidden_landscape_blocker || should_show_dtrader_tablet_view);

const displayed_positions = filtered_positions
.filter(p =>
hidden_positions_ids.every(hidden_position_id => hidden_position_id !== p.contract_info.contract_id)
)
.slice(0, 5);
const displayed_positions = filtered_positions.slice(0, 5);
const closed_positions_ids = displayed_positions
.filter(position => position.contract_info?.is_sold)
.map(p => p.contract_info.contract_id);

const closeModal = () => {
setHiddenPositionsIds([...new Set([...hidden_positions_ids, ...closed_positions_ids])]);
togglePositionsDrawer();
};

// Automatically remove closed positions after 8 seconds
React.useEffect(() => {
closed_positions_ids.map(positionId => {
const timeout = setTimeout(() => {
onClickRemove(positionId);
}, 8000);

return () => clearTimeout(timeout);
});
}, [closed_positions_ids, onClickRemove]);

// Show only 5 most recent open contracts
const body_content = (
<React.Fragment>
Expand Down
2 changes: 2 additions & 0 deletions packages/trader/src/App/Containers/populate-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const PopulateHeader = observer(() => {
error: positions_error,
onClickSell: onPositionsSell,
onClickCancel: onPositionsCancel,
removePositionById: onPositionsRemove,
} = portfolio;

const filtered_positions = positions.filter(
Expand All @@ -43,6 +44,7 @@ const PopulateHeader = observer(() => {
is_empty={!filtered_positions.length}
onClickSell={onPositionsSell}
onClickCancel={onPositionsCancel}
removePositionById={onPositionsRemove}
/>
);
});
Expand Down
45 changes: 43 additions & 2 deletions packages/trader/src/AppV2/Containers/Chart/trade-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React from 'react';

import { ActiveSymbols, TickSpotData } from '@deriv/api-types';
import { ChartBarrierStore, isAccumulatorContract } from '@deriv/shared';
import {
ChartBarrierStore,
isAccumulatorContract,
isContractSupportedAndStarted,
isTurbosContract,
isVanillaContract,
TRADE_TYPES,
} from '@deriv/shared';
import { observer, useStore } from '@deriv/stores';
import { useDevice } from '@deriv-com/ui';

import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers';
import useActiveSymbols from 'AppV2/Hooks/useActiveSymbols';
import useDefaultSymbol from 'AppV2/Hooks/useDefaultSymbol';
import { SmartChart } from 'Modules/SmartChart';
Expand Down Expand Up @@ -52,7 +60,7 @@ const TradeChart = observer(() => {
updateGranularity,
} = contract_trade;
const ref = React.useRef<{ hasPredictionIndicators(): void; triggerPopup(arg: () => void): void }>(null);
const { all_positions } = portfolio;
const { all_positions, removePositionById: onClickRemove } = portfolio;
const { is_chart_countdown_visible, is_chart_layout_default, is_dark_mode_on, is_positions_drawer_on } = ui;
const { current_language, is_socket_opened } = common;
const { activeSymbols: active_symbols } = useActiveSymbols();
Expand Down Expand Up @@ -183,7 +191,40 @@ const TradeChart = observer(() => {
// max ticks to display for mobile view for tick chart
const max_ticks = granularity === 0 ? 8 : 24;

// Filter positions based on current symbol and contract type
const filtered_positions = all_positions.filter(
p =>
isContractSupportedAndStarted(symbol, p.contract_info) &&
(isTurbosContract(contract_type) || isVanillaContract(contract_type)
? filterByContractType(
p.contract_info,
isTurbosContract(contract_type) ? TRADE_TYPES.TURBOS.SHORT : TRADE_TYPES.VANILLA.CALL
) ||
filterByContractType(
p.contract_info,
isTurbosContract(contract_type) ? TRADE_TYPES.TURBOS.LONG : TRADE_TYPES.VANILLA.PUT
)
: filterByContractType(p.contract_info, contract_type))
);

// Get IDs of closed positions to auto-remove
const closed_positions_ids =
filtered_positions &&
filtered_positions.filter(position => position.contract_info?.is_sold).map(p => p.contract_info.contract_id);

// Automatically remove closed positions after 8 seconds
React.useEffect(() => {
closed_positions_ids.map(positionId => {
const timeout = setTimeout(() => {
onClickRemove(positionId);
}, 8000);

return () => clearTimeout(timeout);
});
}, [closed_positions_ids, onClickRemove]);

if (!symbol || !active_symbols.length || !chartData || !chartData.tradingTimes) return null;

return (
<SmartChart
drawingToolFloatingMenuPosition={isMobile ? { x: 100, y: 100 } : { x: 200, y: 200 }}
Expand Down