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

refactor to make live validation work

This commit is contained in:
Matthias Kretschmann 2020-09-21 14:23:04 +02:00
parent 041dfcee08
commit 2a13a760bd
Signed by: m
GPG Key ID: 606EEEF3C479A91F
7 changed files with 86 additions and 114 deletions

View File

@ -10,7 +10,7 @@ const cx = classNames.bind(styles)
export interface InputProps { export interface InputProps {
name: string name: string
label?: string label?: string | ReactNode
placeholder?: string placeholder?: string
required?: boolean required?: boolean
help?: string help?: string
@ -54,8 +54,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
} = props } = props
const hasError = const hasError =
props.form?.touched[field.name] && props.form?.touched[field.name] && props.form?.errors[field.name]
typeof props.form.errors[field.name] === 'string'
const styleClasses = cx({ const styleClasses = cx({
field: true, field: true,
@ -72,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 && field.name !== 'price' && (
<div className={styles.error}> <div className={styles.error}>
<ErrorMessage name={field.name} /> <ErrorMessage name={field.name} />
</div> </div>

View File

@ -6,24 +6,23 @@ import { ReactComponent as Logo } from '../../../../images/logo.svg'
import Conversion from '../../../atoms/Price/Conversion' import Conversion from '../../../atoms/Price/Conversion'
import { DataTokenOptions } from '@oceanprotocol/react' import { DataTokenOptions } from '@oceanprotocol/react'
import RefreshName from './RefreshName' import RefreshName from './RefreshName'
import { useField } from 'formik'
export default function Coin({ export default function Coin({
datatokenOptions, datatokenOptions,
name, name,
value,
weight, weight,
onOceanChange,
generateName, generateName,
readOnly readOnly
}: { }: {
datatokenOptions: DataTokenOptions datatokenOptions: DataTokenOptions
name: string name: string
value: string
weight: string weight: string
onOceanChange?: (event: ChangeEvent<HTMLInputElement>) => void
generateName?: () => void generateName?: () => void
readOnly?: boolean readOnly?: boolean
}): ReactElement { }): ReactElement {
const [field, meta, helpers] = useField(name)
return ( return (
<div className={styles.coin}> <div className={styles.coin}>
<figure className={styles.icon}> <figure className={styles.icon}>
@ -43,15 +42,15 @@ export default function Coin({
<div className={styles.data}> <div className={styles.data}>
<InputElement <InputElement
value={value} value={field.value}
name={name} name={name}
type="number" type="number"
onChange={onOceanChange}
readOnly={readOnly} readOnly={readOnly}
prefix={datatokenOptions?.symbol || 'DT'} prefix={datatokenOptions?.symbol || 'DT'}
{...field}
/> />
{datatokenOptions?.symbol === 'OCEAN' && ( {datatokenOptions?.symbol === 'OCEAN' && (
<Conversion price={value} className={stylesIndex.conversion} /> <Conversion price={field.value} className={stylesIndex.conversion} />
)} )}
</div> </div>
</div> </div>

View File

@ -15,22 +15,18 @@ export default function Dynamic({
ocean, ocean,
priceOptions, priceOptions,
datatokenOptions, datatokenOptions,
onOceanChange,
onLiquidityProviderFeeChange,
generateName, generateName,
content content
}: { }: {
ocean: string ocean: string
priceOptions: PriceOptions priceOptions: PriceOptions
datatokenOptions: DataTokenOptions datatokenOptions: DataTokenOptions
onOceanChange: (event: ChangeEvent<HTMLInputElement>) => void
onLiquidityProviderFeeChange: (event: ChangeEvent<HTMLInputElement>) => void
generateName: () => void generateName: () => void
content: any content: any
}): ReactElement { }): ReactElement {
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const { account, balance, chainId, refreshBalance } = useOcean() const { account, balance, chainId, refreshBalance } = useOcean()
const { weightOnDataToken, tokensToMint, liquidityProviderFee } = priceOptions const { weightOnDataToken } = priceOptions
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const correctNetwork = isCorrectNetwork(chainId) const correctNetwork = isCorrectNetwork(chainId)
@ -77,33 +73,25 @@ export default function Dynamic({
</aside> </aside>
<h4 className={styles.title}> <h4 className={styles.title}>
Data Token Liquidity Pool{' '} Datatoken Liquidity Pool <Tooltip content={content.tooltips.poolInfo} />
<Tooltip content={content.tooltips.poolInfo} />
</h4> </h4>
<div className={styles.tokens}> <div className={styles.tokens}>
<Coin <Coin
name="ocean" name="price.price"
datatokenOptions={{ symbol: 'OCEAN', name: 'Ocean Token' }} datatokenOptions={{ symbol: 'OCEAN', name: 'Ocean Token' }}
value={ocean}
weight={`${100 - Number(Number(weightOnDataToken) * 10)}%`} weight={`${100 - Number(Number(weightOnDataToken) * 10)}%`}
onOceanChange={onOceanChange}
/> />
<Coin <Coin
name="tokensToMint" name="price.tokensToMint"
datatokenOptions={datatokenOptions} datatokenOptions={datatokenOptions}
value={tokensToMint.toString()}
weight={`${Number(weightOnDataToken) * 10}%`} weight={`${Number(weightOnDataToken) * 10}%`}
generateName={generateName} generateName={generateName}
readOnly readOnly
/> />
</div> </div>
<Fees <Fees tooltips={content.tooltips} />
liquidityProviderFee={liquidityProviderFee}
onLiquidityProviderFeeChange={onLiquidityProviderFeeChange}
tooltips={content.tooltips}
/>
<footer className={styles.summary}> <footer className={styles.summary}>
You will get: <br /> You will get: <br />

View File

@ -1,57 +1,68 @@
import React, { ChangeEvent } from 'react' import React, { ChangeEvent, ReactElement } from 'react'
import InputElement from '../../../atoms/Input/InputElement'
import Label from '../../../atoms/Input/Label'
import Tooltip from '../../../atoms/Tooltip' import Tooltip from '../../../atoms/Tooltip'
import styles from './Fees.module.css' import styles from './Fees.module.css'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import { useField } from 'formik'
import Input from '../../../atoms/Input'
export default function Fees({ export default function Fees({
liquidityProviderFee,
onLiquidityProviderFeeChange,
tooltips tooltips
}: { }: {
liquidityProviderFee: string
onLiquidityProviderFeeChange: (event: ChangeEvent<HTMLInputElement>) => void
tooltips: { [key: string]: string } tooltips: { [key: string]: string }
}) { }): ReactElement {
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const [field, meta, helpers] = useField('price.liquidityProviderFee')
// TODO: trigger Yup inline validation
function handleLiquidityProviderFeeChange(
event: ChangeEvent<HTMLInputElement>
) {
helpers.setValue(event.target.value)
}
return ( return (
<div className={styles.fees}> <>
<div> <div className={styles.fees}>
<Label htmlFor="liquidityProviderFee"> <Input
Liquidity Provider Fee{' '} label={
<Tooltip content={tooltips.liquidityProviderFee} /> <>
</Label> Liquidity Provider Fee
<InputElement <Tooltip content={tooltips.liquidityProviderFee} />
</>
}
type="number" type="number"
value={liquidityProviderFee} value={field.value}
name="liquidityProviderFee" name="price.liquidityProviderFee"
postfix="%" postfix="%"
onChange={onLiquidityProviderFeeChange} onChange={handleLiquidityProviderFeeChange}
min="0.1" min="0.1"
max="0.9" max="0.9"
step="0.1" step="0.1"
small small
{...field}
/> />
</div>
<div> <Input
<Label htmlFor="communityFee"> label={
Community Fee <Tooltip content={tooltips.communityFee} /> <>
</Label> Community Fee
<InputElement <Tooltip content={tooltips.communityFee} />
</>
}
value="0.1" value="0.1"
name="communityFee" name="communityFee"
postfix="%" postfix="%"
readOnly readOnly
small small
/> />
</div>
<div> <Input
<Label htmlFor="marketplaceFee"> label={
Marketplace Fee <Tooltip content={tooltips.marketplaceFee} /> <>
</Label> Marketplace Fee
<InputElement <Tooltip content={tooltips.marketplaceFee} />
</>
}
value={appConfig.marketFeeAmount} value={appConfig.marketFeeAmount}
name="marketplaceFee" name="marketplaceFee"
postfix="%" postfix="%"
@ -59,6 +70,7 @@ export default function Fees({
small small
/> />
</div> </div>
</div> {meta.error && meta.touched && <div>{meta.error}</div>}
</>
) )
} }

View File

@ -1,42 +1,41 @@
import React, { ReactElement, ChangeEvent } from 'react' import React, { ReactElement } from 'react'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
import styles from './Fixed.module.css' import styles from './Fixed.module.css'
import FormHelp from '../../../atoms/Input/Help' import FormHelp from '../../../atoms/Input/Help'
import Label from '../../../atoms/Input/Label'
import InputElement from '../../../atoms/Input/InputElement'
import Conversion from '../../../atoms/Price/Conversion' import Conversion from '../../../atoms/Price/Conversion'
import { DataTokenOptions } from '@oceanprotocol/react' import { DataTokenOptions } from '@oceanprotocol/react'
import RefreshName from './RefreshName' import RefreshName from './RefreshName'
import { useField } from 'formik'
import Input from '../../../atoms/Input'
export default function Fixed({ export default function Fixed({
ocean,
datatokenOptions, datatokenOptions,
onChange,
generateName, generateName,
content content
}: { }: {
ocean: string
datatokenOptions: DataTokenOptions datatokenOptions: DataTokenOptions
onChange: (event: ChangeEvent<HTMLInputElement>) => void
generateName: () => void generateName: () => void
content: any content: any
}): ReactElement { }): ReactElement {
const [field, meta, helpers] = useField('price.price')
return ( return (
<div className={styles.fixed}> <div className={styles.fixed}>
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp> <FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
<div className={styles.grid}> <div className={styles.grid}>
<div className={styles.form}> <div className={styles.form}>
<Label htmlFor="ocean">Ocean Token</Label> <Input
<InputElement label="Ocean Token"
value={ocean} value={field.value}
name="ocean" name="price.price"
type="number" type="number"
prefix="OCEAN" prefix="OCEAN"
onChange={onChange} {...field}
/> />
<Conversion price={ocean} className={stylesIndex.conversion} /> <Conversion price={field.value} className={stylesIndex.conversion} />
</div> </div>
{meta.error && meta.touched && <div>{meta.error}</div>}
{datatokenOptions && ( {datatokenOptions && (
<div className={styles.datatoken}> <div className={styles.datatoken}>
<h4> <h4>

View File

@ -44,26 +44,11 @@ export default function Price(props: InputProps): ReactElement {
const content = data.content.edges[0].node.childPagesJson.price const content = data.content.edges[0].node.childPagesJson.price
const { ocean } = useOcean() const { ocean } = useOcean()
const [field, meta, helpers] = useField(props) const [field, meta, helpers] = useField(props.name)
const priceOptions: PriceOptions = field.value const priceOptions: PriceOptions = field.value
const [amountOcean, setAmountOcean] = useState('1')
const [tokensToMint, setTokensToMint] = useState<number>() const [tokensToMint, setTokensToMint] = useState<number>()
const [datatokenOptions, setDatatokenOptions] = useState<DataTokenOptions>() const [datatokenOptions, setDatatokenOptions] = useState<DataTokenOptions>()
const [liquidityProviderFee, setLiquidityProviderFee] = useState<string>(
priceOptions.liquidityProviderFee
)
function handleOceanChange(event: ChangeEvent<HTMLInputElement>) {
setAmountOcean(event.target.value)
}
// TODO: trigger Yup inline validation
function handleLiquidityProviderFeeChange(
event: ChangeEvent<HTMLInputElement>
) {
setLiquidityProviderFee(event.target.value)
}
function handleTabChange(tabName: string) { function handleTabChange(tabName: string) {
const type = tabName.toLowerCase() const type = tabName.toLowerCase()
@ -79,14 +64,10 @@ export default function Price(props: InputProps): ReactElement {
// Always update everything when amountOcean changes // Always update everything when amountOcean changes
useEffect(() => { useEffect(() => {
const tokensToMint = const tokensToMint =
Number(amountOcean) * Number(priceOptions.weightOnDataToken) Number(field.value.price) * Number(priceOptions.weightOnDataToken)
setTokensToMint(tokensToMint) setTokensToMint(tokensToMint)
helpers.setValue({ ...field.value, price: amountOcean, tokensToMint }) helpers.setValue({ ...field.value, tokensToMint })
}, [amountOcean]) }, [field.value.price])
useEffect(() => {
helpers.setValue({ ...field.value, liquidityProviderFee })
}, [liquidityProviderFee])
// Generate new DT name & symbol // Generate new DT name & symbol
useEffect(() => { useEffect(() => {
@ -98,9 +79,7 @@ export default function Price(props: InputProps): ReactElement {
title: content.fixed.title, title: content.fixed.title,
content: ( content: (
<Fixed <Fixed
ocean={amountOcean}
datatokenOptions={datatokenOptions} datatokenOptions={datatokenOptions}
onChange={handleOceanChange}
generateName={generateName} generateName={generateName}
content={content.fixed} content={content.fixed}
/> />
@ -110,11 +89,9 @@ export default function Price(props: InputProps): ReactElement {
title: content.dynamic.title, title: content.dynamic.title,
content: ( content: (
<Dynamic <Dynamic
ocean={amountOcean} ocean={field.value.price}
priceOptions={{ ...priceOptions, tokensToMint, liquidityProviderFee }} priceOptions={{ ...priceOptions, tokensToMint }}
datatokenOptions={datatokenOptions} datatokenOptions={datatokenOptions}
onOceanChange={handleOceanChange}
onLiquidityProviderFeeChange={handleLiquidityProviderFeeChange}
generateName={generateName} generateName={generateName}
content={content.dynamic} content={content.dynamic}
/> />

View File

@ -6,19 +6,17 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
// ---- required fields ---- // ---- required fields ----
name: Yup.string().required('Required'), name: Yup.string().required('Required'),
author: Yup.string().required('Required'), author: Yup.string().required('Required'),
price: Yup.object().shape({ price: Yup.object()
price: Yup.number().required('Required'), .shape({
tokensToMint: Yup.number().required('Required'), price: Yup.number().min(1, 'Must be greater than 0').required('Required'),
type: Yup.string() tokensToMint: Yup.number().required('Required'),
.matches(/fixed|dynamic/g) type: Yup.string()
.required('Required'), .matches(/fixed|dynamic/g)
weightOnDataToken: Yup.string().required('Required'), .required('Required'),
liquidityProviderFee: Yup.string() weightOnDataToken: Yup.string().required('Required'),
.length(3) liquidityProviderFee: Yup.string().min(0.1).max(0.9).required('Required')
.min(0.1) })
.max(0.9) .required('Required'),
.required('Required')
}),
files: Yup.array<FileMetadata>().required('Required').nullable(), files: Yup.array<FileMetadata>().required('Required').nullable(),
description: Yup.string().required('Required'), description: Yup.string().required('Required'),
license: Yup.string().required('Required'), license: Yup.string().required('Required'),