mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Warn about publishing html files, unified file error states (#1279)
* cleanup * warn about html files * kick out toast error * unify and simplify all file error states * remove unused CSS * same principles for provider check * copy * copy & comments * fix files reset with correct initialValues, shorter optional chaining * messaging change * fix error message placement for provider field
This commit is contained in:
parent
b574daee30
commit
48c3190fed
2
package-lock.json
generated
2
package-lock.json
generated
@ -26728,6 +26728,7 @@
|
||||
"cross-fetch": "^3.1.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"decimal.js": "^10.3.1",
|
||||
"web3": "^1.7.1",
|
||||
"web3-core": "^1.7.1",
|
||||
"web3-eth-contract": "^1.7.1"
|
||||
}
|
||||
@ -26830,6 +26831,7 @@
|
||||
"integrity": "sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@oclif/config": "^1.15.1",
|
||||
"@oclif/errors": "^1.3.3",
|
||||
"@oclif/parser": "^3.8.3",
|
||||
"@oclif/plugin-help": "^3",
|
||||
|
@ -6,21 +6,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
border-color: var(--brand-alert-red);
|
||||
background-color: var(--brand-white);
|
||||
}
|
||||
|
||||
.url {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height);
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.info ul {
|
||||
margin: 0;
|
||||
}
|
||||
@ -32,6 +17,25 @@
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.info li.success {
|
||||
color: var(--brand-alert-green);
|
||||
}
|
||||
|
||||
.url {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height);
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin-top: calc(var(--spacer) / 3);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.removeButton {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
@ -43,11 +47,3 @@
|
||||
color: var(--font-color-text);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.info li.success {
|
||||
color: var(--brand-alert-green);
|
||||
}
|
||||
|
||||
.info li.error {
|
||||
color: var(--brand-alert-red);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { prettySize } from './utils'
|
||||
import cleanupContentType from '@utils/cleanupContentType'
|
||||
import styles from './Info.module.css'
|
||||
import { FileMetadata } from '@oceanprotocol/lib'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
|
||||
export default function FileInfo({
|
||||
file,
|
||||
@ -11,29 +12,30 @@ export default function FileInfo({
|
||||
file: FileMetadata
|
||||
handleClose(): void
|
||||
}): ReactElement {
|
||||
return file.valid ? (
|
||||
const contentTypeCleaned = file.contentType
|
||||
? cleanupContentType(file.contentType)
|
||||
: null
|
||||
|
||||
// Prevent accidential publishing of error pages (e.g. 404) for
|
||||
// popular file hosting services by warning about it.
|
||||
// See https://github.com/oceanprotocol/market/issues/1246
|
||||
const shouldWarnAboutFile = file.valid && contentTypeCleaned === 'html'
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<h3 className={styles.url}>{file.url}</h3>
|
||||
<ul>
|
||||
<li className={styles.success}>✓ URL confirmed</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
||||
</ul>
|
||||
<button className={styles.removeButton} onClick={handleClose}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`${styles.info} ${!file.valid ? styles.hasError : ''}`}>
|
||||
<h3 className={styles.url}>{file.url}</h3>
|
||||
<ul>
|
||||
<li className={styles.error}>
|
||||
{' '}
|
||||
✗ No valid file detected. Check your URL and try again.
|
||||
</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
||||
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
||||
</ul>
|
||||
{shouldWarnAboutFile && (
|
||||
<Alert
|
||||
state="info"
|
||||
text={`Your file was detected as ${contentTypeCleaned}, which is unusal for a data asset. If you did not intend to use a ${contentTypeCleaned} file, try a different URL pointing directly to your data asset file.`}
|
||||
className={styles.warning}
|
||||
/>
|
||||
)}
|
||||
<button className={styles.removeButton} onClick={handleClose}>
|
||||
×
|
||||
</button>
|
||||
|
@ -1,80 +1,59 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import FileInfo from './Info'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { initialValues } from 'src/components/Publish/_constants'
|
||||
import { getFileUrlInfo } from '@utils/provider'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
|
||||
export default function FilesInput(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isInvalidUrl, setIsInvalidUrl] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
const { values, setFieldError } = useFormikContext<FormPublishData>()
|
||||
|
||||
const loadFileInfo = useCallback(
|
||||
(url: string) => {
|
||||
const providerUri =
|
||||
(values.services && values.services[0].providerUrl.url) ||
|
||||
'https://provider.mainnet.oceanprotocol.com'
|
||||
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||
e.preventDefault()
|
||||
|
||||
async function validateUrl() {
|
||||
try {
|
||||
const providerUrl = values?.services[0].providerUrl.url
|
||||
setIsLoading(true)
|
||||
const checkedFile = await getFileUrlInfo(url, providerUri)
|
||||
setIsInvalidUrl(!checkedFile[0].valid)
|
||||
checkedFile && helpers.setValue([{ url, ...checkedFile[0] }])
|
||||
const checkedFile = await getFileUrlInfo(url, providerUrl)
|
||||
|
||||
// error if something's not right from response
|
||||
if (!checkedFile)
|
||||
throw Error('Could not fetch file info. Is your network down?')
|
||||
|
||||
if (checkedFile[0].valid === false)
|
||||
throw Error('✗ No valid file detected. Check your URL and try again.')
|
||||
|
||||
// if all good, add file to formik state
|
||||
helpers.setValue([{ url, ...checkedFile[0] }])
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
'Could not fetch file info. Please check URL and try again'
|
||||
)
|
||||
console.error(error.message)
|
||||
setFieldError(`${field.name}[0].url`, error.message)
|
||||
LoggerInstance.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
validateUrl()
|
||||
},
|
||||
[helpers, values.services]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// try load from initial values, kinda hacky but it works
|
||||
if (
|
||||
props.value &&
|
||||
props.value.length > 0 &&
|
||||
typeof props.value[0] === 'string'
|
||||
) {
|
||||
loadFileInfo(props.value[0].toString())
|
||||
}
|
||||
}, [loadFileInfo, props])
|
||||
|
||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||
e.preventDefault()
|
||||
loadFileInfo(url)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
helpers.setValue(initialValues.services[0].files)
|
||||
helpers.setValue(meta.initialValue)
|
||||
helpers.setTouched(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{field?.value && field?.value[0]?.valid !== undefined ? (
|
||||
{field?.value?.[0]?.valid === true ? (
|
||||
<FileInfo file={field.value[0]} handleClose={handleClose} />
|
||||
) : (
|
||||
<UrlInput
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
name={`${field.name}[0].url`}
|
||||
hasError={Boolean(meta.touched && isInvalidUrl)}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleButtonClick}
|
||||
handleButtonClick={handleValidation}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -2,8 +2,14 @@
|
||||
composes: error from '@shared/FormInput/index.module.css';
|
||||
}
|
||||
|
||||
.restore {
|
||||
.default {
|
||||
font-family: var(--font-family-base);
|
||||
text-transform: none;
|
||||
font-weight: var(--font-weight-base);
|
||||
|
||||
/* take it out of layout so error messages
|
||||
are not pushed down */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -30%;
|
||||
}
|
||||
|
@ -1,52 +1,56 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { ErrorMessage, useField } from 'formik'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import FileInfo from '../FilesInput/Info'
|
||||
import styles from './index.module.css'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { initialValues } from 'src/components/Publish/_constants'
|
||||
import { ProviderInstance } from '@oceanprotocol/lib'
|
||||
import { LoggerInstance, ProviderInstance } from '@oceanprotocol/lib'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
|
||||
export default function CustomProvider(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { setFieldError } = useFormikContext<FormPublishData>()
|
||||
|
||||
async function validateProvider(url: string) {
|
||||
setIsLoading(true)
|
||||
async function handleValidation(e: React.SyntheticEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
const isValid = await ProviderInstance.isValidProvider(url)
|
||||
helpers.setValue({ url, valid: isValid })
|
||||
helpers.setError(undefined)
|
||||
} catch (error) {
|
||||
helpers.setError(
|
||||
'Could not validate provider. Please check URL and try again.'
|
||||
setIsLoading(true)
|
||||
const isValid = await ProviderInstance.isValidProvider(field.value.url)
|
||||
|
||||
// error if something's not right from response
|
||||
// No way to detect a failed request with ProviderInstance.isValidProvider,
|
||||
// making this error show up for multiple cases it shouldn't, like network
|
||||
// down.
|
||||
if (!isValid)
|
||||
throw Error(
|
||||
'✗ No valid provider detected. Check your network, your URL and try again.'
|
||||
)
|
||||
|
||||
// if all good, add provider to formik state
|
||||
helpers.setValue({ url: field.value.url, valid: isValid })
|
||||
} catch (error) {
|
||||
setFieldError(`${field.name}.url`, error.message)
|
||||
LoggerInstance.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleValidateButtonClick(
|
||||
e: React.SyntheticEvent,
|
||||
url: string
|
||||
) {
|
||||
e.preventDefault()
|
||||
validateProvider(url)
|
||||
}
|
||||
|
||||
function handleFileInfoClose() {
|
||||
helpers.setValue({ url: '', valid: false })
|
||||
helpers.setTouched(false)
|
||||
}
|
||||
|
||||
function handleRestore(e: React.SyntheticEvent) {
|
||||
function handleDefault(e: React.SyntheticEvent) {
|
||||
e.preventDefault()
|
||||
helpers.setValue(initialValues.services[0].providerUrl)
|
||||
}
|
||||
|
||||
return field?.value?.valid ? (
|
||||
return field?.value?.valid === true ? (
|
||||
<FileInfo file={field.value} handleClose={handleFileInfoClose} />
|
||||
) : (
|
||||
<>
|
||||
@ -54,23 +58,17 @@ export default function CustomProvider(props: InputProps): ReactElement {
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
name={`${field.name}.url`}
|
||||
hasError={Boolean(meta.touched && meta.error)}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleValidateButtonClick}
|
||||
handleButtonClick={handleValidation}
|
||||
/>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleRestore}
|
||||
className={styles.restore}
|
||||
onClick={handleDefault}
|
||||
className={styles.default}
|
||||
>
|
||||
Use Default Provider
|
||||
</Button>
|
||||
{typeof meta.error === 'string' && meta.touched && meta.error && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -10,8 +10,3 @@
|
||||
.error {
|
||||
composes: error from '@shared/FormInput/index.module.css';
|
||||
}
|
||||
|
||||
.success {
|
||||
background: var(--brand-alert-green);
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
@ -12,14 +12,12 @@ export default function URLInput({
|
||||
handleButtonClick,
|
||||
isLoading,
|
||||
name,
|
||||
hasError,
|
||||
...props
|
||||
}: {
|
||||
submitText: string
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
name: string
|
||||
hasError: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
||||
|
Loading…
Reference in New Issue
Block a user