From bd1b72b6837ac5010305e2d8b25a28274688bb07 Mon Sep 17 00:00:00 2001 From: Akmal Djumakhodjaev Date: Wed, 15 Mar 2023 10:40:13 +0800 Subject: [PATCH] Likhith | Akmal / Vanilla options feature (#7033) * Initial commit * added mobile view * feat: :sparkles: added info hints and mobile view * feat: :heavy_plus_sign: displays vanilla options under trade types * feat: :construction: incorporated mobile view * feat: :lipstick: added icons * feat: :lipstick: incorporated new UI changes * feat: :lipstick: added required styling * feat: :art: incorporated the changes for report section (#34) * feat: :sparkles: description changes * feat: :sparkles: incorporated strike field changes * feat: :art: fixed the title for mobile view stake field * feat: :sparkles: incorporated mobile view * feat: :fire: removed master changes * feat: :art: incorporated payout button change * feat: purchase button text css * feat: :sparkles: disabled barrier drag for vanilla * fix: :bug: strike field design fix * feat: :lipstick: incorporated info hint message for Payout button * fix: :bug: title change * fix: :bug: payout per point value fix * feat: :sparkles: incorporated barrier validation condition for vanilla * feat: :sparkles: computing min-max stake values * refactor: :recycle: incorporated review comments * feat: add vanilla contract card for desktop * feat: add vanilla contract card for contract details * feat: change label to strike for vanilla contracts in contract details * feat: use bid price for contract value * feat: add vanilla option card for recent positions * feat: :sparkles: incorporated condition to show contract audit * feat: :sparkles: incorporated strike and Payout per point values in open position * feat: :sparkles: incorporated chart floating message change for vanilla trade * fix: enable collapsible button for vanilla contracts * feat: :art: incorporated chart long code message for vanilla * feat: :zap: incorporated contract card for mobile * feat: :sparkles: incorporated chart markers for mobile and desktop * feat: :sparkles: incorporated currency unit for Payout per point * feat: :art: color refactor * feat: add responsive vanilla options card * fix: strike formatting * feat: add vanilla trade types component * feat: implement new design for vanilla trade types * fix: sort ui store * fix: sort trade store * fix: UI changes for vanilla option trade params * feat: new design for trade parameters on mobile * fix: remove unused variable * fix: strike spot area * feat: change design for vanilla option open positions for mobile * fix: refactor code * fix: merge issue * feat: :recycle: incorporated review comments * fix: review comments * refactor: :recycle: incorporated review comments * refactor: :recycle: incorporated view port sizes * fix: remove comments * fix: unlocalized strings * fix: populate barrier options on error response * fix: :bug: report title fix * fix: daily VO contracts to show in recent positions * fix: update to latest content * fix: content issues * feat: add sorting logic for trade types * fix: :bug: resolved input validation failed error * fix: incorrect label for vanilla calls * fix: the icon is incorrect for some of the trades * fix: :bug: strike price is empty when there is duration change * fix: :bug: strike price dropdown is empty for day duration change * fix: :bug: modified stake boundary calculation for vanilla opts * fix: callout not showing in contract details * fix: :bug: removed unused code snip * fix: :bug: capture stake boundary on error * fix: merge conflicts * fix: :bug: ensuring correct boundary is set based on type of Vanilla contract * fix: :bug: set strike vlaue and list on error response * fix: remove currency based rounding for usd amounts * fix: payout rounding value * fix: format numbers based on currency in p&l callout * fix: change colors for vanilla closed contracts * fix: :bug: call proposal API on strike change * feat: changed vanilla trade description * feat: content change * fix: :bug: added missing label * fix: align profit label only when it appears on the screen * fix: :bug: corrected field name * fix: :bug: refactored code to support additional configs * fix: change icon colors in dark mode * fix: :bug: incorporated + sign in reports * fix: :bug: resolved issue with double error message * fix: exit spot placement on y-axis * feat: modified the content as per figma * fix: tooltip alignment for strike description * fix: size units * feat: change content everywhere according to latest design * feat: :art: added tooltip for stake * feat: incorporated styles to stake tooltip * fix: :bug: fixed the type issue * chore: :recycle: incorporated naming conventions * feat: change paddings and profit/loss label positioning in mobile * feat: content update as per figma * fix: incorrect profit logic for prematurely sold contracts * feat: change mobile modal to tooltip * feat: :lipstick: set the layout for vanilla trade --------- Co-authored-by: Likhith Kolayari Co-authored-by: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> --- .../components/collapsible/collapsible.scss | 14 +- .../contract-card-body.jsx | 114 ++++++- .../contract-type-cell.jsx | 7 +- .../contract-card/contract-card.scss | 7 + .../components/data-list/data-list-cell.jsx | 8 +- .../src/components/data-list/data-list.scss | 3 + .../icon-trade-types/icon-trade-types.tsx | 4 + .../icon/contract/ic-contract-payout.svg | 1 + .../icon/contract/ic-contract-strike.svg | 1 + .../components/src/components/icon/icons.js | 4 + .../ic-tradetype-vanilla-long-call.svg | 1 + .../ic-tradetype-vanilla-long-put.svg | 1 + .../src/components/popover/popover.tsx | 5 +- .../positions-drawer-card.jsx | 3 + .../progress-ticks-mobile.jsx | 18 +- .../src/components/types/popover.types.ts | 1 + packages/components/stories/icon/icons.js | 2 + .../Components/markers/marker-spot-label.jsx | 75 +++-- .../Stores/Helpers/chart-marker-helpers.js | 18 +- packages/core/src/Stores/contract-store.js | 1 - packages/core/src/Stores/ui-store.js | 300 +++++++++--------- .../src/Components/market-symbol-icon-row.jsx | 14 +- .../src/Constants/data-table-constants.js | 23 +- .../reports/src/Containers/open-positions.jsx | 22 +- packages/reports/src/_common/contract.js | 8 + .../components/market-symbol-icon.scss | 24 ++ .../reports/src/sass/app/modules/reports.scss | 12 +- .../src/sass/app/modules/smart-chart.scss | 47 ++- .../shared/src/utils/constants/barriers.ts | 3 + .../shared/src/utils/constants/contract.tsx | 61 ++-- .../shared/src/utils/contract/contract.ts | 2 + .../shared/src/utils/shortcode/shortcode.ts | 3 +- .../ContractAudit/contract-details.jsx | 50 ++- .../ContractDrawer/contract-drawer-card.jsx | 6 +- .../ContractDrawer/contract-drawer.jsx | 11 +- .../helpers/positions-helper.js | 8 +- .../PositionsDrawer/positions-modal-card.jsx | 122 ++++++- .../trade_explanations/img-vanilla.svg | 1 + .../Categories/icon-trade-categories.jsx | 12 + .../Categories/trade-categories-gif.jsx | 3 + .../Trading/Categories/trade-categories.jsx | 261 ++++++++------- packages/trader/src/Constants/contract.js | 50 +-- .../Components/InfoBox/info-box-longcode.jsx | 19 +- .../Contract/Components/InfoBox/info-box.jsx | 3 +- .../Containers/contract-replay-widget.jsx | 12 +- .../Contract/Containers/contract-replay.jsx | 11 +- .../SmartChart/Components/all-markers.jsx | 52 +-- .../Components/Elements/purchase-button.jsx | 59 ++-- .../Components/Elements/purchase-fieldset.jsx | 3 + .../Form/ContractType/contract-type-list.jsx | 14 +- .../ContractType/contract-type-widget.jsx | 13 +- .../Form/Purchase/contract-info.jsx | 122 ++++++- .../TradeParams/Duration/duration-wrapper.jsx | 1 + .../Form/TradeParams/Duration/duration.jsx | 20 +- .../Form/TradeParams/amount-mobile.jsx | 75 +++-- .../Components/Form/TradeParams/amount.jsx | 60 +++- .../Form/TradeParams/strike-field.scss | 38 +++ .../Components/Form/TradeParams/strike.jsx | 169 ++++++++++ .../Form/TradeParams/vanilla-trade-types.jsx | 35 ++ .../Trading/Components/Form/screen-small.jsx | 20 +- .../Modules/Trading/Containers/purchase.jsx | 113 ++++--- .../Trading/Containers/strike-param-modal.jsx | 54 ++++ .../Containers/trade-params-mobile.jsx | 52 ++- .../Trading/Containers/trade-params.jsx | 10 +- .../src/Modules/Trading/Containers/trade.jsx | 4 + .../Trading/Constants/validation-rules.js | 6 +- .../Modules/Trading/Helpers/contract-type.js | 71 +---- .../Modules/Trading/Helpers/proposal.js | 7 +- .../src/Stores/Modules/Trading/trade-store.js | 286 ++++++++++------- packages/trader/src/Stores/base-store.js | 1 - packages/trader/src/sass/app.scss | 1 + .../components/contract-type-list.scss | 14 + .../components/contract-type-widget.scss | 3 + .../_common/components/purchase-button.scss | 29 ++ .../sass/app/_common/components/strike.scss | 25 ++ .../_common/drawer/positions-modal-card.scss | 12 +- .../app/_common/layout/trader-layouts.scss | 1 + .../src/sass/app/modules/smart-chart.scss | 54 +++- .../src/sass/app/modules/trading-mobile.scss | 69 ++++ .../trader/src/sass/app/modules/trading.scss | 114 ++++++- 80 files changed, 2218 insertions(+), 765 deletions(-) create mode 100644 packages/components/src/components/icon/contract/ic-contract-payout.svg create mode 100644 packages/components/src/components/icon/contract/ic-contract-strike.svg create mode 100644 packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-call.svg create mode 100644 packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-put.svg create mode 100644 packages/trader/src/Assets/SvgComponents/trade_explanations/img-vanilla.svg create mode 100644 packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike-field.scss create mode 100644 packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike.jsx create mode 100644 packages/trader/src/Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx create mode 100644 packages/trader/src/Modules/Trading/Containers/strike-param-modal.jsx create mode 100644 packages/trader/src/sass/app/_common/components/strike.scss diff --git a/packages/components/src/components/collapsible/collapsible.scss b/packages/components/src/components/collapsible/collapsible.scss index a8c56ed0cc6b..fc02a5bfa3c7 100644 --- a/packages/components/src/components/collapsible/collapsible.scss +++ b/packages/components/src/components/collapsible/collapsible.scss @@ -73,7 +73,15 @@ margin-left: 0; margin-right: auto; } - //&__content { - // transition: all 0.3s ease; - //} + // Vanilla Options specific styles + .trade-container { + &__fieldset { + flex: 1; + margin-left: 0.4rem; + + .dc-button-menu__wrapper { + height: 4rem; + } + } + } } diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.jsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.jsx index c94eee65b526..d92a15eead0c 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.jsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.jsx @@ -8,7 +8,6 @@ import { getLimitOrderAmount, getCurrentTick, getDisplayStatus, - getTotalProfit, isValidToCancel, isValidToSell, shouldShowCancellation, @@ -23,6 +22,101 @@ import Money from '../../money'; import { ResultStatusIcon } from '../result-overlay/result-overlay.jsx'; import ProgressSliderMobile from '../../progress-slider-mobile'; +const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sold, progress_slider, status }) => { + const { buy_price, bid_price, entry_spot_display_value, barrier, sell_price, profit } = contract_info; + const contract_value = is_sold ? sell_price : bid_price; + + return ( + + +
+ + + + + + + + + + + + + + + +
+ 0} + > + +
+ {status === 'profit' && } + {status === 'loss' && } +
+
+
+ +
+
+ + + + + + + +
+ +
+ + + + + + + +
+ + {is_sold ? ( + + ) : ( + progress_slider + )} + 0} + > + +
+ {status === 'profit' && } + {status === 'loss' && } +
+
+
+
+
+ ); +}; + const MultiplierCardBody = ({ addToast, contract_info, @@ -46,7 +140,6 @@ const MultiplierCardBody = ({ }) => { const { buy_price, bid_price, profit, limit_order, underlying } = contract_info; - const total_profit = getTotalProfit(contract_info); const { take_profit, stop_loss } = getLimitOrderAmount(contract_update || limit_order); const cancellation_price = getCancellationPrice(contract_info); const is_valid_to_cancel = isValidToCancel(contract_info); @@ -130,10 +223,10 @@ const MultiplierCardBody = ({ className='dc-contract-card-item__total-profit-loss' header={getCardLabels().TOTAL_PROFIT_LOSS} is_crypto={isCryptocurrency(currency)} - is_loss={+total_profit < 0} - is_won={+total_profit > 0} + is_loss={+profit < 0} + is_won={+profit > 0} > - +
+ ) : is_vanilla ? ( + ) : (
diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.jsx b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.jsx index fc000ccf22fe..a5f2983e5e26 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.jsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.jsx @@ -1,12 +1,13 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import IconTradeTypes from '../../icon-trade-types'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { isVanillaContract } from '@deriv/shared'; const ContractTypeCell = ({ getContractTypeDisplay, is_high_low, multiplier, type }) => (
diff --git a/packages/components/src/components/contract-card/contract-card.scss b/packages/components/src/components/contract-card/contract-card.scss index f6f28870469c..86e5fc1a64d9 100644 --- a/packages/components/src/components/contract-card/contract-card.scss +++ b/packages/components/src/components/contract-card/contract-card.scss @@ -270,6 +270,12 @@ grid-gap: 0.8rem 0.4rem; flex: 1; padding: 0.4rem 0; + + .dc-contract-card-items-wrapper-group { + .dc-contract-card-item:first-child { + margin-bottom: 0.8rem; + } + } } &--has-progress-slider { grid-template-columns: 1fr 1fr 1fr; @@ -372,6 +378,7 @@ @include mobile { flex-direction: row; justify-content: center; + grid-column: 1 / 4; &-value { margin-left: 0.2rem; diff --git a/packages/components/src/components/data-list/data-list-cell.jsx b/packages/components/src/components/data-list/data-list-cell.jsx index ebeb94c8c63a..596ddb408334 100644 --- a/packages/components/src/components/data-list/data-list-cell.jsx +++ b/packages/components/src/components/data-list/data-list-cell.jsx @@ -1,20 +1,22 @@ -import classNames from 'classnames'; import React from 'react'; +import classNames from 'classnames'; +import { isVanillaContract } from '@deriv/shared'; const DataListCell = ({ className, column, is_footer, passthrough, row }) => { if (!column) return null; const { col_index, title } = column; const cell_value = row[col_index]; + const is_vanilla = isVanillaContract(row.contract_info?.contract_type); return (
{!is_footer && (
- {column.renderHeader ? column.renderHeader({ title }) : title} + {column.renderHeader ? column.renderHeader({ title, is_vanilla }) : title}
)}
{column.renderCellContent - ? column.renderCellContent({ cell_value, is_footer, passthrough, row_obj: row }) + ? column.renderCellContent({ cell_value, is_footer, passthrough, row_obj: row, is_vanilla }) : cell_value}
diff --git a/packages/components/src/components/data-list/data-list.scss b/packages/components/src/components/data-list/data-list.scss index 6f464d9c74f9..6695d3069c10 100644 --- a/packages/components/src/components/data-list/data-list.scss +++ b/packages/components/src/components/data-list/data-list.scss @@ -79,6 +79,9 @@ &--wrapper:not(.data-list__item--dynamic-height-wrapper) { height: 100%; } + &--vanilla { + flex: none; + } } &__desc { &--wrapper { diff --git a/packages/components/src/components/icon-trade-types/icon-trade-types.tsx b/packages/components/src/components/icon-trade-types/icon-trade-types.tsx index b7f0acb5eb68..31c465037993 100644 --- a/packages/components/src/components/icon-trade-types/icon-trade-types.tsx +++ b/packages/components/src/components/icon-trade-types/icon-trade-types.tsx @@ -76,6 +76,10 @@ const IconTradeTypes = ({ type, className, ...props }: TIconTradeTypes) => { return ; case 'upordown': return ; + case 'vanillalongcall': + return ; + case 'vanillalongput': + return ; default: return ; } diff --git a/packages/components/src/components/icon/contract/ic-contract-payout.svg b/packages/components/src/components/icon/contract/ic-contract-payout.svg new file mode 100644 index 000000000000..9363393904a1 --- /dev/null +++ b/packages/components/src/components/icon/contract/ic-contract-payout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/contract/ic-contract-strike.svg b/packages/components/src/components/icon/contract/ic-contract-strike.svg new file mode 100644 index 000000000000..2c312b0842a7 --- /dev/null +++ b/packages/components/src/components/icon/contract/ic-contract-strike.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index 0c3a6e9db6a5..b1d0cab5aa2c 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -568,9 +568,11 @@ import './contract/ic-contract-exit-time-circle.svg'; import './contract/ic-contract-exit-time.svg'; import './contract/ic-contract-flag.svg'; import './contract/ic-contract-id.svg'; +import './contract/ic-contract-payout.svg'; import './contract/ic-contract-safeguard.svg'; import './contract/ic-contract-start-time-circle.svg'; import './contract/ic-contract-start-time.svg'; +import './contract/ic-contract-strike.svg'; import './contract/ic-contract-target.svg'; import './currency/ic-currency-aud.svg'; import './currency/ic-currency-bch.svg'; @@ -747,6 +749,8 @@ import './tradetype/ic-tradetype-runlow.svg'; import './tradetype/ic-tradetype-tickhigh.svg'; import './tradetype/ic-tradetype-ticklow.svg'; import './tradetype/ic-tradetype-upordown.svg'; +import './tradetype/ic-tradetype-vanilla-long-call.svg'; +import './tradetype/ic-tradetype-vanilla-long-put.svg'; import './underlying/ic-underlying-1HZ100V.svg'; import './underlying/ic-underlying-1HZ10V.svg'; import './underlying/ic-underlying-1HZ150V.svg'; diff --git a/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-call.svg b/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-call.svg new file mode 100644 index 000000000000..3dff38e17dd0 --- /dev/null +++ b/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-call.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-put.svg b/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-put.svg new file mode 100644 index 000000000000..65090b433fd4 --- /dev/null +++ b/packages/components/src/components/icon/tradetype/ic-tradetype-vanilla-long-put.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/popover/popover.tsx b/packages/components/src/components/popover/popover.tsx index 6b0292cea6a9..68f363158d74 100644 --- a/packages/components/src/components/popover/popover.tsx +++ b/packages/components/src/components/popover/popover.tsx @@ -32,6 +32,7 @@ const Popover = ({ window_border, zIndex = '1', data_testid, + arrow_styles, }: React.PropsWithChildren) => { const ref = React.useRef(); const [popover_ref, setPopoverRef] = React.useState(undefined); @@ -160,7 +161,9 @@ const Popover = ({ margin: 'auto', bottom: '0px', } - : {} + : { + ...arrow_styles, + } } >
{ const is_multiplier = isMultiplierContract(contract_info.contract_type); + const is_vanilla = isVanillaContract(contract_info.contract_type); const is_crypto = isCryptoContract(contract_info.underlying); const has_progress_slider = !is_multiplier || (is_crypto && is_multiplier); const has_ended = !!getEndTime(contract_info); @@ -83,6 +85,7 @@ const PositionsDrawerCard = ({ is_mobile={is_mobile} is_multiplier={is_multiplier} is_sold={has_ended} + is_vanilla={is_vanilla} has_progress_slider={is_mobile && has_progress_slider} removeToast={removeToast} server_time={server_time} diff --git a/packages/components/src/components/progress-slider-mobile/progress-ticks-mobile.jsx b/packages/components/src/components/progress-slider-mobile/progress-ticks-mobile.jsx index 304de4dae309..2616c52cc813 100644 --- a/packages/components/src/components/progress-slider-mobile/progress-ticks-mobile.jsx +++ b/packages/components/src/components/progress-slider-mobile/progress-ticks-mobile.jsx @@ -3,16 +3,14 @@ import React from 'react'; import TickProgress from '../tick-progress'; import Text from '../text'; -const ProgressTicksMobile = ({ current_tick, getCardLabels, ticks_count }) => { - return ( -
- - {getCardLabels().TICK} {current_tick} - - 5 ? 2 : 1} size={ticks_count} value={current_tick} /> -
- ); -}; +const ProgressTicksMobile = ({ current_tick, getCardLabels, ticks_count }) => ( +
+ + {getCardLabels().TICK} {current_tick} + + 5 ? 2 : 1} size={ticks_count} value={current_tick} /> +
+); ProgressTicksMobile.propTypes = { current_tick: PropTypes.number, diff --git a/packages/components/src/components/types/popover.types.ts b/packages/components/src/components/types/popover.types.ts index 7493131af022..918077595ec0 100644 --- a/packages/components/src/components/types/popover.types.ts +++ b/packages/components/src/components/types/popover.types.ts @@ -26,4 +26,5 @@ export type TPopoverProps = { zIndex?: string; window_border?: number; data_testid?: string; + arrow_styles?: React.CSSProperties; }; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index 6be2a3ccdb87..f28aee72b294 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -774,6 +774,8 @@ export const icons = 'IcTradetypeTickhigh', 'IcTradetypeTicklow', 'IcTradetypeUpordown', + 'IcTradetypeVanillaLongCall', + 'IcTradetypeVanillaLongPut', ], 'underlying': [ 'IcUnderlying1HZ100V', diff --git a/packages/core/src/Components/markers/marker-spot-label.jsx b/packages/core/src/Components/markers/marker-spot-label.jsx index 29c2ceabb1d0..300bc5a25f14 100644 --- a/packages/core/src/Components/markers/marker-spot-label.jsx +++ b/packages/core/src/Components/markers/marker-spot-label.jsx @@ -6,6 +6,7 @@ import { Icon, Text } from '@deriv/components'; import { addComma, toMoment } from '@deriv/shared'; import MarkerSpot from './marker-spot.jsx'; +import { localize } from '@deriv/translations'; const MarkerSpotLabel = ({ align_label, @@ -14,6 +15,7 @@ const MarkerSpotLabel = ({ spot_count, spot_epoch, spot_value, + spot_profit, status, }) => { const [show_label, setShowLabel] = React.useState(!has_hover_toggle); @@ -38,37 +40,59 @@ const MarkerSpotLabel = ({ } return ( -
- {show_label && ( -
-
-
- - - {toMoment(+spot_epoch).format('HH:mm:ss')} - -
+
+
+ {show_label && ( +
-

{addComma(spot_value)}

+
+ + + {toMoment(+spot_epoch).format('HH:mm:ss')} + +
+
+

{addComma(spot_value)}

+
+ )} + {marker_spot} +
+ {spot_profit && ( +
+
+ + {localize('Total profit/loss:')} + + {`${parseFloat(spot_profit) > 0 ? '+' : ''}${spot_profit}`} +
)} - {marker_spot}
); }; @@ -84,6 +108,7 @@ MarkerSpotLabel.propTypes = { spot_count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), spot_epoch: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), spot_value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + spot_profit: PropTypes.string, status: PropTypes.oneOf(['won', 'lost']), }; export default observer(MarkerSpotLabel); diff --git a/packages/core/src/Stores/Helpers/chart-marker-helpers.js b/packages/core/src/Stores/Helpers/chart-marker-helpers.js index c4bca3611d39..c62a63672c52 100644 --- a/packages/core/src/Stores/Helpers/chart-marker-helpers.js +++ b/packages/core/src/Stores/Helpers/chart-marker-helpers.js @@ -1,5 +1,14 @@ import extend from 'extend'; -import { isUserSold, isMultiplierContract, isDigitContract, getEndTime, isMobile } from '@deriv/shared'; +import { + formatMoney, + getEndTime, + isDesktop, + isDigitContract, + isMobile, + isMultiplierContract, + isUserSold, + isVanillaContract, +} from '@deriv/shared'; import { MARKER_TYPES_CONFIG } from '../Constants/markers'; @@ -81,6 +90,8 @@ export const createMarkerSpotExit = (contract_info, tick, idx) => { const should_show_spot_exit = !is_user_sold || isMultiplierContract(contract_info.contract_type); + const should_show_profit_label = isVanillaContract(contract_info.contract_type) && isDesktop(); + const marker_spot_type = should_show_spot_exit ? MARKER_TYPES_CONFIG.SPOT_EXIT.type : MARKER_TYPES_CONFIG.SPOT_SELL.type; @@ -90,8 +101,11 @@ export const createMarkerSpotExit = (contract_info, tick, idx) => { spot_value: `${exit_tick}`, spot_epoch: `${contract_info.exit_tick_time}`, status: `${+contract_info.profit >= 0 ? 'won' : 'lost'}`, - align_label, + align_label: should_show_profit_label ? 'middle' : align_label, spot_count, + spot_profit: + should_show_profit_label && + `${formatMoney(contract_info.currency, contract_info.profit, true)} ${contract_info.currency}`, } : {}; diff --git a/packages/core/src/Stores/contract-store.js b/packages/core/src/Stores/contract-store.js index be23db4a4ad2..a9a8d5369250 100644 --- a/packages/core/src/Stores/contract-store.js +++ b/packages/core/src/Stores/contract-store.js @@ -102,7 +102,6 @@ export default class ContractStore extends BaseStore { this.updateBarriersArray(contract_info, this.root_store.ui.is_dark_mode_on); this.markers_array = createChartMarkers(this.contract_info); this.marker = calculate_marker(this.contract_info); - this.contract_config = getChartConfig(this.contract_info); this.display_status = getDisplayStatus(this.contract_info); this.is_ended = isEnded(this.contract_info); diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index b951afca16d0..5d347ba7093c 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -66,6 +66,9 @@ export default class UIStore extends BaseStore { duration_h = 1; duration_d = 1; + // vanilla trade type selection + vanilla_trade_type = 'VANILLALONGCALL'; + // purchase button states purchase_states = [false, false]; @@ -198,188 +201,189 @@ export default class UIStore extends BaseStore { super({ root_store, local_storage_properties, store_name }); makeObservable(this, { - is_account_settings_visible: observable, - is_positions_drawer_on: observable, - is_reports_visible: observable, - reports_route_tab_index: observable, - is_cashier_visible: observable, - is_history_tab_active: observable, - should_show_cancellation_warning: observable, - footer_extensions: observable, - header_extension: observable, - settings_extension: observable, - notification_messages_ui: observable, - is_dark_mode_on: observable, - is_settings_modal_on: observable, - is_accounts_switcher_on: observable, + account_needed_modal_props: observable, account_switcher_disabled_message: observable, + advanced_duration_unit: observable, + advanced_expiry_type: observable, + app_contents_scroll_ref: observable, + choose_crypto_currency_target: observable, + current_focus: observable, + deposit_real_account_signup_target: observable, + duration_d: observable, + duration_h: observable, + duration_m: observable, + duration_s: observable, + duration_t: observable, + footer_extensions: observable, has_only_forward_starting_contracts: observable, has_read_scam_message: observable, - is_exit_traders_hub_modal_visible: observable, - is_services_error_visible: observable, - is_unsupported_contract_modal_visible: observable, - is_new_account: observable, + has_real_account_signup_ended: observable, + header_extension: observable, + is_account_needed_modal_on: observable, + is_account_settings_visible: observable, is_account_signup_modal_visible: observable, - is_set_residence_modal_visible: observable, - is_reset_password_modal_visible: observable, - is_reset_email_modal_visible: observable, - is_update_email_modal_visible: observable, - is_reset_trading_password_modal_visible: observable, + is_accounts_switcher_on: observable, + is_acuity_modal_open: observable, + is_advanced_duration: observable, + is_app_disabled: observable, + is_cashier_visible: observable, + is_cfd_page: observable, is_cfd_reset_password_modal_enabled: observable, - sub_section_index: observable, is_chart_countdown_visible: observable, is_chart_layout_default: observable, - pwa_prompt_event: observable, - screen_width: observable, - screen_height: observable, - is_onscreen_keyboard_active: observable, - is_advanced_duration: observable, - advanced_duration_unit: observable, - advanced_expiry_type: observable, - simple_duration_unit: observable, - duration_t: observable, - duration_s: observable, - duration_m: observable, - duration_h: observable, - duration_d: observable, - purchase_states: observable, - is_app_disabled: observable, - is_closing_create_real_account_modal: observable, - is_route_modal_on: observable, - is_real_acc_signup_on: observable, - real_account_signup_target: observable, - deposit_real_account_signup_target: observable, - has_real_account_signup_ended: observable, - is_acuity_modal_open: observable, - is_welcome_modal_visible: observable, is_close_mx_mlt_account_modal_visible: observable, is_close_uk_account_modal_visible: observable, + is_closing_create_real_account_modal: observable, + is_dark_mode_on: observable, + is_deriv_account_needed_modal_visible: observable, + is_exit_traders_hub_modal_visible: observable, + is_history_tab_active: observable, + is_landscape: observable, + is_nativepicker_visible: observable, + is_new_account: observable, + is_onscreen_keyboard_active: observable, + is_positions_drawer_on: observable, + is_real_acc_signup_on: observable, + is_real_tab_enabled: observable, + is_reports_visible: observable, + is_reset_email_modal_visible: observable, + is_reset_password_modal_visible: observable, + is_reset_trading_password_modal_visible: observable, + is_route_modal_on: observable, + is_services_error_visible: observable, is_set_currency_modal_visible: observable, - show_positions_toggle: observable, - modal_index: observable, - is_top_up_virtual_open: observable, + is_set_residence_modal_visible: observable, + is_settings_modal_on: observable, + is_switch_to_deriv_account_modal_visible: observable, is_top_up_virtual_in_progress: observable, + is_top_up_virtual_open: observable, is_top_up_virtual_success: observable, - should_show_real_accounts_list: observable, - real_account_signup: observable, - current_focus: observable, - toasts: observable.shallow, - is_cfd_page: observable, - is_nativepicker_visible: observable, - is_landscape: observable, + is_trading_assessment_for_existing_user_enabled: observable, + is_trading_assessment_for_new_user_enabled: observable, + is_unsupported_contract_modal_visible: observable, + is_update_email_modal_visible: observable, + is_welcome_modal_visible: observable, + manage_real_account_tab_index: observable, + modal_index: observable, + notification_messages_ui: observable, prompt_when: observable, promptFn: observable, - is_account_needed_modal_on: observable, - account_needed_modal_props: observable, - manage_real_account_tab_index: observable, - should_show_multipliers_onboarding: observable, - choose_crypto_currency_target: observable, - should_show_cancel: observable, - app_contents_scroll_ref: observable, - is_real_tab_enabled: observable, - is_deriv_account_needed_modal_visible: observable, - should_show_trade_assessment_form: observable, + purchase_states: observable, + pwa_prompt_event: observable, + real_account_signup_target: observable, + real_account_signup: observable, + reports_route_tab_index: observable, + screen_height: observable, + screen_width: observable, + settings_extension: observable, should_show_appropriateness_warning_modal: observable, - should_show_risk_accept_modal: observable, - should_show_cooldown_modal: observable, - should_show_trading_assessment_modal: observable, - is_trading_assessment_for_existing_user_enabled: observable, - is_trading_assessment_for_new_user_enabled: observable, should_show_assessment_complete_modal: observable, - is_switch_to_deriv_account_modal_visible: observable, + should_show_cancel: observable, + should_show_cancellation_warning: observable, + should_show_cooldown_modal: observable, + should_show_multipliers_onboarding: observable, + should_show_real_accounts_list: observable, + should_show_risk_accept_modal: observable, should_show_risk_warning_modal: observable, - is_warning_scam_message_modal_visible: computed, - setScamMessageLocalStorage: action.bound, - setIsNewAccount: action.bound, - init: action.bound, - setAppContentsScrollRef: action.bound, - populateFooterExtensions: action.bound, - populateHeaderExtensions: action.bound, - populateSettingsExtensions: action.bound, - onChangeUiStore: action.bound, + should_show_trade_assessment_form: observable, + should_show_trading_assessment_modal: observable, + show_positions_toggle: observable, + simple_duration_unit: observable, + sub_section_index: observable, + toasts: observable.shallow, + vanilla_trade_type: observable, + addToast: action.bound, + closeAccountNeededModal: action.bound, + closeRealAccountSignup: action.bound, + closeSuccessTopUpModal: action.bound, + closeTopUpModal: action.bound, + continueRouteAfterChooseCrypto: action.bound, + disableApp: action.bound, + disableRouteModal: action.bound, + enableApp: action.bound, handleResize: action.bound, - setPromptHandler: action.bound, - showCloseMxMltAccountPopup: action.bound, - showCloseUKAccountPopup: action.bound, + init: action.bound, + installWithDeferredPrompt: action.bound, + is_account_switcher_disabled: computed, is_mobile: computed, is_tablet: computed, - is_account_switcher_disabled: computed, - setRouteModal: action.bound, - disableRouteModal: action.bound, - disableApp: action.bound, - enableApp: action.bound, - toggleAccountsDialog: action.bound, - setAccountSwitcherDisabledMessage: action.bound, - setPurchaseState: action.bound, - resetPurchaseStates: action.bound, - setChartLayout: action.bound, - setChartCountdown: action.bound, - setDarkMode: action.bound, - toggleSetCurrencyModal: action.bound, - toggleCashier: action.bound, - setModalIndex: action.bound, - toggleSettingsModal: action.bound, + is_warning_scam_message_modal_visible: computed, + notifyAppInstall: action.bound, + onChangeUiStore: action.bound, + openAccountNeededModal: action.bound, + openDerivRealAccountNeededModal: action.bound, openPositionsDrawer: action.bound, openRealAccountSignup: action.bound, - setShouldShowCancel: action.bound, - resetRealAccountSignupTarget: action.bound, - setManageRealAccountActiveTabIndex: action.bound, - closeRealAccountSignup: action.bound, - openAccountNeededModal: action.bound, - closeAccountNeededModal: action.bound, - togglePositionsDrawer: action.bound, - toggleAccountSettings: action.bound, - toggleReports: action.bound, - toggleServicesErrorModal: action.bound, - removePWAPromptEvent: action.bound, - setPWAPromptEvent: action.bound, - setHasOnlyForwardingContracts: action.bound, - toggleUnsupportedContractModal: action.bound, - toggleAccountSignupModal: action.bound, - toggleSetResidenceModal: action.bound, - toggleCancellationWarning: action.bound, - toggleHistoryTab: action.bound, - setTopUpInProgress: action.bound, - closeTopUpModal: action.bound, + openSwitchToRealAccountModal: action.bound, openTopUpModal: action.bound, - closeSuccessTopUpModal: action.bound, - toggleResetPasswordModal: action.bound, - toggleResetEmailModal: action.bound, - toggleUpdateEmailModal: action.bound, - setResetTradingPasswordModalOpen: action.bound, - setRealAccountSignupParams: action.bound, - setRealAccountSignupEnd: action.bound, + populateFooterExtensions: action.bound, + populateHeaderExtensions: action.bound, + populateSettingsExtensions: action.bound, + removePWAPromptEvent: action.bound, + removeToast: action.bound, + resetPurchaseStates: action.bound, resetRealAccountSignupParams: action.bound, - toggleOnScreenKeyboard: action.bound, + resetRealAccountSignupTarget: action.bound, + setAccountSwitcherDisabledMessage: action.bound, + setAppContentsScrollRef: action.bound, + setCFDPasswordResetModal: action.bound, + setChartCountdown: action.bound, + setChartLayout: action.bound, setCurrentFocus: action.bound, - addToast: action.bound, - removeToast: action.bound, + setDarkMode: action.bound, + setHasOnlyForwardingContracts: action.bound, setIsAcuityModalOpen: action.bound, + setIsClosingCreateRealAccountModal: action.bound, setIsNativepickerVisible: action.bound, - setReportsTabIndex: action.bound, - toggleWelcomeModal: action.bound, - notifyAppInstall: action.bound, - installWithDeferredPrompt: action.bound, - toggleExitTradersHubModal: action.bound, - toggleShouldShowRealAccountsList: action.bound, - toggleShouldShowMultipliersOnboarding: action.bound, - shouldNavigateAfterChooseCrypto: action.bound, - continueRouteAfterChooseCrypto: action.bound, - openDerivRealAccountNeededModal: action.bound, - openSwitchToRealAccountModal: action.bound, - setShouldShowRiskWarningModal: action.bound, + setIsNewAccount: action.bound, + setIsRealTabEnabled: action.bound, setIsTradingAssessmentForExistingUserEnabled: action.bound, setIsTradingAssessmentForNewUserEnabled: action.bound, + setManageRealAccountActiveTabIndex: action.bound, + setModalIndex: action.bound, + setPromptHandler: action.bound, + setPurchaseState: action.bound, + setPWAPromptEvent: action.bound, + setRealAccountSignupEnd: action.bound, + setRealAccountSignupParams: action.bound, + setReportsTabIndex: action.bound, + setResetTradingPasswordModalOpen: action.bound, + setRouteModal: action.bound, + setScamMessageLocalStorage: action.bound, setShouldShowAppropriatenessWarningModal: action.bound, - setShouldShowWarningModal: action.bound, setShouldShowAssessmentCompleteModal: action.bound, + setShouldShowCancel: action.bound, setShouldShowCooldownModal: action.bound, - setShouldShowTradingAssessmentModal: action.bound, + setShouldShowRiskWarningModal: action.bound, setShouldShowTradeAssessmentForm: action.bound, - setIsClosingCreateRealAccountModal: action.bound, - setIsRealTabEnabled: action.bound, - setCFDPasswordResetModal: action.bound, + setShouldShowTradingAssessmentModal: action.bound, + setShouldShowWarningModal: action.bound, setSubSectionIndex: action.bound, + setTopUpInProgress: action.bound, + shouldNavigateAfterChooseCrypto: action.bound, + showCloseMxMltAccountPopup: action.bound, + showCloseUKAccountPopup: action.bound, + toggleAccountsDialog: action.bound, + toggleAccountSettings: action.bound, + toggleAccountSignupModal: action.bound, + toggleCancellationWarning: action.bound, + toggleCashier: action.bound, + toggleExitTradersHubModal: action.bound, + toggleHistoryTab: action.bound, + toggleOnScreenKeyboard: action.bound, + togglePositionsDrawer: action.bound, + toggleReports: action.bound, + toggleResetEmailModal: action.bound, + toggleResetPasswordModal: action.bound, + toggleServicesErrorModal: action.bound, + toggleSetCurrencyModal: action.bound, + toggleSetResidenceModal: action.bound, + toggleSettingsModal: action.bound, + toggleShouldShowMultipliersOnboarding: action.bound, + toggleShouldShowRealAccountsList: action.bound, + toggleUnsupportedContractModal: action.bound, + toggleUpdateEmailModal: action.bound, + toggleWelcomeModal: action.bound, }); window.addEventListener('resize', this.handleResize); diff --git a/packages/reports/src/Components/market-symbol-icon-row.jsx b/packages/reports/src/Components/market-symbol-icon-row.jsx index b84fdf012957..56ceb4b2c852 100644 --- a/packages/reports/src/Components/market-symbol-icon-row.jsx +++ b/packages/reports/src/Components/market-symbol-icon-row.jsx @@ -1,17 +1,24 @@ +import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { extractInfoFromShortcode, isHighLow } from '@deriv/shared'; import { Icon, Popover, IconTradeTypes } from '@deriv/components'; import { getMarketName, getTradeTypeName } from '../Helpers/market-underlying'; -const MarketSymbolIconRow = ({ icon, payload, show_description, should_show_multiplier = true }) => { +const MarketSymbolIconRow = ({ icon, payload, show_description, should_show_multiplier = true, is_vanilla }) => { const should_show_category_icon = typeof payload.shortcode === 'string'; const info_from_shortcode = extractInfoFromShortcode(payload.shortcode); const is_high_low = isHighLow({ shortcode_info: info_from_shortcode }); + // We need the condition to update the label for vanilla trade type since the label doesn't match with the trade type key unlike other contracts + const category_label = is_vanilla + ? info_from_shortcode.category.replace('Vanillalong', '').charAt(0).toUpperCase() + + info_from_shortcode.category.replace('Vanillalong', '').slice(1) + : info_from_shortcode.category; + if (should_show_category_icon && info_from_shortcode) { return ( -
+
- {show_description && info_from_shortcode.category} + {show_description && category_label}
{should_show_multiplier && info_from_shortcode.multiplier && (
x{info_from_shortcode.multiplier}
@@ -90,6 +97,7 @@ MarketSymbolIconRow.propTypes = { payload: PropTypes.object, show_description: PropTypes.bool, should_show_multiplier: PropTypes.bool, + is_vanilla: PropTypes.bool, }; export default MarketSymbolIconRow; diff --git a/packages/reports/src/Constants/data-table-constants.js b/packages/reports/src/Constants/data-table-constants.js index 64461d3c1154..99f9aedc33d7 100644 --- a/packages/reports/src/Constants/data-table-constants.js +++ b/packages/reports/src/Constants/data-table-constants.js @@ -167,10 +167,18 @@ export const getOpenPositionsColumnsTemplate = currency => [ { title: isMobile() ? '' : localize('Type'), col_index: 'type', - renderCellContent: ({ cell_value, row_obj, is_footer }) => { + renderCellContent: ({ cell_value, row_obj, is_footer, is_vanilla }) => { if (is_footer) return localize('Total'); - return ; + return ( + + ); }, }, { @@ -192,8 +200,15 @@ export const getOpenPositionsColumnsTemplate = currency => [ { title: localize('Payout limit'), col_index: 'payout', - renderCellContent: ({ cell_value }) => - cell_value ? : -, + renderHeader: ({ title, is_vanilla }) => {is_vanilla ? localize('Strike') : title}, + renderCellContent: ({ cell_value, row_obj, is_vanilla }) => + is_vanilla ? ( + row_obj.barrier?.toFixed(2) + ) : cell_value ? ( + + ) : ( + - + ), }, { title: localize('Indicative profit/loss'), diff --git a/packages/reports/src/Containers/open-positions.jsx b/packages/reports/src/Containers/open-positions.jsx index a203ff43c357..e2c46371715e 100644 --- a/packages/reports/src/Containers/open-positions.jsx +++ b/packages/reports/src/Containers/open-positions.jsx @@ -6,6 +6,7 @@ import { DesktopWrapper, MobileWrapper, ProgressBar, + ProgressSliderMobile, DataList, DataTable, ContractCard, @@ -18,10 +19,12 @@ import { urlFor, isMobile, isMultiplierContract, + isVanillaContract, getTimePercentage, website_name, getTotalProfit, getContractPath, + getCurrentTick, } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { ReportsTableRowLoader } from '../Components/Elements/ContentLoader'; @@ -89,7 +92,8 @@ const MobileRowRenderer = ({ } const { contract_info, contract_update, type, is_sell_requested } = row; - const { currency, status, date_expiry, date_start } = contract_info; + const { currency, status, date_expiry, date_start, tick_count, purchase_time } = contract_info; + const current_tick = tick_count ? getCurrentTick(contract_info) : null; const duration_type = getContractDurationType(contract_info.longcode); const progress_value = getTimePercentage(server_time, date_start, date_expiry) / 100; @@ -114,7 +118,20 @@ const MobileRowRenderer = ({ <>
- + {isVanillaContract(type) ? ( + + ) : ( + + )}
@@ -301,6 +318,7 @@ const OpenPositions = ({ getPositionById, is_loading, is_multiplier, + is_vanilla, NotificationMessages, onClickCancel, onClickSell, diff --git a/packages/reports/src/_common/contract.js b/packages/reports/src/_common/contract.js index 9e015ced31f8..c895330c95b7 100644 --- a/packages/reports/src/_common/contract.js +++ b/packages/reports/src/_common/contract.js @@ -259,6 +259,14 @@ export const getSupportedContracts = is_high_low => ({ name: , position: 'bottom', }, + VANILLALONGCALL: { + name: , + position: 'top', + }, + VANILLALONGPUT: { + name: , + position: 'bottom', + }, }); export const getContractConfig = is_high_low => ({ diff --git a/packages/reports/src/sass/app/_common/components/market-symbol-icon.scss b/packages/reports/src/sass/app/_common/components/market-symbol-icon.scss index c1b39d42b87b..5b7c2732ee03 100644 --- a/packages/reports/src/sass/app/_common/components/market-symbol-icon.scss +++ b/packages/reports/src/sass/app/_common/components/market-symbol-icon.scss @@ -35,4 +35,28 @@ align-items: flex-end; margin: 0 0 0.4rem 0.4rem; } + + @include mobile { + width: 0.8rem; + + &__vanilla { + width: 100%; + .market-symbol-icon { + &-name { + width: 55%; + + .dc-popover { + padding-right: 1rem; + } + } + &-category { + width: 45%; + + .dc-popover { + padding-right: 1rem; + } + } + } + } + } } diff --git a/packages/reports/src/sass/app/modules/reports.scss b/packages/reports/src/sass/app/modules/reports.scss index 68ffc94a35f3..646fa70e81e9 100644 --- a/packages/reports/src/sass/app/modules/reports.scss +++ b/packages/reports/src/sass/app/modules/reports.scss @@ -576,7 +576,7 @@ $side-padding: 1.2em; background-color: var(--general-section-1); border-radius: $BORDER_RADIUS; border: 1px solid var(--border-disabled); - padding: 0; + padding: 0 !important; /* postcss-bem-linter: ignore */ & .dc-progress-slider--completed { @@ -876,11 +876,6 @@ $side-padding: 1.2em; } } } - .market-symbol-icon { - @include mobile { - width: 80px; - } - } } /** @define open-positions-multiplier; weak */ @@ -960,6 +955,11 @@ $side-padding: 1.2em; /* postcss-bem-linter: ignore */ .data-list__item { background-color: var(--general-section-1); + padding: 1.6rem 0; + + &--multiplier { + padding: 0; + } } .currency { &__wrapper { diff --git a/packages/reports/src/sass/app/modules/smart-chart.scss b/packages/reports/src/sass/app/modules/smart-chart.scss index 3ae7a4689178..70a17b965524 100644 --- a/packages/reports/src/sass/app/modules/smart-chart.scss +++ b/packages/reports/src/sass/app/modules/smart-chart.scss @@ -5,7 +5,7 @@ &__spot { position: absolute; - bottom: -11px; + top: -1.1rem; margin-left: -11.5px; display: flex; justify-content: center; @@ -70,6 +70,40 @@ /** @define chart-spot-label */ .chart-spot-label { + &-profit { + position: relative; + top: -2.8rem; + left: 2.5rem; + + .chart-spot-label__value-container { + padding: 0.8rem; + line-height: normal; + border-radius: 0.6rem; + } + + &--lost { + &:before { + border-bottom: 0.5rem solid var(--text-loss-danger); + } + } + + &--won { + &:before { + border-bottom: 0.5rem solid var(--text-profit-success); + } + } + + &:before { + content: ''; + position: absolute; + top: 50%; + left: -1rem; + border-top: 0.5rem solid transparent; + border-right: 0.5rem solid transparent; + border-left: 0.5rem solid transparent; + transform: translateY(-50%) rotate(270deg); + } + } &__info-container { width: 100%; position: relative; @@ -87,7 +121,14 @@ margin-bottom: 2px; } @include mobile { - bottom: 9.5px; + bottom: 1.35rem; + } + } + &--middle { + bottom: 3.8rem; + + .chart-spot-label__time-container { + margin-bottom: 0.2rem; } } &--bottom { @@ -146,7 +187,7 @@ display: flex; justify-content: center; align-items: center; - padding: 0.2rem; + padding: 0.5rem; } } } diff --git a/packages/shared/src/utils/constants/barriers.ts b/packages/shared/src/utils/constants/barriers.ts index 353ef635b148..f25db4339cab 100644 --- a/packages/shared/src/utils/constants/barriers.ts +++ b/packages/shared/src/utils/constants/barriers.ts @@ -1,3 +1,4 @@ +//Configures which trade types have barrier rendered when selected export const CONTRACT_SHADES = { CALL: 'ABOVE', PUT: 'BELOW', @@ -13,6 +14,8 @@ export const CONTRACT_SHADES = { ASIAND: 'BELOW', MULTUP: 'ABOVE', MULTDOWN: 'BELOW', + VANILLALONGCALL: 'NONE_SINGLE', + VANILLALONGPUT: 'NONE_SINGLE', }; // Default non-shade according to number of barriers diff --git a/packages/shared/src/utils/constants/contract.tsx b/packages/shared/src/utils/constants/contract.tsx index bc2ea3b74855..a8523ad03afe 100644 --- a/packages/shared/src/utils/constants/contract.tsx +++ b/packages/shared/src/utils/constants/contract.tsx @@ -18,7 +18,7 @@ type TContractTypesConfig = { basis: string[]; components: string[]; barrier_count?: number; - config?: { hide_duration: boolean }; + config?: { hide_duration?: boolean; should_override?: boolean }; }; type TGetContractTypesConfig = (symbol: string) => Record; @@ -127,8 +127,17 @@ export const getContractTypesConfig: TGetContractTypesConfig = symbol => ({ ], config: { hide_duration: true }, }, // hide Duration for Multiplier contracts for now + vanilla: { + title: localize('Call/Put'), + trade_types: ['VANILLALONGCALL', 'VANILLALONGPUT'], + basis: ['stake'], + components: ['duration', 'strike', 'amount', 'vanilla_trade_type'], + barrier_count: 1, + config: { should_override: true }, + }, }); +// Config for rendering trade options export const getContractCategoriesConfig = () => ({ Multipliers: { name: localize('Multipliers'), categories: ['multiplier'] }, 'Ups & Downs': { @@ -139,6 +148,7 @@ export const getContractCategoriesConfig = () => ({ 'Ins & Outs': { name: localize('Ins & Outs'), categories: ['end', 'stay'] }, 'Look Backs': { name: localize('Look Backs'), categories: ['lb_high_low', 'lb_put', 'lb_call'] }, Digits: { name: localize('Digits'), categories: ['match_diff', 'even_odd', 'over_under'] }, + Vanillas: { name: localize('Vanillas'), categories: ['vanilla'] }, }); export const unsupported_contract_types_list = [ @@ -157,35 +167,38 @@ export const unsupported_contract_types_list = [ export const getCardLabels = () => ({ APPLY: localize('Apply'), - STAKE: localize('Stake:'), - CLOSE: localize('Close'), + BUY_PRICE: localize('Buy price:'), CANCEL: localize('Cancel'), + CLOSE: localize('Close'), + CONTRACT_VALUE: localize('Contract value:'), CURRENT_STAKE: localize('Current stake:'), + DAY: localize('day'), + DAYS: localize('days'), DEAL_CANCEL_FEE: localize('Deal cancel. fee:'), - TAKE_PROFIT: localize('Take profit:'), - BUY_PRICE: localize('Buy price:'), - STOP_LOSS: localize('Stop loss:'), - TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), - PROFIT_LOSS: localize('Profit/Loss:'), - POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), + DECREMENT_VALUE: localize('Decrement value'), + DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), + ENTRY_SPOT: localize('Entry spot:'), + INCREMENT_VALUE: localize('Increment value'), INDICATIVE_PRICE: localize('Indicative price:'), + LOST: localize('Lost'), + NOT_AVAILABLE: localize('N/A'), PAYOUT: localize('Sell price:'), - PURCHASE_PRICE: localize('Buy price:'), POTENTIAL_PAYOUT: localize('Payout limit:'), - TICK: localize('Tick '), - WON: localize('Won'), - LOST: localize('Lost'), - DAYS: localize('days'), - DAY: localize('day'), + POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), + PROFIT_LOSS: localize('Profit/Loss:'), + PURCHASE_PRICE: localize('Buy price:'), + RESALE_NOT_OFFERED: localize('Resale not offered'), SELL: localize('Sell'), - INCREMENT_VALUE: localize('Increment value'), - DECREMENT_VALUE: localize('Decrement value'), + STAKE: localize('Stake:'), + STOP_LOSS: localize('Stop loss:'), + STRIKE: localize('Strike:'), TAKE_PROFIT_LOSS_NOT_AVAILABLE: localize( 'Take profit and/or stop loss are not available while deal cancellation is active.' ), - DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), - RESALE_NOT_OFFERED: localize('Resale not offered'), - NOT_AVAILABLE: localize('N/A'), + TAKE_PROFIT: localize('Take profit:'), + TICK: localize('Tick '), + TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), + WON: localize('Won'), }); export const getMarketNamesMap = () => ({ @@ -414,6 +427,14 @@ export const getSupportedContracts = (is_high_low?: boolean) => ({ name: , position: 'bottom', }, + VANILLALONGCALL: { + name: , + position: 'top', + }, + VANILLALONGPUT: { + name: , + position: 'bottom', + }, }); export const getContractConfig = (is_high_low?: boolean) => ({ diff --git a/packages/shared/src/utils/contract/contract.ts b/packages/shared/src/utils/contract/contract.ts index cfd178f1ab05..58b89fda29da 100644 --- a/packages/shared/src/utils/contract/contract.ts +++ b/packages/shared/src/utils/contract/contract.ts @@ -50,6 +50,8 @@ export const hasContractEntered = (contract_info: TContractInfo) => !!contract_i export const isMultiplierContract = (contract_type: string) => /MULT/i.test(contract_type); +export const isVanillaContract = (contract_type: string) => /VANILLA/i.test(contract_type); + export const isCryptoContract = (underlying: string) => /^cry/.test(underlying); type TGetCurrentTick = TContractInfo & { diff --git a/packages/shared/src/utils/shortcode/shortcode.ts b/packages/shared/src/utils/shortcode/shortcode.ts index 7e3623c0487b..c46330af69d5 100644 --- a/packages/shared/src/utils/shortcode/shortcode.ts +++ b/packages/shared/src/utils/shortcode/shortcode.ts @@ -18,11 +18,12 @@ export const extractInfoFromShortcode = (shortcode: string) => { }; const is_multipliers = /^MULT/i.test(shortcode); + // First group of regex pattern captures the trade category, second group captures the market's underlying const pattern = is_multipliers ? multipliers_regex : options_regex; const extracted = pattern.exec(shortcode); if (extracted !== null) { - info_from_shortcode.category = extracted[1].toLowerCase(); + info_from_shortcode.category = extracted[1].charAt(0).toUpperCase() + extracted[1].slice(1).toLowerCase(); info_from_shortcode.underlying = extracted[2]; if (is_multipliers) { diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx index 74112d4115d8..c3e2ce11975a 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx @@ -4,8 +4,10 @@ import { Money, Icon, ThemedScrollbars } from '@deriv/components'; import { localize } from '@deriv/translations'; import { epochToMoment, + formatMoney, toGMTFormat, getCancellationPrice, + getCurrencyDisplayCode, isMobile, isMultiplierContract, isUserSold, @@ -21,7 +23,7 @@ import { import ContractAuditItem from './contract-audit-item.jsx'; import { isCancellationExpired } from 'Stores/Modules/Trading/Helpers/logic'; -const ContractDetails = ({ contract_end_time, contract_info, duration, duration_unit, exit_spot }) => { +const ContractDetails = ({ contract_end_time, contract_info, duration, duration_unit, exit_spot, is_vanilla }) => { const { commission, contract_type, @@ -33,6 +35,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ date_start, tick_count, transaction_ids: { buy, sell } = {}, + number_of_contracts, } = contract_info; const is_profit = profit >= 0; @@ -86,18 +89,39 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ : `${duration} ${duration_unit}` } /> - - ) : ( - - ) - } - label={getBarrierLabel(contract_info)} - value={getBarrierValue(contract_info) || ' - '} - /> + {is_vanilla ? ( + + } + label={getBarrierLabel(contract_info)} + value={getBarrierValue(contract_info) || ' - '} + /> + } + label={localize('Payout per point')} + value={ + `${formatMoney(currency, number_of_contracts, true)} ${getCurrencyDisplayCode( + currency + )}` || ' - ' + } + /> + + ) : ( + + ) : ( + + ) + } + label={getBarrierLabel(contract_info)} + value={getBarrierValue(contract_info) || ' - '} + /> + )} )} - {(is_sold || is_multiplier) && ( + {(is_sold || is_multiplier || is_vanilla) && ( )} {contract_card} @@ -163,6 +166,7 @@ ContractDrawerCard.propTypes = { is_market_closed: PropTypes.bool, is_mobile: PropTypes.bool, is_multiplier: PropTypes.bool, + is_vanilla: PropTypes.bool, is_sell_requested: PropTypes.bool, onClickCancel: PropTypes.func, onClickSell: PropTypes.func, diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx index 76eb7d56287e..ee714cb1850a 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx @@ -27,6 +27,7 @@ const ContractDrawer = ({ is_dark_theme, is_market_closed, is_multiplier, + is_vanilla, onClickCancel, onClickSell, server_time, @@ -52,8 +53,9 @@ const ContractDrawer = ({ duration={getDurationTime(contract_info)} duration_unit={getDurationUnitText(getDurationPeriod(contract_info))} exit_spot={exit_spot} - has_result={!!is_sold || is_multiplier} + has_result={!!is_sold || is_multiplier || is_vanilla} toggleHistoryTab={toggleHistoryTab} + is_vanilla={is_vanilla} /> ); @@ -66,6 +68,7 @@ const ContractDrawer = ({ is_mobile={is_mobile} is_market_closed={is_market_closed} is_multiplier={is_multiplier} + is_vanilla={is_vanilla} is_sell_requested={is_sell_requested} is_collapsed={should_show_contract_audit} onClickCancel={onClickCancel} @@ -99,8 +102,9 @@ const ContractDrawer = ({ duration={getDurationTime(contract_info)} duration_unit={getDurationUnitText(getDurationPeriod(contract_info))} exit_spot={exit_spot} - has_result={!!is_sold || is_multiplier} + has_result={!!is_sold || is_multiplier || is_vanilla} toggleHistoryTab={toggleHistoryTab} + is_vanilla={is_vanilla} /> ); @@ -118,7 +122,7 @@ const ContractDrawer = ({ id='dt_contract_drawer' className={classNames('contract-drawer', { 'contract-drawer--with-collapsible-btn': - !!getEndTime(contract_info) || (is_multiplier && isMobile()), + !!getEndTime(contract_info) || ((is_multiplier || is_vanilla) && isMobile()), 'contract-drawer--is-multiplier': is_multiplier && isMobile(), 'contract-drawer--is-multiplier-sold': is_multiplier && isMobile() && getEndTime(contract_info), })} @@ -174,6 +178,7 @@ ContractDrawer.propTypes = { is_market_closed: PropTypes.bool, is_mobile: PropTypes.bool, is_multiplier: PropTypes.bool, + is_vanilla: PropTypes.bool, is_history_tab_active: PropTypes.bool, is_sell_requested: PropTypes.bool, onClickCancel: PropTypes.func, diff --git a/packages/trader/src/App/Components/Elements/PositionsDrawer/helpers/positions-helper.js b/packages/trader/src/App/Components/Elements/PositionsDrawer/helpers/positions-helper.js index b01b4ce5d0ff..ddebcdaadec8 100644 --- a/packages/trader/src/App/Components/Elements/PositionsDrawer/helpers/positions-helper.js +++ b/packages/trader/src/App/Components/Elements/PositionsDrawer/helpers/positions-helper.js @@ -1,5 +1,5 @@ import { localize } from '@deriv/translations'; -import { isHighLow, getContractTypesConfig, isCallPut } from '@deriv/shared'; +import { isHighLow, getContractTypesConfig, isCallPut, isVanillaContract } from '@deriv/shared'; export const addCommaToNumber = (num, decimal_places) => { if (!num || isNaN(num)) { @@ -14,6 +14,9 @@ export const getBarrierLabel = contract_info => { if (isDigitType(contract_info.contract_type)) { return localize('Target'); } + if (isVanillaContract(contract_info.contract_type)) { + return localize('Strike'); + } return localize('Barrier'); }; @@ -38,10 +41,11 @@ const digitTypeMap = contract_info => ({ export const filterByContractType = ({ contract_type, shortcode }, trade_contract_type) => { const is_call_put = isCallPut(trade_contract_type); const is_high_low = isHighLow({ shortcode }); + const is_vanilla = isVanillaContract(contract_type); const trade_types = is_call_put ? ['CALL', 'CALLE', 'PUT', 'PUTE'] : getContractTypesConfig()[trade_contract_type]?.trade_types; const match = trade_types?.includes(contract_type); if (trade_contract_type === 'high_low') return is_high_low; - return match && !is_high_low; + return match && (is_vanilla || !is_high_low); }; diff --git a/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx b/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx index c8c6c789b435..e5ced2399b61 100644 --- a/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx +++ b/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx @@ -1,6 +1,7 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; +import { NavLink } from 'react-router-dom'; import { CSSTransition } from 'react-transition-group'; import { ContractCard, CurrencyBadge, Icon, Money, ProgressSliderMobile, Text } from '@deriv/components'; import { @@ -13,6 +14,8 @@ import { isOpen, getSymbolDisplayName, getEndTime, + getTotalProfit, + isVanillaContract, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { BinaryLink } from 'App/Components/Routes'; @@ -60,10 +63,12 @@ const PositionsModalCard = ({
); const is_multiplier = isMultiplierContract(contract_info.contract_type); + const is_vanilla = isVanillaContract(contract_info.contract_type); const is_crypto = isCryptoContract(contract_info.underlying); const has_progress_slider = !is_multiplier || (is_crypto && is_multiplier); const has_ended = !!getEndTime(contract_info); const fallback_result = profit_loss >= 0 ? 'won' : 'lost'; + const total_profit = getTotalProfit(contract_info); const should_show_sell = hasContractEntered(contract_info) && isOpen(contract_info); const display_name = getSymbolDisplayName(active_symbols, getMarketInformation(contract_info.shortcode).underlying); @@ -201,6 +206,121 @@ const PositionsModalCard = ({ ); + const contract_vanilla_el = ( + + 0 && !result, + 'dc-contract-card--red': !is_multiplier && profit_loss < 0 && !result, + })} + to={{ + pathname: `/contract/${contract_info.contract_id}`, + }} + > + + + +
+
+
+ + {localize('Buy price:')} + + + + +
+
+ + {localize('Contract value:')} + + + + +
+
+ +
+
+ + {localize('Entry spot:')} + + + + +
+
+ + {localize('Strike:')} + + + + +
+
+ + {result || !!contract_info.is_sold ? ( + + ) : ( + + )} +
+
+
{getCardLabels().TOTAL_PROFIT_LOSS}
+
0, + })} + > + +
+ {status === 'profit' && } + {status === 'loss' && } +
+
+
+ +
+ ); + const card_multiplier_header = ( ); - const contract_el = is_multiplier ? contract_multiplier_el : contract_options_el; + const contract_el = is_multiplier ? contract_multiplier_el : is_vanilla ? contract_vanilla_el : contract_options_el; return (
diff --git a/packages/trader/src/Assets/SvgComponents/trade_explanations/img-vanilla.svg b/packages/trader/src/Assets/SvgComponents/trade_explanations/img-vanilla.svg new file mode 100644 index 000000000000..6c3e57a84eea --- /dev/null +++ b/packages/trader/src/Assets/SvgComponents/trade_explanations/img-vanilla.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/trader/src/Assets/Trading/Categories/icon-trade-categories.jsx b/packages/trader/src/Assets/Trading/Categories/icon-trade-categories.jsx index 184152291576..39c7c570f428 100644 --- a/packages/trader/src/Assets/Trading/Categories/icon-trade-categories.jsx +++ b/packages/trader/src/Assets/Trading/Categories/icon-trade-categories.jsx @@ -208,6 +208,18 @@ const IconTradeCategory = ({ category, className }) => { ); break; + case 'vanilla': + IconCategory = ( + +
+ +
+
+ +
+
+ ); + break; default: IconCategory = (
diff --git a/packages/trader/src/Assets/Trading/Categories/trade-categories-gif.jsx b/packages/trader/src/Assets/Trading/Categories/trade-categories-gif.jsx index 1fb4cbaed872..28196c62173f 100644 --- a/packages/trader/src/Assets/Trading/Categories/trade-categories-gif.jsx +++ b/packages/trader/src/Assets/Trading/Categories/trade-categories-gif.jsx @@ -17,6 +17,7 @@ import ImageSpread from 'Assets/SvgComponents/trade_explanations/img-spread.svg' import ImageStaysInGoesOut from 'Assets/SvgComponents/trade_explanations/img-stays-in-goes-out.svg'; import ImageTickHighLow from 'Assets/SvgComponents/trade_explanations/img-tick-high-low.svg'; import ImageTouch from 'Assets/SvgComponents/trade_explanations/img-touch.svg'; +import ImageVanilla from 'Assets/SvgComponents/trade_explanations/img-vanilla.svg'; // TODO: Replace static image svgs with themed GIFs or animated SVGs const TradeCategoriesGIF = ({ category }) => { @@ -57,6 +58,8 @@ const TradeCategoriesGIF = ({ category }) => { return ; case 'touch': return ; + case 'vanilla': + return ; default: return null; } diff --git a/packages/trader/src/Assets/Trading/Categories/trade-categories.jsx b/packages/trader/src/Assets/Trading/Categories/trade-categories.jsx index c875ab619960..6b213aaa143a 100644 --- a/packages/trader/src/Assets/Trading/Categories/trade-categories.jsx +++ b/packages/trader/src/Assets/Trading/Categories/trade-categories.jsx @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { localize } from '@deriv/translations'; +import { Text } from '@deriv/components'; +import { localize, Localize } from '@deriv/translations'; // Templates are from Binary 1.0, it should be checked if they need change or not and add all of trade types @@ -11,223 +12,225 @@ const TradeCategories = ({ category }) => { case 'rise_fall': TradeTypeTemplate = ( -

+ {localize( 'If you select "Rise", you win the payout if the exit spot is strictly higher than the entry spot.' )} -

-

+ + {localize( 'If you select "Fall", you win the payout if the exit spot is strictly lower than the entry spot.' )} -

-

+ + {localize( 'If you select "Allow equals", you win the payout if exit spot is higher than or equal to entry spot for "Rise". Similarly, you win the payout if exit spot is lower than or equal to entry spot for "Fall".' )} -

+
); break; case 'rise_fall_equal': TradeTypeTemplate = ( -

+ {localize( 'If you select "Rise", you win the payout if the exit spot is strictly higher than the entry spot.' )} -

-

+ + {localize( 'If you select "Fall", you win the payout if the exit spot is strictly lower than the entry spot.' )} -

-

+ + {localize( 'If you select "Allow equals", you win the payout if exit spot is higher than or equal to entry spot for "Rise". Similarly, you win the payout if exit spot is lower than or equal to entry spot for "Fall".' )} -

+
); break; case 'high_low': TradeTypeTemplate = ( -

+ {localize( 'If you select "Higher", you win the payout if the exit spot is strictly higher than the barrier.' )} -

-

+ + {localize( 'If you select "Lower", you win the payout if the exit spot is strictly lower than the barrier.' )} -

-

{localize("If the exit spot is equal to the barrier, you don't win the payout.")}

+ + + {localize("If the exit spot is equal to the barrier, you don't win the payout.")} +
); break; case 'end': TradeTypeTemplate = ( -

+ {localize( 'If you select "Ends Between", you win the payout if the exit spot is strictly higher than the Low barrier AND strictly lower than the High barrier.' )} -

-

+ + {localize( 'If you select "Ends Outside", you win the payout if the exit spot is EITHER strictly higher than the High barrier, OR strictly lower than the Low barrier.' )} -

-

+ + {localize( "If the exit spot is equal to either the Low barrier or the High barrier, you don't win the payout." )} -

+
); break; case 'stay': TradeTypeTemplate = ( -

+ {localize( 'If you select "Stays Between", you win the payout if the market stays between (does not touch) either the High barrier or the Low barrier at any time during the contract period' )} -

-

+ + {localize( 'If you select "Goes Outside", you win the payout if the market touches either the High barrier or the Low barrier at any time during the contract period.' )} -

+
); break; case 'match_diff': TradeTypeTemplate = ( -

+ {localize( 'If you select "Matches", you will win the payout if the last digit of the last tick is the same as your prediction.' )} -

-

+ + {localize( 'If you select "Differs", you will win the payout if the last digit of the last tick is not the same as your prediction.' )} -

+
); break; case 'even_odd': TradeTypeTemplate = ( -

+ {localize( 'If you select "Even", you will win the payout if the last digit of the last tick is an even number (i.e., 2, 4, 6, 8, or 0).' )} -

-

+ + {localize( 'If you select "Odd", you will win the payout if the last digit of the last tick is an odd number (i.e., 1, 3, 5, 7, or 9).' )} -

+
); break; case 'over_under': TradeTypeTemplate = ( -

+ {localize( 'If you select "Over", you will win the payout if the last digit of the last tick is greater than your prediction.' )} -

-

+ + {localize( 'If you select "Under", you will win the payout if the last digit of the last tick is less than your prediction.' )} -

+
); break; case 'touch': TradeTypeTemplate = ( -

+ {localize( 'If you select "Touch", you win the payout if the market touches the barrier at any time during the contract period.' )} -

-

+ + {localize( 'If you select "No Touch", you win the payout if the market never touches the barrier at any time during the contract period.' )} -

+
); break; case 'asian': TradeTypeTemplate = ( -

+ {localize( 'Asian options settle by comparing the last tick with the average spot over the period.' )} -

-

+ + {localize( 'If you select "Asian Rise", you will win the payout if the last tick is higher than the average of the ticks.' )} -

-

+ + {localize( 'If you select "Asian Fall", you will win the payout if the last tick is lower than the average of the ticks.' )} -

-

+ + {localize( "If the last tick is equal to the average of the ticks, you don't win the payout." )} -

+
); break; case 'run_high_low': TradeTypeTemplate = ( -

+ {localize( 'If you select "Only Ups", you win the payout if consecutive ticks rise successively after the entry spot. No payout if any tick falls or is equal to any of the previous ticks.' )} -

-

+ + {localize( 'If you select "Only Downs", you win the payout if consecutive ticks fall successively after the entry spot. No payout if any tick rises or is equal to any of the previous ticks.' )} -

+
); break; case 'reset': TradeTypeTemplate = ( -

+ {localize( 'If you select "Reset-Up”, you win the payout if the exit spot is strictly higher than either the entry spot or the spot at reset time.' )} -

-

+ + {localize( 'If you select "Reset-Down”, you win the payout if the exit spot is strictly lower than either the entry spot or the spot at reset time.' )} -

-

+ + {localize( "If the exit spot is equal to the barrier or the new barrier (if a reset occurs), you don't win the payout." )} -

+
); break; @@ -235,155 +238,155 @@ const TradeCategories = ({ category }) => { TradeTypeTemplate = (

{localize('Spread Up')}

-

+ {localize( 'Win maximum payout if the exit spot is higher than or equal to the upper barrier.' )} -

-

+ + {localize( 'Win up to maximum payout if exit spot is between lower and upper barrier, in proportion to the difference between exit spot and lower barrier.' )} -

-

{localize('No payout if exit spot is below or equal to the lower barrier.')}

+ + {localize('No payout if exit spot is below or equal to the lower barrier.')}

{localize('Spread Down')}

-

+ {localize( 'Win maximum payout if the exit spot is lower than or equal to the lower barrier.' )} -

-

+ + {localize( 'Win up to maximum payout if exit spot is between lower and upper barrier, in proportion to the difference between upper barrier and exit spot.' )} -

-

{localize('No payout if exit spot is above or equal to the upper barrier.')}

+ + {localize('No payout if exit spot is above or equal to the upper barrier.')}
); break; case 'tick_high_low': TradeTypeTemplate = ( -

+ {localize( 'If you select "High Tick", you win the payout if the selected tick is the highest among the next five ticks.' )} -

-

+ + {localize( 'If you select "Low Tick", you win the payout if the selected tick is the lowest among the next five ticks.' )} -

+
); break; case 'lb_high_low': TradeTypeTemplate = ( -

+ {localize( 'By purchasing the "High-to-Low" contract, you\'ll win the multiplier times the difference between the high and low over the duration of the contract.' )} -

-

+ + {localize( 'The high is the highest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The low is the lowest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The close is the latest tick at or before the end time. If you selected a specific end time, the end time is the selected time.' )} -

+
); break; case 'lb_put': TradeTypeTemplate = ( -

+ {localize( 'By purchasing the "High-to-Close" contract, you\'ll win the multiplier times the difference between the high and close over the duration of the contract.' )} -

-

+ + {localize( 'The high is the highest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The low is the lowest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The close is the latest tick at or before the end time. If you selected a specific end time, the end time is the selected time.' )} -

+
); break; case 'lb_call': TradeTypeTemplate = ( -

+ {localize( 'By purchasing the "Close-to-Low" contract, you\'ll win the multiplier times the difference between the close and low over the duration of the contract.' )} -

-

+ + {localize( 'The high is the highest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The low is the lowest point ever reached by the market during the contract period.' )} -

-

+ + {localize( 'The close is the latest tick at or before the end time. If you selected a specific end time, the end time is the selected time.' )} -

+
); break; case 'multiplier': TradeTypeTemplate = ( -

+ {localize( 'Predict the market direction and select either “Up” or “Down” to open a position. We will charge a commission when you open a position.' )} -

-

+ + {localize( 'If you select “Up”, you will earn a profit by closing your position when the market price is higher than the entry spot.' )} -

-

+ + {localize( 'If you select “Down”, you will earn a profit by closing your position when the market price is lower than the entry spot.' )} -

-

+ + {localize( 'Your profit is the percentage change in market price times your stake and the multiplier of your choice.' )} -

-

+ + {localize( 'The Stop-out level on the chart indicates the price at which your potential loss equals your entire stake. When the market price reaches this level, your position will be closed automatically. This ensures that your loss does not exceed the amount you paid to purchase the contract.' )} -

-

{localize('These are optional parameters for each position that you open:')}

+ + {localize('These are optional parameters for each position that you open:')}
  • {localize( @@ -413,17 +416,45 @@ const TradeCategories = ({ category }) => {
-

+ {localize( 'The entry spot is the market price when your contract is processed by our servers.' )} -

-

{localize('The exit spot is the market price when the contract is closed.')}

+ + {localize('The exit spot is the market price when the contract is closed.')} +
+ ); + break; + case 'vanilla': + TradeTypeTemplate = ( + + + {localize( + 'Predict the market direction and movement size, and select either “Call” or “Put” to open a position.' + )} + + + ,
]} + /> +
+ + ,
]} + /> +
+ + {localize( + 'You can determine the expiry of your contract by setting the duration or end time.' + )} +
); break; default: - TradeTypeTemplate =

{localize('Description not found.')}

; + TradeTypeTemplate = {localize('Description not found.')}; break; } } diff --git a/packages/trader/src/Constants/contract.js b/packages/trader/src/Constants/contract.js index f35b373317f2..86a92177b983 100644 --- a/packages/trader/src/Constants/contract.js +++ b/packages/trader/src/Constants/contract.js @@ -3,35 +3,38 @@ import { localize, Localize } from '@deriv/translations'; export const getCardLabels = () => ({ APPLY: localize('Apply'), - STAKE: localize('Stake:'), - CLOSE: localize('Close'), + BUY_PRICE: localize('Buy price:'), CANCEL: localize('Cancel'), + CLOSE: localize('Close'), + CONTRACT_VALUE: localize('Contract value:'), CURRENT_STAKE: localize('Current stake:'), + DAY: localize('day'), + DAYS: localize('days'), DEAL_CANCEL_FEE: localize('Deal cancel. fee:'), - TAKE_PROFIT: localize('Take profit:'), - BUY_PRICE: localize('Buy price:'), - STOP_LOSS: localize('Stop loss:'), - TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), - PROFIT_LOSS: localize('Profit/Loss:'), - POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), + DECREMENT_VALUE: localize('Decrement value'), + DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), + ENTRY_SPOT: localize('Entry spot:'), + INCREMENT_VALUE: localize('Increment value'), INDICATIVE_PRICE: localize('Indicative price:'), + LOST: localize('Lost'), + NOT_AVAILABLE: localize('N/A'), PAYOUT: localize('Sell price:'), - PURCHASE_PRICE: localize('Buy price:'), POTENTIAL_PAYOUT: localize('Payout limit:'), - TICK: localize('Tick '), - WON: localize('Won'), - LOST: localize('Lost'), - DAYS: localize('days'), - DAY: localize('day'), + POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), + PROFIT_LOSS: localize('Profit/Loss:'), + PURCHASE_PRICE: localize('Buy price:'), + RESALE_NOT_OFFERED: localize('Resale not offered'), SELL: localize('Sell'), - INCREMENT_VALUE: localize('Increment value'), - DECREMENT_VALUE: localize('Decrement value'), + STAKE: localize('Stake:'), + STOP_LOSS: localize('Stop loss:'), + STRIKE: localize('Strike:'), TAKE_PROFIT_LOSS_NOT_AVAILABLE: localize( 'Take profit and/or stop loss are not available while deal cancellation is active.' ), - DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), - RESALE_NOT_OFFERED: localize('Resale not offered'), - NOT_AVAILABLE: localize('N/A'), + TAKE_PROFIT: localize('Take profit:'), + TICK: localize('Tick '), + TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), + WON: localize('Won'), }); export const getMarketNamesMap = () => ({ @@ -202,6 +205,7 @@ export const getUnsupportedContracts = () => ({ }, }); +// Config to display trade button and their position export const getSupportedContracts = is_high_low => ({ CALL: { name: is_high_low ? : , @@ -259,6 +263,14 @@ export const getSupportedContracts = is_high_low => ({ name: , position: 'bottom', }, + VANILLALONGCALL: { + name: , + position: 'top', + }, + VANILLALONGPUT: { + name: , + position: 'bottom', + }, }); export const getContractConfig = is_high_low => ({ diff --git a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx index edc3997c1874..8604cbe7b1e5 100644 --- a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx +++ b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx @@ -2,13 +2,29 @@ import { observer } from 'mobx-react'; import PropTypes from 'prop-types'; import React from 'react'; import { Icon, Text } from '@deriv/components'; +import { localize, Localize } from '@deriv/translations'; +import { isVanillaContract } from '@deriv/shared'; + +const generateMessageForVanillaTrade = contract_info => ( + ]} + values={{ + contract_type: contract_info.contract_type === 'VANILLALONGCALL' ? localize('Call') : localize('Put'), + index_name: contract_info.display_name, + strike_status: contract_info.contract_type === 'VANILLALONGCALL' ? localize('higher') : localize('lower'), + market_status: contract_info.contract_type === 'VANILLALONGCALL' ? localize('lower') : localize('higher'), + }} + /> +); const InfoBoxLongcode = ({ contract_info }) => { + const is_vanilla = isVanillaContract(contract_info.contract_type); return (
- {contract_info.longcode} + {is_vanilla ? generateMessageForVanillaTrade(contract_info) : contract_info.longcode}
); @@ -17,6 +33,7 @@ const InfoBoxLongcode = ({ contract_info }) => { InfoBoxLongcode.propTypes = { contract_info: PropTypes.object, longcode: PropTypes.string, + is_vanilla: PropTypes.bool, }; export default observer(InfoBoxLongcode); diff --git a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx index e943dd13d737..e0c3607add99 100644 --- a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx +++ b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx @@ -5,13 +5,12 @@ import InfoBoxLongcode from './info-box-longcode.jsx'; import ContractError from '../contract-error.jsx'; const InfoBox = ({ contract_info, error_message, removeError }) => { - const Contents = InfoBoxLongcode; const is_ready = !!contract_info.longcode; return ( {!!contract_info.contract_type && (
- +
)} diff --git a/packages/trader/src/Modules/Contract/Containers/contract-replay-widget.jsx b/packages/trader/src/Modules/Contract/Containers/contract-replay-widget.jsx index 989c525ce7cf..676f2046d7b6 100644 --- a/packages/trader/src/Modules/Contract/Containers/contract-replay-widget.jsx +++ b/packages/trader/src/Modules/Contract/Containers/contract-replay-widget.jsx @@ -21,12 +21,18 @@ export const DigitsWidget = connect(({ contract_replay }) => ({ /> )); -export const InfoBoxWidget = connect(({ contract_replay }) => ({ +export const InfoBoxWidget = connect(({ contract_replay, modules }) => ({ contract_info: contract_replay.contract_store.contract_info, error_message: contract_replay.error_message, removeError: contract_replay.removeErrorMessage, -}))(({ contract_info, error_message, removeError }) => ( - + is_vanilla: modules.trade.is_vanilla, +}))(({ contract_info, error_message, removeError, is_vanilla }) => ( + )); // Chart widgets passed into SmartCharts diff --git a/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx b/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx index 587bed6d7d10..735eb17342c6 100644 --- a/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx +++ b/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx @@ -11,14 +11,15 @@ import { FadeWrapper, } from '@deriv/components'; import { + getDurationPeriod, + getDurationUnitText, + getPlatformRedirect, isDesktop, + isEmptyObject, isMobile, isMultiplierContract, - isEmptyObject, - getPlatformRedirect, + isVanillaContract, urlFor, - getDurationPeriod, - getDurationUnitText, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; @@ -74,6 +75,7 @@ const ContractReplay = ({ if (!contract_info.underlying) return null; const is_multiplier = isMultiplierContract(contract_info.contract_type); + const is_vanilla = isVanillaContract(contract_info.contract_type); const contract_drawer_el = ( { return Marker; }; -function get_color({ status, profit, is_dark_theme }) { - const colors = is_dark_theme - ? { - open: '#377cfc', - won: '#00a79e', - lost: '#cc2e3d', - sold: '#ffad3a', - fg: '#ffffff', - bg: '#0e0e0e', - } - : { - open: '#377cfc', - won: '#4bb4b3', - lost: '#ec3f3f', - sold: '#ffad3a', - fg: '#333333', - bg: '#ffffff', - }; +const dark_theme = { + open: '#377cfc', + won: '#00a79e', + lost: '#cc2e3d', + sold: '#ffad3a', + fg: '#ffffff', + bg: '#0e0e0e', +}; + +const light_theme = { + open: '#377cfc', + won: '#4bb4b3', + lost: '#ec3f3f', + sold: '#ffad3a', + fg: '#333333', + bg: '#ffffff', +}; + +function get_color({ status, profit, is_dark_theme, is_vanilla }) { + const colors = is_dark_theme ? dark_theme : light_theme; let color = colors[status || 'open']; + if (is_vanilla) { + if (status === 'open') return colors.open; + return colors[profit > 0 ? 'won' : 'lost']; + } if (status === 'open' && profit) { color = colors[profit > 0 ? 'won' : 'lost']; } @@ -141,10 +147,11 @@ const render_label = ({ ctx, text, tick: { zoom, left, top } }) => { }); }; -const shadowed_text = ({ ctx, is_dark_theme, text, left, top, scale }) => { +const shadowed_text = ({ ctx, color, is_dark_theme, text, left, top, scale }) => { ctx.textAlign = 'center'; const size = Math.floor(scale * 12); ctx.font = `bold ${size}px BinarySymbols, Roboto`; + if (color) ctx.fillStyle = color; if (!is_firefox) { ctx.shadowColor = is_dark_theme ? 'rgba(16,19,31,1)' : 'rgba(255,255,255,1)'; ctx.shadowBlur = 12; @@ -338,7 +345,7 @@ const NonTickContract = RawMarkerMaker( granularity, currency, contract_info: { - // contract_type, + contract_type, // exit_tick_time, // is_expired, is_sold, @@ -359,7 +366,7 @@ const NonTickContract = RawMarkerMaker( } } - const color = get_color({ status, profit, is_dark_theme }); + const color = get_color({ status, profit, is_dark_theme, is_vanilla: isVanillaContract(contract_type) }); ctx.save(); ctx.strokeStyle = color; @@ -455,6 +462,7 @@ const NonTickContract = RawMarkerMaker( const text = `${sign}${symbol}${Math.abs(profit).toFixed(decimal_places)}`; shadowed_text({ ctx, + color: get_color({ status: 'open', profit }), scale, text, is_dark_theme, diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx index 82291e5b28cc..204f5ca18157 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx @@ -33,6 +33,7 @@ const PurchaseButton = ({ is_high_low, is_loading, is_multiplier, + is_vanilla, is_proposal_empty, purchased_states_arr, setPurchaseState, @@ -47,6 +48,22 @@ const PurchaseButton = ({ const { has_increased } = info; const is_button_disabled = (is_disabled && !is_loading) || is_proposal_empty; + let button_value; + + if (is_multiplier) { + button_value = ( + + + + ); + } else if (!is_vanilla) { + button_value = ( + + {!(is_loading || is_disabled) ? info.returns : ''} + + ); + } + return ( ); diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx index 5a2d8124d8cf..58bc4d09b8c1 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx @@ -19,6 +19,7 @@ const PurchaseFieldset = ({ is_loading, is_market_closed, is_multiplier, + is_vanilla, is_proposal_empty, is_proposal_error, purchased_states_arr, @@ -45,6 +46,7 @@ const PurchaseFieldset = ({ is_high_low={is_high_low} is_loading={is_loading} is_multiplier={is_multiplier} + is_vanilla={is_vanilla} is_proposal_empty={is_proposal_empty} purchased_states_arr={purchased_states_arr} onClickPurchase={onClickPurchase} @@ -81,6 +83,7 @@ const PurchaseFieldset = ({ has_increased={info.has_increased} is_loading={is_loading} is_multiplier={is_multiplier} + is_vanilla={is_vanilla} should_fade={should_fade} type={type} /> diff --git a/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-list.jsx b/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-list.jsx index 9a9ceb07d12a..a2afaa7b0f0f 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-list.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-list.jsx @@ -1,6 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import Item from './contract-type-item.jsx'; +import { localize } from '@deriv/translations'; +import { Text } from '@deriv/components'; +import classNames from 'classnames'; const List = ({ handleInfoClick, handleSelect, list, name, value }) => list.map((contract_category, key) => { @@ -16,8 +19,15 @@ const List = ({ handleInfoClick, handleSelect, list, name, value }) => return (
-
- {contract_category.label} +
+ + {contract_category.label} + + {contract_category.key === 'Vanillas' && ( + + {localize('NEW!')} + + )}
{ const contract_type_category_icon = getContractTypeCategoryIcons(); - const multipliers_category = list.filter( + + // Order the list based on categories provided in order_arr + const order_arr = ['Vanillas', 'Ups & Downs', 'Highs & Lows', 'Digits']; + const ordered_list = list.sort((a, b) => order_arr.indexOf(a.key) - order_arr.indexOf(b.key)); + + const multipliers_category = ordered_list.filter( contract_category => contract_category.label === localize('Multipliers') ); - const options_category = list.filter(contract_category => contract_category.label !== localize('Multipliers')); + const options_category = ordered_list.filter( + contract_category => contract_category.label !== localize('Multipliers') + ); const categories = []; if (multipliers_category.length > 0 && options_category.length > 0) { categories.push({ label: localize('All'), - contract_categories: [...list], + contract_categories: [...ordered_list], key: 'All', }); } diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx index 2d3fab2105a9..56b05daae8cf 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx @@ -2,17 +2,19 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { Icon, DesktopWrapper, Money, MobileWrapper, Popover, Text } from '@deriv/components'; -import { localize } from '@deriv/translations'; -import { getCurrencyDisplayCode, getLocalizedBasis } from '@deriv/shared'; +import { localize, Localize } from '@deriv/translations'; +import { getCurrencyDisplayCode, getLocalizedBasis, isMobile } from '@deriv/shared'; import CancelDealInfo from './cancel-deal-info.jsx'; -const ValueMovement = ({ has_error_or_not_loaded, proposal_info, currency, has_increased }) => ( - -
+const ValueMovement = ({ has_error_or_not_loaded, proposal_info, currency, has_increased, is_vanilla }) => ( +
+
{!has_error_or_not_loaded && ( @@ -25,7 +27,7 @@ const ValueMovement = ({ has_error_or_not_loaded, proposal_info, currency, has_i )}
- +
); const ContractInfo = ({ @@ -34,6 +36,7 @@ const ContractInfo = ({ has_increased, is_loading, is_multiplier, + is_vanilla, should_fade, proposal_info, type, @@ -42,23 +45,49 @@ const ContractInfo = ({ const stakeOrPayout = () => { switch (basis) { - case 'stake': + case 'stake': { + if (is_vanilla) { + return localize('Payout per point'); + } return localized_basis.payout; - case 'payout': + } + case 'payout': { return localized_basis.stake; + } default: return basis; } }; + const setBasisText = () => { + if (is_vanilla) { + return 'Payout per point'; + } + return proposal_info.obj_contract_basis.text; + }; + const has_error_or_not_loaded = proposal_info.has_error || !proposal_info.id; - const basis_text = has_error_or_not_loaded - ? stakeOrPayout() - : localize('{{value}}', { value: proposal_info.obj_contract_basis.text }); + const basis_text = has_error_or_not_loaded ? stakeOrPayout() : localize('{{value}}', { value: setBasisText() }); const { message, obj_contract_basis, stake } = proposal_info; + const setHintMessage = () => { + if (['VANILLALONGCALL', 'VANILLALONGPUT'].includes(type)) { + return ( + ]} + values={{ + trade_type: type === 'VANILLALONGCALL' ? localize('above') : localize('below'), + title: type === 'VANILLALONGCALL' ? localize('Call') : localize('Put'), + }} + /> + ); + } + return message; + }; + return (
+ ) : is_vanilla && isMobile() ? ( + + +
+ {basis_text} +
+
+ +
+ ]} + values={{ + trade_type: + type === 'VANILLALONGCALL' + ? localize('above') + : localize('below'), + title: + type === 'VANILLALONGCALL' ? localize('Call') : localize('Put'), + }} + /> + } + /> +
+
+
+
) : ( - !is_multiplier && obj_contract_basis && ( -
{basis_text}
+
+ {basis_text} +
-
+
@@ -119,7 +208,7 @@ const ContractInfo = ({ id={`dt_purchase_${type.toLowerCase()}_info`} is_bubble_hover_enabled margin={216} - message={has_error_or_not_loaded ? '' : message} + message={has_error_or_not_loaded ? '' : setHintMessage()} relative_render /> @@ -133,6 +222,7 @@ ContractInfo.propTypes = { currency: PropTypes.string, has_increased: PropTypes.bool, is_multiplier: PropTypes.bool, + is_vanilla: PropTypes.bool, is_loading: PropTypes.bool, proposal_info: PropTypes.object, should_fade: PropTypes.bool, diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx index e927df3cf8f3..b5191bc16ab0 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx @@ -191,6 +191,7 @@ DurationWrapper.propTypes = { export default connect(({ modules, ui }) => ({ advanced_duration_unit: ui.advanced_duration_unit, advanced_expiry_type: ui.advanced_expiry_type, + contract_type: modules.trade.contract_type, contract_expiry_type: modules.trade.contract_expiry_type, duration: modules.trade.duration, duration_unit: modules.trade.duration_unit, diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.jsx index 2c0262c36e57..3cac13bb55ac 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.jsx @@ -34,7 +34,14 @@ const Duration = ({ server_time, start_date, market_open_times, + contract_type, }) => { + React.useEffect(() => { + if (contract_type === 'vanilla') { + onToggleDurationType({ target: { value: true, name: 'is_advanced_duration' } }); + } + }, [contract_type]); + const expiry_list = [{ text: localize('Duration'), value: 'duration' }]; const has_end_time = expiry_list.find(expiry => expiry.value === 'endtime'); @@ -185,11 +192,13 @@ const Duration = ({ simple_duration_unit={simple_duration_unit} /> )} - + {contract_type !== 'vanilla' && ( + + )} )} @@ -199,6 +208,7 @@ const Duration = ({ Duration.propTypes = { advanced_duration_unit: PropTypes.string, advanced_expiry_type: PropTypes.string, + contract_type: PropTypes.string, duration: PropTypes.number, duration_t: PropTypes.number, duration_unit: PropTypes.string, diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx index 7a17b4dd1a9c..bfc405638c10 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { Tabs, Money, Numpad } from '@deriv/components'; -import { isEmptyObject, getDecimalPlaces } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; +import { Money, Numpad, Tabs, Text } from '@deriv/components'; +import { getDecimalPlaces, isEmptyObject } from '@deriv/shared'; +import React from 'react'; +import classNames from 'classnames'; import { connect } from 'Stores/connect'; const Basis = ({ @@ -21,6 +22,9 @@ const Basis = ({ trade_duration, trade_duration_unit, setAmountError, + contract_type, + stake_boundary, + vanilla_trade_type, }) => { const user_currency_decimal_places = getDecimalPlaces(currency); const onNumberChange = num => { @@ -65,28 +69,46 @@ const Basis = ({ }; return ( -
- { - return ( -
- {parseFloat(v) > 0 ? : v} -
- ); - }} - reset_press_interval={450} - reset_value='' - pip_size={user_currency_decimal_places} - onValidate={validateAmount} - submit_label={localize('OK')} - onValueChange={onNumberChange} - /> +
+ {contract_type === 'vanilla' && ( +
+
+ {localize('Min. stake')} + + {stake_boundary[vanilla_trade_type].min_stake} {currency} + +
+
+ {localize('Max. stake')} + + {stake_boundary[vanilla_trade_type].max_stake} {currency} + +
+
+ )} +
+ { + return ( +
+ {parseFloat(v) > 0 ? : v} +
+ ); + }} + reset_press_interval={450} + reset_value='' + pip_size={user_currency_decimal_places} + onValidate={validateAmount} + submit_label={localize('OK')} + onValueChange={onNumberChange} + /> +
); }; @@ -99,6 +121,9 @@ const AmountWrapper = connect(({ modules, client, ui }) => ({ trade_duration: modules.trade.duration, currency: client.currency, addToast: ui.addToast, + contract_type: modules.trade.contract_type, + vanilla_trade_type: ui.vanilla_trade_type, + stake_boundary: modules.trade.stake_boundary, }))(Basis); const Amount = ({ diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx index aaa1bcd25844..7fbd6ec4ef06 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx @@ -1,15 +1,16 @@ -import classNames from 'classnames'; +import { AMOUNT_MAX_LENGTH, addComma, getDecimalPlaces } from '@deriv/shared'; +import { ButtonToggle, Dropdown, InputField, Text } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; + +import AllowEquals from './allow-equals.jsx'; +import Fieldset from 'App/Components/Form/fieldset.jsx'; import { PropTypes as MobxPropTypes } from 'mobx-react'; +import Multiplier from './Multiplier/multiplier.jsx'; +import MultipliersInfo from './Multiplier/info.jsx'; import PropTypes from 'prop-types'; import React from 'react'; -import { ButtonToggle, Dropdown, InputField } from '@deriv/components'; -import { AMOUNT_MAX_LENGTH, getDecimalPlaces, addComma } from '@deriv/shared'; -import Fieldset from 'App/Components/Form/fieldset.jsx'; +import classNames from 'classnames'; import { connect } from 'Stores/connect'; -import { Localize, localize } from '@deriv/translations'; -import AllowEquals from './allow-equals.jsx'; -import MultipliersInfo from './Multiplier/info.jsx'; -import Multiplier from './Multiplier/multiplier.jsx'; const Input = ({ amount, @@ -68,6 +69,8 @@ const Amount = ({ onChange, setCurrentFocus, validation_errors, + stake_boundary, + vanilla_trade_type, }) => { if (is_minimized) { return ( @@ -92,15 +95,24 @@ const Amount = ({ const getBasisList = () => basis_list.map(item => ({ text: item.text, value: item.value })); + const setTooltipContent = () => { + if (is_multiplier) { + return ( + + ); + } else if (contract_type === 'vanilla') { + return ( + + ); + } + return null; + }; + return (
- ) : undefined - } + header={is_multiplier || ['high_low', 'vanilla'].includes(contract_type) ? localize('Stake') : undefined} + header_tooltip={setTooltipContent()} > {basis_list.length > 1 && ( )} + {contract_type === 'vanilla' && ( +
+
+ {localize('Min. stake')} + + {stake_boundary[vanilla_trade_type].min_stake} {currency} + +
+
+ {localize('Max. stake')} + + {stake_boundary[vanilla_trade_type].max_stake} {currency} + +
+
+ )}
); }; @@ -195,6 +223,8 @@ Amount.propTypes = { setCurrentFocus: PropTypes.func, onChange: PropTypes.func, validation_errors: PropTypes.object, + stake_boundary: PropTypes.object, + vanilla_trade_type: PropTypes.object, }; export default connect(({ modules, client, ui }) => ({ @@ -216,5 +246,7 @@ export default connect(({ modules, client, ui }) => ({ stop_out: modules.trade.stop_out, onChange: modules.trade.onChange, setCurrentFocus: ui.setCurrentFocus, + vanilla_trade_type: ui.vanilla_trade_type, validation_errors: modules.trade.validation_errors, + stake_boundary: modules.trade.stake_boundary, }))(Amount); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike-field.scss b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike-field.scss new file mode 100644 index 000000000000..05999a2eb179 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike-field.scss @@ -0,0 +1,38 @@ +.strike-field { + background-color: var(--general-section-1); + border-radius: $BORDER_RADIUS; + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; + position: absolute; + top: 0; + width: 24rem; + z-index: 3; + + &--header { + padding: 1.6rem; + display: flex; + justify-content: space-between; + border-bottom: 1px solid var(--general-hover); + } + + &--body { + padding: 1.6rem; + display: flex; + flex-direction: column; + cursor: pointer; + gap: 0.8rem; + + &-item { + &:hover { + background-color: var(--state-hover); + } + } + + &--active { + background-color: var(--state-active); + } + } +} diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike.jsx new file mode 100644 index 000000000000..f68a29c62bf4 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/strike.jsx @@ -0,0 +1,169 @@ +import React from 'react'; +import classNames from 'classnames'; +import { DesktopWrapper, InputField, MobileWrapper, Dropdown, Text, Icon } from '@deriv/components'; +import { localize, Localize } from '@deriv/translations'; +import { toMoment } from '@deriv/shared'; +import Fieldset from 'App/Components/Form/fieldset.jsx'; +import { connect } from 'Stores/connect'; +import StrikeParamModal from 'Modules/Trading/Containers/strike-param-modal'; +import './strike-field.scss'; + +const Strike = ({ + barrier_1, + current_focus, + onChange, + validation_errors, + setCurrentFocus, + advanced_duration_unit, + strike_price_choices, + expiry_type, + expiry_date, + server_time, + vanilla_trade_type, +}) => { + const [is_open, setIsOpen] = React.useState(false); + const [should_open_dropdown, setShouldOpenDropdown] = React.useState(false); + const [selected_value, setSelectedValue] = React.useState(barrier_1); + + React.useEffect(() => { + setSelectedValue(barrier_1); + }, [barrier_1]); + + const toggleWidget = () => setIsOpen(!is_open); + + const is_24_hours_contract = expiry_date ? toMoment(expiry_date).isSame(toMoment(server_time), 'day') : false; + + const is_relative_strike_applicable = + expiry_type === 'endtime' ? is_24_hours_contract : advanced_duration_unit !== 'd'; + + const strike_price_list = strike_price_choices.map(strike_price => ({ text: strike_price, value: strike_price })); + + if (should_open_dropdown) { + return ( +
+
+ + {localize('Strike Prices')} + + setShouldOpenDropdown(false)} /> +
+
+ {strike_price_list.map(strike => ( + { + setSelectedValue(strike.value); + setShouldOpenDropdown(false); + onChange({ target: { name: 'barrier_1', value: strike.value } }); + }} + > + {strike.value} + + ))} +
+
+ ); + } + + return ( + + +
]} + values={{ + trade_type: + vanilla_trade_type === 'VANILLALONGCALL' + ? localize('For Call') + : localize('For Put'), + payout_status: + vanilla_trade_type === 'VANILLALONGCALL' ? localize('above') : localize('below'), + }} + /> + } + > + {!is_relative_strike_applicable ? ( + setShouldOpenDropdown(true)} + /> + ) : ( +
+ + {localize('Spot')} + + +
+ )} +
+
+ +
+
+
{{localize('Spot')}}
+
{barrier_1}
+
{localize('Strike price')}
+
+ +
+
+
+ ); +}; + +export default connect(({ modules, ui, common }) => ({ + barrier_1: modules.trade.barrier_1, + current_focus: ui.current_focus, + setCurrentFocus: ui.setCurrentFocus, + onChange: modules.trade.onChange, + validation_errors: modules.trade.validation_errors, + advanced_duration_unit: ui.advanced_duration_unit, + strike_price_choices: modules.trade.strike_price_choices, + expiry_type: modules.trade.expiry_type, + start_date: modules.trade.start_date, + expiry_date: modules.trade.expiry_date, + server_time: common.server_time, + vanilla_trade_type: ui.vanilla_trade_type, +}))(Strike); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx new file mode 100644 index 000000000000..07ed81c8cbd3 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { ButtonToggle } from '@deriv/components'; +import { connect } from 'Stores/connect'; +import Fieldset from 'App/Components/Form/fieldset.jsx'; + +const VanillaTradeTypes = ({ onChange, onChangeUiStore, vanilla_trade_type }) => { + const changeTradeType = ({ target }) => { + const { name, value } = target; + + onChange({ target: { name, value } }); + onChangeUiStore({ name, value }); + }; + + return ( +
+ +
+ ); +}; + +export default connect(({ modules, ui }) => ({ + onChange: modules.trade.onChange, + onChangeUiStore: ui.onChangeUiStore, + vanilla_trade_type: ui.vanilla_trade_type, +}))(VanillaTradeTypes); diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx index cd075e9a30c4..7492577c2ebe 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx @@ -16,6 +16,8 @@ import ContractType from '../../Containers/contract-type.jsx'; import { BarrierMobile, LastDigitMobile } from '../../Containers/trade-params-mobile.jsx'; import Purchase from '../../Containers/purchase.jsx'; import 'Sass/app/_common/mobile-widget.scss'; +import Strike from 'Modules/Trading/Components/Form/TradeParams/strike.jsx'; +import VanillaTradeTypes from 'Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx'; const CollapsibleTradeParams = ({ form_components, @@ -24,6 +26,7 @@ const CollapsibleTradeParams = ({ is_allow_equal, is_trade_params_expanded, is_multiplier, + is_vanilla, setIsTradeParamsExpanded, }) => { React.useEffect(() => { @@ -48,6 +51,7 @@ const CollapsibleTradeParams = ({
{is_multiplier && } + {is_vanilla && }
{isVisible('last_digit') && (
@@ -55,10 +59,15 @@ const CollapsibleTradeParams = ({
)} {isVisible('barrier') && ( -
+
)} + {isVisible('strike') && ( +
+ +
+ )} {has_allow_equals && } {is_multiplier && ( @@ -66,9 +75,13 @@ const CollapsibleTradeParams = ({
)} -
+ {is_vanilla ? ( -
+ ) : ( +
+ +
+ )} ); }; @@ -113,6 +126,7 @@ ScreenSmall.propTypes = { export default connect(({ modules }) => ({ is_allow_equal: !!modules.trade.is_equal, is_multiplier: modules.trade.is_multiplier, + is_vanilla: modules.trade.is_vanilla, duration_unit: modules.trade.duration_unit, contract_types_list: modules.trade.contract_types_list, contract_type: modules.trade.contract_type, diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.jsx b/packages/trader/src/Modules/Trading/Containers/purchase.jsx index ca289c3f54e2..08104c677d01 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.jsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.jsx @@ -1,31 +1,31 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { isEmptyObject } from '@deriv/shared'; +import { isEmptyObject, isMobile } from '@deriv/shared'; import PurchaseFieldset from 'Modules/Trading/Components/Elements/purchase-fieldset.jsx'; import { getContractTypePosition } from 'Constants/contract'; import { connect } from 'Stores/connect'; +import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info.jsx'; const Purchase = ({ basis, contract_type, currency, has_cancellation, - is_multiplier, + is_market_closed, is_mobile, + is_multiplier, is_purchase_enabled, - is_market_closed, - // is_purchase_confirm_on, - purchased_states_arr, - // is_purchase_locked, is_trade_enabled, + is_vanilla, onClickPurchase, onHoverPurchase, - // togglePurchaseLock, - purchase_info, proposal_info, + purchase_info, + purchased_states_arr, setPurchaseState, trade_types, validation_errors, + vanilla_trade_type, }) => { const is_high_low = /^high_low$/.test(contract_type.toLowerCase()); const isLoading = info => { @@ -45,43 +45,59 @@ const Purchase = ({ const is_disabled = !is_trade_enabled || !info.id || !is_purchase_enabled; const is_proposal_error = is_multiplier ? info.has_error && !info.has_error_details : info.has_error; const purchase_fieldset = ( - +
+ {is_vanilla && isMobile() && ( + + )} + +
); - switch (getContractTypePosition(type)) { - case 'top': - components.unshift(purchase_fieldset); - break; - case 'bottom': - components.push(purchase_fieldset); - break; - default: - components.push(purchase_fieldset); - break; + if (!is_vanilla) { + switch (getContractTypePosition(type)) { + case 'top': + components.unshift(purchase_fieldset); + break; + case 'bottom': + components.push(purchase_fieldset); + break; + default: + components.push(purchase_fieldset); + break; + } + } else if (vanilla_trade_type === type) { + components.push(purchase_fieldset); } }); return components; @@ -93,7 +109,6 @@ Purchase.propTypes = { has_cancellation: PropTypes.bool, is_multiplier: PropTypes.bool, is_mobile: PropTypes.bool, - // is_purchase_confirm_on : PropTypes.bool, is_purchase_locked: PropTypes.bool, is_trade_enabled: PropTypes.bool, onClickPurchase: PropTypes.func, @@ -102,29 +117,27 @@ Purchase.propTypes = { purchase_info: PropTypes.object, purchased_states_arr: PropTypes.array, setPurchaseState: PropTypes.func, - // togglePurchaseLock : PropTypes.func, trade_types: PropTypes.object, validation_errors: PropTypes.object, }; export default connect(({ modules, ui }) => ({ - currency: modules.trade.currency, basis: modules.trade.basis, contract_type: modules.trade.contract_type, + currency: modules.trade.currency, has_cancellation: modules.trade.has_cancellation, + is_multiplier: modules.trade.is_multiplier, is_purchase_enabled: modules.trade.is_purchase_enabled, is_trade_enabled: modules.trade.is_trade_enabled, - is_multiplier: modules.trade.is_multiplier, + is_vanilla: modules.trade.is_vanilla, onClickPurchase: modules.trade.onPurchase, onHoverPurchase: modules.trade.onHoverPurchase, proposal_info: modules.trade.proposal_info, purchase_info: modules.trade.purchase_info, trade_types: modules.trade.trade_types, validation_errors: modules.trade.validation_errors, + vanilla_trade_type: modules.trade.vanilla_trade_type, is_mobile: ui.is_mobile, purchased_states_arr: ui.purchase_states, setPurchaseState: ui.setPurchaseState, - // is_purchase_confirm_on : ui.is_purchase_confirm_on, - // is_purchase_locked : ui.is_purchase_lock_on, - // togglePurchaseLock : ui.togglePurchaseLock, }))(Purchase); diff --git a/packages/trader/src/Modules/Trading/Containers/strike-param-modal.jsx b/packages/trader/src/Modules/Trading/Containers/strike-param-modal.jsx new file mode 100644 index 000000000000..a83225974255 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Containers/strike-param-modal.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Localize, localize } from '@deriv/translations'; +import { Div100vhContainer, Modal, Popover, RadioGroup } from '@deriv/components'; +import classNames from 'classnames'; + +const StrikeParamModal = ({ is_open, toggleModal, strike, onChange, name, strike_price_list, vanilla_trade_type }) => ( + + +
+ ]} + values={{ + trade_type: + vanilla_trade_type === 'VANILLALONGCALL' + ? localize('For Call') + : localize('For Put'), + payout_status: + vanilla_trade_type === 'VANILLALONGCALL' ? localize('above') : localize('below'), + }} + /> + } + classNameWrapper='trade-params--modal-wrapper' + classNameBubble='trade-params--modal-wrapper__content' + /> +
+
+ + {strike_price_list.map(item => ( + + ))} + +
+
+
+); + +export default StrikeParamModal; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx index f8d88294cdcc..a8ceb1ef04c8 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx @@ -1,15 +1,17 @@ -import classNames from 'classnames'; -import React from 'react'; -import PropTypes from 'prop-types'; -import { PropTypes as MobxPropTypes } from 'mobx-react'; -import { Div100vhContainer, Tabs, Modal, Money, ThemedScrollbars, usePreventIOSZoom } from '@deriv/components'; -import { connect } from 'Stores/connect'; -import { localize } from '@deriv/translations'; +import 'Sass/app/modules/trading-mobile.scss'; + +import { Div100vhContainer, Modal, Money, Tabs, ThemedScrollbars, usePreventIOSZoom, Popover } from '@deriv/components'; + import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx'; -import DurationMobile from 'Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.jsx'; import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import DurationMobile from 'Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.jsx'; import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; -import 'Sass/app/modules/trading-mobile.scss'; +import { PropTypes as MobxPropTypes } from 'mobx-react'; +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import { connect } from 'Stores/connect'; +import { localize, Localize } from '@deriv/translations'; const DEFAULT_DURATION = Object.freeze({ t: 5, @@ -40,6 +42,7 @@ const TradeParamsModal = ({ toggleModal, currency, duration_units_list, + is_vanilla, }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const getDefaultDuration = React.useCallback(makeGetDefaultDuration(duration, duration_unit), []); @@ -96,6 +99,28 @@ const TradeParamsModal = ({ const isVisible = component_key => form_components.includes(component_key); + const setTooltipContent = () => { + if (is_vanilla && state.trade_param_tab_idx === 1) + return ( +
+ + } + classNameWrapper='trade-params--modal-wrapper' + classNameBubble='trade-params--modal-wrapper__content--vanilla' + /> +
+ ); + return null; + }; + return ( } disableApp={disableApp} toggleModal={toggleModal} height='auto' width='calc(100vw - 32px)' + renderTitle={setTooltipContent} > @@ -171,6 +196,7 @@ export default connect(({ client, modules, ui }) => ({ expiry_type: modules.trade.expiry_type, enableApp: ui.enableApp, disableApp: ui.disableApp, + is_vanilla: modules.trade.is_vanilla, }))(TradeParamsModal); const TradeParamsMobile = ({ @@ -188,6 +214,7 @@ const TradeParamsMobile = ({ duration_tab_idx, has_amount_error, has_duration_error, + is_vanilla, // amount setAmountError, setSelectedAmount, @@ -237,7 +264,9 @@ const TradeParamsMobile = ({ case 'amount': return (
-
{localize('Amount')}
+
+ {is_vanilla ? localize('Stake') : localize('Amount')} +
({ basis_list: modules.trade.basis_list, basis: modules.trade.basis, + is_vanilla: modules.trade.is_vanilla, }))(TradeParamsMobile); export const LastDigitMobile = connect(({ modules }) => ({ diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx index 45d1b75c36d7..17d4894e952c 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx @@ -1,6 +1,7 @@ import { PropTypes as MobxPropTypes } from 'mobx-react'; import PropTypes from 'prop-types'; import React from 'react'; +import classNames from 'classnames'; import Amount from 'Modules/Trading/Components/Form/TradeParams/amount.jsx'; import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; @@ -9,7 +10,10 @@ import CancelDeal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/c import StopLoss from 'Modules/Trading/Components/Form/TradeParams/Multiplier/stop-loss.jsx'; import TakeProfit from 'Modules/Trading/Components/Form/TradeParams/Multiplier/take-profit.jsx'; import Expiration from 'Modules/Trading/Components/Form/TradeParams/Multiplier/expiration.jsx'; +import Strike from 'Modules/Trading/Components/Form/TradeParams/strike.jsx'; +import VanillaTradeTypes from 'Modules/Trading/Components/Form/TradeParams/vanilla-trade-types.jsx'; import { connect } from 'Stores/connect'; +import Fieldset from 'App/Components/Form/fieldset.jsx'; const TradeParams = ({ form_components, is_minimized }) => { const isVisible = component_key => { @@ -20,7 +24,11 @@ const TradeParams = ({ form_components, is_minimized }) => { {isVisible('duration') && } {isVisible('barrier') && } {isVisible('last_digit') && } - {isVisible('amount') && } +
+ {isVisible('vanilla_trade_type') && } + {isVisible('strike') && } + {isVisible('amount') && } +
{isVisible('take_profit') && } {isVisible('stop_loss') && } {isVisible('cancellation') && } diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index 6b5cf4b578ec..5fad01be0edb 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import { DesktopWrapper, Div100vhContainer, MobileWrapper, SwipeableWrapper } from '@deriv/components'; import { isDesktop, isMobile } from '@deriv/shared'; import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; @@ -52,6 +53,7 @@ const Trade = ({ symbol, is_synthetics_available, is_synthetics_trading_market_available, + is_vanilla, }) => { const [digits, setDigits] = React.useState([]); const [tick, setTick] = React.useState({}); @@ -184,6 +186,7 @@ const Trade = ({ is_chart_loading || should_show_active_symbols_loading } + className={classNames({ 'vanilla-trade-chart': is_vanilla })} > {show_digits_stats && } ({ should_show_multipliers_onboarding: ui.should_show_multipliers_onboarding, onChange: modules.trade.onChange, setContractTypes: modules.trade.setContractTypes, + is_vanilla: modules.trade.is_vanilla, }))(Trade); // CHART (ChartTrade)-------------------------------------------------------- diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js index ca9f29043833..3b50b9ad2d90 100644 --- a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js +++ b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js @@ -2,6 +2,8 @@ import { localize } from '@deriv/translations'; import { isHourValid, isMinuteValid, isTimeValid, toMoment } from '@deriv/shared'; import { isSessionAvailable } from '../Helpers/start-date'; +const tradeSpecificBarrierCheck = (is_vanilla, input) => is_vanilla || input !== 0; + export const getValidationRules = () => ({ amount: { rules: [ @@ -31,7 +33,9 @@ export const getValidationRules = () => ({ 'custom', { func: (value, options, store, inputs) => - /^[+-]/.test(inputs.barrier_1) ? +inputs.barrier_1 !== 0 : true, + /^[+-]/.test(inputs.barrier_1) + ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) + : true, message: localize('Barrier cannot be zero.'), }, ], diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js index 185fd6f978a2..49863b717815 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js @@ -36,10 +36,8 @@ export const ContractType = (() => { if (!has_contracts || has_only_forward_starting_contracts) return; const contract_categories = getContractCategoriesConfig(); contract_types = getContractTypesConfig(symbol); - available_contract_types = {}; available_categories = cloneObject(contract_categories); // To preserve the order (will clean the extra items later in this function) - r.contracts_for.available.forEach(contract => { const type = Object.keys(contract_types).find( key => @@ -50,53 +48,6 @@ export const ContractType = (() => { if (!type) return; // ignore unsupported contract types - /* - add to this config if a value you are looking for does not exist yet - accordingly create a function to retrieve the value - config: { - has_spot: 1, - durations: { - min_max: { - spot: { - tick : { min: 5, max: 10 }, // value in ticks, as cannot convert to seconds - intraday: { min: 18000, max: 86400 }, // all values converted to seconds - daily : { min: 86400, max: 432000 }, - }, - forward: { - intraday: { min: 18000, max: 86400 }, - }, - }, - units_display: { - spot: [ - { text: 'ticks', value: 't' }, - { text: 'seconds', value: 's' }, - { text: 'minutes', value: 'm' }, - { text: 'hours', value: 'h' }, - { text: 'days', value: 'd' }, - ], - forward: [ - { text: 'days', value: 'd' }, - ], - }, - }, - forward_starting_dates: [ - { text: 'Mon - 19 Mar, 2018', value: 1517356800, sessions: [{ open: obj_moment, close: obj_moment }] }, - { text: 'Tue - 20 Mar, 2018', value: 1517443200, sessions: [{ open: obj_moment, close: obj_moment }] }, - { text: 'Wed - 21 Mar, 2018', value: 1517529600, sessions: [{ open: obj_moment, close: obj_moment }] }, - ], - trade_types: { - 'CALL': 'Higher', - 'PUT' : 'Lower', - }, - barriers: { - count : 2, - tick : { high_barrier: '+1.12', low_barrier : '-1.12' }, - intraday: { high_barrier: '+2.12', low_barrier : '-2.12' }, - daily : { high_barrier: 1111, low_barrier : 1093 }, - }, - } - */ - if (!available_contract_types[type]) { // extend contract_categories to include what is needed to create the contract list const sub_cats = @@ -206,16 +157,22 @@ export const ContractType = (() => { }; const getComponents = c_type => { + let check = []; + if (contract_types[c_type]?.config?.should_override) { + check = [...contract_types[c_type].components]; + } else { + check = ['duration', 'amount', ...contract_types[c_type].components].filter( + component => + !( + component === 'duration' && + contract_types[c_type].config && + contract_types[c_type].config.hide_duration + ) + ); + } return ( contract_types && { - form_components: ['duration', 'amount', ...contract_types[c_type].components].filter( - component => - !( - component === 'duration' && - contract_types[c_type].config && - contract_types[c_type].config.hide_duration - ) - ), + form_components: check, } ); }; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js index 76cb73419eef..8aa9d8a1635a 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js @@ -1,4 +1,4 @@ -import { getDecimalPlaces, getPropertyValue, convertToUnix, toMoment } from '@deriv/shared'; +import { convertToUnix, getDecimalPlaces, getPropertyValue, toMoment } from '@deriv/shared'; const isVisible = elem => !(!elem || (elem.offsetWidth === 0 && elem.offsetHeight === 0)); @@ -25,7 +25,10 @@ export const getProposalInfo = (store, response, obj_prev_contract_basis) => { const stake = proposal.display_value; const basis_list = store.basis_list; - const contract_basis = basis_list.find(o => o.value !== store.basis) || {}; + const contract_basis = store.is_vanilla + ? { text: 'Payout', value: 'number_of_contracts' } + : basis_list.find(o => o.value !== store.basis) || {}; + const is_stake = contract_basis.text === 'Stake'; const price = is_stake ? stake : proposal[contract_basis.value]; let has_increased = price > obj_prev_contract_basis.value; diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.js b/packages/trader/src/Stores/Modules/Trading/trade-store.js index e40076488ce7..1d2aad3d2288 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.js +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.js @@ -1,42 +1,43 @@ -import debounce from 'lodash.debounce'; -import { action, computed, observable, reaction, runInAction, toJS, when, makeObservable, override } from 'mobx'; +import * as Symbol from './Actions/symbol'; + import { + WS, cloneObject, + convertDurationLimit, extractInfoFromShortcode, + findFirstOpenMarket, + getBarrierPipSize, getMinPayout, getPlatformSettings, getPropertyValue, + isBarrierSupported, isCryptocurrency, isDesktop, isEmptyObject, + isMarketClosed, isMobile, - showDigitalOptionsUnavailableError, - WS, pickDefaultSymbol, - showUnavailableLocationError, - isMarketClosed, - findFirstOpenMarket, - showMxMltUnavailableError, - convertDurationLimit, - resetEndTimeOnVolatilityIndices, - getBarrierPipSize, - isBarrierSupported, removeBarrier, + resetEndTimeOnVolatilityIndices, + showDigitalOptionsUnavailableError, + showMxMltUnavailableError, + showUnavailableLocationError, } from '@deriv/shared'; -import { localize } from '@deriv/translations'; -import { getValidationRules, getMultiplierValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; -import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; +import { action, computed, makeObservable, observable, override, reaction, runInAction, toJS, when } from 'mobx'; +import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; +import { getMultiplierValidationRules, getValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; import { isDigitContractType, isDigitTradeType } from 'Modules/Trading/Helpers/digits'; + +import { BARRIER_COLORS } from '../SmartChart/Constants/barriers'; +import BaseStore from '../../base-store'; +import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; +import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; import ServerTime from '_common/base/server_time'; +import debounce from 'lodash.debounce'; +import { localize } from '@deriv/translations'; import { processPurchase } from './Actions/purchase'; -import * as Symbol from './Actions/symbol'; - import { processTradeParams } from './Helpers/process'; -import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; -import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; -import { BARRIER_COLORS } from '../SmartChart/Constants/barriers'; -import BaseStore from '../../base-store'; const store_name = 'trade_store'; const g_subscribers_map = {}; // blame amin.m @@ -69,6 +70,7 @@ export default class TradeStore extends BaseStore { basis = ''; basis_list = []; currency = ''; + stake_boundary = { VANILLALONGCALL: {}, VANILLALONGPUT: {} }; // Duration duration = 5; @@ -85,6 +87,7 @@ export default class TradeStore extends BaseStore { barrier_count = 0; main_barrier = null; barriers = []; + strike_price_choices = []; // Start Time start_date = Number(0); // Number(0) refers to 'now' @@ -130,6 +133,9 @@ export default class TradeStore extends BaseStore { cancellation_duration = '60m'; cancellation_range_list = []; + // Vanilla trade params + vanilla_trade_type = 'VANILLALONGCALL'; + // Mobile is_trade_params_expanded = true; @@ -177,123 +183,128 @@ export default class TradeStore extends BaseStore { }); makeObservable(this, { - is_trade_component_mounted: observable, - is_purchase_enabled: observable, - is_trade_enabled: observable, - is_equal: observable, - has_equals_only: observable, - symbol: observable, - is_market_closed: observable, - previous_symbol: observable, active_symbols: observable, - form_components: observable, + amount: observable, + barrier_1: observable, + barrier_2: observable, + barrier_count: observable, + barriers: observable, + basis_list: observable, + basis: observable, + cancellation_duration: observable, + cancellation_price: observable, + cancellation_range_list: observable, + commission: observable, contract_expiry_type: observable, contract_start_type: observable, contract_type: observable, contract_types_list: observable, - trade_types: observable, - amount: observable, - basis: observable, - basis_list: observable, currency: observable, - duration: observable, + duration_min_max: observable, duration_unit: observable, duration_units_list: observable, - duration_min_max: observable, + duration: observable, + expiration: observable, expiry_date: observable, expiry_time: observable, expiry_type: observable, - barrier_1: observable, - barrier_2: observable, - barrier_count: observable, + form_components: observable, + has_cancellation: observable, + has_equals_only: observable, + has_stop_loss: observable, + has_take_profit: observable, + hovered_contract_type: observable, + is_chart_loading: observable, + is_equal: observable, + is_market_closed: observable, + is_mobile_digit_view_selected: observable, + is_purchase_enabled: observable, + is_trade_component_mounted: observable, + is_trade_enabled: observable, + is_trade_params_expanded: observable, + last_digit: observable, main_barrier: observable, - barriers: observable, - start_date: observable, - start_dates_list: observable, - start_time: observable, - sessions: observable, - market_open_times: observable, market_close_times: observable, - last_digit: observable, - is_mobile_digit_view_selected: observable, + market_open_times: observable, + multiplier_range_list: observable, + multiplier: observable, + previous_symbol: observable, proposal_info: observable.ref, purchase_info: observable.ref, - is_chart_loading: observable, + sessions: observable, should_show_active_symbols_loading: observable, - multiplier: observable, - multiplier_range_list: observable, + should_skip_prepost_lifecycle: observable, + stake_boundary: observable, + start_date: observable, + start_dates_list: observable, + start_time: observable, stop_loss: observable, - take_profit: observable, - has_stop_loss: observable, - has_take_profit: observable, - has_cancellation: observable, - commission: observable, - cancellation_price: observable, stop_out: observable, - expiration: observable, - hovered_contract_type: observable, - cancellation_duration: observable, - cancellation_range_list: observable, - is_trade_params_expanded: observable, - should_skip_prepost_lifecycle: observable, - is_symbol_in_active_symbols: computed, - setSkipPrePostLifecycle: action.bound, - setTradeStatus: action.bound, - refresh: action.bound, + strike_price_choices: observable, + symbol: observable, + take_profit: observable, + trade_types: observable, + accountSwitcherListener: action.bound, + barrier_pipsize: computed, + barriers_flattened: computed, + changeDurationValidationRules: action.bound, + chartStateChange: action.bound, clearContracts: action.bound, + clearLimitOrderBarriers: action.bound, + clearPurchaseInfo: action.bound, + clientInitListener: action.bound, + enablePurchase: action.bound, + exportLayout: action.bound, + forgetAllProposal: action.bound, + getFirstOpenMarket: action.bound, + has_alternative_source: computed, + initAccountCurrency: action.bound, + is_multiplier: computed, + is_symbol_in_active_symbols: computed, + is_synthetics_available: computed, + is_vanilla: computed, loadActiveSymbols: action.bound, - setDefaultSymbol: action.bound, - setActiveSymbols: action.bound, - setContractTypes: action.bound, - prepareTradeStore: action.bound, - onChangeMultiple: action.bound, + logoutListener: action.bound, + main_barrier_flattened: computed, + manageMxMltRemovalNotification: action.bound, + networkStatusChangeListener: action.bound, + onAllowEqualsChange: action.bound, onChange: action.bound, - setPreviousSymbol: action.bound, - setAllowEqual: action.bound, - setIsTradeParamsExpanded: action.bound, - resetPreviousSymbol: action.bound, - updateBarrierColor: action.bound, + onChangeMultiple: action.bound, + onChartBarrierChange: action.bound, onHoverPurchase: action.bound, - setPurchaseSpotBarrier: action.bound, - updateLimitOrderBarriers: action.bound, - clearLimitOrderBarriers: action.bound, - barrier_pipsize: computed, - main_barrier_flattened: computed, - barriers_flattened: computed, + onMount: action.bound, + onProposalResponse: action.bound, onPurchase: action.bound, + onUnmount: override, + prepareTradeStore: action.bound, + preSwitchAccountListener: action.bound, processPurchase: action.bound, - enablePurchase: action.bound, - updateStore: action.bound, - is_synthetics_available: computed, - show_digits_stats: computed, - setMobileDigitView: action.bound, pushPurchaseDataToGtm: action.bound, - clearPurchaseInfo: action.bound, + refresh: action.bound, requestProposal: action.bound, - forgetAllProposal: action.bound, - setMarketStatus: action.bound, - onProposalResponse: action.bound, - onChartBarrierChange: action.bound, - onAllowEqualsChange: action.bound, - updateSymbol: action.bound, - changeDurationValidationRules: action.bound, - accountSwitcherListener: action.bound, - preSwitchAccountListener: action.bound, - logoutListener: action.bound, - clientInitListener: action.bound, - networkStatusChangeListener: action.bound, - themeChangeListener: action.bound, resetErrorServices: action.bound, - onMount: action.bound, - manageMxMltRemovalNotification: action.bound, + resetPreviousSymbol: action.bound, + setActiveSymbols: action.bound, + setAllowEqual: action.bound, setChartStatus: action.bound, - initAccountCurrency: action.bound, - onUnmount: override, - exportLayout: action.bound, - chartStateChange: action.bound, - has_alternative_source: computed, - is_multiplier: computed, - getFirstOpenMarket: action.bound, + setContractTypes: action.bound, + setDefaultSymbol: action.bound, + setIsTradeParamsExpanded: action.bound, + setMarketStatus: action.bound, + setMobileDigitView: action.bound, + setPreviousSymbol: action.bound, + setPurchaseSpotBarrier: action.bound, + setSkipPrePostLifecycle: action.bound, + setStakeBoundary: action.bound, + setStrikeChoices: action.bound, + setTradeStatus: action.bound, + show_digits_stats: computed, + themeChangeListener: action.bound, + updateBarrierColor: action.bound, + updateLimitOrderBarriers: action.bound, + updateStore: action.bound, + updateSymbol: action.bound, }); // Adds intercept to change min_max value of duration validation @@ -530,7 +541,6 @@ export default class TradeStore extends BaseStore { async onChange(e) { const { name, value } = e.target; - if (name === 'symbol' && value) { // set trade params skeleton and chart loader to true until processNewValuesAsync resolves this.setChartStatus(true); @@ -676,6 +686,7 @@ export default class TradeStore extends BaseStore { // create barrier only when it's available in response this.main_barrier = new ChartBarrierStore(barrier || high_barrier, low_barrier, this.onChartBarrierChange, { color, + not_draggable: this.is_vanilla, }); // this.main_barrier.updateBarrierShade(true, contract_type); } else { @@ -978,7 +989,6 @@ export default class TradeStore extends BaseStore { requestProposal() { const requests = createProposalRequests(this); - if (Object.values(this.validation_errors).some(e => e.length)) { this.proposal_info = {}; this.purchase_info = {}; @@ -989,7 +999,6 @@ export default class TradeStore extends BaseStore { if (!isEmptyObject(requests)) { this.proposal_requests = requests; this.purchase_info = {}; - Object.keys(this.proposal_requests).forEach(type => { WS.subscribeProposal(this.proposal_requests[type], this.onProposalResponse); }); @@ -1007,7 +1016,7 @@ export default class TradeStore extends BaseStore { } onProposalResponse(response) { - const contract_type = response.echo_req.contract_type; + const { contract_type } = response.echo_req; const prev_proposal_info = getPropertyValue(this.proposal_info, contract_type) || {}; const obj_prev_contract_basis = getPropertyValue(prev_proposal_info, 'obj_contract_basis') || {}; @@ -1052,7 +1061,17 @@ export default class TradeStore extends BaseStore { if (response.error) { const error_id = getProposalErrorField(response); if (error_id) { - this.setValidationErrorMessages(error_id, [response.error.message]); + if (this.is_vanilla) { + /** + * This if-block ensures only the particular trade type's error message is selected + * even though 2 proposal calls are made + */ + if (this.vanilla_trade_type === contract_type) { + this.setValidationErrorMessages(error_id, [response.error.message]); + } + } else { + this.setValidationErrorMessages(error_id, [response.error.message]); + } } // Commission for multipliers is normally set from proposal response. // But when we change the multiplier and if it is invalid, we don't get the proposal response to set the commission. We only get error message. @@ -1065,6 +1084,26 @@ export default class TradeStore extends BaseStore { } } + // Sometimes the initial barrier doesn't match with current barrier choices received from API. + // When this happens we want to populate the list of barrier choices to choose from since the value cannot be specified manually + if (this.is_vanilla) { + const { barrier_choices, max_stake, min_stake } = response.error.details; + this.setStakeBoundary(contract_type, min_stake, max_stake); + this.setStrikeChoices(barrier_choices); + if (!this.strike_price_choices.includes(this.barrier_1)) { + // Since on change of duration `proposal` API call is made which returns a new set of barrier values. + // The new list is set and the mid value is assigned + const index = Math.floor(this.strike_price_choices.length / 2); + this.barrier_1 = this.strike_price_choices[index]; + this.onChange({ + target: { + name: 'barrier_1', + value: this.barrier_1, + }, + }); + } + } + // Sometimes when we navigate fast, `forget_all` proposal is called immediately after proposal subscription calls. // But, in the BE, `forget_all` proposal call is processed before the proposal subscriptions are registered. In this case, `forget_all` proposal doesn't forget the new subscriptions. // So when we send new proposal subscription requests, we get `AlreadySubscribed` error. @@ -1079,6 +1118,11 @@ export default class TradeStore extends BaseStore { } } else { this.validateAllProperties(); + if (this.is_vanilla) { + const { max_stake, min_stake, barrier_choices } = response.proposal; + this.setStrikeChoices(barrier_choices); + this.setStakeBoundary(contract_type, min_stake, max_stake); + } } if (!this.is_purchasing_contract) { @@ -1358,6 +1402,10 @@ export default class TradeStore extends BaseStore { return this.contract_type === 'multiplier'; } + get is_vanilla() { + return this.contract_type === 'vanilla'; + } + async getFirstOpenMarket(markets_to_search) { if (this.active_symbols?.length) { return findFirstOpenMarket(this.active_symbols, markets_to_search); @@ -1369,4 +1417,12 @@ export default class TradeStore extends BaseStore { } return findFirstOpenMarket(active_symbols, markets_to_search); } + + setStrikeChoices(strike_prices) { + this.strike_price_choices = strike_prices ?? []; + } + + setStakeBoundary(type, min_stake, max_stake) { + this.stake_boundary[type] = { min_stake, max_stake }; + } } diff --git a/packages/trader/src/Stores/base-store.js b/packages/trader/src/Stores/base-store.js index e7856b72984b..72cd756f2bff 100644 --- a/packages/trader/src/Stores/base-store.js +++ b/packages/trader/src/Stores/base-store.js @@ -288,7 +288,6 @@ export default class BaseStore { validateAllProperties() { const validation_rules = Object.keys(this.validation_rules); const validation_errors = Object.keys(this.validation_errors); - validation_rules.forEach(p => { this.validateProperty(p, this[p]); }); diff --git a/packages/trader/src/sass/app.scss b/packages/trader/src/sass/app.scss index 841cbe4696ac..284e4b3e43a6 100644 --- a/packages/trader/src/sass/app.scss +++ b/packages/trader/src/sass/app.scss @@ -11,6 +11,7 @@ // Components @import 'app/_common/components/amount'; +@import 'app/_common/components/strike'; @import 'app/_common/components/allow-equals'; //@import 'app/_common/components/calendar'; // TODO: [move-to-components] Calendar component should be moved @import 'app/_common/components/card-list'; diff --git a/packages/trader/src/sass/app/_common/components/contract-type-list.scss b/packages/trader/src/sass/app/_common/components/contract-type-list.scss index 02961c25e1c0..d340bef8a67d 100644 --- a/packages/trader/src/sass/app/_common/components/contract-type-list.scss +++ b/packages/trader/src/sass/app/_common/components/contract-type-list.scss @@ -35,9 +35,23 @@ &:hover { background-color: var(--state-hover); } + + &__new { + padding: 0.3rem 0.4rem; + margin-bottom: 0.8rem; + border-radius: $BORDER_RADIUS * 4; + } + &__title { padding-left: 1.6rem; } + + &__container { + display: flex; + align-items: center; + gap: 1.6rem; + } + &__icon { margin-left: auto; diff --git a/packages/trader/src/sass/app/_common/components/contract-type-widget.scss b/packages/trader/src/sass/app/_common/components/contract-type-widget.scss index 71fcea8eed8c..93544bb0d903 100644 --- a/packages/trader/src/sass/app/_common/components/contract-type-widget.scss +++ b/packages/trader/src/sass/app/_common/components/contract-type-widget.scss @@ -100,6 +100,9 @@ &--multiplier { margin-bottom: 0.6rem; } + &--vanilla { + margin-right: 0.4rem; + } .contract-type-widget__display { padding: 0.8rem; } diff --git a/packages/trader/src/sass/app/_common/components/purchase-button.scss b/packages/trader/src/sass/app/_common/components/purchase-button.scss index 177b2ae04363..285dbd4ab51d 100644 --- a/packages/trader/src/sass/app/_common/components/purchase-button.scss +++ b/packages/trader/src/sass/app/_common/components/purchase-button.scss @@ -41,6 +41,9 @@ &__bottom { width: 40vw; // TODO remove this when find a solution for fill-width inside flex. } + &--vanilla { + width: auto; + } &:hover { transform: none !important; } @@ -214,6 +217,19 @@ var(--purchase-main-1) 100% ); } + + &__vanilla-opts { + @include mobile { + background: var(--purchase-section-1); + background: linear-gradient( + 0deg, + var(--purchase-section-1) 0%, + var(--purchase-section-1) 15%, + var(--purchase-main-1) 15%, + var(--purchase-main-1) 100% + ); + } + } } &--2 { background-color: var(--purchase-section-2); @@ -238,6 +254,19 @@ var(--purchase-main-2) 100% ); } + + &__vanilla-opts { + @include mobile { + background: var(--purchase-section-1); + background: linear-gradient( + 0deg, + var(--purchase-section-2) 0%, + var(--purchase-section-2) 15%, + var(--purchase-main-2) 15%, + var(--purchase-main-2) 100% + ); + } + } } &--disabled, &[disabled] { diff --git a/packages/trader/src/sass/app/_common/components/strike.scss b/packages/trader/src/sass/app/_common/components/strike.scss new file mode 100644 index 000000000000..1204bb9fd548 --- /dev/null +++ b/packages/trader/src/sass/app/_common/components/strike.scss @@ -0,0 +1,25 @@ +.strike-widget { + align-items: center; + background-color: var(--general-main-1); + border-radius: $BORDER_RADIUS; + display: grid; + flex: 1; + grid-template-areas: 'spot amount unit'; + grid-template-columns: 1fr 1fr 1fr; + height: 4rem; + margin: 0 0 0.8rem; + padding: 1rem 0.8rem; + + .mobile-widget { + &__spot { + grid-area: spot; + } + &__amount { + grid-area: amount; + } + &__type { + grid-area: unit; + justify-self: end; + } + } +} diff --git a/packages/trader/src/sass/app/_common/drawer/positions-modal-card.scss b/packages/trader/src/sass/app/_common/drawer/positions-modal-card.scss index 29f056373206..d880fb96591a 100644 --- a/packages/trader/src/sass/app/_common/drawer/positions-modal-card.scss +++ b/packages/trader/src/sass/app/_common/drawer/positions-modal-card.scss @@ -51,6 +51,14 @@ &__icon { margin-left: 4px; } + &__item { + display: flex; + justify-content: center; + align-items: center; + font-size: 1.4rem; + border-top: 1px solid var(--general-section-1); + padding-top: 0.8rem; + } &__wrapper { margin: 0.8rem; border-radius: $BORDER_RADIUS; @@ -92,7 +100,6 @@ } } &__progress { - justify-self: end; align-self: center; grid-row: 1 / 3; grid-column: 2 / 3; @@ -246,6 +253,9 @@ border-top: 0rem; } } + .dc-contract-card .dc-contract-card__grid { + grid-template-columns: 1.5fr 1fr; + } } /** @define dc-remaining-time; weak */ diff --git a/packages/trader/src/sass/app/_common/layout/trader-layouts.scss b/packages/trader/src/sass/app/_common/layout/trader-layouts.scss index d7d21f5f42ef..6722fc649046 100644 --- a/packages/trader/src/sass/app/_common/layout/trader-layouts.scss +++ b/packages/trader/src/sass/app/_common/layout/trader-layouts.scss @@ -642,6 +642,7 @@ $FLOATING_HEADER_HEIGHT: 41px; position: relative; min-height: 460px; width: $SIDEBAR_WIDTH; + height: 100%; &:after { transition: opacity 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25); diff --git a/packages/trader/src/sass/app/modules/smart-chart.scss b/packages/trader/src/sass/app/modules/smart-chart.scss index 3ae7a4689178..cf3d0a5f339e 100644 --- a/packages/trader/src/sass/app/modules/smart-chart.scss +++ b/packages/trader/src/sass/app/modules/smart-chart.scss @@ -5,7 +5,7 @@ &__spot { position: absolute; - bottom: -11px; + top: -1.1rem; margin-left: -11.5px; display: flex; justify-content: center; @@ -70,6 +70,40 @@ /** @define chart-spot-label */ .chart-spot-label { + &-profit { + position: relative; + top: -2.8rem; + left: 2.5rem; + + .chart-spot-label__value-container { + padding: 0.8rem; + line-height: normal; + border-radius: 0.6rem; + } + + &--lost { + &:before { + border-bottom: 0.5rem solid var(--text-loss-danger); + } + } + + &--won { + &:before { + border-bottom: 0.5rem solid var(--text-profit-success); + } + } + + &:before { + content: ''; + position: absolute; + top: 50%; + left: -1rem; + border-top: 0.5rem solid transparent; + border-right: 0.5rem solid transparent; + border-left: 0.5rem solid transparent; + transform: translateY(-50%) rotate(270deg); + } + } &__info-container { width: 100%; position: relative; @@ -87,7 +121,14 @@ margin-bottom: 2px; } @include mobile { - bottom: 9.5px; + bottom: 1.35rem; + } + } + &--middle { + bottom: 3.8rem; + + .chart-spot-label__time-container { + margin-bottom: 0.2rem; } } &--bottom { @@ -146,7 +187,7 @@ display: flex; justify-content: center; align-items: center; - padding: 0.2rem; + padding: 0.5rem; } } } @@ -245,3 +286,10 @@ display: inline; } } + +//Vanilla specific styles +.vanilla-trade-chart { + @include mobile { + height: calc(100% - #{$MOBILE_HEADER_HEIGHT}); + } +} diff --git a/packages/trader/src/sass/app/modules/trading-mobile.scss b/packages/trader/src/sass/app/modules/trading-mobile.scss index cc78321b8dfa..d3d863c2cfc0 100644 --- a/packages/trader/src/sass/app/modules/trading-mobile.scss +++ b/packages/trader/src/sass/app/modules/trading-mobile.scss @@ -185,6 +185,17 @@ } } } + &__strike { + display: flex; + flex-direction: column; + .trade-container__stake-field { + justify-content: space-around; + margin-top: 1.6rem; + } + .strike__pos { + margin-top: unset; + } + } &__amount { &-keypad { width: 100%; @@ -196,6 +207,11 @@ align-items: center; justify-content: center; + &--strike { + padding: 0 1.6rem; + margin-bottom: 2.4rem; + } + .dc-numpad--is-currency, .dc-numpad--is-regular { max-width: 100%; @@ -259,6 +275,28 @@ transform-origin: top; margin-top: -0.4rem; } + + .dc-radio-group { + flex-direction: column; + width: 100%; + gap: 10px; + align-items: flex-start; + + .trade-params__amount--mobile { + display: flex; + height: 4.8rem; + align-items: center; + padding: 0 0.8rem; + box-sizing: border-box; + border: 1px solid var(--general-section-2); + border-radius: $BORDER_RADIUS; + width: 100%; + &:hover { + box-shadow: none; + border-color: var(--border-active); + } + } + } } } &__header { @@ -405,5 +443,36 @@ } } } + &--modal-wrapper { + left: 2.4rem !important; + + &__content { + max-width: 32.8rem; + left: 2.5rem; + &--vanilla { + top: -0.9rem; + left: 2.3rem; + } + } + } + &--mobile { + &-strike { + overflow: auto; + margin-top: unset; + } + &__payout-container { + display: flex; + flex-direction: column; + } + } + } +} + +.dc-modal__container_trade-params { + .trade-params__multiplier-ic-info-wrapper { + margin-left: 1.4rem; + } + .dc-modal-header__close { + margin-right: 1rem; } } diff --git a/packages/trader/src/sass/app/modules/trading.scss b/packages/trader/src/sass/app/modules/trading.scss index 3bb6987e318a..651c94a17834 100644 --- a/packages/trader/src/sass/app/modules/trading.scss +++ b/packages/trader/src/sass/app/modules/trading.scss @@ -101,6 +101,32 @@ opacity: 0; } } + + #dt_purchase_vanillalongcall_button, + #dt_purchase_vanillalongput_button { + @include desktop { + .btn-purchase__info--left { + width: 80%; + } + + .btn-purchase__effect-detail--arrow { + left: 12rem; + } + } + + @include tablet { + .btn-purchase__info--left { + width: 80%; + } + + .btn-purchase__effect-detail--arrow { + left: 12rem; + } + } + } + } + &--no-padding { + padding: 0; } .dc-datepicker { .dc-input { @@ -172,6 +198,15 @@ padding: 0 30px; } } + + &__price { + #dt_purchase_vanillalongcall_price, + #dt_purchase_vanillalongput_price { + display: block; + margin-bottom: 0.6rem; + } + } + &__price, &__order-input { line-height: 0.9rem; @@ -198,6 +233,9 @@ justify-content: flex-end; align-items: center; } + &-vanilla { + display: flex; + } } @include desktop { min-height: 2.1rem; @@ -248,6 +286,16 @@ color: var(--text-colored-background); } } + &-strike { + @include mobile { + color: var(--text-less-prominent); + font-size: var(--text-size-xxs); + display: flex; + gap: 0.8rem; + justify-content: space-between; + padding: 1rem 0; + } + } &-currency { margin-left: 4px; margin-right: 1px; @@ -264,9 +312,18 @@ } } } - &__price-info-currency { - @include mobile { - font-size: 0.9rem; + &__price-info { + &-currency { + @include mobile { + font-size: 0.9rem; + } + } + &-modal { + @include mobile { + &--vanilla { + max-width: 33rem; + } + } } } &__barriers { @@ -490,6 +547,34 @@ } } } + + &__strike-field { + display: flex; + align-items: center; + gap: 0.8rem; + + .strike-field--text { + margin-top: 0.8rem; + } + } + + &__stake-field { + margin-top: 0.4rem; + display: flex; + justify-content: space-between; + + &--min { + display: flex; + flex-direction: column; + align-items: flex-start; + } + + &--max { + display: flex; + flex-direction: column; + align-items: flex-end; + } + } /* postcss-bem-linter: ignore */ .symbols.disabled { color: var(--text-disabled); @@ -522,8 +607,7 @@ position: relative; &__option { - padding-top: 8px; - padding-bottom: 8px; + padding: 1.6rem 0.8rem; &:not(:only-child) { &:nth-last-child(2) { @@ -674,3 +758,23 @@ } } } + +.strike { + &--info { + color: var(--text-general); + &__value { + font-size: var(--text-size-xxs); + margin-left: unset; + } + &__wrapper { + padding-right: 1rem; + } + } + &--value-container { + display: flex; + } +} + +.payout-hint { + padding: 0 1.6rem 1.6rem; +}