From 48c3190fedb8328d6482fea67ae5ea2dd1f3f107 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Mon, 4 Apr 2022 15:34:48 +0100 Subject: [PATCH] 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 --- package-lock.json | 2 + .../FormFields/FilesInput/Info.module.css | 42 +++++----- .../@shared/FormFields/FilesInput/Info.tsx | 36 +++++---- .../@shared/FormFields/FilesInput/index.tsx | 77 +++++++------------ .../FormFields/Provider/index.module.css | 8 +- .../@shared/FormFields/Provider/index.tsx | 56 +++++++------- .../FormFields/URLInput/index.module.css | 5 -- .../@shared/FormFields/URLInput/index.tsx | 2 - 8 files changed, 102 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1feff6753..de03faafc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/components/@shared/FormFields/FilesInput/Info.module.css b/src/components/@shared/FormFields/FilesInput/Info.module.css index 94335e065..a9a95d6d8 100644 --- a/src/components/@shared/FormFields/FilesInput/Info.module.css +++ b/src/components/@shared/FormFields/FilesInput/Info.module.css @@ -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); -} diff --git a/src/components/@shared/FormFields/FilesInput/Info.tsx b/src/components/@shared/FormFields/FilesInput/Info.tsx index 495f02e5d..6be397a07 100644 --- a/src/components/@shared/FormFields/FilesInput/Info.tsx +++ b/src/components/@shared/FormFields/FilesInput/Info.tsx @@ -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 (

{file.url}

- -
- ) : ( -
-

{file.url}

- + {shouldWarnAboutFile && ( + + )} diff --git a/src/components/@shared/FormFields/FilesInput/index.tsx b/src/components/@shared/FormFields/FilesInput/index.tsx index 6f0c97c56..5098993e5 100644 --- a/src/components/@shared/FormFields/FilesInput/index.tsx +++ b/src/components/@shared/FormFields/FilesInput/index.tsx @@ -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() + const { values, setFieldError } = useFormikContext() - const loadFileInfo = useCallback( - (url: string) => { - const providerUri = - (values.services && values.services[0].providerUrl.url) || - 'https://provider.mainnet.oceanprotocol.com' - - async function validateUrl() { - try { - setIsLoading(true) - const checkedFile = await getFileUrlInfo(url, providerUri) - setIsInvalidUrl(!checkedFile[0].valid) - checkedFile && helpers.setValue([{ url, ...checkedFile[0] }]) - } catch (error) { - toast.error( - 'Could not fetch file info. Please check URL and try again' - ) - console.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) { + async function handleValidation(e: React.SyntheticEvent, url: string) { // File example 'https://oceanprotocol.com/tech-whitepaper.pdf' e.preventDefault() - loadFileInfo(url) + + try { + const providerUrl = values?.services[0].providerUrl.url + setIsLoading(true) + 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) { + setFieldError(`${field.name}[0].url`, error.message) + LoggerInstance.error(error.message) + } finally { + setIsLoading(false) + } } 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 ? ( ) : ( )} diff --git a/src/components/@shared/FormFields/Provider/index.module.css b/src/components/@shared/FormFields/Provider/index.module.css index 99cf0ffd1..402ba2b4d 100644 --- a/src/components/@shared/FormFields/Provider/index.module.css +++ b/src/components/@shared/FormFields/Provider/index.module.css @@ -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%; } diff --git a/src/components/@shared/FormFields/Provider/index.tsx b/src/components/@shared/FormFields/Provider/index.tsx index c0d92ceee..7678daf76 100644 --- a/src/components/@shared/FormFields/Provider/index.tsx +++ b/src/components/@shared/FormFields/Provider/index.tsx @@ -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() - 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) + 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) { - helpers.setError( - 'Could not validate provider. Please check URL and try again.' - ) + 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 ? ( ) : ( <> @@ -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} /> - {typeof meta.error === 'string' && meta.touched && meta.error && ( -
- -
- )} ) } diff --git a/src/components/@shared/FormFields/URLInput/index.module.css b/src/components/@shared/FormFields/URLInput/index.module.css index dce4c726f..7cc9b6283 100644 --- a/src/components/@shared/FormFields/URLInput/index.module.css +++ b/src/components/@shared/FormFields/URLInput/index.module.css @@ -10,8 +10,3 @@ .error { composes: error from '@shared/FormInput/index.module.css'; } - -.success { - background: var(--brand-alert-green); - opacity: 1 !important; -} diff --git a/src/components/@shared/FormFields/URLInput/index.tsx b/src/components/@shared/FormFields/URLInput/index.tsx index 77aa2d331..3890032c9 100644 --- a/src/components/@shared/FormFields/URLInput/index.tsx +++ b/src/components/@shared/FormFields/URLInput/index.tsx @@ -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)