diff --git a/src/Routes/RoutesCommon.tsx b/src/Routes/RoutesCommon.tsx index 28a02616ec7..d1f476bf00f 100644 --- a/src/Routes/RoutesCommon.tsx +++ b/src/Routes/RoutesCommon.tsx @@ -9,6 +9,7 @@ import { FaWallet, FaWater, } from 'react-icons/fa' +import { GrSync } from 'react-icons/gr' import { AssetsIcon } from 'components/Icons/Assets' import { DashboardIcon } from 'components/Icons/Dashboard' import { Account } from 'pages/Accounts/Account' @@ -25,6 +26,7 @@ import { LiquidityPools } from 'pages/Defi/views/LiquidityPools' import { Overview } from 'pages/Defi/views/Overview' import { StakingVaults } from 'pages/Defi/views/StakingVaults' import { Flags } from 'pages/Flags/Flags' +import { SimpleSwap } from 'pages/Swap/SimpleSwap' import { TransactionHistory } from 'pages/TransactionHistory/TransactionHistory' import { Route as NestedRoute } from './helpers' @@ -156,4 +158,10 @@ export const routes: Array = [ window.location.hostname !== getConfig().REACT_APP_LOCAL_IP, main: Flags, }, -] + { + path: '/swap', + label: 'navBar.swap', + icon: , + main: SimpleSwap, + }, +] \ No newline at end of file diff --git a/src/assets/og-logo.png b/src/assets/og-logo.png new file mode 100644 index 00000000000..3c19d8092e9 Binary files /dev/null and b/src/assets/og-logo.png differ diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 428d768cd9a..50ca7cdb3a3 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -332,7 +332,8 @@ "foxToken": "FOX Opportunities", "featureFlags": "Feature Flags", "demoMode": "Demo Mode", - "clickToConnect": "Click to connect a wallet" + "clickToConnect": "Click to connect a wallet", + "swap": "Swap" }, "transactionRow": { "send": "Sent %{symbol}", @@ -1272,5 +1273,58 @@ "neverSellData": "Never sell data for profit" } } + }, + "simpleSwap": { + "links": { + "fullPlatform": "Full Platform", + "connectWallet": "Connect Wallet" + }, + "classic": { + "buyInstantly": "Buy Coins Instantly", + "noAccountNeeded": "No Account Needed", + "testimonial": "\"I've been waiting for a service like this. Great job!\" - Charlie Lee, Creator of Litecoin", + "yourTradeIs": "Your Trade is ", + "processing": "processing...", + "complete": "COMPLETE!", + "pending": "pending...", + "transaction": "Transaction", + "quote": "When law and morality contradict each other, the citizen has the cruel alternative of either losing his moral sense or losing his respect for the law. -Bastiat", + "sell": { + "title": "Deposit", + "inputLabel": "Deposit amount:", + "inputPlaceholder": "Dep amount", + "footerLabel": "Exchange Rate" + }, + "buy": { + "title": "Receive", + "inputLabel": "You will receive:", + "footerLabel": "Your Receive Address", + "cta": "Start" + } + }, + "modern": { + "start": "Start", + "yourTradeIs": "Your Trade is ", + "processing": "Processing", + "complete": "Complete", + "pending": "pending...", + "sovereign": "Thank You for Being a Sovereign Individual.", + "executeTrade": "Execute Trade", + "sell": { + "title": "Sell", + "walletBalance": "Wallet Balance" + }, + "buy": { + "title": "Buy", + "youGet": "You Get", + "exchangeRate": "Exchange Rate", + "deliveredTo": "Delivered to" + } + }, + "currency": { + "litecoin": "Litecoin", + "bitcoin": "Bitcoin", + "ethereum": "Ethereum" + } } } diff --git a/src/pages/Swap/SimpleSwap.css b/src/pages/Swap/SimpleSwap.css new file mode 100644 index 00000000000..088074d6db2 --- /dev/null +++ b/src/pages/Swap/SimpleSwap.css @@ -0,0 +1,7 @@ +.grSync path { + stroke: var(--chakra-colors-gray-500) +} + +.grSync-white path { + stroke: #fff; +} diff --git a/src/pages/Swap/SimpleSwap.tsx b/src/pages/Swap/SimpleSwap.tsx new file mode 100644 index 00000000000..8f2962e0d67 --- /dev/null +++ b/src/pages/Swap/SimpleSwap.tsx @@ -0,0 +1,227 @@ +import './SimpleSwap.css' + +import { Box, Flex, Grid, GridItem } from '@chakra-ui/layout' +import { Switch, useMediaQuery } from '@chakra-ui/react' +import { useState } from 'react' +import { Page } from 'components/Layout/Page' +import { breakpoints } from 'theme/theme' + +import { CenterColumn } from './components/CenterColumn' +import { CenterRow } from './components/CenterRow' +import { ClassicVersionTradeSubmitted } from './components/ClassicVersionTradeSubmitted' +import { MobileFooter } from './components/MobileFooter' +import { OGHeader } from './components/OGHeader' +import { SwapCardSwitch } from './components/SwapCardSwitch' +import { + BODY_PADDING_TOP, + DEFAULT_BUY_ASSET, + DEFAULT_SELL_ASSET, + OG_COLORS, + PAGE_HEIGHT_OFFSET_CLASSIC, + PAGE_HEIGHT_OFFSET_MOBILE, + PAGE_HEIGHT_OFFSET_MODERN, + PAGE_MARGIN_TOP_MODERN, + TRADE_STEPS_CLASSIC, + TRADE_STEPS_MODERN, +} from './constants' + +export const SimpleSwap = () => { + const [shouldShowClassicVersion, setShouldShowClassicVerion] = useState(false) + const [currentStepIndex, setCurrentStepIndex] = useState(0) + const [sellAsset, setSellAsset] = useState(DEFAULT_SELL_ASSET) + const [buyAsset, setBuyAsset] = useState(DEFAULT_BUY_ASSET) + + const stepsList = shouldShowClassicVersion ? TRADE_STEPS_CLASSIC : TRADE_STEPS_MODERN + const currentStep = stepsList[currentStepIndex] + const bgColor = shouldShowClassicVersion ? OG_COLORS.lightBlue : OG_COLORS.darkBlue + const hasSubmittedTrade = currentStep === 'processing' || currentStep === 'complete' + const isInitialStep = currentStep === 'initial' + const isFinalStep = currentStep === 'complete' + const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`) + + const handleClickNext = () => { + // TODO: This logic will be updated once we integrate with THORchain. For now it just uses + // timers to demo the progression through the flow + if (shouldShowClassicVersion) { + setCurrentStepIndex(1) + + setTimeout(() => { + setCurrentStepIndex(2) + }, 3000) + + return + } + + if (isInitialStep) { + setCurrentStepIndex(currentStepIndex + 1) + return + } + + setCurrentStepIndex(2) + + setTimeout(() => { + setCurrentStepIndex(3) + }, 3000) + } + + // const handleClickPrevious = () => { + // setCurrentStepIndex(currentStepIndex - 1) + // } + + const handleClickSwitchVersions = () => { + setShouldShowClassicVerion(!shouldShowClassicVersion) + setCurrentStepIndex(0) + } + + const handleClickSwitchAssets = () => { + const originalBuyAsset = buyAsset + setBuyAsset(sellAsset) + setSellAsset(originalBuyAsset) + } + + const handleSelectSellAsset = (asset: string) => { + if (!asset) { + setSellAsset(DEFAULT_SELL_ASSET) + return + } + + if (asset === buyAsset) { + handleClickSwitchAssets() + return + } + setSellAsset(asset) + } + + const handleSelectBuyAsset = (asset: string) => { + if (!asset) { + setSellAsset(DEFAULT_BUY_ASSET) + return + } + + if (asset === sellAsset) { + handleClickSwitchAssets() + return + } + setBuyAsset(asset) + } + + const renderClassicVersionNextSteps = () => { + return ( + <> + + + + + + ) + } + + const renderSteps = () => { + if (shouldShowClassicVersion && !isInitialStep) { + return renderClassicVersionNextSteps() + } + + return ( + + + + + + + + {isLargerThanMd ? ( + + ) : ( + + )} + + + + + + + {!isLargerThanMd && ( + + )} + + ) + } + + return ( + + + {shouldShowClassicVersion && } + + {renderSteps()} + + + + ) +} diff --git a/src/pages/Swap/components/CenterColumn.tsx b/src/pages/Swap/components/CenterColumn.tsx new file mode 100644 index 00000000000..e5743e49860 --- /dev/null +++ b/src/pages/Swap/components/CenterColumn.tsx @@ -0,0 +1,143 @@ +import '../SimpleSwap.css' + +import { Box, Button, Flex, Switch } from '@chakra-ui/react' +import { GrSync } from 'react-icons/gr' +import { ImArrowRight } from 'react-icons/im' +import { FoxIcon } from 'components/Icons/FoxIcon' +import { Text } from 'components/Text' + +import { OG_COLORS } from '../constants' + +type CenterColumnProps = { + currentStep: string + handleClickNext: () => void + isFinalStep: boolean + isInitialStep: boolean + shouldShowClassicVersion: boolean + handleClickSwitchAssets: () => void + handleClickSwitchVersions: () => void + hasSubmittedTrade: boolean +} + +export const CenterColumn = ({ + currentStep, + handleClickNext, + isFinalStep, + isInitialStep, + shouldShowClassicVersion, + handleClickSwitchAssets, + handleClickSwitchVersions, + hasSubmittedTrade, +}: CenterColumnProps) => { + const renderModernSkinCta = () => { + if (hasSubmittedTrade) { + return false + } + + return ( + + ) + } + + if (shouldShowClassicVersion) { + return ( + + + + + + + ) + } + + return ( + + + + + {hasSubmittedTrade ? ( + + + + {isFinalStep && ( + + )} + + ) : ( + + )} + + {renderModernSkinCta()} + + + + ) +} diff --git a/src/pages/Swap/components/CenterRow.tsx b/src/pages/Swap/components/CenterRow.tsx new file mode 100644 index 00000000000..edaf5cd6c9a --- /dev/null +++ b/src/pages/Swap/components/CenterRow.tsx @@ -0,0 +1,60 @@ +import '../SimpleSwap.css' + +import { Box, Flex } from '@chakra-ui/react' +import { GrSync } from 'react-icons/gr' +import { ImArrowRight } from 'react-icons/im' +import { Text } from 'components/Text' + +import { OG_COLORS } from '../constants' + +type CenterRowProps = { + currentStep: string + isFinalStep: boolean + shouldShowClassicVersion: boolean + handleClickSwitchAssets: () => void + hasSubmittedTrade: boolean +} + +export const CenterRow = ({ + currentStep, + isFinalStep, + shouldShowClassicVersion, + handleClickSwitchAssets, + hasSubmittedTrade, +}: CenterRowProps) => { + if (shouldShowClassicVersion) { + return ( + + + + ) + } + + return ( + + {hasSubmittedTrade ? ( + + + + {isFinalStep && ( + + )} + + ) : ( + + )} + + ) +} diff --git a/src/pages/Swap/components/ClassicVersionTradeSubmitted.tsx b/src/pages/Swap/components/ClassicVersionTradeSubmitted.tsx new file mode 100644 index 00000000000..cb26d0eae7b --- /dev/null +++ b/src/pages/Swap/components/ClassicVersionTradeSubmitted.tsx @@ -0,0 +1,125 @@ +import '../SimpleSwap.css' + +import { Box, Flex, Text as CText } from '@chakra-ui/react' +import { ImArrowRight } from 'react-icons/im' +import { AssetIcon } from 'components/AssetIcon' +import { Text } from 'components/Text' + +import { OG_COLORS } from '../constants' +import { + DEFAULT_RECEIVE_AMOUNT, + DEFAULT_RECEIVE_TX_ID, + DEFAULT_SELL_AMOUNT, + DEFAULT_SELL_TX_ID, + PAGE_HEIGHT_OFFSET_CLASSIC, +} from '../constants' + +type ClassicVersionTradeSubmittedProps = { + currentStep: string + buyAsset: string + sellAsset: string +} + +export const ClassicVersionTradeSubmitted = ({ + currentStep, + buyAsset, + sellAsset, +}: ClassicVersionTradeSubmittedProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + {sellAsset.toUpperCase()}{' '} + + + + + {DEFAULT_SELL_TX_ID} + + + + {DEFAULT_SELL_AMOUNT}{' '} + + + {sellAsset.toUpperCase()} + + + + + + + + {buyAsset.toUpperCase()}{' '} + + + + {currentStep !== 'complete' ? ( + + ) : ( + <> + + {DEFAULT_RECEIVE_TX_ID} + + + + {DEFAULT_RECEIVE_AMOUNT}{' '} + + + {buyAsset.toUpperCase()} + + + + )} + + + + {currentStep === 'complete' && ( + + )} + + + + + ) +} diff --git a/src/pages/Swap/components/MobileFooter.tsx b/src/pages/Swap/components/MobileFooter.tsx new file mode 100644 index 00000000000..8a4003a3b66 --- /dev/null +++ b/src/pages/Swap/components/MobileFooter.tsx @@ -0,0 +1,70 @@ +import '../SimpleSwap.css' + +import { Flex } from '@chakra-ui/layout' +import { Button, Switch } from '@chakra-ui/react' +import { Text } from 'components/Text' + +import { OG_COLORS } from '../constants' + +type MobileFooterProps = { + handleClickNext: () => void + isInitialStep: boolean + shouldShowClassicVersion: boolean + handleClickSwitchVersions: () => void + hasSubmittedTrade: boolean +} + +export const MobileFooter = ({ + handleClickNext, + isInitialStep, + shouldShowClassicVersion, + handleClickSwitchVersions, + hasSubmittedTrade, +}: MobileFooterProps) => { + if (shouldShowClassicVersion) { + return ( + + + + ) + } + + return ( + + {!hasSubmittedTrade && ( + + )} + + + ) +} diff --git a/src/pages/Swap/components/OGHeader.tsx b/src/pages/Swap/components/OGHeader.tsx new file mode 100644 index 00000000000..9a2911d08d8 --- /dev/null +++ b/src/pages/Swap/components/OGHeader.tsx @@ -0,0 +1,44 @@ +import { Box, Center, Stack } from '@chakra-ui/layout' +import { Image, useMediaQuery } from '@chakra-ui/react' +import ogLogo from 'assets/og-logo.png' +import { Text } from 'components/Text' +import { breakpoints } from 'theme/theme' + +import { OG_COLORS } from '../constants' + +export const OGHeader = () => { + const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`) + return ( +
+ + + + + + +   + + + + + + +
+ ) +} diff --git a/src/pages/Swap/components/SwapCardClassic.tsx b/src/pages/Swap/components/SwapCardClassic.tsx new file mode 100644 index 00000000000..1695b3d98d8 --- /dev/null +++ b/src/pages/Swap/components/SwapCardClassic.tsx @@ -0,0 +1,200 @@ +import { Box, Flex } from '@chakra-ui/layout' +import { Button, Center, Input, Select, Stack, Text as CText } from '@chakra-ui/react' +import { CSSProperties } from 'react' +import { GrSync } from 'react-icons/gr' +import { AssetIcon } from 'components/AssetIcon' +import { Text } from 'components/Text' + +import { OG_COLORS, ROLES } from '../constants' + +const DEFAULT_EXC_RATE = 0.00342 +const DEFAULT_RECEIVING_ADDRESS = 'M8DNR...PGPA96' +const DEFAULT_RECEIVE_AMOUNT = 53.51273 + +type SwapCardClassicProps = { + exchangeRate: number + handleClickNext: () => void + receiveAmount: number + role: string + selectedAsset: string + selectedOtherAsset: string + setAsset: (asset: string) => void +} + +const optionsWrapperStyles: CSSProperties = { + width: '100%', + height: '100%', + position: 'absolute', + top: '0', + left: '0', + pointerEvents: 'none', + padding: '8px', +} +const isVisible: CSSProperties = { + display: 'flex', +} +const optionHiddenStyle: CSSProperties = { + height: '100%', + border: `1px dotted ${OG_COLORS.darkGray}`, + boxSizing: 'border-box', + display: 'none', + padding: '8px', +} + +export const SwapCardClassic = ({ + role, + selectedAsset, + selectedOtherAsset, + handleClickNext, + setAsset, + exchangeRate = DEFAULT_EXC_RATE, + receiveAmount = DEFAULT_RECEIVE_AMOUNT, +}: SwapCardClassicProps) => { + const isSellRole = role === ROLES.sell + const renderTopSectionContent = () => ( + + ) + + const renderMiddleSectionContent = () => { + const renderAmount = () => { + if (isSellRole) { + return ( + + ) + } + return ( + + {receiveAmount} + + ) + } + + return ( + <> + + + +
+ + + + + BTC + + Bitcoin + + + + + + + ETH + + Ethereum + + + + + + + LTC + + Litecoin + + +
+
+
+
+ + + {renderAmount()} + +
+ + ) + } + + const renderBottomSectionContent = () => ( + <> + + + + {isSellRole ? ( + + {exchangeRate} +   + + {selectedAsset.toUpperCase()}/{selectedOtherAsset.toUpperCase()} + + + ) : ( + + {DEFAULT_RECEIVING_ADDRESS} + + + )} + + {!isSellRole && ( + + )} + + + ) + + return ( + + + + {renderTopSectionContent()} + + + {renderMiddleSectionContent()} + + + {renderBottomSectionContent()} + + + + ) +} diff --git a/src/pages/Swap/components/SwapCardModern.tsx b/src/pages/Swap/components/SwapCardModern.tsx new file mode 100644 index 00000000000..a84510dfc88 --- /dev/null +++ b/src/pages/Swap/components/SwapCardModern.tsx @@ -0,0 +1,237 @@ +import { Box, Flex } from '@chakra-ui/layout' +import { + InputGroup, + InputRightElement, + NumberInput, + NumberInputField, + Select, + Text as CText, +} from '@chakra-ui/react' +import { CSSProperties } from 'react' +import { AssetIcon } from 'components/AssetIcon' +import { Text } from 'components/Text' +import { GrSync } from 'react-icons/gr' + +import { ROLES } from '../constants' +const DEFAULT_WALLET_BALANCE = 1.543265 +const DEFAULT_RECEIVE_AMOUNT = 34.48238 +const DEFAULT_SELL_AMOUNT = 0.2423 +const DEFAULT_EXC_RATE = 0.35823 +const DEFAULT_MODERN_RECEIVE_TX_ID = 'f3j35923...' +const DEFAULT_MODERN_SELL_TX_ID = '9834hf34...' +const DEFAULT_RECEIVE_ADDRESS = 'M8Dnk...gPA96'; + +type SwapCardModernProps = { + currentStep: string + exchangeRate: number + receiveAmount: number + role: string + selectedAsset: string + selectedOtherAsset: string + setAsset: (asset: string) => void +} + +export const SwapCardModern = ({ + currentStep, + role, + selectedAsset, + // selectedOtherAsset, + setAsset, +}: SwapCardModernProps) => { + const isSellRole = role === ROLES.sell + const hasSubmittedTrade = currentStep === 'processing' || currentStep === 'complete' + const isFinalStep = currentStep === 'complete' + + const optionsWrapperStyles: CSSProperties = { + width: '100%', + height: '100%', + position: 'absolute', + top: '0', + left: '0', + pointerEvents: 'none', + padding: '8px', + } + const isVisible: CSSProperties = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + } + const optionStyleHidden: CSSProperties = { + height: '100%', + boxSizing: 'border-box', + display: 'none', + } + + const renderPostConfirmationStep = () => { + if (!isSellRole) { + if (isFinalStep) { + return ( + + {DEFAULT_MODERN_RECEIVE_TX_ID} + + {DEFAULT_RECEIVE_AMOUNT} {selectedAsset.toUpperCase()} + + + ) + } + + return ( + + + + {DEFAULT_SELL_AMOUNT} {selectedAsset.toUpperCase()} + + + ) + } + + return ( + + {DEFAULT_MODERN_SELL_TX_ID} + + {DEFAULT_SELL_AMOUNT} {selectedAsset.toUpperCase()} + + + ) + } + + const renderSellConfirmStep = () => ( + <> + + + + + + {selectedAsset.toUpperCase()} + + } + /> + + + + + {DEFAULT_WALLET_BALANCE} {selectedAsset.toUpperCase()} + + + + ) + + const renderBuyConfirmStep = () => ( + <> + + + + + + + + + {DEFAULT_RECEIVE_AMOUNT} {selectedAsset.toUpperCase()} + + + {DEFAULT_EXC_RATE} {selectedAsset.toUpperCase()} + + + {DEFAULT_RECEIVE_ADDRESS} + + + + + + ) + + const renderConfirmStep = () => { + if (isSellRole) { + return renderSellConfirmStep() + } + + return renderBuyConfirmStep() + } + + return ( + + + + +
+ + + + + + BTC + + + + + + + + + ETH + + + + + + + + + LTC + + + +
+
+
+ + {currentStep === 'confirm' && renderConfirmStep()} + {hasSubmittedTrade && renderPostConfirmationStep()} + +
+ ) +} diff --git a/src/pages/Swap/components/SwapCardSwitch.tsx b/src/pages/Swap/components/SwapCardSwitch.tsx new file mode 100644 index 00000000000..9ad8f10ad2f --- /dev/null +++ b/src/pages/Swap/components/SwapCardSwitch.tsx @@ -0,0 +1,17 @@ +import { SwapCardClassic } from './SwapCardClassic' +import { SwapCardModern } from './SwapCardModern' + +export type SwapCardProps = { + currentStep: string + exchangeRate: number + receiveAmount: number + handleClickNext: () => void + selectedAsset: string + setAsset: (asset: string) => void + selectedOtherAsset: string + shouldShowClassicVersion: boolean + role: string +} + +export const SwapCardSwitch = ({ shouldShowClassicVersion, ...rest }: SwapCardProps) => + shouldShowClassicVersion ? : diff --git a/src/pages/Swap/constants.ts b/src/pages/Swap/constants.ts new file mode 100644 index 00000000000..e17e2079b63 --- /dev/null +++ b/src/pages/Swap/constants.ts @@ -0,0 +1,34 @@ +export const ROLES = { + buy: 'buy', + sell: 'sell', +} + +export const TRADE_STEPS_CLASSIC = ['initial', 'processing', 'complete'] +export const TRADE_STEPS_MODERN = ['initial', 'confirm', 'processing', 'complete'] + +export const DEFAULT_BUY_ASSET = 'eth' +export const DEFAULT_SELL_ASSET = 'btc' + +// Styles +export const OG_COLORS = { + lightBlue: '#27394C', + lighterBlue: '#8792A5', + darkBlue: '#021735', + orange: '#fa9400', + green: '#63B866', + darkGray: '#141B23', +} + +const APP_HEADER_HEIGHT = '4.5rem' +const PAGE_HEADER_HEIGHT_CLASSIC = '300px' +export const PAGE_MARGIN_TOP_MODERN = '280px' +export const BODY_PADDING_TOP = '40px' +export const PAGE_HEIGHT_OFFSET_CLASSIC = `(${APP_HEADER_HEIGHT} + ${PAGE_HEADER_HEIGHT_CLASSIC} + ${BODY_PADDING_TOP})` +export const PAGE_HEIGHT_OFFSET_MODERN = `(${APP_HEADER_HEIGHT} + ${PAGE_MARGIN_TOP_MODERN})` +export const PAGE_HEIGHT_OFFSET_MOBILE = APP_HEADER_HEIGHT + +// Defaults to be replaced once hooked up to actual data +export const DEFAULT_RECEIVE_AMOUNT = 34.48238 +export const DEFAULT_SELL_AMOUNT = 0.2423 +export const DEFAULT_RECEIVE_TX_ID = 'f3j35923H20HFFJ29898C32498OIJ52' +export const DEFAULT_SELL_TX_ID = '9834hf34H20HFFJ29898C32498OIJ355'