diff --git a/apps/web/src/features/trade/components/positions/BottomTabs.tsx b/apps/web/src/features/trade/components/positions/BottomTabs.tsx index da65655..45cf4da 100644 --- a/apps/web/src/features/trade/components/positions/BottomTabs.tsx +++ b/apps/web/src/features/trade/components/positions/BottomTabs.tsx @@ -1,6 +1,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@workspace/ui/components/tabs" import { usePositions } from "../../hooks/usePositions" -import { useOrders } from "../../hooks/useOrders" +import { hasFrozenOrders, useOrders } from "../../hooks/useOrders" +import { OrderExecutionFrozenBanner } from "./OrderExecutionFrozenBanner" import { PositionsList } from "./PositionsList" import { OrdersList } from "./OrdersList" import type { Position } from "../../hooks/usePositions" @@ -17,6 +18,7 @@ export function BottomTabs({ onSelectPosition }: Props) { return ( + Positions {positions.length > 0 && `(${positions.length})`} diff --git a/apps/web/src/features/trade/components/positions/OrderExecutionFrozenBanner.tsx b/apps/web/src/features/trade/components/positions/OrderExecutionFrozenBanner.tsx new file mode 100644 index 0000000..11f6389 --- /dev/null +++ b/apps/web/src/features/trade/components/positions/OrderExecutionFrozenBanner.tsx @@ -0,0 +1,38 @@ +import { useState } from "react" +import { ORDER_EXECUTION_FROZEN_MESSAGE } from "@/lib/soroban/errors" + +const SESSION_KEY = "so4-order-execution-frozen-dismissed" + +type Props = { + visible: boolean +} + +export function OrderExecutionFrozenBanner({ visible }: Props) { + const [dismissed, setDismissed] = useState( + () => sessionStorage.getItem(SESSION_KEY) === "1", + ) + + if (!visible || dismissed) return null + + function dismiss() { + sessionStorage.setItem(SESSION_KEY, "1") + setDismissed(true) + } + + return ( +
+ {ORDER_EXECUTION_FROZEN_MESSAGE} + +
+ ) +} diff --git a/apps/web/src/features/trade/components/positions/OrdersList.tsx b/apps/web/src/features/trade/components/positions/OrdersList.tsx index fa030d3..c5b413e 100644 --- a/apps/web/src/features/trade/components/positions/OrdersList.tsx +++ b/apps/web/src/features/trade/components/positions/OrdersList.tsx @@ -63,6 +63,11 @@ export function OrdersList() { > {o.isLong ? "Long" : "Short"} + {o.status === "frozen" && ( + + Frozen + + )} {o.orderType} diff --git a/apps/web/src/features/trade/hooks/useOrders.ts b/apps/web/src/features/trade/hooks/useOrders.ts index 4ca81bb..2165ef6 100644 --- a/apps/web/src/features/trade/hooks/useOrders.ts +++ b/apps/web/src/features/trade/hooks/useOrders.ts @@ -9,6 +9,8 @@ export type OrderType = | "StopLoss" | "Swap" +export type OrderStatus = "active" | "frozen" + export type Order = { key: string account: string @@ -21,6 +23,7 @@ export type Order = { acceptablePrice: number orderType: OrderType isLong: boolean + status: OrderStatus createdAt: number // unix timestamp ms // TODO: Add executionFee, swapPath, decreaseSwapType when live } @@ -42,11 +45,16 @@ async function fetchOrders(account: string): Promise> { acceptablePrice: 65_100, orderType: "LimitIncrease", isLong: true, + status: "active", createdAt: Date.now() - 1000 * 60 * 30, }, ] } +export function hasFrozenOrders(orders: Array): boolean { + return orders.some((order) => order.status === "frozen") +} + const DUMMY_ACCOUNT = "GDUMMY...STELLAR" export function useOrders(account = DUMMY_ACCOUNT) { diff --git a/apps/web/src/lib/soroban/errors.ts b/apps/web/src/lib/soroban/errors.ts index 42b0fb6..c451005 100644 --- a/apps/web/src/lib/soroban/errors.ts +++ b/apps/web/src/lib/soroban/errors.ts @@ -11,6 +11,9 @@ const RPC_ERROR_MESSAGES: Record = { op_underfunded: "Wallet balance is too low for this operation.", } +export const ORDER_EXECUTION_FROZEN_MESSAGE = + "Order execution is temporarily paused. Your order has been saved and will execute when trading resumes." + const CONTRACT_ERROR_MESSAGES: Record = { INSUFFICIENT_COLLATERAL: "Insufficient collateral for this position.", INSUFFICIENT_LIQUIDITY: "Insufficient liquidity for this trade.", @@ -24,6 +27,7 @@ const CONTRACT_ERROR_MESSAGES: Record = { POSITION_NOT_FOUND: "Position was not found.", SLIPPAGE_EXCEEDED: "Price moved beyond your slippage limit. Try again.", UNAUTHORIZED: "You are not authorized to perform this action.", + ORDER_EXECUTION_FROZEN: ORDER_EXECUTION_FROZEN_MESSAGE, } const FALLBACK_MESSAGE = "Transaction failed. Please try again."