1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Add warning to Add Liquidity screen (#177)

* refactor add form, warning component

* dismissable alert

* prototype default warning screen

* add copy, finalize styling

* remove dismiss x
This commit is contained in:
Matthias Kretschmann 2020-10-30 13:31:27 +01:00 committed by GitHub
parent 290e43199c
commit e96d782917
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 185 additions and 126 deletions

View File

@ -34,7 +34,8 @@
"titleIn": "You will receive", "titleIn": "You will receive",
"titleOut": "You will earn" "titleOut": "You will earn"
}, },
"action": "Approve & Supply" "action": "Approve & Supply",
"warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)."
}, },
"remove": { "remove": {
"title": "Remove Liquidity", "title": "Remove Liquidity",

View File

@ -8,6 +8,7 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
padding-top: calc(var(--spacer) / 2); padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2); padding-bottom: calc(var(--spacer) / 2);
position: relative;
} }
.alert + .alert { .alert + .alert {
@ -37,6 +38,24 @@
margin-top: calc(var(--spacer) / 2); margin-top: calc(var(--spacer) / 2);
} }
.close {
position: absolute;
cursor: pointer;
background: none;
border: 0;
box-shadow: none;
outline: 0;
top: 0;
right: 0;
font-size: var(--font-size-large);
color: var(--brand-grey);
}
.close:hover,
.close:focus {
opacity: 0.7;
}
/* States */ /* States */
.error { .error {
border-color: var(--rbrand-alert-ed); border-color: var(--rbrand-alert-ed);

View File

@ -1,36 +1,55 @@
import React, { ReactElement, FormEvent } from 'react' import React, { ReactElement, FormEvent } from 'react'
import classNames from 'classnames/bind'
import styles from './Alert.module.css' import styles from './Alert.module.css'
import Button from './Button' import Button from './Button'
import Markdown from './Markdown' import Markdown from './Markdown'
const cx = classNames.bind(styles)
export default function Alert({ export default function Alert({
title, title,
text, text,
state, state,
action action,
onDismiss,
className
}: { }: {
title?: string title?: string
text: string text: string
state: 'error' | 'warning' | 'info' | 'success' state: 'error' | 'warning' | 'info' | 'success'
action?: { action?: {
name: string name: string
style?: 'text' | 'primary' | 'ghost'
handleAction: (e: FormEvent<HTMLButtonElement>) => void handleAction: (e: FormEvent<HTMLButtonElement>) => void
} }
onDismiss?: () => void
className?: string
}): ReactElement { }): ReactElement {
const styleClasses = cx({
alert: true,
[state]: state,
[className]: className
})
return ( return (
<div className={`${styles.alert} ${styles[state]}`}> <div className={styleClasses}>
{title && <h3 className={styles.title}>{title}</h3>} {title && <h3 className={styles.title}>{title}</h3>}
<Markdown className={styles.text} text={text} /> <Markdown className={styles.text} text={text} />
{action && ( {action && (
<Button <Button
className={styles.action} className={styles.action}
size="small" size="small"
style="primary" style={action.style || 'primary'}
onClick={action.handleAction} onClick={action.handleAction}
> >
{action.name} {action.name}
</Button> </Button>
)} )}
{onDismiss && (
<button className={styles.close} onClick={onDismiss}>
&times;
</button>
)}
</div> </div>
) )
} }

View File

@ -1,23 +1,3 @@
.addInput {
margin: 0 auto calc(var(--spacer) / 1.5) auto;
background: var(--brand-grey-dimmed);
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 2.5);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-top: -2rem;
margin-left: -2rem;
margin-right: -2rem;
position: relative;
}
.addInput input {
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);
@ -41,22 +21,3 @@
transform: scale(0.8); transform: scale(0.8);
transform-origin: right center; transform-origin: right center;
} }
.output {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
}
.output [class*='token'] {
white-space: normal;
}
.output [class*='token'] > figure {
display: none;
}

View File

