From f78857fd18955f5ba1be6ea49f5e1fbebd7de9b6 Mon Sep 17 00:00:00 2001 From: Darius Daniel Date: Tue, 2 Jun 2026 11:14:10 +0100 Subject: [PATCH 1/2] feat: add Stellar Expert explorer links to transaction toasts and fix env loading --- .../trade-panel/ConfirmationDialog.tsx | 289 ++++++++++++++---- .../features/trade/hooks/useFundingRate.ts | 12 +- .../trade/hooks/useOrderEventPolling.ts | 2 +- apps/web/src/features/trade/lib/query-keys.ts | 9 +- apps/web/src/features/trade/lib/stellar.ts | 24 +- .../hooks/{useTxSubmit.ts => useTxSubmit.tsx} | 17 +- apps/web/vite.config.ts | 50 ++- 7 files changed, 315 insertions(+), 88 deletions(-) rename apps/web/src/shared/hooks/{useTxSubmit.ts => useTxSubmit.tsx} (80%) diff --git a/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx b/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx index 6445007..e439de0 100644 --- a/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx +++ b/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx @@ -7,21 +7,36 @@ import { DialogTitle, } from "@workspace/ui/components/dialog" import { Button } from "@workspace/ui/components/button" -import { createSwapOrder, sendBatchOrderTxn, type DecreaseOrderParams, type IncreaseOrderParams } from "../../lib/stellar" +import { + createSwapOrder, + sendBatchOrderTxn, + type DecreaseOrderParams, + type IncreaseOrderParams, +} from "../../lib/stellar" import { formatUsd } from "../../lib/trade-math" import type { useTradeState } from "../../hooks/useTradeState" import { useWalletStore } from "@/features/wallet/store/wallet-store" import { useTradeFees } from "../../hooks/useTradeFees" import { getEstimatedEntryPrice, getPriceImpactPct } from "../../lib/pricing" import { estimateFee } from "@/lib/soroban/simulate" -import { buildBatchOrderTransaction, buildCreateOrderTransaction } from "@/lib/contracts/exchange-router-client" -import { toCreateOrderParams, toDecreaseOrderParams, encodeTokenAmount } from "../../lib/order-encoding" +import { + buildBatchOrderTransaction, + buildCreateOrderTransaction, +} from "@/lib/contracts/exchange-router-client" +import { + toCreateOrderParams, + toDecreaseOrderParams, + encodeTokenAmount, +} from "../../lib/order-encoding" import { fetchPriceUpdateDataForMarket } from "../../lib/pyth" -import { checkAllowance, buildApproveTransaction } from "@/lib/contracts/sac-token-client" +import { + checkAllowance, + buildApproveTransaction, +} from "@/lib/contracts/sac-token-client" import { prepareAndSign } from "@/lib/soroban/tx-builder" import { submitTx } from "@/shared/hooks/useTxSubmit" import { walletKit } from "@/features/wallet/lib/wallet-kit" -import { NETWORK, explorerTxUrl } from "@/app/config/network" +import { NETWORK } from "@/app/config/network" import { CONTRACTS } from "@/app/config/contracts" import { fetchFeeConfig } from "../../lib/data-store" import { useQuery } from "@tanstack/react-query" @@ -38,36 +53,66 @@ type Props = { totalFeesUsd: number } -export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPrice, liquidationPrice }: Props) { +export function ConfirmationDialog({ + open, + onClose, + tradeState, + sizeUsd, + entryPrice, + liquidationPrice, +}: Props) { const [isSubmitting, setIsSubmitting] = useState(false) const [networkFee, setNetworkFee] = useState(null) const [estimateError, setEstimateError] = useState(null) const [estimatingFee, setEstimatingFee] = useState(false) - const [allowanceState, setAllowanceState] = useState<"checking" | "sufficient" | "insufficient" | "approving" | "approved">("checking") + const [allowanceState, setAllowanceState] = useState< + "checking" | "sufficient" | "insufficient" | "approving" | "approved" + >("checking") const [approveError, setApproveError] = useState(null) const account = useWalletStore((state) => state.address) - const { tradeFlags, toTokenAddress, collateralAddress, leverage, fromAmount, triggerPrice, sidecarOrders, clearSidecarOrders } = - tradeState + const { + tradeFlags, + toTokenAddress, + collateralAddress, + leverage, + fromAmount, + triggerPrice, + sidecarOrders, + clearSidecarOrders, + } = tradeState - const fees = useTradeFees({ sizeUsd, marketAddress: tradeState.marketAddress, isIncrease: true, tradeType: tradeState.tradeType }) + const fees = useTradeFees({ + sizeUsd, + marketAddress: tradeState.marketAddress, + isIncrease: true, + tradeType: tradeState.tradeType, + }) const priceImpactPct = getPriceImpactPct(sizeUsd, fees.priceImpactUsd) - const estimatedEntryPrice = getEstimatedEntryPrice(entryPrice, priceImpactPct, tradeFlags.isLong) + const estimatedEntryPrice = getEstimatedEntryPrice( + entryPrice, + priceImpactPct, + tradeFlags.isLong + ) const slippageFactor = tradeState.advanced.slippagePct / 100 const acceptablePrice = tradeFlags.isLong ? entryPrice * (1 + slippageFactor) : entryPrice * (1 - slippageFactor) const { data: feeConfig } = useQuery({ - queryKey: queryKeys.feeConfig("stellar-mainnet", tradeState.marketAddress), + queryKey: queryKeys.trade.feeConfig( + "stellar-mainnet", + tradeState.marketAddress + ), queryFn: () => fetchFeeConfig(tradeState.marketAddress), staleTime: 120_000, enabled: !!tradeState.marketAddress && open, }) - const maxPositionError = !tradeFlags.isSwap && feeConfig && sizeUsd > feeConfig.maxPositionSizeUsd - ? `Maximum position size for ${tradeState.toTokenAddress}/USD is $${feeConfig.maxPositionSizeUsd.toLocaleString()}.` - : null + const maxPositionError = + !tradeFlags.isSwap && feeConfig && sizeUsd > feeConfig.maxPositionSizeUsd + ? `Maximum position size for ${tradeState.toTokenAddress}/USD is $${feeConfig.maxPositionSizeUsd.toLocaleString()}.` + : null const sidecarCreateOrders = useMemo((): Array => { if (!account || sidecarOrders.length === 0) return [] @@ -84,7 +129,16 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr orderType: order.type === "takeProfit" ? "LimitDecrease" : "StopLoss", receiveToken: collateralAddress, })) - }, [account, sidecarOrders, tradeState.marketAddress, collateralAddress, fromAmount, sizeUsd, tradeFlags.isLong, estimatedEntryPrice]) + }, [ + account, + sidecarOrders, + tradeState.marketAddress, + collateralAddress, + fromAmount, + sizeUsd, + tradeFlags.isLong, + estimatedEntryPrice, + ]) useEffect(() => { if (!open || !account || tradeFlags.isSwap || !collateralAddress) return @@ -93,8 +147,15 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr setAllowanceState("checking") setApproveError(null) try { - const needed = encodeTokenAmount(Number(fromAmount || "0"), collateralAddress) - const allowance = await checkAllowance(collateralAddress, account, CONTRACTS.exchangeRouter) + const needed = encodeTokenAmount( + Number(fromAmount || "0"), + collateralAddress + ) + const allowance = await checkAllowance( + collateralAddress, + account, + CONTRACTS.exchangeRouter + ) setAllowanceState(allowance >= needed ? "sufficient" : "insufficient") } catch { setAllowanceState("sufficient") @@ -119,44 +180,80 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr sizeDeltaUsd: sizeUsd, isLong: tradeFlags.isLong, acceptablePrice, - triggerPrice: tradeFlags.isMarket ? undefined : Number(triggerPrice) || estimatedEntryPrice, + triggerPrice: tradeFlags.isMarket + ? undefined + : Number(triggerPrice) || estimatedEntryPrice, orderType: tradeFlags.isMarket ? "MarketIncrease" : "LimitIncrease", leverage, } const priceUpdateData = await fetchPriceUpdateDataForMarket( tradeState.marketAddress, - toTokenAddress, + toTokenAddress ) const tx = sidecarCreateOrders.length ? await buildBatchOrderTransaction(account, [ - { actionType: "createOrder", orderParams: toCreateOrderParams(parentOrder, priceUpdateData), cancelKey: null }, - ...sidecarCreateOrders.map((order) => ({ actionType: "createOrder" as const, orderParams: toDecreaseOrderParams(order, priceUpdateData), cancelKey: null })), + { + actionType: "createOrder", + orderParams: toCreateOrderParams(parentOrder, priceUpdateData), + cancelKey: null, + }, + ...sidecarCreateOrders.map((order) => ({ + actionType: "createOrder" as const, + orderParams: toDecreaseOrderParams(order, priceUpdateData), + cancelKey: null, + })), ]) - : await buildCreateOrderTransaction(toCreateOrderParams(parentOrder, priceUpdateData)) + : await buildCreateOrderTransaction( + toCreateOrderParams(parentOrder, priceUpdateData) + ) const fee = await estimateFee(tx) setNetworkFee(fee.total) } catch (error) { - setEstimateError(error instanceof Error ? error.message : "Failed to estimate fee") + setEstimateError( + error instanceof Error ? error.message : "Failed to estimate fee" + ) } finally { setEstimatingFee(false) } } void run() - }, [open, account, tradeFlags.isSwap, tradeState.marketAddress, collateralAddress, fromAmount, sizeUsd, tradeFlags.isLong, tradeFlags.isMarket, triggerPrice, leverage, estimatedEntryPrice, sidecarCreateOrders]) + }, [ + open, + account, + tradeFlags.isSwap, + tradeState.marketAddress, + collateralAddress, + fromAmount, + sizeUsd, + tradeFlags.isLong, + tradeFlags.isMarket, + triggerPrice, + leverage, + estimatedEntryPrice, + sidecarCreateOrders, + ]) async function handleApprove() { if (!account || !collateralAddress) return setAllowanceState("approving") setApproveError(null) try { - const amount = encodeTokenAmount(Number(fromAmount || "0"), collateralAddress) + const amount = encodeTokenAmount( + Number(fromAmount || "0"), + collateralAddress + ) await submitTx( async () => { - const tx = await buildApproveTransaction(collateralAddress, account, CONTRACTS.exchangeRouter, amount) + const tx = await buildApproveTransaction( + collateralAddress, + account, + CONTRACTS.exchangeRouter, + amount + ) return prepareAndSign(tx, walletKit, NETWORK.networkPassphrase) }, { @@ -164,12 +261,14 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr successMessage: "Collateral approved", successDescription: (hash) => `Tx: ${hash.slice(0, 8)}...`, onError: parseSorobanError, - }, + } ) setAllowanceState("approved") } catch (error) { setAllowanceState("insufficient") - setApproveError(error instanceof Error ? error.message : "Approval failed") + setApproveError( + error instanceof Error ? error.message : "Approval failed" + ) } } @@ -202,7 +301,9 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr sizeDeltaUsd: sizeUsd, isLong: tradeFlags.isLong, acceptablePrice, - triggerPrice: tradeFlags.isMarket ? undefined : Number(triggerPrice) || estimatedEntryPrice, + triggerPrice: tradeFlags.isMarket + ? undefined + : Number(triggerPrice) || estimatedEntryPrice, orderType: tradeFlags.isMarket ? "MarketIncrease" : "LimitIncrease", leverage, } @@ -219,7 +320,11 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr } } - const typeLabel = tradeFlags.isSwap ? "Swap" : tradeFlags.isLong ? "Long" : "Short" + const typeLabel = tradeFlags.isSwap + ? "Swap" + : tradeFlags.isLong + ? "Long" + : "Short" return ( !v && onClose()}> @@ -238,43 +343,87 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr

