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

refactor all the URL inputs

* only act on user action, we were firing events left and right on every keystroke
* remove all local state management, the field has the value already
* add success state for provider input
This commit is contained in:
Matthias Kretschmann 2021-11-24 12:29:14 +00:00
parent 5fd97b11e4
commit 5bf8543150
Signed by: m
GPG Key ID: 606EEEF3C479A91F
6 changed files with 68 additions and 72 deletions

View File

@ -1,56 +1,41 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState } from 'react'
import { useField } from 'formik' import { useField } from 'formik'
import { toast } from 'react-toastify' import UrlInput from './URLInput'
import CustomInput from './URLInput/Input'
import { useOcean } from '@context/Ocean' import { useOcean } from '@context/Ocean'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
export default function CustomProvider(props: InputProps): ReactElement { export default function CustomProvider(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name) const [field, meta, helpers] = useField(props.name)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [providerUrl, setProviderUrl] = useState<string>() const [isValid, setIsValid] = useState(false)
const { ocean, config } = useOcean() const { ocean, config } = useOcean()
function loadProvider() { async function validateProvider(url: string) {
if (!providerUrl) return setIsLoading(true)
async function validateProvider() {
let valid: boolean
try {
setIsLoading(true)
valid = await ocean.provider.isValidProvider(providerUrl)
} catch (error) {
valid = false
console.error(error.message)
} finally {
valid
? toast.success('Perfect! That provider URL looks good 🐳')
: toast.error(
'Could not validate provider. Please check URL and try again'
)
setIsLoading(false) try {
} const isValid = await ocean.provider.isValidProvider(url)
setIsValid(isValid)
helpers.setError(undefined)
} catch (error) {
setIsValid(false)
helpers.setError(
'Could not validate provider. Please check URL and try again'
)
} finally {
setIsLoading(false)
} }
validateProvider()
} }
useEffect(() => {
loadProvider()
}, [providerUrl, config?.providerUri])
async function handleButtonClick(e: React.SyntheticEvent, url: string) { async function handleButtonClick(e: React.SyntheticEvent, url: string) {
helpers.setTouched(false)
e.preventDefault() e.preventDefault()
if (providerUrl === url) { validateProvider(url)
loadProvider()
}
setProviderUrl(url)
} }
return ( return (
<CustomInput <UrlInput
submitText="Validate" submitText="Validate"
isValid={isValid}
{...props} {...props}
{...field} {...field}
isLoading={isLoading} isLoading={isLoading}

View File

@ -1,8 +1,8 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState } from 'react'
import { useField } from 'formik' import { useField } from 'formik'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import FileInfo from './Info' import FileInfo from './Info'
import UrlInput from '../URLInput/Input' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
import { getFileInfo } from '@utils/provider' import { getFileInfo } from '@utils/provider'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
@ -12,22 +12,21 @@ import { useCancelToken } from '@hooks/useCancelToken'
export default function FilesInput(props: InputProps): ReactElement { export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name) const [field, meta, helpers] = useField(props.name)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [fileUrl, setFileUrl] = useState<string>()
const { chainId } = useWeb3() const { chainId } = useWeb3()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
function loadFileInfo() { function loadFileInfo(url: string) {
const config = getOceanConfig(chainId || 1) const config = getOceanConfig(chainId || 1)
async function validateUrl() { async function validateUrl() {
try { try {
setIsLoading(true) setIsLoading(true)
const checkedFile = await getFileInfo( const checkedFile = await getFileInfo(
fileUrl, url,
config?.providerUri, config?.providerUri,
newCancelToken() newCancelToken()
) )
checkedFile && helpers.setValue([{ url: fileUrl, ...checkedFile[0] }]) checkedFile && helpers.setValue([{ url, ...checkedFile[0] }])
} catch (error) { } catch (error) {
toast.error('Could not fetch file info. Please check URL and try again') toast.error('Could not fetch file info. Please check URL and try again')
console.error(error.message) console.error(error.message)
@ -36,27 +35,13 @@ export default function FilesInput(props: InputProps): ReactElement {
} }
} }
fileUrl && validateUrl() validateUrl()
} }
useEffect(() => {
loadFileInfo()
}, [fileUrl])
async function handleButtonClick(e: React.SyntheticEvent, url: string) { async function handleButtonClick(e: React.SyntheticEvent, url: string) {
// hack so the onBlur-triggered validation does not show,
// like when this field is required
helpers.setTouched(false)
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf' // File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
e.preventDefault() e.preventDefault()
loadFileInfo(url)
// In the case when the user re-add the same URL after it was removed (by accident or intentionally)
if (fileUrl === url) {
loadFileInfo()
}
setFileUrl(url)
} }
return ( return (

View File

@ -10,3 +10,8 @@
.error { .error {
composes: error from '@shared/FormInput/index.module.css'; composes: error from '@shared/FormInput/index.module.css';
} }
.success {
background: var(--brand-alert-green);
opacity: 1 !important;
}

View File

@ -1,8 +1,8 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import { ErrorMessage, FieldInputProps, useField } from 'formik' import { ErrorMessage, useField } from 'formik'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import styles from './Input.module.css' import styles from './index.module.css'
import InputGroup from '@shared/FormInput/InputGroup' import InputGroup from '@shared/FormInput/InputGroup'
import InputElement from '@shared/FormInput/InputElement' import InputElement from '@shared/FormInput/InputElement'
@ -12,6 +12,7 @@ export default function URLInput({
isLoading, isLoading,
name, name,
value, value,
isValid,
...props ...props
}: { }: {
submitText: string submitText: string
@ -19,6 +20,7 @@ export default function URLInput({
isLoading: boolean isLoading: boolean
name: string name: string
value: string value: string
isValid?: boolean
}): ReactElement { }): ReactElement {
const [field, meta] = useField(name) const [field, meta] = useField(name)
const isButtonDisabled = const isButtonDisabled =
@ -36,17 +38,24 @@ export default function URLInput({
value={value} value={value}
type="url" type="url"
/> />
<Button
style="primary" {isValid ? (
size="small" <Button size="small" disabled className={styles.success}>
onClick={(e: React.SyntheticEvent) => { Valid
e.preventDefault() </Button>
handleButtonClick(e, field.value) ) : (
}} <Button
disabled={isButtonDisabled} style="primary"
> size="small"
{isLoading ? <Loader /> : submitText} onClick={(e: React.SyntheticEvent) => {
</Button> e.preventDefault()
handleButtonClick(e, field.value)
}}
disabled={isButtonDisabled}
>
{isLoading ? <Loader /> : submitText}
</Button>
)}
</InputGroup> </InputGroup>
{meta.touched && meta.error && ( {meta.touched && meta.error && (

View File

@ -6,6 +6,8 @@ import IconCompute from '@images/compute.svg'
import content from '../../../../content/publish/form.json' import content from '../../../../content/publish/form.json'
import { getFieldContent } from '../_utils' import { getFieldContent } from '../_utils'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { useWeb3 } from '@context/Web3'
import { getOceanConfig } from '@utils/ocean'
const accessTypeOptionsTitles = getFieldContent( const accessTypeOptionsTitles = getFieldContent(
'access', 'access',
@ -14,7 +16,8 @@ const accessTypeOptionsTitles = getFieldContent(
export default function ServicesFields(): ReactElement { export default function ServicesFields(): ReactElement {
// connect with Form state, use for conditional field rendering // connect with Form state, use for conditional field rendering
const { values, setFieldValue } = useFormikContext<FormPublishData>() const { values, setFieldValue, setTouched } =
useFormikContext<FormPublishData>()
const accessTypeOptions = [ const accessTypeOptions = [
{ {
@ -44,7 +47,16 @@ export default function ServicesFields(): ReactElement {
'services[0].access', 'services[0].access',
values.services[0].algorithmPrivacy === true ? 'compute' : 'download' values.services[0].algorithmPrivacy === true ? 'compute' : 'download'
) )
}, [values.services[0].algorithmPrivacy]) }, [values.services[0].algorithmPrivacy, setFieldValue])
// Auto-change default providerUrl on user network change
useEffect(() => {
if (!values?.user?.chainId) return
const config = getOceanConfig(values.user.chainId)
config && setFieldValue('services[0].providerUrl', config.providerUri)
setTouched({ services: [{ providerUrl: true }] })
}, [values.user.chainId, setFieldValue, setTouched])
return ( return (
<> <>

View File

@ -56,7 +56,7 @@ export const initialValues: FormPublishData = {
dataTokenOptions: { name: '', symbol: '' }, dataTokenOptions: { name: '', symbol: '' },
timeout: '', timeout: '',
access: '', access: '',
providerUrl: 'https://provider.oceanprotocol.com' providerUrl: 'https://provider.mainnet.oceanprotocol.com'
} }
], ],
pricing: { pricing: {