mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #109 from oceanprotocol/feature/add-liquidity
Add liquidity checks
This commit is contained in:
commit
e5563c88b1
@ -10,7 +10,7 @@
|
|||||||
"titleIn": "You will receive",
|
"titleIn": "You will receive",
|
||||||
"titleOut": "You will earn"
|
"titleOut": "You will earn"
|
||||||
},
|
},
|
||||||
"action": "Supply"
|
"action": "Approve & Supply"
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"title": "Remove Liquidity",
|
"title": "Remove Liquidity",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"titleIn": "You will spend",
|
"titleIn": "You will spend",
|
||||||
"titleOut": "You will receive"
|
"titleOut": "You will receive"
|
||||||
},
|
},
|
||||||
"action": "Remove"
|
"action": "Approve & Remove"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
transition: 0.2s ease-out;
|
transition: 0.2s ease-out;
|
||||||
min-height: 43px;
|
height: 43px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
display: block;
|
display: block;
|
||||||
@ -49,6 +49,11 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
composes: input;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
composes: input;
|
composes: input;
|
||||||
height: 43px;
|
height: 43px;
|
||||||
@ -179,15 +184,16 @@
|
|||||||
.prefix,
|
.prefix,
|
||||||
.postfix {
|
.postfix {
|
||||||
border: 1px solid var(--brand-grey-lighter);
|
border: 1px solid var(--brand-grey-lighter);
|
||||||
min-height: 43px;
|
height: 43px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: calc(var(--spacer) / 4);
|
padding-left: calc(var(--spacer) / 4);
|
||||||
padding-right: calc(var(--spacer) / 4);
|
padding-right: calc(var(--spacer) / 4);
|
||||||
color: var(--color-secondary);
|
color: var(--brand-grey);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
transition: border 0.2s ease-out;
|
transition: border 0.2s ease-out;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefix {
|
.prefix {
|
||||||
|
@ -57,7 +57,12 @@ export default function InputElement({
|
|||||||
)
|
)
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
return (
|
return (
|
||||||
<textarea name={name} id={name} className={styles.input} {...props} />
|
<textarea
|
||||||
|
name={name}
|
||||||
|
id={name}
|
||||||
|
className={styles.textarea}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
case 'radio':
|
case 'radio':
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
|
@ -13,12 +13,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size: var(--font-size-small);
|
display: inline-block;
|
||||||
color: var(--brand-alert-red);
|
font-size: var(--font-size-mini);
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--brand-white);
|
||||||
|
background: var(--brand-alert-red);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: right;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 85%;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError label {
|
.hasError label {
|
||||||
@ -26,7 +32,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hasError input,
|
.hasError input,
|
||||||
|
.hasError input:focus,
|
||||||
.hasError select,
|
.hasError select,
|
||||||
.hasError textarea {
|
.hasError textarea,
|
||||||
|
.hasError [class*='prefix'],
|
||||||
|
.hasError [class*='postfix'] {
|
||||||
border-color: var(--brand-alert-red);
|
border-color: var(--brand-alert-red);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import InputElement from './InputElement'
|
|||||||
import Help from './Help'
|
import Help from './Help'
|
||||||
import Label from './Label'
|
import Label from './Label'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { ErrorMessage } from 'formik'
|
import { ErrorMessage, FieldInputProps } from 'formik'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
@ -33,7 +33,7 @@ export interface InputProps {
|
|||||||
max?: string
|
max?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
field?: any
|
field?: FieldInputProps<any>
|
||||||
form?: any
|
form?: any
|
||||||
prefix?: string | ReactElement
|
prefix?: string | ReactElement
|
||||||
postfix?: string | ReactElement
|
postfix?: string | ReactElement
|
||||||
@ -71,7 +71,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
|||||||
</Label>
|
</Label>
|
||||||
<InputElement small={small} {...field} {...props} />
|
<InputElement small={small} {...field} {...props} />
|
||||||
|
|
||||||
{field && field.name !== 'price' && (
|
{field && field.name !== 'price' && hasError && (
|
||||||
<div className={styles.error}>
|
<div className={styles.error}>
|
||||||
<ErrorMessage name={field.name} />
|
<ErrorMessage name={field.name} />
|
||||||
</div>
|
</div>
|
||||||
|
4
src/components/atoms/SuccessConfetti.module.css
Normal file
4
src/components/atoms/SuccessConfetti.module.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.action {
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
import Alert from '../../atoms/Alert'
|
import Alert from './Alert'
|
||||||
import Button from '../../atoms/Button'
|
import React, { ReactElement, ReactNode, useEffect } from 'react'
|
||||||
import React, { ReactElement, useEffect } from 'react'
|
|
||||||
import { confetti } from 'dom-confetti'
|
import { confetti } from 'dom-confetti'
|
||||||
import styles from './Success.module.css'
|
import styles from './SuccessConfetti.module.css'
|
||||||
|
|
||||||
const confettiConfig = {
|
const confettiConfig = {
|
||||||
angle: 90,
|
angle: 90,
|
||||||
@ -24,33 +23,29 @@ const confettiConfig = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Success({
|
export default function SuccessConfetti({
|
||||||
success,
|
success,
|
||||||
did
|
action
|
||||||
}: {
|
}: {
|
||||||
success: string
|
success: string
|
||||||
did: string
|
action: ReactNode
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
// Have some confetti upon success
|
// Have some confetti upon success
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!success || typeof window === 'undefined') return
|
if (!success || typeof window === 'undefined') return
|
||||||
|
|
||||||
const startElement: HTMLElement = document.querySelector('a[data-confetti]')
|
const startElement: HTMLElement = document.querySelector(
|
||||||
|
'span[data-confetti]'
|
||||||
|
)
|
||||||
confetti(startElement, confettiConfig)
|
confetti(startElement, confettiConfig)
|
||||||
}, [success])
|
}, [success])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Alert text={success} state="success" />
|
<Alert text={success} state="success" />
|
||||||
<Button
|
<span className={styles.action} data-confetti>
|
||||||
style="primary"
|
{action}
|
||||||
size="small"
|
</span>
|
||||||
href={`/asset/${did}`}
|
|
||||||
className={styles.action}
|
|
||||||
data-confetti
|
|
||||||
>
|
|
||||||
Go to data set →
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -11,18 +11,18 @@
|
|||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small) !important;
|
||||||
border: 1px solid var(--brand-grey-lighter);
|
border: 1px solid var(--brand-grey-lighter);
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
margin-right: -3px;
|
margin-right: -3px;
|
||||||
padding: calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
|
height: 35px;
|
||||||
|
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||||
|
calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
|
||||||
border-top-left-radius: var(--border-radius);
|
border-top-left-radius: var(--border-radius);
|
||||||
border-bottom-left-radius: var(--border-radius);
|
border-bottom-left-radius: var(--border-radius);
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
.balance strong {
|
|
||||||
color: var(--brand-grey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { DataTokenOptions, useOcean } from '@oceanprotocol/react'
|
import { DataTokenOptions, useOcean } from '@oceanprotocol/react'
|
||||||
|
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { PriceOptionsMarket } from '../../../../@types/MetaData'
|
import { PriceOptionsMarket } from '../../../../@types/MetaData'
|
||||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||||
@ -66,9 +67,12 @@ export default function Dynamic({
|
|||||||
|
|
||||||
<aside className={styles.wallet}>
|
<aside className={styles.wallet}>
|
||||||
{balance?.ocean && (
|
{balance?.ocean && (
|
||||||
<div className={styles.balance}>
|
<PriceUnit
|
||||||
OCEAN <strong>{balance.ocean}</strong>
|
className={styles.balance}
|
||||||
</div>
|
price={balance.ocean}
|
||||||
|
symbol="OCEAN"
|
||||||
|
small
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Wallet />
|
<Wallet />
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Loader from '../../../atoms/Loader'
|
import Loader from '../../../atoms/Loader'
|
||||||
import Button from '../../../atoms/Button'
|
import Button from '../../../atoms/Button'
|
||||||
import Alert from '../../../atoms/Alert'
|
|
||||||
import styles from './Actions.module.css'
|
import styles from './Actions.module.css'
|
||||||
import EtherscanLink from '../../../atoms/EtherscanLink'
|
import EtherscanLink from '../../../atoms/EtherscanLink'
|
||||||
|
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||||
|
|
||||||
export default function Actions({
|
export default function Actions({
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -30,15 +30,14 @@ export default function Actions({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{txId && (
|
{txId && (
|
||||||
<>
|
<SuccessConfetti
|
||||||
<Alert
|
success="Successfully added liquidity."
|
||||||
text={`Successfully added liquidity. Transaction ID: ${txId}`}
|
action={
|
||||||
state="success"
|
|
||||||
/>
|
|
||||||
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
|
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
|
||||||
Etherscan
|
See on Etherscan
|
||||||
</EtherscanLink>
|
</EtherscanLink>
|
||||||
</>
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
.addInput {
|
.addInput {
|
||||||
margin: 0 auto calc(var(--spacer) / 1.5) auto;
|
margin: 0 auto calc(var(--spacer) / 1.5) auto;
|
||||||
background: var(--brand-grey-dimmed);
|
background: var(--brand-grey-dimmed);
|
||||||
padding: var(--spacer) calc(var(--spacer) * 3) calc(var(--spacer) * 1.2)
|
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
|
||||||
calc(var(--spacer) * 3);
|
calc(var(--spacer) * 2.5);
|
||||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||||
margin-top: -2rem;
|
margin-top: -2rem;
|
||||||
margin-left: -2rem;
|
margin-left: -2rem;
|
||||||
@ -14,45 +14,34 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addInput div[class*='field'] {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.buttonMax {
|
.buttonMax {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
bottom: calc(var(--spacer) / 2);
|
bottom: calc(var(--spacer) / 2);
|
||||||
right: calc(var(--spacer) * 3);
|
right: calc(var(--spacer) * 2.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.userLiquidity {
|
.userLiquidity > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
margin-bottom: calc(var(--spacer) / 4);
|
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userLiquidity > div:last-child {
|
||||||
|
margin-bottom: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
.userLiquidity span + div {
|
.userLiquidity span + div {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
transform-origin: right center;
|
transform-origin: right center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.coinswitch,
|
|
||||||
.coinPopover li {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coinswitch svg {
|
|
||||||
width: 0.6em;
|
|
||||||
height: 0.6em;
|
|
||||||
display: inline-block;
|
|
||||||
fill: currentColor;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coinPopover li {
|
|
||||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.output {
|
.output {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
import styles from './Add.module.css'
|
import styles from './Add.module.css'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import Header from './Header'
|
import Header from './Header'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import InputElement from '../../../atoms/Input/InputElement'
|
|
||||||
import Button from '../../../atoms/Button'
|
import Button from '../../../atoms/Button'
|
||||||
import Token from './Token'
|
import Token from './Token'
|
||||||
import { Balance } from './'
|
import { Balance } from './'
|
||||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||||
import Actions from './Actions'
|
import Actions from './Actions'
|
||||||
import Tooltip from '../../../atoms/Tooltip'
|
|
||||||
import { ReactComponent as Caret } from '../../../../images/caret.svg'
|
|
||||||
import { graphql, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { Field, FieldInputProps, Formik } from 'formik'
|
||||||
|
import Input from '../../../atoms/Input'
|
||||||
|
import CoinSelect from './CoinSelect'
|
||||||
|
|
||||||
const contentQuery = graphql`
|
const contentQuery = graphql`
|
||||||
query PoolAddQuery {
|
query PoolAddQuery {
|
||||||
@ -36,6 +37,14 @@ const contentQuery = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
interface FormAddLiquidity {
|
||||||
|
amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: FormAddLiquidity = {
|
||||||
|
amount: undefined
|
||||||
|
}
|
||||||
|
|
||||||
export default function Add({
|
export default function Add({
|
||||||
setShowAdd,
|
setShowAdd,
|
||||||
poolAddress,
|
poolAddress,
|
||||||
@ -57,106 +66,158 @@ export default function Add({
|
|||||||
const content = data.content.edges[0].node.childContentJson.pool.add
|
const content = data.content.edges[0].node.childContentJson.pool.add
|
||||||
|
|
||||||
const { ocean, accountId, balance } = useOcean()
|
const { ocean, accountId, balance } = useOcean()
|
||||||
const [amount, setAmount] = useState('')
|
const [txId, setTxId] = useState<string>()
|
||||||
const [txId, setTxId] = useState<string>('')
|
const [coin, setCoin] = useState('OCEAN')
|
||||||
const [isLoading, setIsLoading] = useState<boolean>()
|
|
||||||
const [coin, setCoin] = useState<string>('OCEAN')
|
|
||||||
const [dtBalance, setDtBalance] = useState<string>()
|
const [dtBalance, setDtBalance] = useState<string>()
|
||||||
|
const [amountMax, setAmountMax] = useState<string>()
|
||||||
|
|
||||||
const newPoolTokens =
|
// Live validation rules
|
||||||
totalBalance &&
|
// https://github.com/jquense/yup#number
|
||||||
((Number(amount) / Number(totalBalance.ocean)) * 100).toFixed(2)
|
const validationSchema = Yup.object().shape<FormAddLiquidity>({
|
||||||
|
amount: Yup.number()
|
||||||
const newPoolShare =
|
.min(1, 'Must be more or equal to 1')
|
||||||
totalBalance &&
|
.max(
|
||||||
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
Number(amountMax),
|
||||||
|
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
|
||||||
|
)
|
||||||
|
.required('Required')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get datatoken balance when datatoken selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ocean) return
|
if (!ocean || coin === 'OCEAN') return
|
||||||
|
|
||||||
async function getDtBalance() {
|
async function getDtBalance() {
|
||||||
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
|
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
|
||||||
setDtBalance(dtBalance)
|
setDtBalance(dtBalance)
|
||||||
}
|
}
|
||||||
getDtBalance()
|
getDtBalance()
|
||||||
}, [ocean, accountId, dtAddress])
|
}, [ocean, accountId, dtAddress, coin])
|
||||||
|
|
||||||
async function handleAddLiquidity() {
|
// Get maximum amount for either OCEAN or datatoken
|
||||||
setIsLoading(true)
|
useEffect(() => {
|
||||||
|
if (!ocean) return
|
||||||
|
|
||||||
|
async function getMaximum() {
|
||||||
|
const amountMaxPool =
|
||||||
|
coin === 'OCEAN'
|
||||||
|
? await ocean.pool.getOceanMaxAddLiquidity(poolAddress)
|
||||||
|
: await ocean.pool.getDTMaxAddLiquidity(poolAddress)
|
||||||
|
|
||||||
|
const amountMax =
|
||||||
|
coin === 'OCEAN'
|
||||||
|
? Number(balance.ocean) > Number(amountMaxPool)
|
||||||
|
? amountMaxPool
|
||||||
|
: balance.ocean
|
||||||
|
: Number(dtBalance) > Number(amountMaxPool)
|
||||||
|
? amountMaxPool
|
||||||
|
: dtBalance
|
||||||
|
setAmountMax(amountMax)
|
||||||
|
}
|
||||||
|
getMaximum()
|
||||||
|
}, [ocean, poolAddress, coin, dtBalance, balance.ocean])
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
async function handleAddLiquidity(amount: number, resetForm: () => void) {
|
||||||
try {
|
try {
|
||||||
const result =
|
const result =
|
||||||
coin === 'OCEAN'
|
coin === 'OCEAN'
|
||||||
? await ocean.pool.addOceanLiquidity(accountId, poolAddress, amount)
|
? await ocean.pool.addOceanLiquidity(
|
||||||
: await ocean.pool.addDTLiquidity(accountId, poolAddress, amount)
|
accountId,
|
||||||
|
poolAddress,
|
||||||
|
`${amount}`
|
||||||
|
)
|
||||||
|
: await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
|
||||||
|
|
||||||
setTxId(result?.transactionHash)
|
setTxId(result?.transactionHash)
|
||||||
|
resetForm()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
|
|
||||||
setAmount(e.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMax() {
|
|
||||||
setAmount(coin === 'OCEAN' ? balance.ocean : dtBalance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this is only a prototype and is an accessibility nightmare.
|
|
||||||
// Needs to be refactored to either use custom select element instead of tippy.js,
|
|
||||||
// or use <button> in this implementation.
|
|
||||||
// Also needs to be closed when users click an option.
|
|
||||||
const CoinSelect = () => (
|
|
||||||
<ul className={styles.coinPopover}>
|
|
||||||
<li onClick={() => setCoin('OCEAN')}>OCEAN</li>
|
|
||||||
<li onClick={() => setCoin(dtSymbol)}>{dtSymbol}</li>
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header title={content.title} backAction={() => setShowAdd(false)} />
|
<Header title={content.title} backAction={() => setShowAdd(false)} />
|
||||||
|
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={async (values, { setSubmitting, resetForm }) => {
|
||||||
|
await handleAddLiquidity(values.amount, resetForm)
|
||||||
|
setSubmitting(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
values,
|
||||||
|
touched,
|
||||||
|
setTouched,
|
||||||
|
isSubmitting,
|
||||||
|
setFieldValue,
|
||||||
|
submitForm,
|
||||||
|
handleChange
|
||||||
|
}) => {
|
||||||
|
const newPoolTokens =
|
||||||
|
totalBalance &&
|
||||||
|
((values.amount / Number(totalBalance.ocean)) * 100).toFixed(2)
|
||||||
|
|
||||||
|
const newPoolShare =
|
||||||
|
totalBalance &&
|
||||||
|
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className={styles.addInput}>
|
<div className={styles.addInput}>
|
||||||
<div className={styles.userLiquidity}>
|
<div className={styles.userLiquidity}>
|
||||||
<span>Available: </span>
|
<div>
|
||||||
|
<span>Available:</span>
|
||||||
{coin === 'OCEAN' ? (
|
{coin === 'OCEAN' ? (
|
||||||
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
|
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
|
||||||
) : (
|
) : (
|
||||||
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
|
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Maximum:</span>
|
||||||
|
<PriceUnit price={amountMax} symbol={coin} small />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<InputElement
|
<Field name="amount">
|
||||||
value={amount}
|
{({
|
||||||
name="coin"
|
field,
|
||||||
|
form
|
||||||
|
}: {
|
||||||
|
field: FieldInputProps<FormAddLiquidity>
|
||||||
|
form: any
|
||||||
|
}) => (
|
||||||
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
max={amountMax}
|
||||||
|
value={`${values.amount}`}
|
||||||
prefix={
|
prefix={
|
||||||
<Tooltip
|
<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />
|
||||||
content={<CoinSelect />}
|
|
||||||
trigger="click focus"
|
|
||||||
className={styles.coinswitch}
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
{coin}
|
|
||||||
<Caret aria-hidden="true" />
|
|
||||||
</Tooltip>
|
|
||||||
}
|
}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
onChange={handleAmountChange}
|
field={field}
|
||||||
|
form={form}
|
||||||
|
onChange={(e) => {
|
||||||
|
// Workaround so validation kicks in on first touch
|
||||||
|
!touched?.amount && setTouched({ amount: true })
|
||||||
|
handleChange(e)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
{(balance.ocean || dtBalance) > amount && (
|
{(Number(balance.ocean) || dtBalance) >
|
||||||
|
(values.amount || 0) && (
|
||||||
<Button
|
<Button
|
||||||
className={styles.buttonMax}
|
className={styles.buttonMax}
|
||||||
style="text"
|
style="text"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleMax}
|
onClick={() => setFieldValue('amount', amountMax)}
|
||||||
>
|
>
|
||||||
Use Max
|
Use Max
|
||||||
</Button>
|
</Button>
|
||||||
@ -176,12 +237,16 @@ export default function Add({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Actions
|
<Actions
|
||||||
isLoading={isLoading}
|
isLoading={isSubmitting}
|
||||||
loaderMessage="Adding Liquidity..."
|
loaderMessage="Adding Liquidity..."
|
||||||
actionName={content.action}
|
actionName={content.action}
|
||||||
action={handleAddLiquidity}
|
action={submitForm}
|
||||||
txId={txId}
|
txId={txId}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
.coinSelect {
|
||||||
|
composes: select from '../../../atoms/Input/InputElement.module.css';
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-base);
|
||||||
|
border: none;
|
||||||
|
margin-left: -0.5rem;
|
||||||
|
margin-right: -0.5rem;
|
||||||
|
background-color: var(--brand-grey-dimmed);
|
||||||
|
width: auto;
|
||||||
|
padding: 0 1.25rem 0 0.25rem;
|
||||||
|
height: 41px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
/* custom arrow, without the divider line */
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent 50%,
|
||||||
|
var(--brand-purple) 50%
|
||||||
|
),
|
||||||
|
linear-gradient(135deg, var(--brand-grey) 50%, transparent 50%);
|
||||||
|
background-position: calc(100% - 14px) 1.2rem, calc(100% - 9px) 1.2rem, 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
color: var(--brand-grey-dark);
|
||||||
|
}
|
24
src/components/organisms/AssetActions/Pool/CoinSelect.tsx
Normal file
24
src/components/organisms/AssetActions/Pool/CoinSelect.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import styles from './CoinSelect.module.css'
|
||||||
|
|
||||||
|
export default function CoinSelect({
|
||||||
|
dtSymbol,
|
||||||
|
setCoin
|
||||||
|
}: {
|
||||||
|
dtSymbol: string
|
||||||
|
setCoin: (coin: string) => void
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
className={styles.coinSelect}
|
||||||
|
onChange={(e) => setCoin(e.target.value)}
|
||||||
|
>
|
||||||
|
<option className={styles.option} value="OCEAN">
|
||||||
|
OCEAN
|
||||||
|
</option>
|
||||||
|
<option className={styles.option} value={dtSymbol}>
|
||||||
|
{dtSymbol}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import Alert from '../../atoms/Alert'
|
import Alert from '../../atoms/Alert'
|
||||||
import Success from './Success'
|
|
||||||
import Button from '../../atoms/Button'
|
import Button from '../../atoms/Button'
|
||||||
import Loader from '../../atoms/Loader'
|
import Loader from '../../atoms/Loader'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import styles from './Feedback.module.css'
|
import styles from './Feedback.module.css'
|
||||||
|
import SuccessConfetti from '../../atoms/SuccessConfetti'
|
||||||
|
|
||||||
export default function Feedback({
|
export default function Feedback({
|
||||||
error,
|
error,
|
||||||
@ -18,6 +18,17 @@ export default function Feedback({
|
|||||||
publishStepText: string
|
publishStepText: string
|
||||||
setError: (error: string) => void
|
setError: (error: string) => void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
|
const SuccessAction = () => (
|
||||||
|
<Button
|
||||||
|
style="primary"
|
||||||
|
size="small"
|
||||||
|
href={`/asset/${did}`}
|
||||||
|
className={styles.action}
|
||||||
|
>
|
||||||
|
Go to data set →
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.feedback}>
|
<div className={styles.feedback}>
|
||||||
<div className={styles.box}>
|
<div className={styles.box}>
|
||||||
@ -35,7 +46,7 @@ export default function Feedback({
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : success ? (
|
) : success ? (
|
||||||
<Success success={success} did={did} />
|
<SuccessConfetti success={success} action={<SuccessAction />} />
|
||||||
) : (
|
) : (
|
||||||
<Loader message={publishStepText} />
|
<Loader message={publishStepText} />
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement, useEffect, FormEvent } from 'react'
|
import React, { ReactElement, useEffect, FormEvent } from 'react'
|
||||||
import styles from './PublishForm.module.css'
|
import styles from './PublishForm.module.css'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import { useFormikContext, Form, Field } from 'formik'
|
import { useFormikContext, Field } from 'formik'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Button from '../../atoms/Button'
|
import Button from '../../atoms/Button'
|
||||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||||
@ -37,7 +37,7 @@ export default function PublishForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<form
|
||||||
className={styles.form}
|
className={styles.form}
|
||||||
// do we need this?
|
// do we need this?
|
||||||
onChange={() => status === 'empty' && setStatus(null)}
|
onChange={() => status === 'empty' && setStatus(null)}
|
||||||
@ -61,6 +61,6 @@ export default function PublishForm({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
</Form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
.action {
|
|
||||||
margin-top: calc(var(--spacer) / 1.5);
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user