UNPKG

@orderly.network/ui-orders

Version:

1,570 lines (1,563 loc) 205 kB
'use strict'; var React2 = require('react'); var hooks = require('@orderly.network/hooks'); var i18n = require('@orderly.network/i18n'); var types = require('@orderly.network/types'); var utils = require('@orderly.network/utils'); var jsxRuntime = require('react/jsx-runtime'); var ui = require('@orderly.network/ui'); var reactApp = require('@orderly.network/react-app'); var uiTpsl = require('@orderly.network/ui-tpsl'); var dateFns = require('date-fns'); var uiShare = require('@orderly.network/ui-share'); var uiConnector = require('@orderly.network/ui-connector'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React2__default = /*#__PURE__*/_interopDefault(React2); var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; exports.useOrdersScript = void 0; var init_orders_script = __esm({ "src/components/orders.script.ts"() { exports.useOrdersScript = (props) => { const { current, pnlNotionalDecimalPrecision, sharePnLConfig } = props; const orderListRef = React2.useRef(null); React2.useImperativeHandle(props.ref, () => ({ download: () => { orderListRef.current?.download?.(); } })); return { current, pnlNotionalDecimalPrecision, orderListRef, sharePnLConfig }; }; } }); function parseBadgesFor(record) { const orderType = record.type; const algoType = record.algo_type; if (typeof orderType !== "undefined") { const list = []; if (!!record.parent_algo_type) { if (algoType === types.AlgoOrderType.STOP_LOSS) { const types$1 = orderType === types.OrderType.CLOSE_POSITION ? [i18n.i18n.t("common.position"), i18n.i18n.t("tpsl.sl")] : [i18n.i18n.t("tpsl.sl")]; list.push(...types$1); } if (algoType === types.AlgoOrderType.TAKE_PROFIT) { const types$1 = orderType === types.OrderType.CLOSE_POSITION ? [i18n.i18n.t("common.position"), i18n.i18n.t("tpsl.tp")] : [i18n.i18n.t("tpsl.tp")]; list.push(...types$1); } return list; } const type = typeof orderType === "string" ? orderType.replace("_ORDER", "") : ""; if ([types.OrderType.ASK, types.OrderType.BID].includes(orderType)) { return [i18n.i18n.t("orderEntry.orderType.limit")]; } if (record.algo_order_id === void 0 || record.algo_order_id && algoType === "BRACKET") { const typeMap = { [types.OrderType.LIMIT]: i18n.i18n.t("orderEntry.orderType.limit"), [types.OrderType.MARKET]: i18n.i18n.t("orderEntry.orderType.market"), [types.OrderType.POST_ONLY]: i18n.i18n.t("orderEntry.orderType.postOnly"), [types.OrderType.IOC]: i18n.i18n.t("orderEntry.orderType.ioc"), [types.OrderType.FOK]: i18n.i18n.t("orderEntry.orderType.fok") }; return [ typeMap[type] || upperCaseFirstLetter(type) ]; } if (algoType === types.AlgoOrderRootType.TRAILING_STOP) { return [i18n.i18n.t("orderEntry.orderType.trailingStop")]; } if (type) { const typeMap = { [types.OrderType.LIMIT]: i18n.i18n.t("orderEntry.orderType.stopLimit"), [types.OrderType.MARKET]: i18n.i18n.t("orderEntry.orderType.stopMarket") }; return [typeMap[type] || type]; } } if (typeof algoType !== "undefined") { const list = []; if (algoType === types.AlgoOrderRootType.POSITIONAL_TP_SL) { list.push(i18n.i18n.t("common.position")); } const tpOrder = record?.child_orders?.find( (order) => order.algo_type === types.AlgoOrderType.TAKE_PROFIT && !!order.trigger_price ); const slOrder = record?.child_orders?.find( (order) => order.algo_type === types.AlgoOrderType.STOP_LOSS && !!order.trigger_price ); if (tpOrder || slOrder) { list.push( tpOrder && slOrder ? i18n.i18n.t("common.tpsl") : tpOrder ? i18n.i18n.t("tpsl.tp") : i18n.i18n.t("tpsl.sl") ); } return list; } return void 0; } function grayCell(record) { return record.status === types.OrderStatus.CANCELLED || record.algo_status === types.OrderStatus.CANCELLED; } function getOrderStatus(record) { return record.status || record.algo_status; } function isGrayCell(status2) { return status2 === types.OrderStatus.CANCELLED; } function findBracketTPSLOrder(order) { if (order.algo_type !== types.AlgoOrderRootType.BRACKET) { return { tpOrder: void 0, slOrder: void 0 }; } const innerOrder = order.child_orders?.[0]; if (!innerOrder) return { tpOrder: void 0, slOrder: void 0 }; const tpOrder = innerOrder?.child_orders?.find( (item) => item.algo_type === types.AlgoOrderType.TAKE_PROFIT ); const slOrder = innerOrder?.child_orders?.find( (item) => item.algo_type === types.AlgoOrderType.STOP_LOSS ); return { tpOrder, slOrder }; } function calcBracketRoiAndPnL(order) { const defaultCallback = { pnl: { tpPnL: void 0, slPnL: void 0 } // roi: { // tpRoi: undefined, // slRoi: undefined, // }, }; const { tpOrder, slOrder } = findBracketTPSLOrder(order); if (!tpOrder && !slOrder) return defaultCallback; if (typeof order.price === void 0 || !order.price) return defaultCallback; const quantity2 = order.side === types.OrderSide.BUY ? order.quantity : order.quantity * -1; const tpPnL = tpOrder?.trigger_price && hooks.utils.priceToPnl({ qty: quantity2, price: tpOrder?.trigger_price, entryPrice: order.price, // @ts-ignore orderSide: order.side, // @ts-ignore orderType: tpOrder.algo_type }); const slPnL = slOrder?.trigger_price && hooks.utils.priceToPnl({ qty: quantity2, // trigger price price: slOrder?.trigger_price, // entryPrice: order.price, // @ts-ignore orderSide: order.side, // @ts-ignore orderType: slOrder.algo_type }); return { pnl: { tpPnL, slPnL } // roi: { // tpRoi, // slRoi, // }, }; } function areDatesEqual(date1, date2) { if (!date1 || !date2) return false; return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate(); } function isTrailingStopOrder(order) { return order.algo_type === types.OrderType.TRAILING_STOP; } function getNotional(order, dp = 2) { if (order.price && order.quantity) { return new utils.Decimal(order.price).mul(order.quantity).toFixed(dp, utils.Decimal.ROUND_DOWN); } return 0; } function convertApiOrderTypeToOrderEntryType(order) { if (order.algo_type === types.OrderType.TRAILING_STOP) { return order.algo_type; } const isAlgoOrder = order.algo_order_id !== void 0; if (isAlgoOrder && order.algo_type !== types.AlgoOrderRootType.BRACKET) { return `STOP_${order.type}`; } return order.type; } var upperCaseFirstLetter; var init_util = __esm({ "src/utils/util.ts"() { upperCaseFirstLetter = (str) => { if (str === void 0) return str; if (str.length === 0) return str; if (str.length === 1) return str.charAt(0).toUpperCase(); return str.charAt(0).toUpperCase() + str.toLowerCase().slice(1); }; } }); var SymbolContext, useSymbolContext; var init_symbolContext = __esm({ "src/components/provider/symbolContext.tsx"() { SymbolContext = React2.createContext( {} ); useSymbolContext = () => { return React2.useContext(SymbolContext); }; } }); var SymbolProvider; var init_symbolProvider = __esm({ "src/components/provider/symbolProvider.tsx"() { init_symbolContext(); SymbolProvider = (props) => { const { symbol, children } = props; const symbolInfo = hooks.useSymbolsInfo()[symbol]; const memoizedValue = React2.useMemo(() => { return { symbol, base_dp: symbolInfo("base_dp"), quote_dp: symbolInfo("quote_dp"), base_tick: symbolInfo("base_tick"), quote_tick: symbolInfo("quote_tick"), base: symbolInfo("base"), quote: symbolInfo("quote"), origin: symbolInfo(), quote_max: symbolInfo("quote_max"), quote_min: symbolInfo("quote_min") }; }, [symbol, symbolInfo]); return /* @__PURE__ */ jsxRuntime.jsx(SymbolContext.Provider, { value: memoizedValue, children }); }; } }); var ShareButton; var init_shareButton_ui = __esm({ "src/components/shareButton/shareButton.ui.tsx"() { ShareButton = (props) => { if (props.sharePnLConfig == null) { return null; } return /* @__PURE__ */ jsxRuntime.jsx( "button", { type: "button", onClick: (e) => { e.stopPropagation(); props.showModal(); }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.ShareIcon, { color: "white", opacity: 0.54, size: props.iconSize ?? 16 }) } ); }; } }); var useShareButtonScript; var init_shareButton_script = __esm({ "src/components/shareButton/shareButton.script.tsx"() { useShareButtonScript = (props) => { const { sharePnLConfig, order, iconSize } = props; const { t } = i18n.useTranslation(); const { getFirstRefCode } = hooks.useReferralInfo(); const refCode = React2.useMemo(() => { return getFirstRefCode()?.code; }, [getFirstRefCode]); const leverage = hooks.useLeverageBySymbol(order.symbol); const showModal = () => { ui.modal.show(props.modalId, { pnl: { entity: { symbol: order.symbol, pnl: order.realized_pnl, side: order.side == "BUY" ? t("share.pnl.share.long") : t("share.pnl.share.short"), openPrice: order.average_executed_price, openTime: order.updated_time, quantity: order.quantity }, refCode, leverage, ...sharePnLConfig } }); }; return { iconSize, sharePnLConfig, showModal }; }; } }); var ShareButtonWidget; var init_shareButton_widget = __esm({ "src/components/shareButton/shareButton.widget.tsx"() { init_shareButton_script(); init_shareButton_ui(); ShareButtonWidget = (props) => { const state = useShareButtonScript(props); return /* @__PURE__ */ jsxRuntime.jsx(ShareButton, { ...state }); }; } }); // src/components/shareButton/index.ts var init_shareButton = __esm({ "src/components/shareButton/index.ts"() { init_shareButton_ui(); init_shareButton_widget(); } }); var AvgPrice; var init_avgPrice = __esm({ "src/components/orderList/desktop/avgPrice.tsx"() { AvgPrice = React2.memo((props) => { const symbolsInfo = hooks.useSymbolsInfo(); const info = symbolsInfo[props.symbol]; if (!props.value) { return "--"; } return /* @__PURE__ */ jsxRuntime.jsx( ui.Text.numeral, { padding: false, dp: info("quote_dp", 2), rm: utils.Decimal.ROUND_UP, children: props.value } ); }); } }); var BracketOrderPrice, EditBracketOrder, Price; var init_bracketOrderPrice = __esm({ "src/components/orderList/desktop/bracketOrderPrice.tsx"() { init_util(); init_symbolContext(); BracketOrderPrice = (props) => { const { order } = props; const { quote_dp, base_dp } = useSymbolContext(); const { t } = i18n.useTranslation(); const { sl_trigger_price, tp_trigger_price } = React2.useMemo(() => { if (!("algo_type" in order) || !Array.isArray(order.child_orders)) { return {}; } return hooks.utils.findTPSLFromOrder(props.order.child_orders[0]); }, [props.order]); const { pnl } = calcBracketRoiAndPnL(order); if (!tp_trigger_price && !sl_trigger_price) { return "--"; } return /* @__PURE__ */ jsxRuntime.jsx( ui.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { direction: "column", itemAlign: "start", gap: 1, children: [ typeof pnl.tpPnL !== "undefined" && /* @__PURE__ */ jsxRuntime.jsx( ui.Text.numeral, { prefix: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { intensity: 80, children: [ `${t("tpsl.tpPnl")}:`, " \xA0" ] }), suffix: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { intensity: 20, children: " USDC" }), dp: quote_dp, color: "buy", showIdentifier: true, children: pnl.tpPnL } ), typeof pnl.slPnL !== "undefined" && /* @__PURE__ */ jsxRuntime.jsx( ui.Text.numeral, { prefix: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { intensity: 80, children: [ `${t("tpsl.slPnl")}:`, " \xA0" ] }), suffix: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { intensity: 20, children: " USDC" }), dp: quote_dp, color: "sell", children: pnl.slPnL } ) ] }), className: "oui-bg-base-6", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { itemAlign: "center", justify: "start", gap: 2, children: [ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { direction: "column", justify: "start", itemAlign: "start", children: [ /* @__PURE__ */ jsxRuntime.jsx(Price, { type: "TP", value: tp_trigger_price, quote_dp }), /* @__PURE__ */ jsxRuntime.jsx(Price, { type: "SL", value: sl_trigger_price, quote_dp }) ] }), /* @__PURE__ */ jsxRuntime.jsx(EditBracketOrder, { order }) ] }) } ); }; EditBracketOrder = (props) => { const { order } = props; const onEdit = () => { ui.modal.show("EditBracketOrderDialogId", { order }); }; return /* @__PURE__ */ jsxRuntime.jsx( ui.EditIcon, { size: 16, className: "oui-text-base-contrast oui-cursor-pointer", onClick: () => { onEdit(); } } ); }; Price = (props) => { const { type, value, quote_dp } = props; const { t } = i18n.useTranslation(); return value ? /* @__PURE__ */ jsxRuntime.jsx( ui.Text.numeral, { className: ui.cn( "oui-gap-0 oui-decoration-white/20 oui-border-b oui-border-dashed oui-border-base-contrast-12", type === "TP" ? "oui-text-trade-profit" : "oui-text-trade-loss" ), rule: "price", dp: quote_dp, prefix: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "oui-text-base-contrast-54", children: [ type === "TP" ? `${t("tpsl.tp")} -` : `${t("tpsl.sl")} -`, "\xA0" ] }), children: value }, "tp" ) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {}); }; } }); var OrderListContext, useOrderListContext; var init_orderListContext = __esm({ "src/components/orderList/orderListContext.tsx"() { OrderListContext = React2.createContext( {} ); useOrderListContext = () => { return React2.useContext(OrderListContext); }; } }); var CancelButton; var init_cancelBtn = __esm({ "src/components/orderList/desktop/cancelBtn.tsx"() { init_orderListContext(); CancelButton = (props) => { const { order } = props; const { t } = i18n.useTranslation(); const { onCancelOrder } = useOrderListContext(); const [isLoading, setIsLoading] = React2.useState(false); return /* @__PURE__ */ jsxRuntime.jsx( ui.ThrottledButton, { size: "sm", variant: "outlined", color: "secondary", onClick: (event) => { if (!onCancelOrder) return; event.preventDefault(); event.stopPropagation(); setIsLoading(true); onCancelOrder(order).then( (res) => res, (error) => { ui.toast.error(error.message); } ).finally(() => { setIsLoading(false); }); }, loading: isLoading, children: t("common.cancel") } ); }; } }); // src/type.ts var init_type = __esm({ "src/type.ts"() { } }); var ConfirmContent; var init_confirmContent = __esm({ "src/components/orderList/desktop/editOrder/confirmContent.tsx"() { init_type(); ConfirmContent = React2.memo((props) => { const { type, base, value, cancelPopover, isSubmitting, onConfirm } = props; const { t } = i18n.useTranslation(); const renderLabel = () => { const common = { values: { base, value: utils.commify(value) }, components: [/* @__PURE__ */ jsxRuntime.jsx("span", { className: "oui-text-warning-darken" }, "0")] }; switch (type) { case 0 /* quantity */: return ( // @ts-ignore /* @__PURE__ */ jsxRuntime.jsx(i18n.Trans, { i18nKey: "order.edit.confirm.quantity", ...common }) ); case 1 /* price */: return ( // @ts-ignore /* @__PURE__ */ jsxRuntime.jsx(i18n.Trans, { i18nKey: "order.edit.confirm.price", ...common }) ); case 2 /* triggerPrice */: return ( // @ts-ignore /* @__PURE__ */ jsxRuntime.jsx(i18n.Trans, { i18nKey: "order.edit.confirm.triggerPrice", ...common }) ); case 3 /* callbackValue */: return ( // @ts-ignore /* @__PURE__ */ jsxRuntime.jsx(i18n.Trans, { i18nKey: "order.edit.confirm.callbackValue", ...common }) ); case 4 /* callbackRate */: return ( // @ts-ignore /* @__PURE__ */ jsxRuntime.jsx(i18n.Trans, { i18nKey: "order.edit.confirm.callbackRate", ...common }) ); } }; return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oui-relative oui-pt-5", children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "desktop:oui-text-sm oui-text-2xs oui-text-base-contrast-54", children: renderLabel() }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oui-mt-5 oui-grid oui-grid-cols-2 oui-gap-2", children: [ /* @__PURE__ */ jsxRuntime.jsx( ui.Button, { color: "secondary", size: "md", onClick: cancelPopover, disabled: isSubmitting, children: t("common.cancel") } ), /* @__PURE__ */ jsxRuntime.jsx(ui.ThrottledButton, { size: "md", loading: isSubmitting, onClick: onConfirm, children: t("common.confirm") }) ] }), /* @__PURE__ */ jsxRuntime.jsx( "button", { className: "oui-absolute oui-right-0 oui-top-0 oui-text-base-contrast-54", onClick: cancelPopover, children: /* @__PURE__ */ jsxRuntime.jsx(ui.CloseIcon, { size: 16, color: "white", opacity: 1 }) } ) ] }); }); ConfirmContent.displayName = "ConfirmContent"; } }); function usePopoverState(props) { const { originValue, value, setValue } = props; const [open, setOpen] = React2.useState(false); const [editing, setEditing] = React2.useState(false); const containerRef = React2.useRef(null); const closePopover = React2.useCallback(() => { setOpen(false); setEditing(false); }, []); const cancelPopover = React2.useCallback(() => { closePopover(); setValue(originValue); }, [originValue]); const onClick = React2.useCallback( async (event) => { event.stopPropagation(); event.preventDefault(); if (value === originValue) { setEditing(false); return; } setOpen(true); }, [value, originValue] ); React2.useEffect(() => { const handleClickOutside = (event) => { if (containerRef.current && !containerRef.current.contains(event.target) && !open) { cancelPopover(); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [open, cancelPopover]); return { value, setValue, open, setOpen, editing, setEditing, containerRef, closePopover, cancelPopover, onClick }; } var init_usePopoverState = __esm({ "src/components/orderList/desktop/hooks/usePopoverState.ts"() { } }); function useValidateField(props) { const { order, field, originValue, value, fieldValues } = props; const changeFields = fieldValues || { [field]: value }; const orderType = React2.useMemo( () => convertApiOrderTypeToOrderEntryType(order), [order] ); const { errors, validate, clearErrors } = hooks.useOrderEntity({ symbol: order.symbol, order_type: orderType, side: order.side, ...changeFields }); const { getErrorMsg } = reactApp.useOrderEntryFormErrorMsg(errors); const error = React2.useMemo( () => field ? getErrorMsg(field) : void 0, [getErrorMsg, field] ); React2.useEffect(() => { if (value === originValue) { clearErrors(); return; } validate().then((order2) => { }).catch((errors2) => { }); }, [value, originValue]); return { error, errors, getErrorMsg }; } var init_useValidateField = __esm({ "src/components/orderList/desktop/hooks/useValidateField.ts"() { init_util(); } }); var EditableCellInput; var init_editableCellInput = __esm({ "src/components/orderList/desktop/components/editableCellInput.tsx"() { EditableCellInput = (props) => { const { onClick } = props; const inputRef = React2.useRef(null); const handleKeyDown = React2.useCallback( (event) => { if (event.key === "Enter") { onClick(event); } }, [onClick] ); React2.useEffect(() => { const input = inputRef.current; if (input) { const length = input.value.length; input.setSelectionRange(length, length); } }, [props.value]); return /* @__PURE__ */ jsxRuntime.jsx( ui.Input.tooltip, { ref: inputRef, tooltip: props.error, autoComplete: "off", autoFocus: true, type: "text", size: "sm", placeholder: props.readonly ? "" : props.placeholder || "", id: props.id, name: props.name, color: props.error ? "danger" : void 0, value: props.readonly ? "" : props.value || "", onValueChange: props.onChange, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, onFocus: props.onFocus, onBlur: props.onBlur, onKeyDown: props.handleKeyDown || handleKeyDown, formatters: props.overrideFormatters || [ ...props.formatters ?? types.EMPTY_LIST, ui.inputFormatter.numberFormatter, ui.inputFormatter.decimalPointFormatter ], classNames: { root: "oui-bg-base-700 oui-px-2 oui-py-1 oui-rounded", input: "oui-pr-2" }, readOnly: props.readonly, suffix: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gapX: 1, children: [ props.suffix, /* @__PURE__ */ jsxRuntime.jsx("button", { onClick, disabled: !!props.error, children: /* @__PURE__ */ jsxRuntime.jsx( ui.CheckIcon, { size: 18, color: "white", opacity: 1, className: ui.cn( "oui-opacity-50", props.error ? "oui-cursor-not-allowed" : "oui-cursor-pointer hover:oui-opacity-100" ) } ) }) ] }) } ); }; } }); var PreviewCell; var init_previewCell = __esm({ "src/components/orderList/desktop/components/previewCell.tsx"() { init_util(); PreviewCell = React2.memo((props) => { const { status: status2, value, disabled } = props; return /* @__PURE__ */ jsxRuntime.jsx( "div", { className: ui.cn( "oui-flex oui-items-center oui-justify-start", "oui-relative oui-max-w-[110px] oui-gap-1 oui-font-semibold", isGrayCell(status2) && "oui-text-base-contrast-20", props.className ), onClick: (e) => { if (!disabled) { e.stopPropagation(); e.preventDefault(); props.setEditing(true); } }, children: /* @__PURE__ */ jsxRuntime.jsx( ui.Flex, { r: "base", className: ui.cn( "oui-h-[28px] oui-min-w-[70px]", !disabled && "oui-border oui-border-line-12 oui-bg-base-7 oui-px-2" ), children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "2xs", children: [ utils.commifyOptional(value), props.suffix ] }) } ) } ); }); } }); var ActivedPriceCell; var init_activedPriceCell = __esm({ "src/components/orderList/desktop/components/activedPriceCell.tsx"() { init_type(); init_util(); init_symbolContext(); init_orderListContext(); init_confirmContent(); init_usePopoverState(); init_useValidateField(); init_editableCellInput(); init_previewCell(); ActivedPriceCell = (props) => { const { order } = props; const { t } = i18n.useTranslation(); const originValue = order.activated_price?.toString() ?? ""; const [value, setValue] = React2.useState(originValue); const [submitting, setSubmitting] = React2.useState(false); const disabled = props.disabled || order.is_activated || order.is_triggered; const { editAlgoOrder } = useOrderListContext(); const { base, quote_dp } = useSymbolContext(); const { open, setOpen, editing, setEditing, containerRef, closePopover, cancelPopover, onClick } = usePopoverState({ originValue, value, setValue }); const { error } = useValidateField({ order, field: "activated_price", originValue, value }); const onConfirm = React2.useCallback(() => { setSubmitting(true); const data = { algo_order_id: order.algo_order_id, activated_price: value }; editAlgoOrder(order.algo_order_id.toString(), data).then((result) => { closePopover(); setValue(value); }).catch((err) => { ui.toast.error(err.message); cancelPopover(); }).finally(() => setSubmitting(false)); }, [order.algo_order_id, value, cancelPopover]); if (!order.activated_price && order.type === types.OrderType.MARKET) { return /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("common.marketPrice") }); } const renderContent = () => { if (!editing || disabled) { return /* @__PURE__ */ jsxRuntime.jsx( PreviewCell, { value, status: getOrderStatus(order), setEditing, disabled } ); } return /* @__PURE__ */ jsxRuntime.jsx( EditableCellInput, { value, onChange: setValue, onClick, error: value ? error : t("orderEntry.triggerPrice.error.required"), formatters: [ui.inputFormatter.dpFormatter(quote_dp)] } ); }; return /* @__PURE__ */ jsxRuntime.jsx( ui.Popover, { open, onOpenChange: setOpen, content: /* @__PURE__ */ jsxRuntime.jsx( ConfirmContent, { type: 2 /* triggerPrice */, base, value, cancelPopover, isSubmitting: submitting, onConfirm } ), children: /* @__PURE__ */ jsxRuntime.jsx( "div", { onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, ref: containerRef, children: renderContent() } ) } ); }; } }); var PriceCell; var init_priceCell = __esm({ "src/components/orderList/desktop/components/priceCell.tsx"() { init_type(); init_util(); init_symbolContext(); init_orderListContext(); init_confirmContent(); init_usePopoverState(); init_useValidateField(); init_editableCellInput(); init_previewCell(); PriceCell = (props) => { const { order } = props; const { t } = i18n.useTranslation(); const originValue = React2.useMemo(() => { if (order.type === types.OrderType.MARKET && !order.price) { return "Market"; } return order.price?.toString() ?? "Market"; }, [order.price, order.type]); const [value, setValue] = React2.useState(originValue); const isAlgoOrder = order?.algo_order_id !== void 0; const [submitting, setSubmitting] = React2.useState(false); const { editOrder, editAlgoOrder } = useOrderListContext(); const { base, quote_dp } = useSymbolContext(); const { open, setOpen, editing, setEditing, containerRef, closePopover, cancelPopover, onClick } = usePopoverState({ originValue, value, setValue }); const { error, getErrorMsg } = useValidateField({ order, field: "order_price", originValue, value, fieldValues: { order_price: value, // add order quantity to validate total order_quantity: order.quantity?.toString(), // need to pass trigger_price to validate order_price, because order_price is depend on trigger_price when type is stop limit trigger_price: order.trigger_price?.toString() } }); const onConfirm = () => { setSubmitting(true); let order_id = order.order_id; let data = { symbol: order.symbol, order_type: order.type, side: order.side, order_quantity: order.quantity, order_price: value }; if (order.reduce_only !== void 0) { data.reduce_only = order.reduce_only; } if (order.order_tag) { data.order_tag = order.order_tag; } if (order.client_order_id) { data.client_order_id = order.client_order_id; } if (isAlgoOrder) { order_id = order.algo_order_id; data = { ...data, order_id, price: value, algo_order_id: order_id }; } if (order.tag !== void 0) { data["order_tag"] = order.tag; } let future; if (order.algo_order_id !== void 0) { future = editAlgoOrder(order.algo_order_id.toString(), data); } else { future = editOrder(order.order_id.toString(), data); } future.then((result) => { closePopover(); setValue(value); }).catch((err) => { ui.toast.error(err.message); cancelPopover(); }).finally(() => setSubmitting(false)); }; const isAlgoMarketOrder = order.algo_order_id && order.type == "MARKET"; if (isAlgoMarketOrder || value === "Market") { return /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("common.marketPrice") }); } const renderContent = () => { if (!editing || props.disabled) { return /* @__PURE__ */ jsxRuntime.jsx( PreviewCell, { value, status: getOrderStatus(order), setEditing, disabled: props.disabled } ); } return /* @__PURE__ */ jsxRuntime.jsx( EditableCellInput, { value, onChange: setValue, onClick, error: error || getErrorMsg("total"), formatters: [ui.inputFormatter.dpFormatter(quote_dp)] } ); }; return /* @__PURE__ */ jsxRuntime.jsx( ui.Popover, { open, onOpenChange: setOpen, content: /* @__PURE__ */ jsxRuntime.jsx( ConfirmContent, { type: 1 /* price */, base, value, cancelPopover, isSubmitting: submitting, onConfirm } ), children: /* @__PURE__ */ jsxRuntime.jsx( "div", { onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, ref: containerRef, children: renderContent() } ) } ); }; } }); function calcTPSLPnL(props) { const { order, position, quote_dp } = props; if (!position) return { sl_trigger_price: void 0, tp_trigger_price: void 0, slPnL: void 0, tpPnL: void 0 }; const isTPSLOrder = "algo_type" in order && Array.isArray(order.child_orders); const { sl_trigger_price, tp_trigger_price } = isTPSLOrder ? hooks.findTPSLFromOrder(order) : { sl_trigger_price: void 0, tp_trigger_price: void 0 }; const { sl_order_price, tp_order_price } = isTPSLOrder ? hooks.findTPSLOrderPriceFromOrder(order) : { sl_order_price: void 0, tp_order_price: void 0 }; let quantity2 = order.quantity; if (quantity2 === 0) { if (order.child_orders?.[0].type === "CLOSE_POSITION") { quantity2 = position.position_qty; } } const avgOpenPrice = position.average_open_price; const tpPnL = typeof quantity2 === "number" && typeof tp_trigger_price === "number" && typeof avgOpenPrice === "number" ? hooks.utils.priceToPnl( { qty: quantity2, price: tp_trigger_price, entryPrice: position.average_open_price, orderSide: order.side, orderType: types.AlgoOrderType.TAKE_PROFIT }, { symbol: { quote_dp } } ) : void 0; const slPnL = typeof quantity2 === "number" && typeof sl_trigger_price === "number" && typeof avgOpenPrice === "number" ? hooks.utils.priceToPnl( { qty: quantity2, price: sl_trigger_price, entryPrice: position.average_open_price, orderSide: order.side, orderType: types.AlgoOrderType.STOP_LOSS }, { symbol: { quote_dp } } ) : void 0; return { sl_trigger_price, tp_trigger_price, sl_order_price, tp_order_price, slPnL, tpPnL }; } var TPSLOrderRowContext, useTPSLOrderRowContext, TPSLOrderRowProvider; var init_tpslOrderRowContext = __esm({ "src/components/orderList/tpslOrderRowContext.tsx"() { init_symbolContext(); TPSLOrderRowContext = React2.createContext( {} ); useTPSLOrderRowContext = () => { return React2.useContext(TPSLOrderRowContext); }; TPSLOrderRowProvider = (props) => { const { order, children } = props; const { quote_dp } = useSymbolContext(); const [position, setPosition] = React2.useState(); const [doDeleteOrder] = hooks.useMutation("/v1/algo/order", "DELETE"); const [doUpdateOrder] = hooks.useMutation("/v1/algo/order", "PUT"); const config = hooks.useSWRConfig(); const { state } = hooks.useAccount(); const positionKey = React2.useMemo(() => { return hooks.unstable_serialize(() => ["/v1/positions", state.accountId]); }, [state.accountId]); const onCancelOrder = hooks.useMemoizedFn(async (order2) => { return doDeleteOrder(null, { order_id: order2.algo_order_id, symbol: order2.symbol }); }); const onUpdateOrder = hooks.useMemoizedFn( async (order2, params) => { return doUpdateOrder({ order_id: order2.algo_order_id, child_orders: order2.child_orders.map((order3) => ({ order_id: order3.algo_order_id, quantity: params.order_quantity })) }); } ); const getRelatedPosition = hooks.useMemoizedFn( (symbol) => { const positions = config.cache.get(positionKey); return positions?.data?.rows?.find( (p) => p.symbol === symbol ); } ); const { sl_trigger_price, tp_trigger_price, tpPnL, slPnL, sl_order_price, tp_order_price } = calcTPSLPnL({ order, position, quote_dp }); React2.useEffect(() => { if ("algo_type" in order || (order?.reduce_only ?? false)) { const position2 = getRelatedPosition(order.symbol); if (position2) { setPosition(position2); } } }, [order.symbol]); const memoizedValue = React2.useMemo(() => { return { order, sl_trigger_price, tp_trigger_price, sl_order_price, tp_order_price, tpPnL, slPnL, position, onCancelOrder, onUpdateOrder, getRelatedPosition }; }, [ order, sl_trigger_price, tp_trigger_price, sl_order_price, tp_order_price, tpPnL, slPnL, position, onCancelOrder, onUpdateOrder, getRelatedPosition ]); return /* @__PURE__ */ jsxRuntime.jsx(TPSLOrderRowContext.Provider, { value: memoizedValue, children }); }; } }); var InnerInput; var init_innerInput = __esm({ "src/components/orderList/desktop/editOrder/innerInput.tsx"() { InnerInput = (props) => { const { inputRef, dp, value, setValue, setEditing, error, handleKeyDown, onClick, onClose, onFocus, onBlur, hintInfo } = props; React2.useEffect(() => { const input = inputRef.current; if (input) { const length = input.value.length; input.setSelectionRange(length, length); } setEditing(true); }, []); const open = (hintInfo?.length || 0) > 0; return /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: hintInfo, open, children: /* @__PURE__ */ jsxRuntime.jsx( ui.Input, { ref: inputRef, type: "text", size: "sm", formatters: [ ui.inputFormatter.numberFormatter, ui.inputFormatter.dpFormatter(dp), ui.inputFormatter.currencyFormatter ], value, onValueChange: (e) => setValue(e), helpText: error, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, autoComplete: "off", onFocus, onBlur, onKeyDown: handleKeyDown, autoFocus: true, color: open ? "danger" : void 0, classNames: { root: "oui-bg-base-700 oui-px-2 oui-py-1 oui-rounded", input: "oui-pr-2" }, suffix: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick, children: /* @__PURE__ */ jsxRuntime.jsx( ui.CheckIcon, { size: 18, color: "white", opacity: 1, className: "oui-cursor-pointer oui-opacity-50 hover:oui-opacity-100" } ) }) } ) }); }; } }); var QuantityCell, PreviewCell2, EditState, Buttons; var init_quantityCell = __esm({ "src/components/orderList/desktop/components/quantityCell.tsx"() { init_type(); init_util(); init_symbolContext(); init_orderListContext(); init_tpslOrderRowContext(); init_confirmContent(); init_innerInput(); QuantityCell = (props) => { const { order } = props; const { reduce_only } = order; const originValue = order.quantity.toString(); const [value, setValue] = React2.useState(originValue); const [editing, setEditing] = React2.useState(false); const { t } = i18n.useTranslation(); const [open, setOpen] = React2.useState(false); const [error, setError] = React2.useState(); const { editOrder, editAlgoOrder, checkMinNotional } = useOrderListContext(); const { onUpdateOrder: onUpdateTPSLOrder, position } = useTPSLOrderRowContext(); const { base_dp, base, base_tick } = useSymbolContext(); const setQuantity = async (qty, maxQty) => { setValue(qty); const positionQty = Math.abs(position?.position_qty || 0); if (position && reduce_only && Number(qty) > positionQty) { setError( t("orders.quantity.lessThanPosition", { quantity: positionQty }) ); } else { const quantity2 = Number(qty); if (maxQty && quantity2 > maxQty) { setError( t("orders.quantity.lessThan", { quantity: utils.commifyOptional(maxQty, { fix: base_dp }) }) ); } else { setError(void 0); } } return Promise.resolve(); }; React2.useEffect(() => { setQuantity(order.quantity.toString()); }, [props.order.quantity]); const closePopover = () => { setOpen(false); setEditing(false); }; const cancelPopover = () => { setOpen(false); setQuantity(order.quantity.toString()); setEditing(false); }; const [isSubmitting, setIsSubmitting] = React2.useState(false); const inputRef = React2.useRef(null); const clickHandler = () => { if (!!error) { return; } if (Number(value) === Number(order.quantity)) { setEditing(false); return; } const price2 = order.algo_order_id !== void 0 ? order.trigger_price : order.price; if (price2 !== null && order.reduce_only !== true) { const notionalText = checkMinNotional(order.symbol, price2, value); if (notionalText) { ui.toast.error(notionalText); setIsSubmitting(false); cancelPopover(); return; } } setOpen(true); }; const onClick = (event) => { event?.stopPropagation(); event?.preventDefault(); clickHandler(); }; const handleKeyDown = (event) => { if (event.key === "Enter") { event?.stopPropagation(); event?.preventDefault(); clickHandler(); } }; const onConfirm = React2.useCallback(() => { setIsSubmitting(true); let params = { symbol: order.symbol, order_type: order.type, side: order.side, order_price: order.price, order_quantity: value, // reduce_only: Boolean(order.reduce_only), algo_order_id: order.algo_order_id }; if (typeof params.algo_order_id !== "undefined" && params.order_type === "MARKET") { const { order_price, ...rest } = params; params = rest; } if (typeof order.reduce_only !== "undefined") { params.reduce_only = order.reduce_only; } if (order.order_tag) { params.order_tag = order.order_tag; } if (order.client_order_id) { params.client_order_id = order.client_order_id; } if (order?.visible_quantity === 0) { params["visible_quantity"] = 0; } if (order?.tag !== void 0) { params["order_tag"] = order.tag; } let future; if ("algo_type" in order && order.algo_type === types.AlgoOrderRootType.TP_SL) { future = onUpdateTPSLOrder(order, params); } else { if (order.algo_order_id !== void 0) { future = editAlgoOrder(order.algo_order_id.toString(), params); } else { future = editOrder(order.order_id.toString(), params); } } future.then( (result) => { closePopover(); setQuantity(value.toString()); }, (err) => { ui.toast.error(err.message); setQuantity(order.quantity.toString()); cancelPopover(); } ).finally(() => setIsSubmitting(false)); }, [value]); const componentRef = React2.useRef(null); const quantitySliderRef = React2.useRef(null); const handleClickOutside = (event) => { if (componentRef.current && quantitySliderRef.current && !componentRef.current.contains(event.target) && !quantitySliderRef.current.contains(event.target) && !open) { cancelPopover(); } }; React2.useEffect(() => { document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [open, order.quantity]); const renderContent = () => { if (!editing || props.disabled) { return /* @__PURE__ */ jsxRuntime.jsx( PreviewCell2, { order, value, setEditing, disable: props.disabled } ); } return /* @__PURE__ */ jsxRuntime.jsx( EditState, { inputRef, quantitySliderRef, base_dp, base_tick, quantity: value, setQuantity, editing, setEditing, handleKeyDown, onClick, onClose: cancelPopover, symbol: order.symbol, reduce_only, positionQty: position?.position_qty, error, confirmOpen: open, side: order.side, order, setError } ); }; return /* @__PURE__ */ jsxRuntime.jsx( ui.Popover, { open, onOpenChange: setOpen, content: /* @__PURE__ */ jsxRuntime.jsx( ConfirmContent, { type: 0 /* quantity */, base, value, cancelPopover, isSubmitting, onConfirm } ), contentProps: { onOpenAutoFocus: (e) => { } }, children: /* @__PURE__ */ jsxRuntime.jsx( "div", { onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, ref: componentRef, children: renderContent() } ) } ); }; PreviewCell2 = (props) => { const { order, value } = props; const executed = order.total_executed_quantity; return /* @__PURE__ */ jsxRuntime.jsxs( ui.Flex, { direction: "row", justify: "start", gap: 1, className: ui.cn( "oui-relative oui-max-w-[110px]", order.side === types.OrderSide.BUY && "oui-text-trade-profit", order.side === types.OrderSide.SELL && "oui-text-trade-loss", grayCell(order) && "oui-text-base-contrast-20" ), onClick: (e) => { e.stopPropagation(); e.preventDefault(); props.setEditing(true); }, children: [ "algo_type" in order && order.algo_type === types.AlgoOrderRootType.TP_SL ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx("span", { children: executed }), /* @__PURE__ */ jsxRuntime.jsx("span", { children: "/" }) ] }), /* @__PURE__ */ jsxRuntime.jsx( ui.Flex, { r: "base", className: ui.cn( "oui-h-[28px] oui-min-w-[70px]", !props.disable && "oui-border oui-border-line-12 oui-bg-base-7 oui-px-2" ), children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "2xs", children: value }) } ) ] } ); }; EditState = (props) => { const { inputRef, quantitySliderRef, base_dp, base_tick, quantity: quantity2, setQuantity, setEditing, handleKeyDown, onClick, onClose, error, symbol, reduce_only, positionQty, confirmOpen, order } = props; const maxBuyQty = hooks.useMaxQty(symbol, order.side, order.reduce_only); const maxQty = React2.useMemo(() => {