diff --git a/package-lock.json b/package-lock.json index 2c2e5168..6ce885c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7088,6 +7088,10 @@ "resolved": "packages/thena", "link": true }, + "node_modules/@orbs-network/twap-ui-tradingpost": { + "resolved": "packages/tradingpost", + "link": true + }, "node_modules/@parcel/watcher": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", @@ -36647,6 +36651,56 @@ "react": "*", "react-dom": "*" } + }, + "packages/tradingpost": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@defi.org/web3-candies": "^4.20", + "@mui/material": "5.x", + "@mui/system": "5.x", + "@orbs-network/twap": "^1.18.x", + "@orbs-network/twap-ui": "^0.10.14", + "lodash": "4.x", + "web3": "1.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "packages/tradingpost/node_modules/@orbs-network/twap-ui": { + "version": "0.10.23", + "resolved": "https://registry.npmjs.org/@orbs-network/twap-ui/-/twap-ui-0.10.23.tgz", + "integrity": "sha512-gx/kpl71eTdA0tTzqBFyFSzcvUGV3IeuPJjiFFw+fuesTzdnPvs3Pg4cGMN+wlI4siLi+eSm/vw0n8IapYDPug==", + "dependencies": { + "@defi.org/web3-candies": "^4.20", + "@emotion/react": "11.x", + "@emotion/styled": "11.x", + "@mui/material": "5.x", + "@mui/system": "5.x", + "@orbs-network/twap": "^1.18.x", + "@react-icons/all-files": "^4.1.0", + "@tanstack/react-query": "4.x", + "@types/async-retry": "^1.4.5", + "@types/lodash": "4.x", + "async-retry": "^1.3.3", + "bignumber.js": "9.x", + "emotion-theming": "11.x", + "isomorphic-fetch": "3.x", + "lodash": "4.x", + "moment": "2.x", + "qrcode.react": "^3.1.0", + "react-error-boundary": "^4.0.10", + "react-number-format": "5.x", + "react-text-overflow": "^1.0.2", + "web3": "1.x", + "zustand": "4.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } } } } diff --git a/packages/dapp-example/src/TradingPost.tsx b/packages/dapp-example/src/TradingPost.tsx new file mode 100644 index 00000000..9b75151e --- /dev/null +++ b/packages/dapp-example/src/TradingPost.tsx @@ -0,0 +1,305 @@ +import { StyledModalContent, StyledTradingPost, StyledPancakeBackdrop, StyledTradingPostLayout, StyledPancakeOrders, StyledPancakeTwap } from "./styles"; +import { TWAP, Orders, parseToken } from "@orbs-network/twap-ui-tradingpost"; +import { useConnectWallet, useGetTokens, useIsMobile, usePriceUSD, useTheme, useTrade } from "./hooks"; +import { Configs } from "@orbs-network/twap"; +import { useWeb3React } from "@web3-react/core"; +import { Dapp, TokensList, UISelector } from "./Components"; +import { Popup } from "./Components"; +import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import _ from "lodash"; +import { erc20s, isNativeAddress, zeroAddress } from "@defi.org/web3-candies"; +import { SelectorOption, TokenListItem } from "./types"; +import { Box } from "@mui/system"; +import { Button, styled, Tooltip, Typography } from "@mui/material"; +import { Components, hooks, Styles } from "@orbs-network/twap-ui"; +import BN from "bignumber.js"; +const config = Configs.PancakeSwap; + +const tradingPostConfig = { + ...config, + name: "TradingPost", +}; + +let native = { + ...tradingPostConfig.nativeToken, + logoURI: tradingPostConfig.nativeToken.logoUrl, +}; + +const parseListToken = (tokenList: any) => { + const result = tokenList.tokens.map(({ symbol, address, decimals, logoURI, name }: any) => ({ + decimals, + symbol, + name, + address, + logoURI: logoURI.replace("_1", ""), + })); + + return [native, ...result]; +}; +export const useDappTokens = () => { + return useGetTokens({ + chainId: config.chainId, + parse: parseListToken, + modifyList: (tokens: any) => ({ ..._.mapKeys(tokens, (t) => t.address) }), + baseAssets: erc20s.bsc, + url: `https://tokens.pancakeswap.finance/pancakeswap-extended.json`, + }); +}; + +interface TokenSelectModalProps { + onCurrencySelect: (value: any) => void; + selectedCurrency?: any; + otherSelectedCurrency?: any; +} + +const parseList = (rawList?: any): TokenListItem[] => { + return _.map(rawList, (rawToken) => { + return { + token: { + address: rawToken.address, + decimals: rawToken.decimals, + symbol: rawToken.symbol, + logoUrl: rawToken.logoURI, + }, + rawToken, + }; + }); +}; + +const ConnectButton = () => { + const connect = useConnectWallet(); + return ( +
+ +
+ ); +}; + +interface ContextProps { + openModal: (value: boolean) => void; + close: () => void; + showModal?: boolean; + isFrom?: boolean; + setIsFrom?: (value: boolean) => void; +} +const Context = createContext({} as ContextProps); + +const ContextWrapper = ({ children }: { children: ReactNode }) => { + const [showModal, setShowModal] = useState(false); + const [isFrom, setIsFrom] = useState(true); + + const openModal = (value: boolean) => { + setIsFrom(value); + setShowModal(true); + }; + + return setShowModal(false) }}>{children}; +}; + +const TokenSelectModal = ({ onCurrencySelect }: TokenSelectModalProps) => { + const { data: dappTokens } = useDappTokens(); + + const tokensListSize = _.size(dappTokens); + const parsedList = useMemo(() => parseList(dappTokens), [tokensListSize]); + + return ( + + + + ); +}; + +const useDecimals = (fromToken?: string, toToken?: string) => { + const { data: dappTokens } = useDappTokens(); + const fromTokenDecimals = dappTokens?.[fromToken || ""]?.decimals; + const toTokenDecimals = dappTokens?.[toToken || ""]?.decimals; + + return { fromTokenDecimals, toTokenDecimals }; +}; + +const handleAddress = (address?: string) => { + return !address ? "" : "BNB" ? zeroAddress : address; +}; + +const useTokenModal = (item1: any, item2: any, item3: any, isFrom?: boolean) => { + const context = useContext(Context); + return () => context.openModal(!!isFrom); +}; + +const useTooltip = (content: ReactNode, options?: any, children?: ReactNode) => { + const targetRef = useRef(null); + + const tooltip = ( + + {children} + + ); + + return { + targetRef, + tooltip, + }; +}; + +const DappButton = ({ isLoading, disabled, children, onClick }: any) => { + return ( + + {children} + + ); +}; + +const StyledButton = styled(Button)({ + width: "100%", +}); + +const ApproveModalContent = ({ title, isBonus, isMM }: { title: string; isBonus: boolean; isMM: boolean }) => { + return

Approving

; +}; + +const SwapTransactionErrorContent = ({ message }: { message: string }) => { + return

{message}

; +}; +const SwapPendingModalContent = ({ title }: { title: string }) => { + return

{title}

; +}; + +const TWAPComponent = ({ limit }: { limit?: boolean }) => { + const { isDarkTheme } = useTheme(); + const { account, library, chainId } = useWeb3React(); + const { data: dappTokens } = useDappTokens(); + const isMobile = useIsMobile(); + + const _useTrade = (fromToken?: string, toToken?: string, amount?: string) => { + const { fromTokenDecimals, toTokenDecimals } = useDecimals(handleAddress(fromToken), handleAddress(toToken)); + return useTrade(fromToken, toToken, amount, fromTokenDecimals, toTokenDecimals); + }; + + const connector = useMemo(() => { + return { + getProvider: () => library, + }; + }, [library]); + + return ( + + console.log(it)} + nativeToken={native} + connector={connector} + isMobile={isMobile} + useTooltip={useTooltip} + Button={DappButton} + ApproveModalContent={ApproveModalContent} + SwapTransactionErrorContent={SwapTransactionErrorContent} + SwapPendingModalContent={SwapPendingModalContent} + SwapTransactionReceiptModalContent={SwapPendingModalContent} + TradePrice={TradePrice} + TradePriceToggle={TradePriceToggle} + /> + + ); +}; + +const logo = "https://avatars.githubusercontent.com/u/170947423?s=200&v=4"; +const DappComponent = () => { + const { isDarkTheme } = useTheme(); + const [selected, setSelected] = useState(SelectorOption.TWAP); + const isMobile = useIsMobile(); + + return ( + + + + {isMobile && ( + + + + )} + + + + + + + {!isMobile && ( + + + + )} + + + ); +}; + +const Tokens = () => { + const context = useContext(Context); + + const selectToken = hooks.useSelectTokenCallback(parseToken); + + const onSelect = useCallback( + (token: any) => { + selectToken({ isSrc: !!context.isFrom, token }); + context.close(); + }, + [selectToken, context.isFrom, context.close] + ); + + return ( + + ; + + ); +}; +const Wrapper = ({ children, className = "" }: { children: ReactNode; className?: string }) => { + const { isDarkTheme } = useTheme(); + + return ( + + +
{children}
+
+ ); +}; + +const StyledWrapper = styled(Box)({ + position: "relative", + width: "100%", +}); + +const dapp: Dapp = { + Component: DappComponent, + logo, + config: tradingPostConfig, +}; + +export default dapp; + +const TradePriceToggle = ({ onClick }: { onClick: () => void }) => { + return ; +}; + +const TradePrice = (props: { leftSymbol?: string; rightSymbol?: string; price?: string }) => { + return ( + + 1 {props.leftSymbol} = {props.price} {props.rightSymbol} + + ); +}; + +export const amountUi = (decimals?: number, amount?: BN) => { + if (!decimals || !amount) return ""; + const percision = BN(10).pow(decimals || 0); + return amount.times(percision).idiv(percision).div(percision).toString(); +}; diff --git a/packages/dapp-example/src/config.ts b/packages/dapp-example/src/config.ts index fa649fec..f380b1ec 100644 --- a/packages/dapp-example/src/config.ts +++ b/packages/dapp-example/src/config.ts @@ -12,6 +12,24 @@ import lynex from "./Lynex"; import arbidex from "./Arbidex"; import syncswap from "./SyncSwap"; import kinetix from "./kinetix"; +import tradingpost from "./TradingPost"; -export const defaultDapp = quickswap; -export const dapps = [quickswap, spookyswap, spiritswap, pangolin, pangolinDaas, chronos, thena, baseswap, arbidex, lynex, stellaswap, pancake, sushiswap, syncswap, kinetix]; +export const defaultDapp = tradingpost; +export const dapps = [ + quickswap, + spookyswap, + spiritswap, + pangolin, + pangolinDaas, + chronos, + thena, + baseswap, + arbidex, + lynex, + stellaswap, + pancake, + tradingpost, + sushiswap, + syncswap, + kinetix, +]; diff --git a/packages/dapp-example/src/styles.ts b/packages/dapp-example/src/styles.ts index ced6cb90..bcc40820 100644 --- a/packages/dapp-example/src/styles.ts +++ b/packages/dapp-example/src/styles.ts @@ -134,6 +134,25 @@ export const StyledPancake = styled(StyledDapp)<{ isDarkTheme: number }>(({ isDa }, })); +export const StyledTradingPost = styled(StyledDapp)<{ isDarkTheme: number }>(({ isDarkTheme }) => ({ + background: isDarkTheme ? "#131311" : "#F7E4D4", + ".ui-selector-btn": { + background: "#1fc7d4", + color: isDarkTheme ? "white" : "black", + }, + ".ui-selector-btn-selected": { + background: "#7a6eaa", + color: "white", + }, + ".menu-button": { + svg: { + "* ": { + color: isDarkTheme ? "#FBFBFB" : "#000315", + }, + }, + }, +})); + export const StyledStella = styled(StyledDapp)<{ isDarkMode: number }>(({ isDarkMode }) => ({ background: isDarkMode ? "#251842" : "#F4F5F6", ".ui-selector-btn": { @@ -421,7 +440,7 @@ export const StyledThenaLayout = styled(DappLayout)({ }); export const StyledPancakeTwap = styled(Box)<{ isDarkTheme: number }>(({ isDarkTheme }) => ({ - background: isDarkTheme ? "#27262C" : "#FFFFFF", + background: isDarkTheme ? "#27262C" : "#F7E4D4", padding: 16, borderRadius: 24, position: "relative", @@ -458,6 +477,15 @@ export const StyledPancakeLayout = styled(DappLayout)({ maxWidth: 326, }); +export const StyledTradingPostLayout = styled(DappLayout)({ + width: "calc(100% - 30px)", + display: "flex", + flexDirection: "column", + alignItems: "center", + position: "relative", + maxWidth: 540, +}); + export const StyledSushiLayout = styled(DappLayout)({ maxWidth: 490, width: "calc(100% - 30px)", diff --git a/packages/lib/src/components/Labels.tsx b/packages/lib/src/components/Labels.tsx index 1449d166..345d997e 100644 --- a/packages/lib/src/components/Labels.tsx +++ b/packages/lib/src/components/Labels.tsx @@ -118,3 +118,18 @@ export const OrdersLabel = ({ className = "" }: { className?: string }) => { ); }; + +export const PartDurationLabel = () => { + const translations = useTwapContext().translations; + return {translations.partDuration}; +} + +export const TotalDuarationLabel = () => { + const translations = useTwapContext().translations; + return {translations.totalDuration}; +} + +export const TradingPostLimitPriceLabel = () => { + const translations = useTwapContext().translations; + return {translations.limitPrice}; +} diff --git a/packages/lib/src/components/OrdersComponents.tsx b/packages/lib/src/components/OrdersComponents.tsx index a3b6f590..0ef69f80 100644 --- a/packages/lib/src/components/OrdersComponents.tsx +++ b/packages/lib/src/components/OrdersComponents.tsx @@ -6,6 +6,7 @@ import { useOrdersHistoryQuery, useOrdersTabs } from "../hooks"; import { useOrdersStore } from "../store"; import { StyledOrdersLists, StyledOrdersTab, StyledOrdersTabs } from "../styles"; import OrdersList from "../orders/OrdersList"; +import { Dex } from "../consts"; function a11yProps(index: number) { return { @@ -39,7 +40,26 @@ export const OrdersSelectTabs = ({ className = "" }: { className?: string }) => ); }; -export const SelectedOrders = ({ className = "" }: { className?: string }) => { +/** + * The `SelectedOrders` component renders a list of `OrdersList` components based on the selected tab + * and the specified decentralized exchange (DEX). + * + * This component dynamically selects and renders the appropriate `OrdersList` based on the selected + * tab and the `dex` prop. If the `dex` is `TradingPost`, a specific `OrdersList` with customized + * props is rendered. + * + * @param {string} [className=""] - Optional additional class names for styling the component. + * @param {Dex} [dex] - The decentralized exchange (DEX) being used. This prop controls the behavior + * of the component. The available options for `dex` are: + * - `Dex.Uniswap` ("uniswap") + * - `Dex.Sushiswap` ("sushiswap") + * - `Dex.Quickswap` ("quickswap") + * - `Dex.TradingPost` ("tradingpost") + * + * @returns {JSX.Element} A list of `OrdersList` components rendered based on the selected tab value + * and the specified `dex`. + */ +export const SelectedOrders = ({ className = "", dex }: { className?: string, dex?: string }) => { const { orders, isLoading } = useOrdersHistoryQuery(); const { tab } = useOrdersStore(); const tabs = useOrdersTabs(); @@ -54,7 +74,9 @@ export const SelectedOrders = ({ className = "" }: { className?: string }) => { if (tabValue === "All") { return ; } - + if (dex === Dex.TradingPost) { + return ; + } return ; })} diff --git a/packages/lib/src/consts.ts b/packages/lib/src/consts.ts index 5887134a..4d4aeeec 100644 --- a/packages/lib/src/consts.ts +++ b/packages/lib/src/consts.ts @@ -17,3 +17,10 @@ export const QUERY_PARAMS = { }; export const SUGGEST_CHUNK_VALUE = 100; + +export enum Dex { + Uniswap = "uniswap", + Sushiswap = "sushiswap", + Quickswap = "quickswap", + TradingPost = "tradingpost", +} diff --git a/packages/lib/src/hooks.ts b/packages/lib/src/hooks.ts index a4550171..bf6d1e93 100644 --- a/packages/lib/src/hooks.ts +++ b/packages/lib/src/hooks.ts @@ -1367,8 +1367,8 @@ export const useLimitPriceV2 = () => { original: BN(original || "0").isZero() ? "" : original, }; }, [marketPrice, enableQueryParams, limitPriceStore.inverted, limitPriceStore.limitPrice, limitPriceStore.isCustom, limitPriceStore.priceFromQueryParams]); - console.log({limitPrice}); - + console.log({ limitPrice }); + const onInvert = useCallback(() => { limitPriceStore.toggleInverted(); }, [limitPriceStore.toggleInverted, limitPrice]); diff --git a/packages/lib/src/i18n/en.json b/packages/lib/src/i18n/en.json index 2d295249..41df6ea4 100644 --- a/packages/lib/src/i18n/en.json +++ b/packages/lib/src/i18n/en.json @@ -47,6 +47,8 @@ "tradeSizeMustBeEqual": "Trade size must be equal to at least 10 USD", "tradeSize": "Trade size", "tradeInterval": "Trade interval", + "partDuration": "Part Duration", + "totalDuration": "Total Duration", "maxDuration": "Max duration", "totalTrades": "Total trades", "deadline": "Deadline", @@ -68,11 +70,13 @@ "Expired": "Expired", "Canceled": "Canceled", "noOrdersFound": "You currently don't have", + "noOrdersFound_Swap": "Currently, there are no", "noOrdersFound_Open": "open", "noOrdersFound_Completed": "completed", "noOrdersFound_Expired": "expired", "noOrdersFound_Canceled": "canceled", "noOrdersFound1": "orders", + "noOrdersFound2": "create a new one !", "confirmTx": "Confirm Transaction", "expiration": "Expiration", "orderType": "Order type", diff --git a/packages/lib/src/orders/OrdersList.tsx b/packages/lib/src/orders/OrdersList.tsx index 85ddb416..640d6d88 100644 --- a/packages/lib/src/orders/OrdersList.tsx +++ b/packages/lib/src/orders/OrdersList.tsx @@ -1,4 +1,4 @@ -import { Box, styled } from "@mui/material"; +import { Box, styled, Typography, Button } from "@mui/material"; import { useState } from "react"; import Order, { OrderLoader } from "./Order/Order"; import CircularProgress from "@mui/material/CircularProgress"; @@ -9,8 +9,11 @@ import { usePagination } from "../hooks"; import { StyledColumnFlex } from "../styles"; import { Pagination } from "../components/base"; import { useTwapStore } from "../store"; +import Bear from "./assets/components/Bear"; +import ArrowOutward from "./assets/components/ArrowOutward"; +import { Dex } from "../consts"; -function OrdersList({ orders, status, isLoading }: { orders?: ParsedOrder[]; status?: string; isLoading: boolean }) { +function OrdersList({ orders, status, isLoading, dex }: { orders?: ParsedOrder[]; status?: string; isLoading: boolean, dex?: Dex }) { const { uiPreferences } = useTwapContext(); const showPagination = uiPreferences.orders?.paginationChunks && _.size(orders) > uiPreferences.orders?.paginationChunks; @@ -25,7 +28,13 @@ function OrdersList({ orders, status, isLoading }: { orders?: ParsedOrder[]; sta if (showPagination) { return ; } - return ; + + switch (dex) { + case Dex.TradingPost: + return ; + default: + return ; + } } const PaginationList = ({ orders, status }: { orders?: ParsedOrder[]; status?: string }) => { @@ -41,7 +50,7 @@ const PaginationList = ({ orders, status }: { orders?: ParsedOrder[]; status?: s ); }; -const List = ({ orders, status }: { orders?: ParsedOrder[]; status?: string }) => { +const List = ({ orders, status, useCustomComponent, CustomComponent }: { orders?: ParsedOrder[]; status?: string; useCustomComponent?: boolean; CustomComponent?: (props: { status?: string }) => React.ReactElement; }) => { const [selected, setSelected] = useState(undefined); const { translations } = useTwapContext(); const waitingForOrdersUpdate = useTwapStore((s) => s.waitingForOrdersUpdate); @@ -53,6 +62,12 @@ const List = ({ orders, status }: { orders?: ParsedOrder[]; status?: string }) = if (!_.size(orders)) { return waitingForOrdersUpdate ? ( + ) : useCustomComponent ? ( + + + {CustomComponent && } + + ) : ( @@ -72,6 +87,21 @@ const List = ({ orders, status }: { orders?: ParsedOrder[]; status?: string }) = ); }; +const TradingPostListComponent = ({status}: {status?: string}) => { + const { translations } = useTwapContext(); + return ( + <> + + No Open Orders + {!status ? "You currently don't have orders" : `${translations.noOrdersFound_Swap} ${(translations as any)["noOrdersFound_" + status]} ${translations.noOrdersFound1}`} + + Learn More + + + + ); +} + export default OrdersList; const StyledEmptyList = styled(Box)({ @@ -96,3 +126,23 @@ const StyledLoader = styled(StyledContainer)({ color: "white", }, }); + +const StyledEmtpyListTitle = styled(Typography)({ + fontSize: 18, + fontWeight: 700, + fontFamily: "Slackey", +}); + +const StyledEmptyListButton = styled(Button)(({ theme }) => ({ + display: "flex", + alignItems: "center", + gap: 5, + marginTop: 20, + width: 200, + height: 40, + borderRadius: 16, + backgroundColor: "transparent", + color: theme.palette.mode === "dark" ? "#FBF4EF" : "#453936", + border: `1px solid ${theme.palette.mode === "dark" ? "#353531" : "#D5BAA5"}`, + fontFamily: "Slackey", +})); diff --git a/packages/lib/src/orders/assets/components/ArrowOutward.tsx b/packages/lib/src/orders/assets/components/ArrowOutward.tsx new file mode 100644 index 00000000..ba93bb2d --- /dev/null +++ b/packages/lib/src/orders/assets/components/ArrowOutward.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react"; +const SvgArrowOutward = (props: SVGProps) => ( + + + +); +export default SvgArrowOutward; diff --git a/packages/lib/src/orders/assets/components/Bear.tsx b/packages/lib/src/orders/assets/components/Bear.tsx new file mode 100644 index 00000000..f9ce484e --- /dev/null +++ b/packages/lib/src/orders/assets/components/Bear.tsx @@ -0,0 +1,179 @@ +import type { SVGProps } from "react"; +const SvgBear = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default SvgBear; diff --git a/packages/lib/src/orders/assets/components/index.ts b/packages/lib/src/orders/assets/components/index.ts new file mode 100644 index 00000000..7545cc7f --- /dev/null +++ b/packages/lib/src/orders/assets/components/index.ts @@ -0,0 +1,2 @@ +export { default as Bear } from "./Bear"; +export { default as ArrowOutward } from "./ArrowOutward"; diff --git a/packages/lib/src/store.ts b/packages/lib/src/store.ts index 0708e7ef..e2759147 100644 --- a/packages/lib/src/store.ts +++ b/packages/lib/src/store.ts @@ -54,6 +54,8 @@ const getInitialState = (queryParamsEnabled?: boolean): State => { waitingForOrdersUpdate: false, srcUsd: undefined, dstUsd: undefined, + + priceImpact: 0, }; }; const initialState = getInitialState(); @@ -115,6 +117,10 @@ export const useTwapStore = create( setQueryParam(QUERY_PARAMS.TRADE_INTERVAL, !fillDelay.amount ? undefined : fillDelay.amount?.toString()); set({ customFillDelay: fillDelay }); }, + setPriceImpact: (priceImpact: number) => {}, + getPriceImpact: () => { + return get().priceImpact; + }, getFillDelayText: (translations: Translations) => fillDelayText((get() as any).getFillDelayUiMillis(), translations), getFillDelayUiMillis: () => get().customFillDelay.amount! * get().customFillDelay.resolution, getMinimumDelayMinutes: () => (get().lib?.estimatedDelayBetweenChunksMillis() || 0) / 1000 / 60, diff --git a/packages/lib/src/styles.ts b/packages/lib/src/styles.ts index a2413791..0494fa93 100644 --- a/packages/lib/src/styles.ts +++ b/packages/lib/src/styles.ts @@ -21,6 +21,12 @@ export const StyledText = styled(Typography)({ fontSize: "inherit", }); +export const StyledPriceImpactText = styled(Typography)({ + fontFamily: "inherit", + fontSize: 16, + fontWeight: 600, +}); + export const StyledColumnFlex = styled(Box)(({ gap = 10 }: { gap?: number }) => ({ display: "flex", flexDirection: "column", @@ -158,3 +164,9 @@ export const StyledSummaryRow = styled(StyledRowFlex)({ }, }, }); + +export const StyledH1 = styled(Typography)(({ theme }) => ({ + fontSize: 16, + fontWeight: 400, + color: theme.palette.mode === "dark" ? "#767676" : "#866C65", +})); diff --git a/packages/lib/src/types.ts b/packages/lib/src/types.ts index 36217c01..53baef2b 100644 --- a/packages/lib/src/types.ts +++ b/packages/lib/src/types.ts @@ -80,6 +80,8 @@ export interface Translations { confirmationLimitPriceTooltip: string; ordersTooltip: string; noOrdersFound1: string; + noOrdersFound2: string; + noOrdersFound_Swap: string; poweredBy: string; insertLimitPriceWarning: string; unwrap: string; @@ -98,6 +100,8 @@ export interface Translations { viewOrders: string; view: string; estimate: string; + partDuration: string; + totalDuration: string; } export interface BaseComponentProps { @@ -308,6 +312,8 @@ export interface State { dstUsd?: BN; srcUsdLoading?: boolean; dstUsdLoading?: boolean; + + priceImpact: number; } export type SwitchVariant = "ios" | "default"; diff --git a/packages/tradingpost/LICENSE b/packages/tradingpost/LICENSE new file mode 100644 index 00000000..63f9e3f0 --- /dev/null +++ b/packages/tradingpost/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Orbs Network + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tradingpost/package.json b/packages/tradingpost/package.json new file mode 100644 index 00000000..e070b526 --- /dev/null +++ b/packages/tradingpost/package.json @@ -0,0 +1,45 @@ +{ + "name": "@orbs-network/twap-ui-tradingpost", + "version": "1.0.0", + "description": "TWAP UI for Trading Post", + "author": "TradingPost", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/orbs-network/twap-ui.git" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "./dist" + ], + "scripts": { + "prettier": "prettier -w '{src,test}/**/*.{ts,tsx,js,jsx,json,sol}'", + "transform-svg": "ts-node transform-svg.ts", + "build": "npm run prettier && rm -rf dist && tsc && npm run copy-assets", + "start": "npm run prettier && nodemon --ext js,jsx,ts,tsx,json --watch ./src --exec tsc", + "test": "eslint src", + "copy-assets": "copyfiles -u 1 src/assets/*.svg dist/" + }, + "dependencies": { + "@defi.org/web3-candies": "^4.20", + "@mui/material": "5.x", + "@mui/system": "5.x", + "@orbs-network/twap": "^1.18.x", + "@orbs-network/twap-ui": "^0.10.14", + "lodash": "4.x", + "web3": "1.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@svgr/cli": "^8.1.0", + "@types/node": "^22.0.2", + "copyfiles": "^2.4.1" + } +} diff --git a/packages/tradingpost/src/OrderSummary.tsx b/packages/tradingpost/src/OrderSummary.tsx new file mode 100644 index 00000000..4ac5a6e2 --- /dev/null +++ b/packages/tradingpost/src/OrderSummary.tsx @@ -0,0 +1,157 @@ +import { styled } from "@mui/material"; +import { Styles as TwapStyles, Components, store, hooks } from "@orbs-network/twap-ui"; +import { StyledMarketPriceContainer, StyledOrderSummary } from "./styles"; +import { MdArrowDownward } from "@react-icons/all-files/md/MdArrowDownward"; +import { useAdapterContext } from "./context"; +import { useCallback, useState } from "react"; + +export const OrderSummary = ({ onSubmit, disabled, isLimitPanel }: { onSubmit: () => void; disabled?: boolean; isLimitPanel?: boolean }) => { + const Button = useAdapterContext().Button; + return ( + + + + + + + + + + {isLimitPanel ? ( + <> + + + + + + + ) : ( + <> + + + + + + + + + )} + + + + + + + + + + + + + + + + + + + ); +}; + +const SummaryPrice = () => { + const { TradePrice: DappTradePrice } = useAdapterContext(); + const [inverted, setInvert] = useState(false); + const { isLimitOrder, srcToken, dstToken } = store.useTwapStore((store) => ({ + isLimitOrder: store.isLimitOrder, + srcToken: store.srcToken, + dstToken: store.dstToken, + })); + const { isLoading, getToggled } = hooks.useLimitPriceV2(); + const { marketPrice } = hooks.useMarketPriceV2(inverted); + const price = isLimitOrder ? getToggled(inverted, true) : marketPrice?.original; + const value = hooks.useFormatNumber({ value: price || "", decimalScale: 5 }); + + const onInvert = useCallback(() => { + setInvert((prev) => !prev); + }, [setInvert]); + + const leftSymbol = inverted ? dstToken?.symbol : srcToken?.symbol; + const rightSymbol = inverted ? srcToken?.symbol : dstToken?.symbol; + + return ( + + Price + + + ); +}; + +const StyledButtonContainer = styled(TwapStyles.StyledRowFlex)({ + width: "100%", + button: { + width: "100%", + }, +}); + +const StyledSummaryDetails = styled(TwapStyles.StyledColumnFlex)({ + gap: 9, + ".twap-token-logo": { + display: "none", + }, + "@media(max-width: 700px)": { + gap: 6, + }, +}); + +const TokenDisplay = ({ isSrc }: { isSrc?: boolean }) => { + const { token, srcAmount } = store.useTwapStore((store) => ({ + token: isSrc ? store.srcToken : store.dstToken, + srcAmount: store.srcAmountUi, + })); + const dstAmount = hooks.useDstAmount().outAmount.ui; + + const _amount = isSrc ? srcAmount : dstAmount; + + const amount = hooks.useFormatNumber({ value: _amount, decimalScale: 3 }); + + return ( + + {amount} + + {token?.symbol} + + + + ); +}; + +const StyledTokens = styled(TwapStyles.StyledColumnFlex)({ + gap: 12, + alignItems: "center", +}); + +const StyledArrow = styled(MdArrowDownward)({ + width: 24, + height: 24, +}); + +const StyledTokenDisplayRight = styled(TwapStyles.StyledRowFlex)({ + width: "auto", + p: { + fontSize: 14, + }, + ".twap-token-logo": { + width: 24, + height: 24, + }, +}); + +const StyledTokenDisplayAmount = styled(TwapStyles.StyledOneLineText)({ + fontWeight: 600, + fontSize: 24, +}); +const StyledTokenDisplay = styled(TwapStyles.StyledRowFlex)({ + justifyContent: "space-between", + gap: 30, +}); diff --git a/packages/tradingpost/src/TradingPostOrders.tsx b/packages/tradingpost/src/TradingPostOrders.tsx new file mode 100644 index 00000000..c591781b --- /dev/null +++ b/packages/tradingpost/src/TradingPostOrders.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { useMediaQuery } from "@mui/material"; +import { hooks, OrdersPortal, SelectedOrders, store } from "@orbs-network/twap-ui"; +import { StyledOrders, StyledOrdersHeader, StyledOrdersTab, StyledOrdersTabs, StyledBody } from "./styles"; + +const useMobile = () => { + return useMediaQuery("(max-width:700px)"); +}; + +export default function TradingPostOrders() { + return ( + + + + + + + + + ); +} + +const OrdersHeaderLayout = () => { + const mobile = useMobile(); + + const { tab, setTab } = store.useOrdersStore(); + + const tabs = hooks.useOrdersTabs(); + const selectedTab = React.useMemo(() => { + return Object.keys(tabs)[tab as any]; + }, [tabs, tab]); + + const onSelect = (index: number) => { + setTab(index); + }; + + return ( + <> + + + {Object.keys(tabs) + .map((key, index) => { + return ( + onSelect(index)}> + {key} + + ); + })} + + + + ); +}; diff --git a/packages/tradingpost/src/assets/components/Arrow.tsx b/packages/tradingpost/src/assets/components/Arrow.tsx new file mode 100644 index 00000000..18045247 --- /dev/null +++ b/packages/tradingpost/src/assets/components/Arrow.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgArrow = (props: SVGProps) => ( + + + +); +export default SvgArrow; diff --git a/packages/tradingpost/src/assets/components/ArrowOutward.tsx b/packages/tradingpost/src/assets/components/ArrowOutward.tsx new file mode 100644 index 00000000..918f6ade --- /dev/null +++ b/packages/tradingpost/src/assets/components/ArrowOutward.tsx @@ -0,0 +1,13 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgArrowOutward = (props: SVGProps) => ( + + + +); +export default SvgArrowOutward; diff --git a/packages/tradingpost/src/assets/components/index.ts b/packages/tradingpost/src/assets/components/index.ts new file mode 100644 index 00000000..a6c6f8fb --- /dev/null +++ b/packages/tradingpost/src/assets/components/index.ts @@ -0,0 +1,2 @@ +export { default as ArrowOutward } from "./ArrowOutward"; +export { default as Arrow } from "./Arrow"; diff --git a/packages/tradingpost/src/components.tsx b/packages/tradingpost/src/components.tsx new file mode 100644 index 00000000..bb8acd0b --- /dev/null +++ b/packages/tradingpost/src/components.tsx @@ -0,0 +1,52 @@ +import { Components, hooks, store } from "@orbs-network/twap-ui"; +import { useAdapterContext } from "./context"; +import BN from "bignumber.js"; +import { StyledMarketPriceContainer } from "./styles"; +import { styled } from "@mui/material"; +import { useMemo } from "react"; + +export function Price() { + const { TradePrice: DappTradePrice } = useAdapterContext(); + const { srcToken, dstToken, srcAmount, isLimitOrder } = store.useTwapStore((s) => ({ + srcToken: s.srcToken, + dstToken: s.dstToken, + srcAmount: s.getSrcAmount().toString(), + isLimitOrder: s.isLimitOrder, + })); + + const { limitPrice, isLoading, inverted } = hooks.useLimitPriceV2(); + const { marketPrice } = hooks.useMarketPriceV2(inverted); + + const price = hooks.useFormatNumber({ value: isLimitOrder ? limitPrice?.toggled : marketPrice?.toggled, decimalScale: 3, disableDynamicDecimals: false }); + + if (!DappTradePrice) { + return ; + } + + if (!srcToken || !dstToken || BN(srcAmount || "0").isZero()) { + return null; + } + + const leftSymbol = inverted ? dstToken?.symbol : srcToken?.symbol; + const rightSymbol = inverted ? srcToken?.symbol : dstToken?.symbol; + + return ( + +
+ +
+ +
+ ); +} + +const StyledLoader = styled(Components.Base.Loader)<{ loading: number }>(({ loading }) => ({ + position: "absolute", + width: "30%!important", + height: "15px!important", + top: "50%", + transform: "translateY(-50%)", + right: 0, + display: loading ? "block" : ("none" as const), + posinterEvents: "none", +})); diff --git a/packages/tradingpost/src/context.ts b/packages/tradingpost/src/context.ts new file mode 100644 index 00000000..11dc9f4a --- /dev/null +++ b/packages/tradingpost/src/context.ts @@ -0,0 +1,27 @@ +import { TWAPProps } from "@orbs-network/twap-ui"; +import { createContext, FC, JSXElementConstructor, useContext } from "react"; + +export interface AdapterProps extends TWAPProps { + dappTokens?: { [key: string]: any }; + isDarkTheme?: boolean; + ConnectButton: JSXElementConstructor; + useTokenModal: any; + nativeToken: any; + connector?: any; + isMobile?: boolean; + useTooltip: any; + Button: any; + ApproveModalContent?: any; + SwapTransactionErrorContent?: any; + SwapPendingModalContent?: any; + SwapTransactionReceiptModalContent?: any; + AddToWallet?: any; + TradePrice?: any; + TradePriceToggle: FC<{ onClick: () => void; loading: boolean }>; +} + +const AdapterContext = createContext({} as AdapterProps); + +export const AdapterContextProvider = AdapterContext.Provider; + +export const useAdapterContext = () => useContext(AdapterContext); diff --git a/packages/tradingpost/src/i18n/en.json b/packages/tradingpost/src/i18n/en.json new file mode 100644 index 00000000..1201d864 --- /dev/null +++ b/packages/tradingpost/src/i18n/en.json @@ -0,0 +1,4 @@ +{ + "enterAmount": "Enter an amount", + "tradeSizeMustBeEqual": "Trade size must be equal to at least 50 USD" +} diff --git a/packages/tradingpost/src/index.tsx b/packages/tradingpost/src/index.tsx new file mode 100644 index 00000000..5628af33 --- /dev/null +++ b/packages/tradingpost/src/index.tsx @@ -0,0 +1,612 @@ +import { GlobalStyles, Box, ThemeProvider, Typography, styled } from "@mui/material"; +import { + Components, + hooks, + Translations, + TwapAdapter, + Styles as TwapStyles, + store, + Orders, + TwapContextUIPreferences, + Styles, + TooltipProps, + parseError, +} from "@orbs-network/twap-ui"; +import translations from "./i18n/en.json"; +import { + Card, + configureStyles, + darkTheme, + lightTheme, + StyledBalanceContainer, + StyledColumnFlex, + StyledLimitPrice, + StyledLimitPriceBody, + StyledModalHeader, + StyledModalHeaderClose, + StyledModalHeaderTitle, + StyledPoweredBy, + StyledReset, + StyledResetLimitButton, + StyledSwapModalContent, + StyledText, + StyledTimeSelect, + StyledTimeSelectBody, + StyledTimeSelectContainer, + StyledTimeSelectHeader, +} from "./styles"; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { StyledBalance, StyledTokenChange, StyledTokenChangeContainer, StyledTokenPanel, StyledTokenPanelInput, StyledTokenSelect } from "./styles"; +import { isNativeAddress, zeroAddress } from "@defi.org/web3-candies"; +import { Configs, TokenData } from "@orbs-network/twap"; +import Web3 from "web3"; +import _ from "lodash"; +import BN from "bignumber.js"; +import { MdArrowDropDown } from "@react-icons/all-files/md/MdArrowDropDown"; +import Arrow from "./assets/components/Arrow"; +import TradingPostOrders from "./TradingPostOrders"; +import { getTokenFromTokensList } from "@orbs-network/twap-ui"; +import { IoMdClose } from "@react-icons/all-files/io/IoMdClose"; +import { OrderSummary } from "./OrderSummary"; +import { useTwapContext } from "@orbs-network/twap-ui"; +import { useAdapterContext, AdapterContextProvider, AdapterProps } from "./context"; +import { Price } from "./components"; +import { create } from "zustand"; +const Button = (props: any) => { + const DappButton = useAdapterContext().Button; + return ( + + {props.children} + + ); +}; +const Tooltip = ({ text, children, childrenStyles = {} }: TooltipProps) => { + const useTooltip = useAdapterContext().useTooltip; + const { targetRef, tooltip, tooltipVisible } = useTooltip(text, { placement: "top", hideTimeout: 0 }); + return ( + + {children} {tooltipVisible && tooltip} + + ); +}; +const uiPreferences: TwapContextUIPreferences = { + usdSuffix: " USD", + usdPrefix: "~", + usdEmptyUI: <>, + balanceEmptyUI: <>, + switchVariant: "ios", + inputPlaceholder: "0.0", + Tooltip, + Button, + orders: { + paginationChunks: 4, + hideUsd: true, + }, + modal: { + styles: { + zIndex: 1, + }, + }, +}; +const config = Configs.PancakeSwap; +export const parseToken = (rawToken: any): TokenData | undefined => { + const { address, decimals, symbol, logoURI } = rawToken; + if (!symbol) { + console.error("Invalid token", rawToken); + return; + } + if (!address || isNativeAddress(address) || address === "BNB") { + return config.nativeToken; + } + return { + address: Web3.utils.toChecksumAddress(address), + decimals, + symbol, + logoUrl: logoURI, + }; +}; +const storeOverride = { + isLimitOrder: true, + chunks: 1, + customDuration: { resolution: store.TimeResolution.Days, amount: 7 }, + customFillDelay: { resolution: store.TimeResolution.Minutes, amount: 2 }, +}; +const Balance = ({ isSrc }: { isSrc?: boolean }) => { + const onPercentClick = hooks.useCustomActions(); + return ( + onPercentClick(1) : () => {}}> + + + ); +}; + +const TokenPanel = ({ isSrcToken = false }: { isSrcToken?: boolean }) => { + const selectToken = hooks.useSelectTokenCallback(); + const { dstToken, srcToken } = hooks.useDappRawSelectedTokens(); + const onSelect = useCallback( + (token: any) => { + selectToken({ isSrc: !!isSrcToken, token }); + }, + [selectToken, isSrcToken] + ); + const onTokenSelectClick = useAdapterContext().useTokenModal(onSelect, srcToken, dstToken, isSrcToken); + return ( + + + + + {isSrcToken ? "Sell" : "Receive at Least"} + + + + + + + + {" "} + + ); +}; + +const ChangeTokensOrder = () => { + return ( + + } /> + + ); +}; +const handleAddress = (address?: string) => { + return isNativeAddress(address || "") ? "BNB" : address; +}; +export const useProvider = (props: AdapterProps) => { + const [provider, setProvider] = useState(undefined); + const setProviderFromConnector = useCallback(async () => { + const res = await props.connector?.getProvider(); + setProvider(res); + }, [setProvider, props.connector]); + useEffect(() => { + setProviderFromConnector(); + }, [props.account, props.connectedChainId, setProviderFromConnector]); + return provider; +}; +const useTrade = (props: AdapterProps) => { + const { srcToken, toToken, srcAmount } = store.useTwapStore((s) => ({ + srcToken: s.srcToken?.address, + toToken: s.dstToken?.address, + srcAmount: s.getSrcAmount().toString(), + })); + const res = props.useTrade!(handleAddress(srcToken), handleAddress(toToken), srcAmount === "0" ? undefined : srcAmount); + return { + outAmount: res?.outAmount, + isLoading: BN(srcAmount || "0").gt(0) && res?.isLoading, + }; +}; +const TWAP = memo((props: AdapterProps) => { + const provider = useProvider(props); + const trade = useTrade(props); + const theme = useMemo(() => { + return props.isDarkTheme ? darkTheme : lightTheme; + }, [props.isDarkTheme]); + const dappTokens = useMemo(() => { + if (!props.dappTokens || !props.nativeToken) return undefined; + return { + ...props.dappTokens, + [zeroAddress]: props.nativeToken, + }; + }, [props.dappTokens, props.nativeToken]); + return ( + + + + + + {props.limit ? : } + + + + + + ); +}); +const TopPanel = () => { + return ( + + + + + + ); +}; + +const LimitTopPanel = () => { + return ( + + + + + + + ); +}; +const OpenConfirmationModalButton = () => { + const { ConnectButton, provider, Button } = useAdapterContext(); + const { onClick, text, disabled } = useShowSwapModalButton(); + if (!provider) { + return ( + + + + ); + } + return ( + + + + ); +}; +const StyledButtonContainer = styled("div")({ + width: "100%", + "> *": { + width: "100%", + }, + marginTop: 10, +}); + +const PartDuration = () => { + return ( + + + + + + + + + + + + ); +}; + +const TotalDuration = () => { + return ( + + + + + + + + + + + + ); +}; + +const LimitPanel = () => { + const { onInvert } = hooks.useLimitPriceV2(); + return ( +
+ + + + + + + + + +
+ ); +}; +const TWAPPanel = () => { + return ( +
+ + + + + + + + +
+ ); +}; +const LimitPrice = ({ limitOnly }: { limitOnly?: boolean }) => { + const isLimitOrder = store.useTwapStore((store) => store.isLimitOrder); + const { onInvert, isLoading } = hooks.useLimitPriceV2(); + const { TradePriceToggle } = useAdapterContext(); + return ( + + + {isLimitOrder && ( + + +
+ + + Set To Market + + + + +
+ +
+
+ )} +
+
+ ); +}; +export { TWAP, Orders }; +export enum SwapState { + REVIEW, + APPROVE, + ATTEMTPING_TX, + PENDING_CONFIRMATION, + ERROR, + COMPLETED, +} +interface Store { + swapState: SwapState; + setSwapState: (value: SwapState) => void; +} +export const useOrdersStore = create((set, get) => ({ + swapState: SwapState.REVIEW, + setSwapState: (swapState) => set({ swapState }), +})); +const SwapModal = ({ limitPanel }: { limitPanel: boolean }) => { + const { swapState, setSwapState } = useOrdersStore(); + const { dappTokens, ApproveModalContent, SwapPendingModalContent, SwapTransactionErrorContent, AddToWallet, SwapTransactionReceiptModalContent } = useAdapterContext(); + const { fromToken, setShowConfirmation, showConfirmation, txHash, isLimitOrder, disclaimerAccepted } = store.useTwapStore((s) => ({ + fromToken: s.srcToken, + setShowConfirmation: s.setShowConfirmation, + showConfirmation: s.showConfirmation, + txHash: s.txHash, + isLimitOrder: s.isLimitOrder, + disclaimerAccepted: s.disclaimerAccepted, + })); + const reset = hooks.useResetStore(); + const { mutateAsync: approveCallback } = hooks.useApproveToken(true); + const { data: allowance, isLoading, refetch: refetchAllowance } = hooks.useHasAllowanceQuery(); + const { mutateAsync: createOrder } = hooks.useCreateOrder(true); + const inputCurrency = useMemo(() => getTokenFromTokensList(dappTokens, fromToken?.address), [dappTokens, fromToken]); + const [error, setError] = useState(""); + const { data: hasNativeBalance } = hooks.useHasMinNativeTokenBalance("0.0035"); + const id = useRef(1); + const onSubmit = useCallback(async () => { + let _id = id.current; + try { + if (!hasNativeBalance) { + setError(`Insufficient BNB balance, you need at least 0.0035BNB to cover the transaction fees.`); + setSwapState(SwapState.ERROR); + return; + } + if (!allowance) { + setSwapState(SwapState.APPROVE); + await approveCallback(); + const approved = await refetchAllowance(); + if (!approved.data) { + setError("Insufficient allowance to perform the swap. Please approve the token first."); + setSwapState(SwapState.ERROR); + return; + } + } + if (id.current === _id) { + setSwapState(SwapState.ATTEMTPING_TX); + } + await createOrder(); + if (id.current === _id) { + setSwapState(SwapState.COMPLETED); + } + } catch (error) { + if (id.current === _id) { + setSwapState(SwapState.ERROR); + setError(parseError(error) || "An error occurred"); + } + } + }, [allowance, approveCallback, createOrder, setSwapState, setError, hasNativeBalance, id]); + const wrongNetwork = store.useTwapStore((store) => store.wrongNetwork); + let content = null; + let title: string | undefined = undefined; + const resetPoupupState = () => { + setTimeout(() => { + setSwapState(SwapState.REVIEW); + }, 300); + }; + const onClose = () => { + id.current = id.current + 1; + setShowConfirmation(false); + resetPoupupState(); + if (txHash) { + reset({ waitingForOrdersUpdate: true }); + } + if (swapState === SwapState.COMPLETED) { + reset(); + } + }; + useEffect(() => { + if (txHash && swapState === SwapState.ATTEMTPING_TX) { + setSwapState(SwapState.PENDING_CONFIRMATION); + } + }, [txHash, swapState]); + const addToWallet = !AddToWallet ? null : ; + if (swapState === SwapState.REVIEW) { + title = "Confirm Order"; + content = ; + } + if (swapState === SwapState.APPROVE) { + content = !ApproveModalContent ? null : ; + } + if (swapState === SwapState.ERROR) { + content = !SwapTransactionErrorContent ? null : {}} onDismiss={onClose} message={error} />; + } + if (swapState === SwapState.ATTEMTPING_TX) { + content = !SwapPendingModalContent ? null : ( + {addToWallet} + ); + } + if (swapState === SwapState.PENDING_CONFIRMATION) { + content = ( + + {addToWallet} + + ); + } + if (swapState === SwapState.COMPLETED) { + content = ( + + {addToWallet} + + ); + } + if (wrongNetwork) { + content = null; + } + return ( + } title={title} onClose={onClose} open={showConfirmation}> + + {content} + + + ); +}; +const StyledSwapModalContentChildren = styled("div")` + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + width: 100%; +`; +const ModalHeader = ({ title, onClose }: { title?: string; onClose: () => void }) => { + return ( + + {title && {title}} + + + + + ); +}; +export const useShowSwapModalButton = () => { + const translations = useTwapContext()?.translations; + const { shouldWrap, shouldUnwrap, wrongNetwork, setShowConfirmation, createOrderLoading, srcAmount } = store.useTwapStore((store) => ({ + maker: store.lib?.maker, + shouldWrap: store.shouldWrap(), + shouldUnwrap: store.shouldUnwrap(), + wrongNetwork: store.wrongNetwork, + setShowConfirmation: store.setShowConfirmation, + createOrderLoading: store.loading, + srcAmount: store.srcAmountUi, + })); + const warning = hooks.useFillWarning(); + const { isLoading: dstAmountLoading, dexAmounOut } = hooks.useDstAmount(); + const { mutate: unwrap, isLoading: unwrapLoading } = hooks.useUnwrapToken(true); + const { mutate: wrap, isLoading: wrapLoading } = hooks.useWrapToken(true); + const { loading: changeNetworkLoading, changeNetwork } = hooks.useChangeNetwork(); + const srcUsd = hooks.useSrcUsd().value; + const dstUsd = hooks.useDstUsd().value; + const noLiquidity = useMemo(() => { + if (!srcAmount || BN(srcAmount).isZero() || dstAmountLoading) return false; + return !dexAmounOut.raw || BN(dexAmounOut.raw).isZero(); + }, [dexAmounOut.raw, dstAmountLoading, srcAmount]); + if (wrongNetwork) + return { + text: translations.switchNetwork, + onClick: changeNetwork, + loading: changeNetworkLoading, + disabled: changeNetworkLoading, + }; + if (!srcAmount || BN(srcAmount || "0").isZero()) { + return { + text: translations.enterAmount, + disabled: true, + }; + } + if (dstAmountLoading) { + return { text: "Searching for the best price", onClick: undefined, disabled: true }; + } + if (noLiquidity) { + return { + text: "Insufficient liquidity for this trade.", + disabled: true, + loading: false, + }; + } + if (!srcUsd || srcUsd.isZero() || !dstUsd || dstUsd.isZero()) { + return { + text: "Searching for the best price", + disabled: true, + }; + } + if (warning) + return { + text: warning, + onClick: undefined, + disabled: true, + loading: false, + }; + if (shouldUnwrap) + return { + text: translations.unwrap, + onClick: unwrap, + loading: unwrapLoading, + disabled: unwrapLoading, + }; + if (shouldWrap) + return { + text: translations.wrap, + onClick: wrap, + loading: wrapLoading, + disabled: wrapLoading, + }; + if (createOrderLoading) { + return { + text: translations.placeOrder, + onClick: () => { + setShowConfirmation(true); + }, + }; + } + return { + text: translations.placeOrder, + onClick: () => { + setShowConfirmation(true); + }, + loading: false, + disabled: false, + }; +}; diff --git a/packages/tradingpost/src/styles.tsx b/packages/tradingpost/src/styles.tsx new file mode 100644 index 00000000..877f8ec3 --- /dev/null +++ b/packages/tradingpost/src/styles.tsx @@ -0,0 +1,979 @@ +import { Box, Button, createTheme, styled, Theme, Typography } from "@mui/material"; +import { display } from "@mui/system"; +import { Components, OrdersContainer, Styles } from "@orbs-network/twap-ui"; +import { CSSProperties, ReactNode } from "react"; +const isDarkMode = (theme: Theme) => theme.palette.mode === "dark"; + +export const darkTheme = createTheme({ + palette: { + mode: "dark", + }, +}); + +export const lightTheme = createTheme({ + palette: { + mode: "light", + }, +}); + +export const baseStyles = (theme: Theme) => { + const darkMode = isDarkMode(theme); + return { + primaryColor: "#1fc7d4", + cardColor: darkMode ? "#362F47" : "#eee", + primaryTextColor: darkMode ? "#FBF4EF" : "#453936", + secondaryColor: darkMode ? "#767676" : "#866C65", + secondarySubTextColor: darkMode ? "#866C65" : "#767676", + cardBox: darkMode ? "#2A2A27" : "#E4CAB4", + editableCardBox: darkMode ? "#20201D" : "#EFD9C7", + inputShadow: darkMode ? "" : "inset 0px 2px 2px -1px rgba(74,74,104,.1)", + border: darkMode ? "#383241" : "#e7e3eb", + labelIcon: darkMode ? "#f4eeff" : "black", + darkMode, + subtitle: darkMode ? "#2A2A27" : "#E4CAB4", + subValue: darkMode ? "#FBF4EF" : "#453936", + }; +}; + +const getTootlipStyles = (theme: Theme) => { + const darkTheme = isDarkMode(theme); + + return { + background: darkTheme ? "white" : "#27262C", + color: darkTheme ? "#27262C" : "white", + fontSize: 15, + borderRadius: 10, + padding: "10px 15px", + lineHeight: "20px", + fontWeight: 400, + "*": { + color: "inherit", + }, + }; +}; + +const getButtonStyles = (theme: Theme) => { + const styles = baseStyles(theme); + return { + fontWeight: 600, + fontSize: 16, + boxShadow: "rgba(14, 14, 44, 0.4) 0px -1px 0px 0px inset", + borderRadius: 16, + background: styles.primaryColor, + transition: "0.2s all", + color: isDarkMode(theme) ? "#191326" : "white", + border: "unset", + "&:hover": { + opacity: 0.65, + }, + "*, p": { + color: isDarkMode(theme) ? "#191326" : "white", + }, + }; +}; + +export const StyledCardBody = styled(Box)<{ editable?: number; opacity?: number }>(({ theme, editable, opacity = 100 }) => { + const styles = baseStyles(theme); + + return { + opacity: opacity / 100, + display: "flex", + flexDirection: "row", + width: "100%", + pointerEvents: editable ? "all" : "none", + background: editable ? styles.editableCardBox : styles.cardBox, + padding: 16, + borderRadius: 16, + boxShadow: "0 2px 0px rgba(213, 186, 165, 0.76)", + }; +}); + +export const StyledPoweredBy = styled(Components.PoweredBy)(({ theme }) => { + const styles = baseStyles(theme); + + return { + color: styles.primaryTextColor, + marginTop: 20, + fontSize: 14, + + "*": { + color: "inherit", + }, + }; +}); + +export const configureStyles = (theme: Theme) => { + const styles = baseStyles(theme); + + const darkMode = isDarkMode(theme); + + return { + ".twap-order-expanded-cancel-wraper": { + marginTop: "40px", + button: { + color: !darkMode ? "white!important" : "#191326!important", + }, + }, + ".twap-cancel-order": { + background: "unset!important", + borderRadius: "30px!important", + fontWeight: 500, + fontSize: "14px!important", + padding: "6px 40px!important", + transition: "0.2s all", + height: "unset!important", + cursor: "pointer", + marginTop: "20px", + minHeight: "unset!important", + boxShadow: "unset!important", + }, + ".twap-token-input-loading": { + opacity: 0.5, + }, + ".twap-odnp-button": { + ...getButtonStyles(theme), + background: darkMode ? "#B8ADD2" : "#492F79", + padding: "6px 12px!important", + width: "fit-content", + boxShadow: "unset", + marginLeft: "auto", + fontWeight: 500, + "&-children": { + gap: "5px!important", + }, + }, + ".twap-label": { + p: { + fontWeight: "400!important", + color: styles.primaryTextColor, + }, + svg: { + color: `${styles.labelIcon}!important`, + maxWidth: 14, + maxHeight: 14, + }, + }, + ".twap-container": { + color: styles.primaryTextColor, + }, + ".twap-button": { + minHeight: 48, + ...getButtonStyles(theme), + padding: "10px", + }, + ".twap-order-separator": { + background: `${styles.primaryTextColor}!important`, + opacity: 0.4, + }, + ".twap-spinner": { + color: `${styles.primaryTextColor}!important`, + }, + ".twap-orders-lists": { + width: "100%", + }, + ".twap-orders-list": { + padding: 0, + width: "100%", + gap: "15px!important", + }, + ".twap-order-token-display": { + flex: "unset!important", + }, + ".twap-adapter-wrapper": { + width: "100%", + background: "transparent", + }, + ".twap-modal": { + ".MuiBackdrop-root": { + background: darkMode ? "rgba(244, 238, 255, 0.6)" : "", + }, + }, + + ".twap-modal-content": { + background: darkMode ? "#27262C" : "white", + color: styles.primaryTextColor, + display: "flex", + flexDirection: "column", + padding: "0px", + maxWidth: "370px!important", + borderRadius: 32, + minHeight: "415px", + overflow: "hidden", + "*::-webkit-scrollbar": { + display: "none", + }, + "&-header": { + marginBottom: 10, + }, + }, + ".twap-trade-size": { + ".twap-label": { + whiteSpace: "nowrap", + }, + ".twap-token-logo": { + display: "none", + }, + "*": { + color: styles.primaryTextColor, + }, + }, + ".twap-orders-title": { + p: { + fontWeight: 600, + color: `${styles.primaryTextColor}!important`, + }, + }, + ".twap-order": { + border: `1px solid ${styles.border}`, + borderRadius: 16, + padding: 15, + transition: "0.2s all", + color: `${styles.primaryTextColor}!important`, + background: darkMode ? "#362F47" : "#EEEAF4", + ".twap-order-expanded-right": { + fontWeight: "400!important", + }, + ".twap-market-price-section": { + "*": { + fontSize: 13, + fontWeight: "400!important", + }, + }, + }, + ".twap-order-progress": { + background: darkMode ? "#2D2836!important" : "#1fc7d4!important", + "&::after": { + display: "none!important", + }, + ".MuiLinearProgress-bar": { + background: darkMode ? `${styles.subtitle}!important` : "#7a6eaa!important", + }, + }, + + ".twap-switch": { + ".MuiSwitch-thumb": { + background: darkMode ? `#27262C!important` : "white!important", + }, + ".MuiSwitch-track": { + backgroundColor: darkMode ? `#b8add2!important` : "#EDEAF4!important", + opacity: "1!important", + }, + ".Mui-checked+.MuiSwitch-track": { + background: "#32D0AA!important", + }, + }, + ".twap-time-selector": { + ".twap-input": { + input: { + fontWeight: 400, + }, + }, + }, + ".twap-price-impact-selector": { + border: `1px solid ${styles.cardBox}`, + borderRadius: 16, + + ".twap-input": { + input: { + fontWeight: 700, + fontSize: 16, + }, + }, + }, + ".twap-time-selector-selected": { + "*": { + color: `${styles.primaryTextColor}!important`, + }, + }, + ".twap-time-selector-list": { + background: styles.cardBox, + border: `1px solid ${styles.border}`, + borderRadius: "16px!important", + padding: "0px!important", + }, + ".twap-time-selector-list-item": { + p: { color: styles.primaryTextColor, fontWeight: "400!important" }, + + "&:hover": { + background: darkMode ? "rgba(255,255,255, 0.06)" : "rgba(0,0,0, 0.06)", + }, + }, + ".twap-button-disabled": { + background: darkMode ? "#3c3742!important" : "#e9eaeb!important", + opacity: "1!important", + cursor: "not-allowed!important", + boxShadow: "unset!important", + "*": { + color: "#bdc2c4!important", + }, + p: { + opacity: "0.4!important", + }, + }, + ".twap-tooltip": { + ".MuiTooltip-arrow": { + color: darkMode ? "white!important" : "#27262C!important", + }, + "& .MuiTooltip-tooltip": { + ...getTootlipStyles(theme), + fontFamily: "Kanit", + }, + }, + ".twap-loader": { + background: darkMode ? "rgba(255,255,255, 0.1)!important" : "rgba(0,0,0, 0.1)!important", + right: 0, + }, + ".twap-market-price": { + justifyContent: "center!important", + width: "100%", + ".twap-price-compare": { + justifyContent: "center", + }, + ">div": { + width: "100%", + }, + "*": { + fontSize: 14, + color: styles.primaryTextColor, + }, + }, + ".twap-label, .twap-market-price .title": { + fontSize: 13, + color: styles.primaryTextColor, + fontWeight: 600, + "*, p": { + color: "inherit", + fontWeight: "inherit", + }, + }, + ".twap-input": { + input: { + color: styles.primaryTextColor, + fontSize: 24, + fontWeight: 700, + "&::placeholder": { + color: `${styles.primaryTextColor}!important`, + opacity: 0.5, + fontWeight: "inherit", + }, + }, + }, + ".twap-usd": { + fontSize: 12, + "*": { + color: styles.subtitle, + }, + }, + "@media (max-width:970px)": { + ".twap-orders-title": { + p: { + fontSize: "14px!important", + }, + }, + ".twap-order-expanded": { + ".twap-token-logo": { + display: "none", + }, + }, + ".twap-order-preview-tokens": { + ".twap-order-preview-icon svg": { + width: "16px!important", + height: "16px!important", + position: "relative", + top: 5, + }, + ".twap-token-logo": { + display: "none", + }, + }, + }, + }; +}; + +export const StyledText = styled(Styles.StyledText)(({ theme }) => { + const styles = baseStyles(theme); + return { + color: styles.secondaryColor, + fontSize: 16, + fontWeight: 400, + }; +}); + +export const StyledTokenPanelInput = styled(Components.TokenPanelInput)({ + input: { + width: "100%", + }, +}); + +export const StyledBalanceContainer = styled("div")({ + flex: 1, + overflow: "hidden", + display: "flex", + justifyContent: "flex-end", +}); +export const StyledBalance = styled(Components.TokenBalance)(({ theme }) => { + const styles = baseStyles(theme); + return { + cursor: "pointer", + fontSize: 12, + color: styles.primaryTextColor, + fontWeight: "400", + "*": { + color: "inherit", + fontWeight: "700", + }, + }; +}); + +export const StyledMarketPrice = styled(Components.MarketPrice)({ + flexDirection: "column", + alignItems: "flex-start", + + gap: 5, + ".twap-loader": { + marginLeft: "auto", + }, + + ".twap-price-compare": { + justifyContent: "flex-end", + width: "auto", + marginLeft: "auto", + "*": { + fontSize: 13, + }, + }, +}); + +export const StyledMarketPriceContainer = styled(Styles.StyledRowFlex)(({ theme }) => { + const darkMode = baseStyles(theme).darkMode; + return { + position: "relative", + padding: 12, + borderRadius: 16, + background: darkMode ? "#20201D" : "#EFD9C7", + justifyContent: "space-between", + p: { color: darkMode ? "#767676" : "#866C65", fontWeight: "400!important" }, + ".twap-token-logo": { + display: "none", + }, + ".twap-label": { + p: { + whiteSpace: "nowrap", + }, + }, + "@media(max-width: 700px)": { + ".twap-label": { + p: { + fontSize: "12px!important", + }, + }, + }, + }; +}); + +export const StyledTokenSelect = styled(Components.TokenSelect)(({ theme }) => { + const styles = baseStyles(theme); + return { + background: styles.cardBox, + borderRadius: 16, + padding: 12, + + ".twap-token-name": { + fontWeight: 700, + fontSize: 12, + color: styles.primaryTextColor, + }, + }; +}); + +export const StyledColumnFlex = styled(Styles.StyledColumnFlex)({ + gap: 14, +}); + +export const StyledPercentSelect = styled(Styles.StyledRowFlex)({ + marginTop: 14, + gap: 5, + justifyContent: "flex-end", +}); + +export const StyledTokenChangeContainer = styled(Styles.StyledRowFlex)(({ theme }) => { + const styles = baseStyles(theme); + + const darkMode = isDarkMode(theme); + return { + width: 32, + height: 32, + marginLeft: "auto", + marginRight: "auto", + "&:hover": { + button: { + opacity: darkMode ? 0.65 : 1, + }, + }, + }; +}); + +export const StyledTokenChange = styled(Components.ChangeTokensOrder)(({ theme }) => { + const styles = baseStyles(theme); + return { + button: { + width: "100%", + height: "100%", + transition: "unset", + svg: { + color: styles.primaryTextColor, + fill: styles.primaryTextColor, + width: 17, + height: 17, + }, + }, + }; +}); + +export const StyledLimitPrice = styled(Styles.StyledRowFlex)(({ theme }) => { + const styles = baseStyles(theme); + return { + justifyContent: "space-between", + marginTop: 10, + ".twap-limit-price-input": { + "*": { + color: styles.primaryTextColor, + }, + input: { + position: "relative", + top: -2, + }, + }, + ".twap-token-logo": { + display: "none", + }, + ".twap-limit-reset": { + left: 10, + "*": { + stroke: styles.primaryColor, + }, + }, + }; +}); + +export const StyledLimitPriceInput = styled(Components.LimitPriceInput)({ + paddingLeft: 0, +}); + +const borderButtonStyles = { + background: "unset", + borderRadius: 16, + fontWeight: 600, + fontSize: 12, + border: "2px solid #1fc7d4", + color: "#1fc7d4", + padding: "0px 8px", + transition: "0.2s all", + cursor: "pointer", + "&:hover": { + opacity: 0.65, + }, +}; + +export const StyledButton = styled("button")<{ selected?: number }>(({ theme, selected }) => { + const styles = baseStyles(theme); + return { + ...borderButtonStyles, + background: selected ? styles.primaryColor : "unset", + color: !selected ? "#1fc7d4" : styles.darkMode ? "#191326" : "white", + }; +}); + +export const StyledReset = styled(StyledButton)({ + p: { + fontSize: 13, + color: "#DE7F3B", + }, + border: "none", + padding: "0px", +}); + +export const StyledAcceptDisclaimer = styled(Components.AcceptDisclaimer)({ + justifyContent: "space-between", +}); + +export const StyledOutputAddress = styled(Components.OutputAddress)({ + marginTop: 20, + fontSize: 14, +}); + +export const StyledOrderSummary = styled(Styles.StyledColumnFlex)(({ theme }) => { + const styles = baseStyles(theme); + return { + ".twap-order-summary-output-address": { + alignItems: "center", + fontSize: 14, + }, + ".twap-order-summary-details-item": { + ".twap-label": { + maxWidth: "unset", + fontSize: 14, + fontWeight: 400, + }, + ".twap-token-display": { + order: 1, + }, + }, + ".twap-order-summary-limit-price": { + p: { + fontSize: 14, + }, + }, + ".twap-order-summary-details-item-right": { + fontSize: 14, + gap: 3, + }, + ".twap-ui-close": { + "*": { + color: `${styles.primaryTextColor}`, + }, + }, + ".twap-card": { + border: `1px solid ${styles.border}`, + borderRadius: 16, + padding: 12, + transition: "0.2s all", + + background: styles.darkMode ? "#353547" : "#D5BAA5", + "*": { + color: `${styles.primaryTextColor}`, + }, + }, + ".twap-label": { + p: { + color: styles.primaryTextColor, + }, + svg: { + color: `${styles.labelIcon}!important`, + }, + }, + + ".twap-orders-summary-token-display-amount": { + p: { + fontSize: 16, + }, + }, + ".twap-orders-summary-token-display": { + ".twap-token-logo": { + width: 35, + height: 35, + }, + }, + "@media (max-width:700px)": { + ".twap-order-summary-limit-price": { + "*": { + fontSize: "12px!important", + }, + }, + }, + }; +}); + +export const Card = ({ children, className = "" }: { children: ReactNode; className?: string }) => { + return ( + + {children} + + ); +}; + +const CardHeader = ({ children, className = "" }: { children: ReactNode; className?: string }) => { + return ( + + {" "} + {children} + + ); +}; + +const CardBody = ({ children, editable, className = "", opacity }: { children: ReactNode; editable?: boolean; className?: string; opacity?: number }) => { + return ( + + {children} + + ); +}; + +Card.Body = CardBody; +Card.Header = CardHeader; + +export const StyledDisabledCardBody = styled(Card.Body)(({ opacity = 1 }: { opacity: number }) => ({ + opacity: opacity, +})); + +export const StyledTokenPanel = styled(Card)({ + width: "100%", + gap: 7, + ".twap-input": { + width: "100%", + input: { + textAlign: "left", + }, + }, + ".twap-token-logo": { + width: 24, + height: 24, + }, +}); + +export const StyledTradeSize = styled(Styles.StyledRowFlex)({ + justifyContent: "space-between", + flexWrap: "wrap", + alignItems: "center", +}); + +export const StyledOrders = styled(OrdersContainer)(({ theme }) => { + const styles = baseStyles(theme); + return { + gap: 0, + ".twap-orders-empty-list": { + marginBottom: "40px", + paddingTop: "30px", + color: styles.primaryTextColor, + justifyContent: "center", + alignItems: "center", + display: "flex", + flexDirection: "column", + }, + ".twap-orders-pagination": { + color: styles.primaryTextColor, + "*": { + color: styles.primaryTextColor, + }, + }, + }; +}); + +export const StyledTimeSelect = styled(Styles.StyledColumnFlex)({ + display: "flex", + alignItems: "flex-end", + width: "auto", + padding: 2, + flex: 1, +}); + +export const StyledTimeSelectBody = styled(CardBody)({ + display: "flex", + alignItems: "center", + + padding: "4px 10px", + width: "auto", + boxShadow: "none", +}); + +export const StyledTimeSelectContainer = styled(Styles.StyledRowFlex)(({ theme }) => { + const styles = baseStyles(theme); + + return { + background: styles.editableCardBox, + borderRadius: 16, + padding: 16, + ".MuiButtonBase-root": { + padding: "0px!important", + background: "unset!important", + height: "100%", + p: { + fontSize: "12px!important", + fontWeight: 400, + }, + }, + ".twap-input": { + input: { + fontSize: 14, + paddingRight: 3, + }, + }, + }; +}); + +export const StyledTimeSelectHeader = styled(Card.Header)({ + marginTop: 1, + width: "auto", +}); + +export const StyledOrdersHeader = styled(Box)(({ theme }) => { + const styles = baseStyles(theme); + return { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + background: styles.darkMode ? "#20201D" : "#EFD9C7", + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + height: 60, + width: "100%", + padding: "0px 0px", + position: "relative", + }; +}); + +export const StyledOrdersTab = styled(Box)<{ selected: number }>(({ selected, theme }) => { + const styles = baseStyles(theme); + const selectedColor = styles.darkMode ? "#FBF4EF" : "#453936"; + return { + cursor: "pointer", + background: selected ? (theme.palette.mode === "dark" ? "#2A2A27" : "#E4CAB4") : "transparent", + height: "100%", + padding: "0px 24px", + display: "flex", + alignItems: "center", + borderRadius: "16px", + flex: 1, + justifyContent: "center", + color: selectedColor, + "@media (max-width:700px)": { + fontSize: 12, + padding: "0px 10px", + }, + }; +}); + +export const StyledOrdersTabs = styled(Box)(({ theme }) => { + const styles = baseStyles(theme); + return { + display: "flex", + flex: 1, + border: `1px solid ${styles.darkMode ? "#353531" : "#D5BAA5"}`, + alignItems: "center", + padding: "2px", + borderRadius: 16, + justifyContent: "space-between", + height: "100%", + + "@media (max-width:700px)": {}, + }; +}); + +export const StyledLimitPriceBody = styled(Card.Body)({ + padding: "10px 10px", + alignItems: "center", + position: "relative", + input: { + textAlign: "right", + }, +}); + +export const StyledResetLimitButton = styled(Components.ResetLimitButton)({ + position: "absolute", + top: "0", + right: "0", + zIndex: 1, +}); + +export const StyledLimitPriceLabel = styled(Styles.StyledRowFlex)({ + width: "auto", + minHeight: 24, +}); + +export const StyledSubmitButtonContainer = styled(Styles.StyledRowFlex)({ + button: { + width: "100%", + }, +}); + +export const StyledModalHeaderClose = styled("button")(({ theme }) => { + const darkMode = baseStyles(theme).darkMode; + + return { + margin: 0, + marginLeft: "auto", + background: "transparent", + padding: 0, + border: "unset", + width: 48, + height: 48, + display: "flex", + justifyContent: "center", + alignItems: "center", + cursor: "pointer", + svg: { + color: darkMode ? "#f4eeff" : "#1fc7d4", + width: 20, + height: 20, + }, + "&:hover": { + opacity: 0.8, + }, + }; +}); + +export const StyledModalHeader = styled(Styles.StyledRowFlex)<{ withTitle: number }>(({ theme, withTitle }) => { + const darkMode = baseStyles(theme).darkMode; + + return { + justifyContent: "space-between", + alignItems: "center", + background: !withTitle ? "transparent" : darkMode ? "#3B394D" : "linear-gradient(111.68deg,#f2ecf2,#e8f2f6)", + padding: "12px 24px", + paddingBottom: !withTitle ? "0" : "12px", + borderBottom: !withTitle ? "1px solid transparent" : darkMode ? "1px solid #383241" : "1px solid #e7e3eb", + }; +}); + +export const StyledSwapModalContent = styled(Styles.StyledColumnFlex)<{ style: CSSProperties }>(({ style }) => ({ + padding: "0px 24px 24px 24px", + alignItems: "center", + justifyContent: "center", + flex: 1, + overflowY: "auto", + ...style, +})); +export const StyledModalHeaderTitle = styled(Typography)(({ theme }) => { + const darkMode = baseStyles(theme).darkMode; + return { + fontSize: 20, + fontWeight: 600, + color: darkMode ? "#f4eeff" : "#280d5f", + }; +}); + +export const StyledBody = styled(Styles.StyledColumnFlex)(({ theme }) => { + const styles = baseStyles(theme); + return { + padding: "15px 20px 20px 20px", + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + alignItems: "center", + gap: 15, + backgroundColor: styles.darkMode ? "#20201D" : "#EFD9C7", + }; +}); + +export const StyledButtonTab = styled("button")<{ selected?: boolean }>(({ selected, theme }) => { + const styles = baseStyles(theme); + + let color; + + if (styles.darkMode) { + color = selected ? "#DE7F3B" : "#767676"; + } else { + color = selected ? "#DE7F3B" : "#866C65"; + } + + return { + color: color, + backgroundColor: "inherit", + border: "none", + padding: "5px", + cursor: "pointer", + outline: "none", + fontSize: 16, + fontWeight: 700, + }; +}); + +export const StyledLine = styled(Box)(({ theme }) => { + const styles = baseStyles(theme); + return { + height: "1px", + backgroundColor: styles.darkMode ? "#FBF4EF" : "#D5BAA5", + width: "95%", + margin: "0 auto", + }; +}); diff --git a/packages/tradingpost/src/types.ts b/packages/tradingpost/src/types.ts new file mode 100644 index 00000000..2cd7853f --- /dev/null +++ b/packages/tradingpost/src/types.ts @@ -0,0 +1,6 @@ +export interface TradeStatCardProps { + title: string; + value: string; + editable?: boolean; + opacity?: number; +} diff --git a/packages/tradingpost/transform-svg.ts b/packages/tradingpost/transform-svg.ts new file mode 100644 index 00000000..94c4e038 --- /dev/null +++ b/packages/tradingpost/transform-svg.ts @@ -0,0 +1,12 @@ +import { execSync } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; + +const svgDir = path.join(__dirname, 'src', 'assets'); +const outputDir = path.join(__dirname, 'src', 'assets', 'components'); + +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +execSync(`npx @svgr/cli --typescript --out-dir ${outputDir} ${svgDir}`); diff --git a/packages/tradingpost/tsconfig.json b/packages/tradingpost/tsconfig.json new file mode 100644 index 00000000..6bb1a2eb --- /dev/null +++ b/packages/tradingpost/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "commonjs", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "outDir": "dist", + "declaration": true, + }, + "include": ["./src"] +}