1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-06-30 05:41:41 +02:00

refactor add form

This commit is contained in:
Matthias Kretschmann 2020-10-26 20:54:07 +01:00
parent 478e18c68d
commit 5a7488fa04
Signed by: m
GPG Key ID: 606EEEF3C479A91F
7 changed files with 315 additions and 279 deletions

View File

@ -1,275 +0,0 @@
import React, { ReactElement, useState, useEffect } from 'react'
import styles from './Add.module.css'
import { useOcean } from '@oceanprotocol/react'
import Header from './Header'
import { toast } from 'react-toastify'
import Button from '../../../atoms/Button'
import Token from './Token'
import { Balance } from './'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Actions from './Actions'
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`
query PoolAddQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
add {
title
output {
titleIn
titleOut
}
action
}
}
}
}
}
}
}
`
interface FormAddLiquidity {
amount: number
}
const initialValues: FormAddLiquidity = {
amount: undefined
}
export default function Add({
setShowAdd,
poolAddress,
totalPoolTokens,
totalBalance,
swapFee,
dtSymbol,
dtAddress
}: {
setShowAdd: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
totalBalance: Balance
swapFee: string
dtSymbol: string
dtAddress: string
}): ReactElement {
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool.add
const { ocean, accountId, balance } = useOcean()
const [txId, setTxId] = useState<string>()
const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>()
// Live validation rules
// https://github.com/jquense/yup#number
const validationSchema = Yup.object().shape<FormAddLiquidity>({
amount: Yup.number()
.min(1, 'Must be more or equal to 1')
.max(
Number(amountMax),
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
)
.required('Required')
})
// Get datatoken balance when datatoken selected
useEffect(() => {
if (!ocean || coin === 'OCEAN') return
async function getDtBalance() {
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
setDtBalance(dtBalance)
}
getDtBalance()
}, [ocean, accountId, dtAddress, coin])
// Get maximum amount for either OCEAN or datatoken
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 {
const result =
coin === 'OCEAN'
? await ocean.pool.addOceanLiquidity(
accountId,
poolAddress,
`${amount}`
)
: await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
setTxId(result?.transactionHash)
resetForm()
} catch (error) {
console.error(error.message)
toast.error(error.message)
}
}
return (
<>
<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, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
useEffect(() => {
async function calculatePoolShares() {
if (!values.amount) {
setNewPoolTokens('0')
setNewPoolShare('0')
return
}
if (Number(values.amount) > Number(amountMax)) return
const poolTokens = await ocean.pool.calcPoolOutGivenSingleIn(
poolAddress,
coin === 'OCEAN'
? ocean.pool.oceanAddress
: ocean.pool.dtAddress,
values.amount.toString()
)
setNewPoolTokens(poolTokens)
setNewPoolShare(
totalBalance &&
(
(Number(poolTokens) /
(Number(totalPoolTokens) + Number(poolTokens))) *
100
).toFixed(2)
)
}
calculatePoolShares()
}, [values.amount])
return (
<>
<div className={styles.addInput}>
<div className={styles.userLiquidity}>
<div>
<span>Available:</span>
{coin === 'OCEAN' ? (
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
) : (
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
)}
</div>
<div>
<span>Maximum:</span>
<PriceUnit price={amountMax} symbol={coin} small />
</div>
</div>
<Field name="amount">
{({
field,
form
}: {
field: FieldInputProps<FormAddLiquidity>
form: any
}) => (
<Input
type="number"
max={amountMax}
value={`${values.amount}`}
prefix={
<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />
}
placeholder="0"
field={field}
form={form}
onChange={(e) => {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
handleChange(e)
}}
disabled={!ocean}
/>
)}
</Field>
{(Number(balance.ocean) || dtBalance) >
(values.amount || 0) && (
<Button
className={styles.buttonMax}
style="text"
size="small"
onClick={() => setFieldValue('amount', amountMax)}
>
Use Max
</Button>
)}
</div>
<div className={styles.output}>
<div>
<p>{content.output.titleIn}</p>
<Token symbol="pool shares" balance={newPoolTokens} />
<Token symbol="% of pool" balance={newPoolShare} />
</div>
<div>
<p>{content.output.titleOut}</p>
<Token symbol="% swap fee" balance={swapFee} />
</div>
</div>
<Actions
isLoading={isSubmitting}
loaderMessage="Adding Liquidity..."
actionName={content.action}
action={submitForm}
txId={txId}
/>
</>
)
}}
</Formik>
</>
)
}

View File

