diff --git a/packages/yoroi-extension/app/assets/images/revamp/icons/chevron-down.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/icons/chevron-down.inline.svg new file mode 100644 index 0000000000..8f20fcc172 --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/revamp/icons/chevron-down.inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/yoroi-extension/app/assets/images/revamp/icons/edit.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/icons/edit.inline.svg new file mode 100644 index 0000000000..330f53347b --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/revamp/icons/edit.inline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/yoroi-extension/app/assets/images/revamp/icons/info.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/icons/info.inline.svg index dc2f19dbf1..9df6509f1d 100644 --- a/packages/yoroi-extension/app/assets/images/revamp/icons/info.inline.svg +++ b/packages/yoroi-extension/app/assets/images/revamp/icons/info.inline.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/packages/yoroi-extension/app/assets/images/revamp/icons/refresh.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/icons/refresh.inline.svg new file mode 100644 index 0000000000..708130d3f4 --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/revamp/icons/refresh.inline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/yoroi-extension/app/assets/images/revamp/icons/switch.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/icons/switch.inline.svg new file mode 100644 index 0000000000..107db0b327 --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/revamp/icons/switch.inline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/yoroi-extension/app/assets/images/revamp/token-default.inline.svg b/packages/yoroi-extension/app/assets/images/revamp/token-default.inline.svg new file mode 100644 index 0000000000..a914ec366e --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/revamp/token-default.inline.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/yoroi-extension/app/components/swap/PriceInput.js b/packages/yoroi-extension/app/components/swap/PriceInput.js new file mode 100644 index 0000000000..1ff346c554 --- /dev/null +++ b/packages/yoroi-extension/app/components/swap/PriceInput.js @@ -0,0 +1,75 @@ +// @flow +import type { Node } from 'react'; +import { Box, Typography } from '@mui/material'; +import type { AssetAmount } from './types' +import { BigNumber } from 'bignumber.js'; + +type Props = {| + label: string, + baseCurrency: AssetAmount, + quoteCurrency: AssetAmount, + readonly?: boolean, +|}; + +export default function PriceInput({ + label, + baseCurrency, + quoteCurrency, + readonly = false, +}: Props): Node { + return ( + + + {label} + + + + + + {baseCurrency.ticker || '-'} + / + {quoteCurrency.ticker || '-'} + + + + ); +} diff --git a/packages/yoroi-extension/app/components/swap/SwapInput.js b/packages/yoroi-extension/app/components/swap/SwapInput.js new file mode 100644 index 0000000000..f97f144a84 --- /dev/null +++ b/packages/yoroi-extension/app/components/swap/SwapInput.js @@ -0,0 +1,150 @@ +// @flow +import type { Node } from 'react'; +import { useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import { ReactComponent as ChevronDownIcon } from '../../assets/images/revamp/icons/chevron-down.inline.svg'; +import { ReactComponent as DefaultTokenImage } from '../../assets/images/revamp/token-default.inline.svg'; +import type { AssetAmount } from './types'; + +type Props = {| + label: string, + asset: AssetAmount, + onAssetSelect: function, + handleAmountChange: function, + showMax?: boolean, + image?: Node | null, + isFrom?: boolean, +|}; + +export default function SwapInput({ + label, + asset, + isFrom = false, + showMax = false, + image = null, + onAssetSelect, + handleAmountChange, +}: Props): Node { + const { amount, walletAmount, ticker } = asset; + const [error, setError] = useState(''); + const [inputValue, setInputValue] = useState(amount); + const [isFocused, setIsFocused] = useState(false); + + const handleChange = e => { + if (e.target.value === '') { + setError(''); + setInputValue(''); + return; + } + + const val = Number(e.target.value); + const checkAmount = isFrom ? walletAmount : Infinity; + + if (val !== 0 && val > checkAmount) { + setError('Not enough balance'); + } else if (Number.isNaN(val)) { + setError('Invalid amount'); + } else { + handleAmountChange(val); + } + + setInputValue(e.target.value); + }; + + const isFocusedColor = isFocused ? 'black' : 'gray.400'; + + return ( + + + + {label} + + + setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + + + svg': { width: '100%', height: '100%' } }}> + {ticker ? image || : } + + {ticker || 'Select asset'} + + + + + + {!error && showMax ? ( + + { + setInputValue(walletAmount); + handleAmountChange(walletAmount); + }} + > + MAX + + + ) : ( + + )} + + + Current balance: {walletAmount || 0} {ticker} + + + + {error && ( + + {error} + + )} + + ); +} diff --git a/packages/yoroi-extension/app/components/swap/types.js b/packages/yoroi-extension/app/components/swap/types.js new file mode 100644 index 0000000000..f59a54c91d --- /dev/null +++ b/packages/yoroi-extension/app/components/swap/types.js @@ -0,0 +1,7 @@ +//@flow + +export type AssetAmount = {| + ticker: string, + amount: string, + walletAmount: number, +|}; \ No newline at end of file diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapForm.js b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapForm.js new file mode 100644 index 0000000000..f6d067382c --- /dev/null +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapForm.js @@ -0,0 +1,116 @@ +// @flow +import type { Node } from 'react'; +import { useState } from 'react'; +import { Box, Button, Typography } from '@mui/material'; +import { ReactComponent as SwitchIcon } from '../../../assets/images/revamp/icons/switch.inline.svg'; +import { ReactComponent as RefreshIcon } from '../../../assets/images/revamp/icons/refresh.inline.svg'; +import { ReactComponent as AdaTokenImage } from './img.inline.svg'; +import { ReactComponent as UsdaTokenImage } from './usda.inline.svg'; +import SwapInput from '../../../components/swap/SwapInput'; +import PriceInput from '../../../components/swap/PriceInput'; + +const defaultFromAsset = { + amount: '', + ticker: 'TADA', + walletAmount: 212, +}; +const defaultToAsset = { + amount: '', + ticker: '', + walletAmount: 0, +}; + +export default function SwapForm(): Node { + const [isMarketOrder, setIsMarketOrder] = useState(true); + const [fromAsset, setFromAsset] = useState(defaultFromAsset); + const [toAsset, setToAsset] = useState(defaultToAsset); + + const handleSwitchSelectedAssets = () => { + setFromAsset(toAsset); + setToAsset(fromAsset); + }; + + const handleAmountChange = (amount, type) => { + const func = type === 'from' ? setFromAsset : setToAsset; + func(p => ({ ...p, amount })); + }; + + const handleResetForm = () => { + setFromAsset(defaultFromAsset); + setToAsset(defaultToAsset); + }; + + return ( + + + + setIsMarketOrder(true)} + p="8px" + borderRadius="8px" + bgcolor={isMarketOrder ? 'grayscale.200' : ''} + > + + Market + + + setIsMarketOrder(false)} + p="8px" + borderRadius="8px" + bgcolor={!isMarketOrder ? 'grayscale.200' : ''} + > + + Limit + + + + + + + + + {/* From Field */} + : } + asset={fromAsset} + onAssetSelect={() => null} + handleAmountChange={amount => handleAmountChange(amount, 'from')} + showMax + isFrom + /> + + {/* Clear and switch */} + + + + + + + + + + {/* To Field */} + : } + asset={toAsset} + onAssetSelect={() => null} + handleAmountChange={amount => handleAmountChange(amount, 'to')} + /> + + {/* Price between assets */} + + + + + ); +} diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js index 29ee9a8a03..5858eb8b87 100644 --- a/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/SwapPage.js @@ -1,6 +1,49 @@ // @flow import type { Node } from 'react'; +import { useState } from 'react'; +import { Box, Button } from '@mui/material'; +import SwapForm from './SwapForm'; export default function SwapPage(): Node { - return <>Swap form here; + const [step, setStep] = useState(0); + + // + // eslint-disable-next-line no-unused-vars + const [isSuccessful, setIsSuccessful] = useState(false); + + const handleNextStep = () => setStep(s => s + 1); + const handlePrevStep = () => setStep(s => s - 1); + + return ( + + {step === 0 && } + {step < 2 && ( + + {step === 1 && ( + + )} + + + )} + + ); } diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/img.inline.svg b/packages/yoroi-extension/app/containers/swap/asset-swap/img.inline.svg new file mode 100644 index 0000000000..ca6bd935b4 --- /dev/null +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/img.inline.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/yoroi-extension/app/containers/swap/asset-swap/usda.inline.svg b/packages/yoroi-extension/app/containers/swap/asset-swap/usda.inline.svg new file mode 100644 index 0000000000..d49f12d12e --- /dev/null +++ b/packages/yoroi-extension/app/containers/swap/asset-swap/usda.inline.svg @@ -0,0 +1,9 @@ + + + + + + + + +