{maxPositionError}

)} - 0 ? formatUsd(estimatedEntryPrice) : "-"} /> - 0.5} /> - 0 ? formatUsd(liquidationPrice) : "-"} /> - + 0 ? formatUsd(estimatedEntryPrice) : "-" + } + /> + 0.5} + /> + 0 ? formatUsd(liquidationPrice) : "-"} + /> + - {estimateError &&

Fee estimation warning: {estimateError}

} - {allowanceState !== "sufficient" && allowanceState !== "checking" && !tradeFlags.isSwap && ( -
-

Step 1/2: Approve collateral

-

- {allowanceState === "approving" - ? "Approving..." - : allowanceState === "approved" - ? "Approved! Proceeding with order..." - : `${collateralAddress} needs to be approved for trading.`} -

- {approveError &&

{approveError}

} -
+ {estimateError && ( +

+ Fee estimation warning: {estimateError} +

)} + {allowanceState !== "sufficient" && + allowanceState !== "checking" && + !tradeFlags.isSwap && ( +
+

+ Step 1/2: Approve collateral +

+

+ {allowanceState === "approving" + ? "Approving..." + : allowanceState === "approved" + ? "Approved! Proceeding with order..." + : `${collateralAddress} needs to be approved for trading.`} +

+ {approveError && ( +

+ {approveError} +

+ )} +
+ )} {allowanceState === "approving" && (
-

Step 2/2: Submit Order

+

+ Step 2/2: Submit Order +

)} {sidecarOrders.length > 0 && (
-

TP/SL sidecar orders

+

+ TP/SL sidecar orders +

{sidecarOrders.map((order, i) => ( -

- {order.type === "takeProfit" ? "TP" : "SL"} at {order.triggerPrice} ({order.sizePct}%) +

+ {order.type === "takeProfit" ? "TP" : "SL"} at{" "} + {order.triggerPrice} ({order.sizePct}%)

))}
)} )} - +
@@ -286,10 +435,26 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr @@ -297,7 +462,17 @@ export function ConfirmationDialog({ open, onClose, tradeState, sizeUsd, entryPr ) } -function Row({ label, value, bold, highlight }: { label: string; value: string; bold?: boolean; highlight?: boolean }) { +function Row({ + label, + value, + bold, + highlight, +}: { + label: string + value: string + bold?: boolean + highlight?: boolean +}) { return (
{label} diff --git a/apps/web/src/features/trade/hooks/useFundingRate.ts b/apps/web/src/features/trade/hooks/useFundingRate.ts index a34170a..96fcbcf 100644 --- a/apps/web/src/features/trade/hooks/useFundingRate.ts +++ b/apps/web/src/features/trade/hooks/useFundingRate.ts @@ -25,7 +25,9 @@ function computeFundingRatePerHour(marketAddress: string): number { const market = MARKETS.find((m) => m.address === marketAddress) if (!market) return BASE_FUNDING_RATE_PER_HOUR - const variation = ((hashString(market.address) % 2001) / 1000 - 1) * FUNDING_VARIANCE_PER_MARKET + const variation = + ((hashString(market.address) % 2001) / 1000 - 1) * + FUNDING_VARIANCE_PER_MARKET return BASE_FUNDING_RATE_PER_HOUR + variation } @@ -35,7 +37,9 @@ function computeNextEpoch(): number { return now - elapsed + FUNDING_INTERVAL_MS } -async function fetchFundingRate(marketAddress: string): Promise { +async function fetchFundingRate( + marketAddress: string +): Promise { // TODO: replace with on-chain DataStore read once contracts are deployed return { ratePerHour: computeFundingRatePerHour(marketAddress), @@ -45,8 +49,8 @@ async function fetchFundingRate(marketAddress: string): Promise export function useFundingRate(marketAddress: string = DEFAULT_MARKET_ADDRESS) { return useQuery({ - queryKey: queryKeys.trade.fundingRate("stellar-mainnet"), - queryFn: fetchFundingRate, + queryKey: queryKeys.trade.fundingRate(CHAIN_ID, marketAddress), + queryFn: () => fetchFundingRate(marketAddress), staleTime: 60_000, refetchInterval: 60_000, }) diff --git a/apps/web/src/features/trade/hooks/useOrderEventPolling.ts b/apps/web/src/features/trade/hooks/useOrderEventPolling.ts index 71f1ad1..4a11599 100644 --- a/apps/web/src/features/trade/hooks/useOrderEventPolling.ts +++ b/apps/web/src/features/trade/hooks/useOrderEventPolling.ts @@ -3,7 +3,7 @@ import { useQueryClient } from "@tanstack/react-query" import { CONTRACTS } from "@/app/config/contracts" import { sorobanRpc } from "@/lib/soroban/client" import { useWalletStore } from "@/features/wallet/store/wallet-store" -import { queryKeys } from "./query-keys" +import { queryKeys } from "../lib/query-keys" const CHAIN_ID = "stellar-mainnet" const POLL_INTERVAL_MS = 5000 diff --git a/apps/web/src/features/trade/lib/query-keys.ts b/apps/web/src/features/trade/lib/query-keys.ts index 8887081..2d74a4f 100644 --- a/apps/web/src/features/trade/lib/query-keys.ts +++ b/apps/web/src/features/trade/lib/query-keys.ts @@ -1,6 +1,8 @@ // Centralized TanStack Query key factory — keeps cache invalidation consistent -export const queryKeys = { +// Centralized TanStack Query key factory — keeps cache invalidation consistent + +const keys = { // Token prices from oracle keeper (or Stellar oracle) tokenPrices: (chainId: string) => ["tokenPrices", chainId] as const, @@ -41,3 +43,8 @@ export const queryKeys = { tokenBalances: (chainId: string, account: string) => ["tokenBalances", chainId, account] as const, } + +export const queryKeys = { + ...keys, + trade: keys, +} diff --git a/apps/web/src/features/trade/lib/stellar.ts b/apps/web/src/features/trade/lib/stellar.ts index 5706572..41e1357 100644 --- a/apps/web/src/features/trade/lib/stellar.ts +++ b/apps/web/src/features/trade/lib/stellar.ts @@ -89,10 +89,9 @@ export async function createIncreaseOrder(params: IncreaseOrderParams): Promise< successDescription: (hash) => `Tx: ${hash.slice(0, 8)}...`, onSuccess: (hash) => { void invalidateTradeQueries(params.account) - window.open(explorerTxUrl(hash), "_blank", "noopener,noreferrer") }, onError: parseSorobanError, - }, + } ) } @@ -119,7 +118,7 @@ export async function createDecreaseOrder(params: DecreaseOrderParams): Promise< onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.trade.positions(CHAIN_ID, params.account) }), onError: parseSorobanError, - }, + } ) } @@ -152,7 +151,7 @@ export async function createSwapOrder(params: SwapOrderParams): Promise queryKey: queryKeys.trade.tokenBalances(CHAIN_ID, params.account), }), onError: parseSorobanError, - }, + } ) } @@ -170,9 +169,12 @@ export async function cancelOrder(account: string, orderKey: OrderKey): Promise< loadingMessage: "Cancelling order...", successMessage: "Order cancelled", successDescription: (hash) => `Tx: ${hash.slice(0, 8)}...`, - onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.trade.orders(CHAIN_ID, account) }), + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: queryKeys.trade.orders(CHAIN_ID, account), + }), onError: parseSorobanError, - }, + } ) } @@ -192,10 +194,9 @@ export async function claimFundingFees(account: string, marketAddresses: Array `${marketAddresses.length} market(s) | Tx: ${hash.slice(0, 8)}...`, onSuccess: (hash) => { void invalidateTradeQueries(account) - window.open(explorerTxUrl(hash), "_blank", "noopener,noreferrer") }, onError: parseSorobanError, - }, + } ) } @@ -254,10 +255,9 @@ export async function sendBatchOrderTxn(account: string, params: BatchOrderParam successDescription: (hash) => `${opCount} operations | Tx: ${hash.slice(0, 8)}...`, onSuccess: (hash) => { void invalidateTradeQueries(account) - window.open(explorerTxUrl(hash), "_blank", "noopener,noreferrer") }, onError: parseSorobanError, - }, + } ) } @@ -272,7 +272,9 @@ export type SidecarOrderParams = { indexToken: string } -export async function createSidecarOrder(_params: SidecarOrderParams): Promise { +export async function createSidecarOrder( + _params: SidecarOrderParams +): Promise { await fakeTxDelay(900) return "DUMMY_TX_HASH" } diff --git a/apps/web/src/shared/hooks/useTxSubmit.ts b/apps/web/src/shared/hooks/useTxSubmit.tsx similarity index 80% rename from apps/web/src/shared/hooks/useTxSubmit.ts rename to apps/web/src/shared/hooks/useTxSubmit.tsx index 6489445..ac5e1c8 100644 --- a/apps/web/src/shared/hooks/useTxSubmit.ts +++ b/apps/web/src/shared/hooks/useTxSubmit.tsx @@ -1,6 +1,7 @@ import { useCallback, useState } from "react" import { toast } from "sonner" import { sendAndPoll } from "@/lib/tx-builder" +import { explorerTxUrl } from "@/app/config/network" export type SubmitTxOptions = { loadingMessage: string @@ -28,9 +29,23 @@ export async function submitTx( await options.onSuccess?.(hash) + const description = options.successDescription?.(hash) + toast.success(options.successMessage, { id: toastId, - description: options.successDescription?.(hash), + description: ( +
+ {description && {description}} + + View transaction → + +
+ ), }) return hash diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 402be25..1922785 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,20 +1,44 @@ -import { defineConfig } from "vite" +import { defineConfig, loadEnv } from "vite" import { tanstackStart } from "@tanstack/react-start/plugin/vite" import viteReact from "@vitejs/plugin-react" import viteTsConfigPaths from "vite-tsconfig-paths" import tailwindcss from "@tailwindcss/vite" import { nitro } from "nitro/vite" +import path from "path" +import { fileURLToPath } from "url" -const config = defineConfig({ - plugins: [ - nitro(), - viteTsConfigPaths({ - projects: ["./tsconfig.json"], - }), - tailwindcss(), - tanstackStart(), - viteReact(), - ], -}) +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +export default defineConfig(({ mode }) => { + // 1. Check root .env for VITE_NETWORK (defaults to testnet if not found) + const rootEnv = loadEnv(mode, path.resolve(__dirname, "../../"), "VITE_") + const network = rootEnv.VITE_NETWORK || "testnet" -export default config + // 2. Load the actual variables for that network from apps/web/.env.{network} + const networkEnv = loadEnv(network, __dirname, "VITE_") + + return { + define: { + // Ensure the network variable itself is set + "import.meta.env.VITE_NETWORK": JSON.stringify(network), + // Spread in all variables from the network-specific env file + ...Object.entries(networkEnv).reduce( + (acc, [key, val]) => { + acc[`import.meta.env.${key}`] = JSON.stringify(val) + return acc + }, + {} as Record + ), + }, + plugins: [ + nitro(), + viteTsConfigPaths({ + projects: ["./tsconfig.json"], + }), + tailwindcss(), + tanstackStart(), + viteReact(), + ], + } +}) From 886b8b99fc8f4db023b1229f487f70171fa11aba Mon Sep 17 00:00:00 2001 From: Darius Daniel Date: Tue, 2 Jun 2026 11:26:18 +0100 Subject: [PATCH 2/2] fix: sync toast theme with application theme --- apps/web/src/routes/__root.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index 8b3fe22..a3c4b01 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -2,7 +2,7 @@ import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router" import { Toaster } from "sonner" import appCss from "@workspace/ui/globals.css?url" import { AppProviders } from "../app/providers" - +import { useTheme } from "../ui/theme-provider" // Update this to your production domain before going live. const SITE_URL = "https://so4.market" @@ -87,7 +87,8 @@ export const Route = createRootRoute({ { property: "og:image:type", content: "image/svg+xml" }, { property: "og:image:alt", - content: "SO4 — On-chain perpetuals DEX · $8.42B 24h volume · 184 markets", + content: + "SO4 — On-chain perpetuals DEX · $8.42B 24h volume · 184 markets", }, // ── Twitter / X Card ──────────────────────────────────────── @@ -99,7 +100,8 @@ export const Route = createRootRoute({ { name: "twitter:image", content: OG_IMAGE }, { name: "twitter:image:alt", - content: "SO4 — On-chain perpetuals DEX · $8.42B 24h volume · 184 markets", + content: + "SO4 — On-chain perpetuals DEX · $8.42B 24h volume · 184 markets", }, ], links: [ @@ -114,7 +116,11 @@ export const Route = createRootRoute({ // ── Fonts ─────────────────────────────────────────────────── { rel: "preconnect", href: "https://fonts.googleapis.com" }, - { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous", + }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&display=swap", @@ -127,7 +133,9 @@ export const Route = createRootRoute({ notFoundComponent: () => (

404

-

The requested page could not be found.

+

+ The requested page could not be found. +

), shellComponent: RootDocument, @@ -139,6 +147,11 @@ export const Route = createRootRoute({ const THEME_SCRIPT = `(function(){try{var t=localStorage.getItem('so4-theme');var d=t==='dark'||((!t||t==='system')&&window.matchMedia('(prefers-color-scheme:dark)').matches);document.documentElement.classList.add(d?'dark':'light')}catch(e){}})()` as const +function ThemedToaster() { + const { theme } = useTheme() + return +} + function RootDocument({ children }: { children: React.ReactNode }) { return ( // suppressHydrationWarning: the blocking script adds a class before React @@ -158,7 +171,7 @@ function RootDocument({ children }: { children: React.ReactNode }) { {children} - +