@ -0,0 +1,137 @@
import PriceUnit from '../../../../atoms/Price/PriceUnit'
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react'
import styles from './FormAdd.module.css'
import Input from '../../../../atoms/Input'
import { FormikContextType, useField, useFormikContext } from 'formik'
import Button from '../../../../atoms/Button'
import Token from '../Token'
import CoinSelect from '../CoinSelect'
import { FormAddLiquidity } from '.'
import { useOcean } from '@oceanprotocol/react'
import { Balance } from '..'
export default function FormAdd({
content,
coin,
dtBalance,
dtSymbol,
amountMax,
setCoin,
totalPoolTokens,
totalBalance,
swapFee,
poolAddress
}: {
content: any
coin: string
dtBalance: string
dtSymbol: string
amountMax: string
setCoin: (value: string) => void
totalPoolTokens: string
totalBalance: Balance
swapFee: string
poolAddress: string
}): ReactElement {
const { ocean, balance } = useOcean()
// Connect with form
const {
isSubmitting,
touched,
setTouched,
handleChange,
submitForm,
setFieldValue,
values
}: FormikContextType<FormAddLiquidity> = useFormikContext()
const [field, meta] = useField('amount')
const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
useEffect(() => {
async function calculatePoolShares() {
if (!values.amount) {
setNewPoolTokens('0')
setNewPoolShare('0')
return
}
if (Number(values.amount) > Number(amountMax)) return
const poolTokens = await ocean.pool.calcPoolOutGivenSingleIn(
poolAddress,
coin === 'OCEAN' ? ocean.pool.oceanAddress : ocean.pool.dtAddress,
values.amount.toString()
)
setNewPoolTokens(poolTokens)
setNewPoolShare(
totalBalance &&
(
(Number(poolTokens) /
(Number(totalPoolTokens) + Number(poolTokens))) *
100
).toFixed(2)
)
}
calculatePoolShares()
}, [values.amount])
return (
<>
<div className={styles.addInput}>
<div className={styles.userLiquidity}>
<div>
<span>Available:</span>
{coin === 'OCEAN' ? (
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
) : (
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
)}
</div>
<div>
<span>Maximum:</span>
<PriceUnit price={amountMax} symbol={coin} small />
</div>
</div>
<Input
type="number"
max={amountMax}
value={`${values.amount}`}
prefix={<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />}
placeholder="0"
field={field}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
handleChange(e)
}}
disabled={!ocean}
/>
{(Number(balance.ocean) || dtBalance) > (values.amount || 0) && (
<Button
className={styles.buttonMax}
style="text"
size="small"
onClick={() => setFieldValue('amount', amountMax)}
>
Use Max
</Button>
)}
</div>
<div className={styles.output}>
<div>
<p>{content.output.titleIn}</p>
<Token symbol="pool shares" balance={newPoolTokens} />
<Token symbol="% of pool" balance={newPoolShare} />
</div>
<div>
<p>{content.output.titleOut}</p>
<Token symbol="% swap fee" balance={swapFee} />
</div>
</div>
</>
)
}

View File

@ -0,0 +1,175 @@
import React, { ReactElement, useState, useEffect } from 'react'
import { useOcean } from '@oceanprotocol/react'
import Header from '../Header'
import { toast } from 'react-toastify'
import { Balance } from '..'
import Actions from '../Actions'
import { graphql, useStaticQuery } from 'gatsby'
import * as Yup from 'yup'
import { Formik } from 'formik'
import FormAdd from './FormAdd'
import styles from './index.module.css'
const contentQuery = graphql`
query PoolAddQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
add {
title
output {
titleIn
titleOut
}
action
}
}
}
}
}
}
}
`
export interface FormAddLiquidity {
amount: number
}
const initialValues: FormAddLiquidity = {
amount: undefined
}
export default function Add({
setShowAdd,
poolAddress,
totalPoolTokens,
totalBalance,
swapFee,
dtSymbol,
dtAddress
}: {
setShowAdd: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
totalBalance: Balance
swapFee: string
dtSymbol: string
dtAddress: string
}): ReactElement {
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool.add
const { ocean, accountId, balance } = useOcean()
const [txId, setTxId] = useState<string>()
const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>()
// Live validation rules
// https://github.com/jquense/yup#number
const validationSchema = Yup.object().shape<FormAddLiquidity>({
amount: Yup.number()
.min(1, 'Must be more or equal to 1')
.max(
Number(amountMax),
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
)
.required('Required')
})
// Get datatoken balance when datatoken selected
useEffect(() => {
if (!ocean || coin === 'OCEAN') return
async function getDtBalance() {
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
setDtBalance(dtBalance)
}
getDtBalance()
}, [ocean, accountId, dtAddress, coin])
// Get maximum amount for either OCEAN or datatoken
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 {
const result =
coin === 'OCEAN'
? await ocean.pool.addOceanLiquidity(
accountId,
poolAddress,
`${amount}`
)
: await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
setTxId(result?.transactionHash)
resetForm()
} catch (error) {
console.error(error.message)
toast.error(error.message)
}
}
return (
<>
<Header title={content.title} backAction={() => setShowAdd(false)} />
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, { setSubmitting, resetForm }) => {
await handleAddLiquidity(values.amount, resetForm)
setSubmitting(false)
}}
>
{({ isSubmitting, submitForm }) => (
<>
<FormAdd
coin={coin}
content={content}
dtBalance={dtBalance}
dtSymbol={dtSymbol}
amountMax={amountMax}
setCoin={setCoin}
totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance}
swapFee={swapFee}
poolAddress={poolAddress}
/>
<Actions
isLoading={isSubmitting}
loaderMessage="Adding Liquidity..."
actionName={content.action}
action={submitForm}
txId={txId}
/>
</>
)}
</Formik>
</>
)
}

View File

@ -1,11 +1,11 @@
.removeInput {
composes: addInput from './Add.module.css';
composes: addInput from './Add/index.module.css';
padding-left: calc(var(--spacer) * 2);
padding-right: calc(var(--spacer) * 2);
}
.userLiquidity {
composes: userLiquidity from './Add.module.css';
composes: userLiquidity from './Add/index.module.css';
max-width: 12rem;
margin-top: -1rem;
margin-left: auto;
@ -55,7 +55,7 @@
}
.output {
composes: output from './Add.module.css';
composes: output from './Add/index.module.css';
}
.output [class*='token'] {

View File

@ -4,7 +4,6 @@ import { DDO, Logger } from '@oceanprotocol/lib'
import styles from './index.module.css'
import stylesActions from './Actions.module.css'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button'
import Add from './Add'
import Remove from './Remove'