@ -1,5 +1,5 @@
import PriceUnit from '../../../../atoms/Price/PriceUnit' import PriceUnit from '../../../../atoms/Price/PriceUnit'
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react' import React, { ChangeEvent, ReactElement, useEffect } from 'react'
import styles from './FormAdd.module.css' import styles from './FormAdd.module.css'
import Input from '../../../../atoms/Input' import Input from '../../../../atoms/Input'
import { import {
@ -9,14 +9,12 @@ import {
useFormikContext useFormikContext
} from 'formik' } from 'formik'
import Button from '../../../../atoms/Button' import Button from '../../../../atoms/Button'
import Token from '../Token'
import CoinSelect from '../CoinSelect' import CoinSelect from '../CoinSelect'
import { FormAddLiquidity } from '.' import { FormAddLiquidity } from '.'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import { Balance } from '..' import { Balance } from '..'
export default function FormAdd({ export default function FormAdd({
content,
coin, coin,
dtBalance, dtBalance,
dtSymbol, dtSymbol,
@ -24,10 +22,10 @@ export default function FormAdd({
setCoin, setCoin,
totalPoolTokens, totalPoolTokens,
totalBalance, totalBalance,
swapFee, poolAddress,
poolAddress setNewPoolTokens,
setNewPoolShare
}: { }: {
content: any
coin: string coin: string
dtBalance: string dtBalance: string
dtSymbol: string dtSymbol: string
@ -35,8 +33,9 @@ export default function FormAdd({
setCoin: (value: string) => void setCoin: (value: string) => void
totalPoolTokens: string totalPoolTokens: string
totalBalance: Balance totalBalance: Balance
swapFee: string
poolAddress: string poolAddress: string
setNewPoolTokens: (value: string) => void
setNewPoolShare: (value: string) => void
}): ReactElement { }): ReactElement {
const { ocean, balance } = useOcean() const { ocean, balance } = useOcean()
@ -49,9 +48,6 @@ export default function FormAdd({
values values
}: FormikContextType<FormAddLiquidity> = useFormikContext() }: FormikContextType<FormAddLiquidity> = useFormikContext()
const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
useEffect(() => { useEffect(() => {
async function calculatePoolShares() { async function calculatePoolShares() {
if (!values.amount) { if (!values.amount) {
@ -80,71 +76,58 @@ export default function FormAdd({
return ( return (
<> <>
<div className={styles.addInput}> <div className={styles.userLiquidity}>
<div className={styles.userLiquidity}> <div>
<div> <span>Available:</span>
<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>
<span>Maximum:</span>
<PriceUnit price={amountMax} symbol={coin} small />
</div>
</div>
<Field name="amount">
{({
field,
form
}: {
field: FieldInputProps<FormAddLiquidity>
form: any
}) => (
<Input
type="number"
name="amount"
max={amountMax}
value={`${values.amount}`}
prefix={<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />}
placeholder="0"
field={field}
form={form}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
handleChange(e)
}}
disabled={!ocean}
/>
)} )}
</Field> </div>
<div>
<span>Maximum:</span>
<PriceUnit price={amountMax} symbol={coin} small />
</div>
</div>
{(Number(balance.ocean) || dtBalance) > (values.amount || 0) && ( <Field name="amount">
<Button {({
className={styles.buttonMax} field,
style="text" form
size="small" }: {
onClick={() => setFieldValue('amount', amountMax)} field: FieldInputProps<FormAddLiquidity>
> form: any
Use Max }) => (
</Button> <Input
type="number"
name="amount"
max={amountMax}
value={`${values.amount}`}
prefix={<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />}
placeholder="0"
field={field}
form={form}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
handleChange(e)
}}
disabled={!ocean}
/>
)} )}
</div> </Field>
<div className={styles.output}> {(Number(balance.ocean) || dtBalance) > (values.amount || 0) && (
<div> <Button
<p>{content.output.titleIn}</p> className={styles.buttonMax}
<Token symbol="pool shares" balance={newPoolTokens} /> style="text"
<Token symbol="% of pool" balance={newPoolShare} /> size="small"
</div> onClick={() => setFieldValue('amount', amountMax)}
<div> >
<p>{content.output.titleOut}</p> Use Max
<Token symbol="% swap fee" balance={swapFee} /> </Button>
</div> )}
</div>
</> </>
) )
} }

View File

@ -0,0 +1,43 @@
.addInput {
margin: 0 auto calc(var(--spacer) / 1.5) auto;
background: var(--brand-grey-dimmed);
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 2.5);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-top: -2rem;
margin-left: -2rem;
margin-right: -2rem;
position: relative;
}
.addInput input {
text-align: center;
}
.addInput div[class*='field'] {
margin-bottom: 0;
}
.output {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
}
.output [class*='token'] {
white-space: normal;
}
.output [class*='token'] > figure {
display: none;
}
.warning {
margin-left: -3rem;
margin-right: -3rem;
}

View File

@ -9,6 +9,8 @@ import * as Yup from 'yup'
import { Formik } from 'formik' import { Formik } from 'formik'
import FormAdd from './FormAdd' import FormAdd from './FormAdd'
import styles from './index.module.css' import styles from './index.module.css'
import Token from '../Token'
import Alert from '../../../../atoms/Alert'
const contentQuery = graphql` const contentQuery = graphql`
query PoolAddQuery { query PoolAddQuery {
@ -24,6 +26,7 @@ const contentQuery = graphql`
titleOut titleOut
} }
action action
warning
} }
} }
} }
@ -68,6 +71,9 @@ export default function Add({
const [coin, setCoin] = useState('OCEAN') const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>() const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>() const [amountMax, setAmountMax] = useState<string>()
const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
const [isWarningAccepted, setIsWarningAccepted] = useState(false)
// Live validation rules // Live validation rules
// https://github.com/jquense/yup#number // https://github.com/jquense/yup#number
@ -150,18 +156,45 @@ export default function Add({
> >
{({ isSubmitting, submitForm }) => ( {({ isSubmitting, submitForm }) => (
<> <>
<FormAdd <div className={styles.addInput}>
coin={coin} {isWarningAccepted ? (
content={content} <FormAdd
dtBalance={dtBalance} coin={coin}
dtSymbol={dtSymbol} dtBalance={dtBalance}
amountMax={amountMax} dtSymbol={dtSymbol}
setCoin={setCoin} amountMax={amountMax}
totalPoolTokens={totalPoolTokens} setCoin={setCoin}
totalBalance={totalBalance} totalPoolTokens={totalPoolTokens}
swapFee={swapFee} totalBalance={totalBalance}
poolAddress={poolAddress} poolAddress={poolAddress}
/> setNewPoolTokens={setNewPoolTokens}
setNewPoolShare={setNewPoolShare}
/>
) : (
<Alert
className={styles.warning}
text={content.warning}
state="info"
action={{
name: 'I understand',
style: 'text',
handleAction: () => setIsWarningAccepted(true)
}}
/>
)}
</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 <Actions
isLoading={isSubmitting} isLoading={isSubmitting}