From cfde59188b1a19349490fa202099787a421dbe93 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Wed, 17 Jul 2024 12:00:00 +0000 Subject: [PATCH 01/16] project setup --- package-lock.json | 54 ++++++++++++++++++++++++++++++ packages/tradingpost/LICENSE | 21 ++++++++++++ packages/tradingpost/package.json | 35 +++++++++++++++++++ packages/tradingpost/tsconfig.json | 22 ++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 packages/tradingpost/LICENSE create mode 100644 packages/tradingpost/package.json create mode 100644 packages/tradingpost/tsconfig.json 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/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..27562c19 --- /dev/null +++ b/packages/tradingpost/package.json @@ -0,0 +1,35 @@ +{ + "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}'", + "build": "npm run prettier && rm -rf dist && tsc", + "start": "npm run prettier && nodemon --ext js,jsx,ts,tsx,json --watch ./src --exec tsc", + "test": "eslint src" + }, + "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": "*" + } +} diff --git a/packages/tradingpost/tsconfig.json b/packages/tradingpost/tsconfig.json new file mode 100644 index 00000000..7152b297 --- /dev/null +++ b/packages/tradingpost/tsconfig.json @@ -0,0 +1,22 @@ +{ + "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"] +} From 9430daa78c313ec1a593d832d59fb9f52b3ddc38 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Thu, 18 Jul 2024 21:19:37 -0600 Subject: [PATCH 02/16] style: modify card and button styles --- packages/tradingpost/src/OrderSummary.tsx | 157 +++ .../tradingpost/src/TradingPostOrders.tsx | 51 + packages/tradingpost/src/components.tsx | 53 + packages/tradingpost/src/context.ts | 27 + packages/tradingpost/src/i18n/en.json | 4 + packages/tradingpost/src/styles.tsx | 1002 +++++++++++++++++ 6 files changed, 1294 insertions(+) create mode 100644 packages/tradingpost/src/OrderSummary.tsx create mode 100644 packages/tradingpost/src/TradingPostOrders.tsx create mode 100644 packages/tradingpost/src/components.tsx create mode 100644 packages/tradingpost/src/context.ts create mode 100644 packages/tradingpost/src/i18n/en.json create mode 100644 packages/tradingpost/src/styles.tsx 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..a0918527 --- /dev/null +++ b/packages/tradingpost/src/TradingPostOrders.tsx @@ -0,0 +1,51 @@ +import * as React from "react"; +import { styled } from "@mui/material"; +import { hooks, OrdersPortal, SelectedOrders, store } from "@orbs-network/twap-ui"; +import { StyledOrders, StyledOrdersHeader, StyledOrdersTab, StyledOrdersTabs } from "./styles"; +import { Styles } from "@orbs-network/twap-ui"; + +export default function PancakeOrders() { + return ( + + + + + + + + + + + ); +} + +const StyledBody = styled(Styles.StyledColumnFlex)({ + padding: "15px 20px 20px 20px", + alignItems: "center", + gap: 15, +}); + +const Tabs = () => { + 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/components.tsx b/packages/tradingpost/src/components.tsx new file mode 100644 index 00000000..5f0b3cdd --- /dev/null +++ b/packages/tradingpost/src/components.tsx @@ -0,0 +1,53 @@ +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 ( + +
+ +
+ + $3.05 $8.72 +
+ ); +} + +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/styles.tsx b/packages/tradingpost/src/styles.tsx new file mode 100644 index 00000000..58f9360c --- /dev/null +++ b/packages/tradingpost/src/styles.tsx @@ -0,0 +1,1002 @@ +import { Box, Button, createTheme, styled, Theme, Typography } from "@mui/material"; +import { border, fontSize, fontWeight, padding, width } 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 ? "#866C65" : "#767676", + 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 StyledValue = styled(Styles.StyledText)(({ theme }) => { + const styles = baseStyles(theme); + return { + color: styles.subValue, + fontSize: 24, + fontWeight: 700, + }; +}); + +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 StyledUSD = styled(Components.TokenUSD)({}); + +export const StyledEmptyUSD = styled(Box)({ + height: 18, + opacity: 0, +}); + +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 StyledSelectAndBalance = styled(Styles.StyledRowFlex)({ + justifyContent: "space-between", +}); + +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 StyledChunksInput = styled(Components.ChunksInput)({ + marginLeft: "auto", + fontWeight: 600, + color: "#1fc7d4", + div: { + height: "100%", + }, + input: { + height: "100%", + }, +}); + +export const StyledChunksSlider = styled(Components.ChunksSliderSelect)(({ theme }) => { + const styles = baseStyles(theme); + return { + marginLeft: 10, + ".MuiSlider-thumb": { + background: styles.darkMode ? styles.primaryTextColor : "#1fc7d4", + }, + ".MuiSlider-track": { + background: styles.primaryColor, + border: `1px solid ${styles.primaryColor}`, + }, + ".MuiSlider-valueLabel": { + ...getTootlipStyles(theme), + }, + }; +}); + +export const StyledLimitPrice = styled(Styles.StyledRowFlex)(({ theme }) => { + const styles = baseStyles(theme); + return { + justifyContent: "space-between", + ".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, + }, + svg: { + width: 12, + height: 12, + }, +}); + +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 StyledTimingAndDistribution = styled(Card)({ + width: "100%", + gap: 7, + ".twap-input": { + width: "100%", + input: { + textAlign: "left", + }, + }, +}); + +export const StyledPriceImpactPanel = styled(Card)({ + width: "100%", + marginTop: 20, + ".twap-input": { + input: { + textAlign: "left", + }, + }, +}); + +export const StyledTradeSize = styled(Styles.StyledRowFlex)({ + justifyContent: "space-between", + flexWrap: "wrap", + alignItems: "center", +}); + +export const StyledTotalChunks = styled(Card)({ + ".twap-input": { + height: 25, + }, +}); + +export const StyledTradeSizeRight = styled(Styles.StyledColumnFlex)({ + gap: 0, + width: "auto", + alignItems: "center", +}); + +export const StyledOrdersMenuButton = styled(Button)(({ theme }) => ({ + width: "auto!important", + background: baseStyles(theme).darkMode ? "#353547" : "", +})); + +export const StyledOrders = styled(OrdersContainer)(({ theme }) => { + const styles = baseStyles(theme); + return { + gap: 0, + ".twap-orders-empty-list": { + marginBottom: "40px", + paddingTop: "30px", + color: styles.primaryTextColor, + }, + ".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 StyledPriceImpactSelect = styled(Components.PriceProtectionSelector)(({ theme }) => { + const styles = baseStyles(theme); + return { + background: styles.cardBox, + borderRadius: 16, + padding: 12, + }; +}); + +export const StyledTimeSelectBody = styled(CardBody)({ + display: "flex", + alignItems: "center", + + padding: "4px 10px", + width: "auto", +}); + +export const StyledTimeSelectContainer = styled(Styles.StyledRowFlex)({ + padding: 0, + ".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: "column", + background: styles.darkMode ? "#372f47" : "#eeeaf4", + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + height: 48, + }; +}); + +export const StyledOrdersTab = styled(Box)<{ selected: number }>(({ selected, theme }) => { + const styles = baseStyles(theme); + const color = styles.darkMode ? "#b8add2" : "#7a6eaa"; + const selectedColor = styles.darkMode ? "#f4eeff" : "#280d5f"; + return { + cursor: "pointer", + background: !selected ? "transparent" : styles.darkMode ? "#27262c" : "white", + height: "100%", + padding: " 0px 24px", + display: "flex", + alignItems: "center", + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + flex: 1, + justifyContent: "center", + fontWeight: 500, + color: !selected ? color : selectedColor, + "@media (max-width:700px)": { + fontSize: 11, + padding: " 0px 10px", + }, + }; +}); + +export const StyledOrdersTabs = styled(Box)({ + display: "flex", + alignItems: "center", + width: "100%", + justifyContent: "space-between", + height: "100%", + flex: 1, + "@media (max-width:700px)": {}, +}); + +export const StyledLimitPriceBody = styled(Card.Body)({ + padding: "10px 10px", + input: { + textAlign: "right", + }, +}); + +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", + }; +}); From 898ea2c2f5b738f66206a47570bc727ffe26301d Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Sat, 20 Jul 2024 21:26:58 -0600 Subject: [PATCH 03/16] feat: update build process and add SVG handling --- packages/tradingpost/package.json | 8 ++++++-- packages/tradingpost/src/assets/arrow.svg | 1 + .../src/assets/price-protection-arrow-selector.svg | 4 ++++ packages/tradingpost/src/assets/twap-dropdown.svg | 3 +++ packages/tradingpost/src/declarations.d.ts | 6 ++++++ packages/tradingpost/tsconfig.json | 3 +-- 6 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 packages/tradingpost/src/assets/arrow.svg create mode 100644 packages/tradingpost/src/assets/price-protection-arrow-selector.svg create mode 100644 packages/tradingpost/src/assets/twap-dropdown.svg create mode 100644 packages/tradingpost/src/declarations.d.ts diff --git a/packages/tradingpost/package.json b/packages/tradingpost/package.json index 27562c19..83b962e2 100644 --- a/packages/tradingpost/package.json +++ b/packages/tradingpost/package.json @@ -15,9 +15,10 @@ ], "scripts": { "prettier": "prettier -w '{src,test}/**/*.{ts,tsx,js,jsx,json,sol}'", - "build": "npm run prettier && rm -rf dist && tsc", + "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" + "test": "eslint src", + "copy-assets": "copyfiles -u 1 src/assets/*.svg dist/" }, "dependencies": { "@defi.org/web3-candies": "^4.20", @@ -31,5 +32,8 @@ "peerDependencies": { "react": "*", "react-dom": "*" + }, + "devDependencies": { + "copyfiles": "^2.4.1" } } diff --git a/packages/tradingpost/src/assets/arrow.svg b/packages/tradingpost/src/assets/arrow.svg new file mode 100644 index 00000000..f5282490 --- /dev/null +++ b/packages/tradingpost/src/assets/arrow.svg @@ -0,0 +1 @@ + diff --git a/packages/tradingpost/src/assets/price-protection-arrow-selector.svg b/packages/tradingpost/src/assets/price-protection-arrow-selector.svg new file mode 100644 index 00000000..56aeb97c --- /dev/null +++ b/packages/tradingpost/src/assets/price-protection-arrow-selector.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/tradingpost/src/assets/twap-dropdown.svg b/packages/tradingpost/src/assets/twap-dropdown.svg new file mode 100644 index 00000000..402e11a9 --- /dev/null +++ b/packages/tradingpost/src/assets/twap-dropdown.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/tradingpost/src/declarations.d.ts b/packages/tradingpost/src/declarations.d.ts new file mode 100644 index 00000000..d18f6895 --- /dev/null +++ b/packages/tradingpost/src/declarations.d.ts @@ -0,0 +1,6 @@ +declare module "*.svg" { + import * as React from "react"; + export const ReactComponent: React.FunctionComponent>; + const src: string; + export default src; +} diff --git a/packages/tradingpost/tsconfig.json b/packages/tradingpost/tsconfig.json index 7152b297..e339c0c9 100644 --- a/packages/tradingpost/tsconfig.json +++ b/packages/tradingpost/tsconfig.json @@ -16,7 +16,6 @@ "jsx": "react-jsx", "outDir": "dist", "declaration": true, - }, - "include": ["./src"] + "include": ["./src/**/*", "declarations.d.ts"], } From 568aa16d7159f750d4edca8151fba7d950362162 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Mon, 22 Jul 2024 21:35:54 -0600 Subject: [PATCH 04/16] feat: add PriceProtectionSelector component --- .../components/base/PriceImpactSelector.tsx | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 packages/lib/src/components/base/PriceImpactSelector.tsx diff --git a/packages/lib/src/components/base/PriceImpactSelector.tsx b/packages/lib/src/components/base/PriceImpactSelector.tsx new file mode 100644 index 00000000..85cc366b --- /dev/null +++ b/packages/lib/src/components/base/PriceImpactSelector.tsx @@ -0,0 +1,85 @@ +import { useState } from "react"; +import { Box, fontSize, styled } from "@mui/system"; +import { useTwapContext } from "../../context"; +import NumericInput from "./NumericInput"; +import { StyledPriceImpactText } from "../../styles"; +import { IoIosArrowDown } from "@react-icons/all-files/io/IoIosArrowDown"; + +interface Props { + value: number; + onChange: (value: number) => void; + disabled?: boolean; + className?: string; + onFocus?: () => void; + onBlur?: () => void; + placeholder?: string; + icon?: React.ReactNode; +} + +function PriceProtectionSelector({ value, onChange, disabled = false, className = "", onFocus, onBlur, placeholder = "0", icon = }: Props) { + const [showList, setShowList] = useState(false); + const translations = useTwapContext().translations; + + const onOpenListClick = () => { + if (disabled) return; + setShowList(true); + }; + + return ( + + + + + onChange(6)} placeholder={placeholder} /> + + % + + {icon} + + + ); +} + +export default PriceProtectionSelector; + +const StyledInput = styled(Box)({ + flex: "1 1 auto", + display: "flex", + alignItems: "center", + ".twap-input": { + width: "100%", + input: { + fontSize: 16, + textAlign: "right", + width: "100%", + "&::placeholder": { + color: "white", + }, + }, + }, +}); + +const StyledContainer = styled(Box)({ + display: "flex", + alignItems: "center", + flex: 1, + gap: 2, +}); + +const StyledPriceImpactSelect = styled(Box)({ + display: "flex", + alignItems: "center", + padding: 12, + gap: 8, + maxWidth: 115, + "& p": { + fontSize: 16, + fontWeight: 600, + }, +}); + +const StyledTextAndIconContainer = styled(Box)({ + display: "flex", + alignItems: "center", + width: "calc(100% - 24px)", +}); From 1c48996983169570a9a0a22d31e5c9f2637fe955 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 23 Jul 2024 21:44:24 -0600 Subject: [PATCH 05/16] feat: add StyledPriceImpactText and integrate PriceProtectionSelector --- packages/lib/src/components/Components.tsx | 8 ++++++++ packages/lib/src/components/base/index.ts | 1 + packages/lib/src/store.ts | 6 ++++++ packages/lib/src/styles.ts | 6 ++++++ 4 files changed, 21 insertions(+) diff --git a/packages/lib/src/components/Components.tsx b/packages/lib/src/components/Components.tsx index 54f979cc..39cad7e6 100644 --- a/packages/lib/src/components/Components.tsx +++ b/packages/lib/src/components/Components.tsx @@ -8,6 +8,7 @@ import { Slider, Switch, TimeSelector, + PriceImpactSelector, TokenName, TokenPriceCompare, Tooltip, @@ -345,6 +346,13 @@ export function TradeIntervalSelector({ placeholder }: { placeholder?: string }) return ; } +export const PriceProtectionSelector = ({ placeholder, icon }: { placeholder?: string; icon?: any }) => { + const setPriceImpact = useTwapStore((store) => store.setPriceImpact); + const priceImpact = useTwapStore((store) => store.getPriceImpact)(); + + return ; +}; + interface TokenSelectProps extends TWAPTokenSelectProps { Component?: FC; isOpen: boolean; diff --git a/packages/lib/src/components/base/index.ts b/packages/lib/src/components/base/index.ts index 60e44dfc..9a23a7a6 100644 --- a/packages/lib/src/components/base/index.ts +++ b/packages/lib/src/components/base/index.ts @@ -5,6 +5,7 @@ export { default as Label } from "./Label"; export { default as SmallLabel } from "./SmallLabel"; export * from "./Switch"; export { default as TimeSelector } from "./TimeSelector"; +export { default as PriceImpactSelector } from "./PriceImpactSelector"; export { default as TokenLogo } from "./TokenLogo"; export { default as TokenName } from "./TokenName"; export { default as Layout } from "./Layout"; 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..315ddafa 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", From a9e402db901c1b3318652ae36d3909b12a5f2549 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Wed, 24 Jul 2024 21:49:46 -0600 Subject: [PATCH 06/16] feat: integrate TWAP UI for new DEX --- packages/dapp-example/src/TradingPost.tsx | 305 ++++++++++ packages/dapp-example/src/config.ts | 22 +- packages/dapp-example/src/styles.ts | 30 +- packages/lib/src/hooks.ts | 4 +- packages/tradingpost/src/index.tsx | 709 ++++++++++++++++++++++ packages/tradingpost/src/types.ts | 6 + 6 files changed, 1071 insertions(+), 5 deletions(-) create mode 100644 packages/dapp-example/src/TradingPost.tsx create mode 100644 packages/tradingpost/src/index.tsx create mode 100644 packages/tradingpost/src/types.ts 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/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/tradingpost/src/index.tsx b/packages/tradingpost/src/index.tsx new file mode 100644 index 00000000..3f657ac2 --- /dev/null +++ b/packages/tradingpost/src/index.tsx @@ -0,0 +1,709 @@ +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, + StyledLimitPriceLabel, + StyledPoweredBy, + StyledReset, + StyledModalHeaderClose, + StyledModalHeader, + StyledSwapModalContent, + StyledModalHeaderTitle, + StyledText, + StyledTimingAndDistribution, + StyledValue, +} from "./styles"; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { StyledBalance, StyledTokenChange, StyledTokenChangeContainer, StyledTokenPanel, StyledPriceImpactPanel, 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 { ReactComponent as TwapDropDpwn } from "./assets/twap-dropdown.svg"; +import { ReactComponent as Arrow } from "./assets/arrow.svg"; +import { ReactComponent as PriceImpactArrow } from "./assets/price-protection-arrow-selector.svg"; +import { GrPowerReset } from "@react-icons/all-files/gr/GrPowerReset"; +import PancakeOrders 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"; +import { TradeStatCardProps } from "./types"; + +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 PriceProtection = () => { + return ( + + + + + Price Protection + + + + } /> + + + + + ); +}; + +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 TotalDuration = () => { + return ( + + + + Total Duration + 1 h + + + + + ); +}; + +const TradeStatCard: React.FC = ({ title, value, editable = true, opacity = 40 }) => { + return ( + + + + {title} + {value} + + + + ); +}; + +const TradeTimingAndDistribution = () => { + return ( + + + + + + + + + + + + + ); +}; + +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 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 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 ( + + + + + + + + + + + Reset + + + + + + {!limitOnly && } + + + + + {isLimitOrder && ( + + + + + + )} + + + ); +}; + +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/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; +} From bcf533ebdb09ebdb417e7333153ffeefee423cf1 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 16:24:14 -0600 Subject: [PATCH 07/16] feat(i18n): Added new translation keys --- packages/lib/src/i18n/en.json | 4 ++++ 1 file changed, 4 insertions(+) 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", From 72341a359ceb0871465cd41c4881716349894885 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 16:28:05 -0600 Subject: [PATCH 08/16] feat(components): Added new SVG components ArrowOutward and Bear --- .../orders/assets/components/ArrowOutward.tsx | 12 ++ .../lib/src/orders/assets/components/Bear.tsx | 179 ++++++++++++++++++ .../lib/src/orders/assets/components/index.ts | 2 + 3 files changed, 193 insertions(+) create mode 100644 packages/lib/src/orders/assets/components/ArrowOutward.tsx create mode 100644 packages/lib/src/orders/assets/components/Bear.tsx create mode 100644 packages/lib/src/orders/assets/components/index.ts 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"; From 5aebad55bf5b4d8acea9071164e17d8367f7b56e Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 17:01:33 -0600 Subject: [PATCH 09/16] refactor(components): Wrapped SVG in a React component for better reusability --- packages/tradingpost/src/assets/arrow.svg | 1 - .../tradingpost/src/assets/components/Arrow.tsx | 11 +++++++++++ .../src/assets/components/ArrowOutward.tsx | 13 +++++++++++++ packages/tradingpost/src/assets/components/index.ts | 2 ++ .../src/assets/price-protection-arrow-selector.svg | 4 ---- packages/tradingpost/src/assets/twap-dropdown.svg | 3 --- 6 files changed, 26 insertions(+), 8 deletions(-) delete mode 100644 packages/tradingpost/src/assets/arrow.svg create mode 100644 packages/tradingpost/src/assets/components/Arrow.tsx create mode 100644 packages/tradingpost/src/assets/components/ArrowOutward.tsx create mode 100644 packages/tradingpost/src/assets/components/index.ts delete mode 100644 packages/tradingpost/src/assets/price-protection-arrow-selector.svg delete mode 100644 packages/tradingpost/src/assets/twap-dropdown.svg diff --git a/packages/tradingpost/src/assets/arrow.svg b/packages/tradingpost/src/assets/arrow.svg deleted file mode 100644 index f5282490..00000000 --- a/packages/tradingpost/src/assets/arrow.svg +++ /dev/null @@ -1 +0,0 @@ - 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/assets/price-protection-arrow-selector.svg b/packages/tradingpost/src/assets/price-protection-arrow-selector.svg deleted file mode 100644 index 56aeb97c..00000000 --- a/packages/tradingpost/src/assets/price-protection-arrow-selector.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/tradingpost/src/assets/twap-dropdown.svg b/packages/tradingpost/src/assets/twap-dropdown.svg deleted file mode 100644 index 402e11a9..00000000 --- a/packages/tradingpost/src/assets/twap-dropdown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From 7ebaf8ef2a4c9e45f48d7153bef193b356b78b87 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 17:09:54 -0600 Subject: [PATCH 10/16] chore: Added (optional) script to convert SVGs to React components using SVGR --- packages/tradingpost/package.json | 6 ++++++ packages/tradingpost/transform-svg.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 packages/tradingpost/transform-svg.ts diff --git a/packages/tradingpost/package.json b/packages/tradingpost/package.json index 83b962e2..e070b526 100644 --- a/packages/tradingpost/package.json +++ b/packages/tradingpost/package.json @@ -15,6 +15,7 @@ ], "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", @@ -33,7 +34,12 @@ "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/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}`); From 795e7e89781e5587485281fb656a06eacc4dedbe Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 17:18:53 -0600 Subject: [PATCH 11/16] refactor(ui): Removed PriceProtectionSelector component temporarily. Will reintroduce down the road --- packages/lib/src/components/Components.tsx | 8 -- .../components/base/PriceImpactSelector.tsx | 85 ------------------- packages/lib/src/components/base/index.ts | 1 - 3 files changed, 94 deletions(-) delete mode 100644 packages/lib/src/components/base/PriceImpactSelector.tsx diff --git a/packages/lib/src/components/Components.tsx b/packages/lib/src/components/Components.tsx index 39cad7e6..54f979cc 100644 --- a/packages/lib/src/components/Components.tsx +++ b/packages/lib/src/components/Components.tsx @@ -8,7 +8,6 @@ import { Slider, Switch, TimeSelector, - PriceImpactSelector, TokenName, TokenPriceCompare, Tooltip, @@ -346,13 +345,6 @@ export function TradeIntervalSelector({ placeholder }: { placeholder?: string }) return ; } -export const PriceProtectionSelector = ({ placeholder, icon }: { placeholder?: string; icon?: any }) => { - const setPriceImpact = useTwapStore((store) => store.setPriceImpact); - const priceImpact = useTwapStore((store) => store.getPriceImpact)(); - - return ; -}; - interface TokenSelectProps extends TWAPTokenSelectProps { Component?: FC; isOpen: boolean; diff --git a/packages/lib/src/components/base/PriceImpactSelector.tsx b/packages/lib/src/components/base/PriceImpactSelector.tsx deleted file mode 100644 index 85cc366b..00000000 --- a/packages/lib/src/components/base/PriceImpactSelector.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState } from "react"; -import { Box, fontSize, styled } from "@mui/system"; -import { useTwapContext } from "../../context"; -import NumericInput from "./NumericInput"; -import { StyledPriceImpactText } from "../../styles"; -import { IoIosArrowDown } from "@react-icons/all-files/io/IoIosArrowDown"; - -interface Props { - value: number; - onChange: (value: number) => void; - disabled?: boolean; - className?: string; - onFocus?: () => void; - onBlur?: () => void; - placeholder?: string; - icon?: React.ReactNode; -} - -function PriceProtectionSelector({ value, onChange, disabled = false, className = "", onFocus, onBlur, placeholder = "0", icon = }: Props) { - const [showList, setShowList] = useState(false); - const translations = useTwapContext().translations; - - const onOpenListClick = () => { - if (disabled) return; - setShowList(true); - }; - - return ( - - - - - onChange(6)} placeholder={placeholder} /> - - % - - {icon} - - - ); -} - -export default PriceProtectionSelector; - -const StyledInput = styled(Box)({ - flex: "1 1 auto", - display: "flex", - alignItems: "center", - ".twap-input": { - width: "100%", - input: { - fontSize: 16, - textAlign: "right", - width: "100%", - "&::placeholder": { - color: "white", - }, - }, - }, -}); - -const StyledContainer = styled(Box)({ - display: "flex", - alignItems: "center", - flex: 1, - gap: 2, -}); - -const StyledPriceImpactSelect = styled(Box)({ - display: "flex", - alignItems: "center", - padding: 12, - gap: 8, - maxWidth: 115, - "& p": { - fontSize: 16, - fontWeight: 600, - }, -}); - -const StyledTextAndIconContainer = styled(Box)({ - display: "flex", - alignItems: "center", - width: "calc(100% - 24px)", -}); diff --git a/packages/lib/src/components/base/index.ts b/packages/lib/src/components/base/index.ts index 9a23a7a6..60e44dfc 100644 --- a/packages/lib/src/components/base/index.ts +++ b/packages/lib/src/components/base/index.ts @@ -5,7 +5,6 @@ export { default as Label } from "./Label"; export { default as SmallLabel } from "./SmallLabel"; export * from "./Switch"; export { default as TimeSelector } from "./TimeSelector"; -export { default as PriceImpactSelector } from "./PriceImpactSelector"; export { default as TokenLogo } from "./TokenLogo"; export { default as TokenName } from "./TokenName"; export { default as Layout } from "./Layout"; From c7ca75e616d6622237730aeb85fd75f92710fb6a Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 17:26:31 -0600 Subject: [PATCH 12/16] feat: Added new label components with translations --- packages/lib/src/components/Labels.tsx | 15 +++++++++++++++ packages/lib/src/styles.ts | 6 ++++++ packages/lib/src/types.ts | 6 ++++++ 3 files changed, 27 insertions(+) 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/styles.ts b/packages/lib/src/styles.ts index 315ddafa..0494fa93 100644 --- a/packages/lib/src/styles.ts +++ b/packages/lib/src/styles.ts @@ -164,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"; From 6d841dd4dd05e95da8765fa1793d3fba6fdaea3e Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 19:09:53 -0600 Subject: [PATCH 13/16] feat: Add DEX (Trading Post) support to OrdersList component - Introduced a `dex` prop to the `OrdersList` component to support different decentralized exchanges (DEXs). - Implemented custom behavior for the `TradingPost` DEX, rendering a specific `OrdersList` with customized props. - Updated the `SelectedOrders` component to utilize the new `dex` prop, allowing dynamic rendering based on the selected DEX. - Refactored and added JSDoc documentation for better clarity and maintainability. --- .../lib/src/components/OrdersComponents.tsx | 26 ++++++++- packages/lib/src/consts.ts | 7 +++ packages/lib/src/orders/OrdersList.tsx | 58 +++++++++++++++++-- 3 files changed, 85 insertions(+), 6 deletions(-) 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/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", +})); From f8a0820835173971448a4109c4df09f04005a1bc Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 19:13:05 -0600 Subject: [PATCH 14/16] refactor: Remove hardcoded price labels from Price component --- packages/tradingpost/src/components.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tradingpost/src/components.tsx b/packages/tradingpost/src/components.tsx index 5f0b3cdd..bb8acd0b 100644 --- a/packages/tradingpost/src/components.tsx +++ b/packages/tradingpost/src/components.tsx @@ -36,7 +36,6 @@ export function Price() { - $3.05 $8.72 ); } From 428414b810242e582b2beb4aef0634427f7480a0 Mon Sep 17 00:00:00 2001 From: Carlos Monterrosa Date: Tue, 20 Aug 2024 19:26:35 -0600 Subject: [PATCH 15/16] chore: Update UI components and structure for new package implementation - Removed obsolete elements and ensured a cleaner, more efficient design. - Updated and reorganized UI components to align with the new package structure. --- .../tradingpost/src/TradingPostOrders.tsx | 48 ++-- packages/tradingpost/src/index.tsx | 229 +++++----------- packages/tradingpost/src/styles.tsx | 253 ++++++++---------- 3 files changed, 206 insertions(+), 324 deletions(-) diff --git a/packages/tradingpost/src/TradingPostOrders.tsx b/packages/tradingpost/src/TradingPostOrders.tsx index a0918527..c591781b 100644 --- a/packages/tradingpost/src/TradingPostOrders.tsx +++ b/packages/tradingpost/src/TradingPostOrders.tsx @@ -1,31 +1,28 @@ import * as React from "react"; -import { styled } from "@mui/material"; +import { useMediaQuery } from "@mui/material"; import { hooks, OrdersPortal, SelectedOrders, store } from "@orbs-network/twap-ui"; -import { StyledOrders, StyledOrdersHeader, StyledOrdersTab, StyledOrdersTabs } from "./styles"; -import { Styles } from "@orbs-network/twap-ui"; +import { StyledOrders, StyledOrdersHeader, StyledOrdersTab, StyledOrdersTabs, StyledBody } from "./styles"; -export default function PancakeOrders() { +const useMobile = () => { + return useMediaQuery("(max-width:700px)"); +}; + +export default function TradingPostOrders() { return ( - - - + - + ); } -const StyledBody = styled(Styles.StyledColumnFlex)({ - padding: "15px 20px 20px 20px", - alignItems: "center", - gap: 15, -}); +const OrdersHeaderLayout = () => { + const mobile = useMobile(); -const Tabs = () => { const { tab, setTab } = store.useOrdersStore(); const tabs = hooks.useOrdersTabs(); @@ -38,14 +35,19 @@ const Tabs = () => { }; return ( - - {Object.keys(tabs).map((key, index) => { - return ( - onSelect(index)}> - {key} - - ); - })} - + <> + + + {Object.keys(tabs) + .map((key, index) => { + return ( + onSelect(index)}> + {key} + + ); + })} + + + ); }; diff --git a/packages/tradingpost/src/index.tsx b/packages/tradingpost/src/index.tsx index 3f657ac2..5628af33 100644 --- a/packages/tradingpost/src/index.tsx +++ b/packages/tradingpost/src/index.tsx @@ -22,30 +22,29 @@ import { StyledColumnFlex, StyledLimitPrice, StyledLimitPriceBody, - StyledLimitPriceLabel, + StyledModalHeader, + StyledModalHeaderClose, + StyledModalHeaderTitle, StyledPoweredBy, StyledReset, - StyledModalHeaderClose, - StyledModalHeader, + StyledResetLimitButton, StyledSwapModalContent, - StyledModalHeaderTitle, StyledText, - StyledTimingAndDistribution, - StyledValue, + StyledTimeSelect, + StyledTimeSelectBody, + StyledTimeSelectContainer, + StyledTimeSelectHeader, } from "./styles"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { StyledBalance, StyledTokenChange, StyledTokenChangeContainer, StyledTokenPanel, StyledPriceImpactPanel, StyledTokenPanelInput, StyledTokenSelect } from "./styles"; +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 { ReactComponent as TwapDropDpwn } from "./assets/twap-dropdown.svg"; -import { ReactComponent as Arrow } from "./assets/arrow.svg"; -import { ReactComponent as PriceImpactArrow } from "./assets/price-protection-arrow-selector.svg"; -import { GrPowerReset } from "@react-icons/all-files/gr/GrPowerReset"; -import PancakeOrders from "./TradingPostOrders"; +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"; @@ -53,18 +52,14 @@ import { useTwapContext } from "@orbs-network/twap-ui"; import { useAdapterContext, AdapterContextProvider, AdapterProps } from "./context"; import { Price } from "./components"; import { create } from "zustand"; -import { TradeStatCardProps } from "./types"; - 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 }); @@ -74,7 +69,6 @@ const Tooltip = ({ text, children, childrenStyles = {} }: TooltipProps) => { ); }; - const uiPreferences: TwapContextUIPreferences = { usdSuffix: " USD", usdPrefix: "~", @@ -94,12 +88,9 @@ const uiPreferences: TwapContextUIPreferences = { }, }, }; - 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; @@ -114,17 +105,14 @@ export const parseToken = (rawToken: any): TokenData | undefined => { 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) : () => {}}> @@ -132,28 +120,9 @@ const Balance = ({ isSrc }: { isSrc?: boolean }) => { ); }; -const PriceProtection = () => { - return ( - - - - - Price Protection - - - - } /> - - - - - ); -}; - const TokenPanel = ({ isSrcToken = false }: { isSrcToken?: boolean }) => { const selectToken = hooks.useSelectTokenCallback(); const { dstToken, srcToken } = hooks.useDappRawSelectedTokens(); - const onSelect = useCallback( (token: any) => { selectToken({ isSrc: !!isSrcToken, token }); @@ -179,50 +148,6 @@ const TokenPanel = ({ isSrcToken = false }: { isSrcToken?: boolean }) => { ); }; -const TotalDuration = () => { - return ( - - - - Total Duration - 1 h - - - - - ); -}; - -const TradeStatCard: React.FC = ({ title, value, editable = true, opacity = 40 }) => { - return ( - - - - {title} - {value} - - - - ); -}; - -const TradeTimingAndDistribution = () => { - return ( - - - - - - - - - - - - - ); -}; - const ChangeTokensOrder = () => { return ( @@ -230,49 +155,38 @@ const ChangeTokensOrder = () => { ); }; - 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 { @@ -280,7 +194,6 @@ const TWAP = memo((props: AdapterProps) => { [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 ( @@ -341,7 +261,6 @@ const OpenConfirmationModalButton = () => { ); } - return (