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

Merge branch 'feature/v4-c2d' into 'fix/compute-jobs'

This commit is contained in:
Bogdan Fazakas 2022-04-04 22:35:37 +03:00
commit 1b744760ce
17 changed files with 396 additions and 8428 deletions

8397
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
"@coingecko/cryptoformat": "^0.4.4", "@coingecko/cryptoformat": "^0.4.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^1.0.0-next.32", "@oceanprotocol/lib": "^1.0.0-next.33",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@portis/web3": "^4.0.7", "@portis/web3": "^4.0.7",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",

View File

@ -1,4 +1,4 @@
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance, Pool } from '@oceanprotocol/lib'
import { isValidNumber } from '@utils/numbers' import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import React, { import React, {
@ -16,6 +16,7 @@ import {
} from 'src/@types/subgraph/PoolData' } from 'src/@types/subgraph/PoolData'
import { useAsset } from '../Asset' import { useAsset } from '../Asset'
import { useWeb3 } from '../Web3' import { useWeb3 } from '../Web3'
import { calculateSharesVL } from '@utils/pool'
import { PoolProviderValue, PoolInfo, PoolInfoUser } from './_types' import { PoolProviderValue, PoolInfo, PoolInfoUser } from './_types'
import { getFee, getPoolData, getWeight } from './_utils' import { getFee, getPoolData, getWeight } from './_utils'
@ -37,7 +38,7 @@ const initialPoolInfoUser: Partial<PoolInfoUser> = {
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
function PoolProvider({ children }: { children: ReactNode }): ReactElement { function PoolProvider({ children }: { children: ReactNode }): ReactElement {
const { accountId } = useWeb3() const { accountId, web3, chainId } = useWeb3()
const { isInPurgatory, asset, owner } = useAsset() const { isInPurgatory, asset, owner } = useAsset()
const [poolData, setPoolData] = useState<PoolDataPoolData>() const [poolData, setPoolData] = useState<PoolDataPoolData>()
@ -54,6 +55,8 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false) const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false) const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
// const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>() // const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
const [ownerPoolShares, setOwnerPoolShares] = useState('0')
const [userPoolShares, setUserPoolShares] = useState('0')
const fetchAllData = useCallback(async () => { const fetchAllData = useCallback(async () => {
if ( if (
@ -84,30 +87,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots) LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId]) }, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
// Helper: start interval fetching
// const initFetchInterval = useCallback(() => {
// if (fetchInterval) return
// const newInterval = setInterval(() => {
// fetchAllData()
// LoggerInstance.log(
// `[pool] Refetch interval fired after ${refreshInterval / 1000}s`
// )
// }, refreshInterval)
// setFetchInterval(newInterval)
// // Having `accountId` as dependency is important for interval to
// // change after user account switch.
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [fetchInterval, fetchAllData, accountId])
// useEffect(() => {
// return () => {
// clearInterval(fetchInterval)
// }
// }, [fetchInterval])
//
// 0 Fetch all the data on mount if we are on a pool. // 0 Fetch all the data on mount if we are on a pool.
// All further effects depend on the fetched data // All further effects depend on the fetched data
// and only do further data checking and manipulation. // and only do further data checking and manipulation.
@ -116,7 +95,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
if (asset?.accessDetails?.type !== 'dynamic') return if (asset?.accessDetails?.type !== 'dynamic') return
fetchAllData() fetchAllData()
// initFetchInterval()
}, [fetchAllData, asset?.accessDetails?.type]) }, [fetchAllData, asset?.accessDetails?.type])
// //
@ -125,12 +103,19 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
useEffect(() => { useEffect(() => {
if (!poolData) return if (!poolData) return
// once we have poolData, we need to get owner's pool shares (OVL)
calculateSharesVL(
poolData.id,
poolData.baseToken.address,
poolData.shares[0].shares,
asset.chainId
).then((shares) => {
setOwnerPoolShares(shares)
})
// Total Liquidity // Total Liquidity
const totalLiquidityInOcean = isValidNumber(poolData.spotPrice) const totalLiquidityInOcean = new Decimal(
? new Decimal(poolData.baseTokenLiquidity).add( poolData.baseTokenLiquidity * 2 || 0
new Decimal(poolData.datatokenLiquidity).mul(poolData.spotPrice) )
)
: new Decimal(0)
const newPoolInfo = { const newPoolInfo = {
liquidityProviderSwapFee: getFee(poolData.liquidityProviderSwapFee), liquidityProviderSwapFee: getFee(poolData.liquidityProviderSwapFee),
@ -145,48 +130,42 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
totalPoolTokens: poolData.totalShares, totalPoolTokens: poolData.totalShares,
totalLiquidityInOcean totalLiquidityInOcean
} }
setPoolInfo(newPoolInfo) setPoolInfo(newPoolInfo)
LoggerInstance.log('[pool] Created new pool info:', newPoolInfo) LoggerInstance.log('[pool] Created new pool info:', newPoolInfo)
}, [poolData]) }, [asset?.chainId, chainId, poolData, web3])
// //
// 2 Pool Creator Info // 2 Pool Creator Info
// //
useEffect(() => { useEffect(() => {
if (!poolData || !poolInfo?.totalPoolTokens) return if (
!poolData ||
// Staking bot receives half the pool shares so for display purposes !poolInfo?.totalPoolTokens ||
// we can multiply by 2 as we have a hardcoded 50/50 pool weight. !poolInfo.totalLiquidityInOcean ||
const ownerPoolShares = new Decimal(poolData.shares[0]?.shares) ownerPoolShares === '0'
.mul(2) )
.toString() return
// Liquidity in base token, calculated from pool share tokens.
const liquidity =
isValidNumber(ownerPoolShares) &&
isValidNumber(poolInfo.totalPoolTokens) &&
isValidNumber(poolData.baseTokenLiquidity)
? new Decimal(ownerPoolShares)
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
.mul(poolData.baseTokenLiquidity)
: new Decimal(0)
// Pool share tokens. // Pool share tokens.
const poolShare = const poolShare = new Decimal(ownerPoolShares)
isValidNumber(ownerPoolShares) && isValidNumber(poolInfo.totalPoolTokens) .dividedBy(poolInfo.totalLiquidityInOcean)
? new Decimal(ownerPoolShares) .mul(100)
.dividedBy(new Decimal(poolInfo.totalPoolTokens)) .toFixed(2)
.mul(100)
.toFixed(2)
: '0'
const newPoolOwnerInfo = { const newPoolOwnerInfo = {
liquidity, liquidity: new Decimal(ownerPoolShares), // liquidity in base token, values from from `calcSingleOutGivenPoolIn` method
poolShares: ownerPoolShares, poolShares: ownerPoolShares,
poolShare poolShare
} }
setPoolInfoOwner(newPoolOwnerInfo) setPoolInfoOwner(newPoolOwnerInfo)
LoggerInstance.log('[pool] Created new owner pool info:', newPoolOwnerInfo) LoggerInstance.log('[pool] Created new owner pool info:', newPoolOwnerInfo)
}, [poolData, poolInfo?.totalPoolTokens]) }, [
ownerPoolShares,
poolData,
poolInfo.totalLiquidityInOcean,
poolInfo.totalPoolTokens
])
// //
// 3 User Pool Info // 3 User Pool Info
@ -196,40 +175,34 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
!poolData || !poolData ||
!poolInfo?.totalPoolTokens || !poolInfo?.totalPoolTokens ||
!poolInfoUser?.poolShares || !poolInfoUser?.poolShares ||
!poolInfo?.totalLiquidityInOcean ||
!poolData?.baseTokenLiquidity ||
!asset?.chainId || !asset?.chainId ||
!accountId || !accountId ||
!poolInfoUser !poolInfoUser
) )
return return
// Staking bot receives half the pool shares so for display purposes
// we can multiply by 2 as we have a hardcoded 50/50 pool weight. // once we have poolData, we need to get user's pool shares (VL)
const userPoolShares = new Decimal(poolInfoUser.poolShares || 0) calculateSharesVL(
.mul(2) poolData.id,
.toString() poolData.baseToken.address,
poolInfoUser.poolShares,
asset.chainId
).then((shares) => {
setUserPoolShares(shares)
})
// Pool share in %. // Pool share in %.
const poolShare = const poolShare = new Decimal(userPoolShares)
isValidNumber(userPoolShares) && .dividedBy(new Decimal(poolInfo.totalLiquidityInOcean))
isValidNumber(poolInfo.totalPoolTokens) && .mul(100)
new Decimal(userPoolShares) .toFixed(2)
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
.mul(100)
.toFixed(2)
setUserHasAddedLiquidity(Number(poolShare) > 0) setUserHasAddedLiquidity(Number(poolShare) > 0)
// Liquidity in base token, calculated from pool share tokens.
const liquidity =
isValidNumber(userPoolShares) &&
isValidNumber(poolInfo.totalPoolTokens) &&
isValidNumber(poolData.baseTokenLiquidity)
? new Decimal(userPoolShares)
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
.mul(poolData.baseTokenLiquidity)
: new Decimal(0)
const newPoolInfoUser = { const newPoolInfoUser = {
liquidity, liquidity: new Decimal(userPoolShares), // liquidity in base token, values from from `calcSingleOutGivenPoolIn` method
poolShare poolShare
} }
setPoolInfoUser((prevState: PoolInfoUser) => ({ setPoolInfoUser((prevState: PoolInfoUser) => ({
@ -247,6 +220,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
poolData, poolData,
poolInfoUser?.poolShares, poolInfoUser?.poolShares,
accountId, accountId,
userPoolShares,
asset?.chainId, asset?.chainId,
owner, owner,
poolInfo?.totalPoolTokens poolInfo?.totalPoolTokens

View File

@ -5,7 +5,8 @@ import {
Nft, Nft,
ProviderInstance, ProviderInstance,
DDO, DDO,
MetadataAndTokenURI MetadataAndTokenURI,
NftCreateData
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { SvgWaves } from './SvgWaves' import { SvgWaves } from './SvgWaves'
import Web3 from 'web3' import Web3 from 'web3'
@ -62,12 +63,18 @@ export function generateNftMetadata(): NftMetadata {
const tokenUriPrefix = 'data:application/json;base64,' const tokenUriPrefix = 'data:application/json;base64,'
export function generateNftCreateData(nftMetadata: NftMetadata): any { export function generateNftCreateData(
const nftCreateData = { nftMetadata: NftMetadata,
accountId: string,
transferable = true
): any {
const nftCreateData: NftCreateData = {
name: nftMetadata.name, name: nftMetadata.name,
symbol: nftMetadata.symbol, symbol: nftMetadata.symbol,
templateIndex: 1, templateIndex: 1,
tokenURI: '' tokenURI: '',
transferable,
owner: accountId
} }
return nftCreateData return nftCreateData

View File

@ -114,3 +114,26 @@ export function calculateUserTVL(
const tvl = new Decimal(liquidity).mul(2) // we multiply by 2 because of 50/50 weight const tvl = new Decimal(liquidity).mul(2) // we multiply by 2 because of 50/50 weight
return tvl.toDecimalPlaces(MAX_DECIMALS).toString() return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
} }
export async function calculateSharesVL(
pool: string,
tokenAddress: string,
shares: string,
chainId?: number
): Promise<string> {
if (!chainId) throw new Error("chainId can't be undefined at the same time!")
// we only use the dummyWeb3 connection here
const web3 = await getDummyWeb3(chainId)
const poolInstance = new Pool(web3)
// get shares VL in ocean
const amountOcean = await poolInstance.calcSingleOutGivenPoolIn(
pool,
tokenAddress,
shares
)
const tvl = new Decimal(amountOcean || 0).mul(2) // we multiply by 2 because of 50/50 weight
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
}

View File

@ -6,21 +6,6 @@
position: relative; 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 { .info ul {
margin: 0; margin: 0;
} }
@ -32,6 +17,25 @@
color: var(--color-secondary); 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 { .removeButton {
cursor: pointer; cursor: pointer;
border: none; border: none;
@ -43,11 +47,3 @@
color: var(--font-color-text); color: var(--font-color-text);
background-color: transparent; background-color: transparent;
} }
.info li.success {
color: var(--brand-alert-green);
}
.info li.error {
color: var(--brand-alert-red);
}

View File

@ -3,6 +3,7 @@ import { prettySize } from './utils'
import cleanupContentType from '@utils/cleanupContentType' import cleanupContentType from '@utils/cleanupContentType'
import styles from './Info.module.css' import styles from './Info.module.css'
import { FileMetadata } from '@oceanprotocol/lib' import { FileMetadata } from '@oceanprotocol/lib'
import Alert from '@shared/atoms/Alert'
export default function FileInfo({ export default function FileInfo({
file, file,
@ -11,29 +12,30 @@ export default function FileInfo({
file: FileMetadata file: FileMetadata
handleClose(): void handleClose(): void
}): ReactElement { }): 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}> <div className={styles.info}>
<h3 className={styles.url}>{file.url}</h3> <h3 className={styles.url}>{file.url}</h3>
<ul> <ul>
<li className={styles.success}> URL confirmed</li> <li className={styles.success}> URL confirmed</li>
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>} {file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>} {contentTypeCleaned && <li>{contentTypeCleaned}</li>}
</ul>
<button className={styles.removeButton} onClick={handleClose}>
&times;
</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>}
</ul> </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 className={styles.removeButton} onClick={handleClose}>
&times; &times;
</button> </button>

View File

@ -1,80 +1,59 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react' import React, { ReactElement, useState } from 'react'
import { useField, useFormikContext } from 'formik' import { useField, useFormikContext } from 'formik'
import { toast } from 'react-toastify'
import FileInfo from './Info' import FileInfo from './Info'
import UrlInput from '../URLInput' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
import { initialValues } from 'src/components/Publish/_constants'
import { getFileUrlInfo } from '@utils/provider' import { getFileUrlInfo } from '@utils/provider'
import { FormPublishData } from 'src/components/Publish/_types' import { FormPublishData } from 'src/components/Publish/_types'
import { LoggerInstance } from '@oceanprotocol/lib'
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 [isInvalidUrl, setIsInvalidUrl] = useState(false)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { values } = useFormikContext<FormPublishData>() const { values, setFieldError } = useFormikContext<FormPublishData>()
const loadFileInfo = useCallback( async function handleValidation(e: React.SyntheticEvent, url: string) {
(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) {
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf' // File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
e.preventDefault() 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() { function handleClose() {
helpers.setValue(initialValues.services[0].files) helpers.setValue(meta.initialValue)
helpers.setTouched(false) helpers.setTouched(false)
} }
return ( return (
<> <>
{field?.value && field?.value[0]?.valid !== undefined ? ( {field?.value?.[0]?.valid === true ? (
<FileInfo file={field.value[0]} handleClose={handleClose} /> <FileInfo file={field.value[0]} handleClose={handleClose} />
) : ( ) : (
<UrlInput <UrlInput
submitText="Validate" submitText="Validate"
{...props} {...props}
name={`${field.name}[0].url`} name={`${field.name}[0].url`}
hasError={Boolean(meta.touched && isInvalidUrl)}
isLoading={isLoading} isLoading={isLoading}
handleButtonClick={handleButtonClick} handleButtonClick={handleValidation}
/> />
)} )}
</> </>

View File

@ -17,7 +17,7 @@ const getEstGasFee = async (
return return
const { web3 } = nftFactory const { web3 } = nftFactory
const nft = generateNftCreateData(nftMetadata) const nft = generateNftCreateData(nftMetadata, address)
const gasPrice = await web3.eth.getGasPrice() const gasPrice = await web3.eth.getGasPrice()
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft) const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)

View File

@ -2,8 +2,14 @@
composes: error from '@shared/FormInput/index.module.css'; composes: error from '@shared/FormInput/index.module.css';
} }
.restore { .default {
font-family: var(--font-family-base); font-family: var(--font-family-base);
text-transform: none; text-transform: none;
font-weight: var(--font-weight-base); font-weight: var(--font-weight-base);
/* take it out of layout so error messages
are not pushed down */
position: absolute;
left: 0;
bottom: -30%;
} }

View File

@ -1,52 +1,56 @@
import React, { ReactElement, useState } from 'react' import React, { ReactElement, useState } from 'react'
import { ErrorMessage, useField } from 'formik' import { useField, useFormikContext } from 'formik'
import UrlInput from '../URLInput' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
import FileInfo from '../FilesInput/Info' import FileInfo from '../FilesInput/Info'
import styles from './index.module.css' import styles from './index.module.css'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import { initialValues } from 'src/components/Publish/_constants' 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 { 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 { setFieldError } = useFormikContext<FormPublishData>()
async function validateProvider(url: string) { async function handleValidation(e: React.SyntheticEvent) {
setIsLoading(true) e.preventDefault()
try { try {
const isValid = await ProviderInstance.isValidProvider(url) setIsLoading(true)
helpers.setValue({ url, valid: isValid }) const isValid = await ProviderInstance.isValidProvider(field.value.url)
helpers.setError(undefined)
// 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) { } catch (error) {
helpers.setError( setFieldError(`${field.name}.url`, error.message)
'Could not validate provider. Please check URL and try again.' LoggerInstance.error(error.message)
)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
} }
async function handleValidateButtonClick(
e: React.SyntheticEvent,
url: string
) {
e.preventDefault()
validateProvider(url)
}
function handleFileInfoClose() { function handleFileInfoClose() {
helpers.setValue({ url: '', valid: false }) helpers.setValue({ url: '', valid: false })
helpers.setTouched(false) helpers.setTouched(false)
} }
function handleRestore(e: React.SyntheticEvent) { function handleDefault(e: React.SyntheticEvent) {
e.preventDefault() e.preventDefault()
helpers.setValue(initialValues.services[0].providerUrl) helpers.setValue(initialValues.services[0].providerUrl)
} }
return field?.value?.valid ? ( return field?.value?.valid === true ? (
<FileInfo file={field.value} handleClose={handleFileInfoClose} /> <FileInfo file={field.value} handleClose={handleFileInfoClose} />
) : ( ) : (
<> <>
@ -54,23 +58,17 @@ export default function CustomProvider(props: InputProps): ReactElement {
submitText="Validate" submitText="Validate"
{...props} {...props}
name={`${field.name}.url`} name={`${field.name}.url`}
hasError={Boolean(meta.touched && meta.error)}
isLoading={isLoading} isLoading={isLoading}
handleButtonClick={handleValidateButtonClick} handleButtonClick={handleValidation}
/> />
<Button <Button
style="text" style="text"
size="small" size="small"
onClick={handleRestore} onClick={handleDefault}
className={styles.restore} className={styles.default}
> >
Use Default Provider Use Default Provider
</Button> </Button>
{typeof meta.error === 'string' && meta.touched && meta.error && (
<div className={styles.error}>
<ErrorMessage name={field.name} />
</div>
)}
</> </>
) )
} }

View File

@ -10,8 +10,3 @@
.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

@ -12,14 +12,12 @@ export default function URLInput({
handleButtonClick, handleButtonClick,
isLoading, isLoading,
name, name,
hasError,
...props ...props
}: { }: {
submitText: string submitText: string
handleButtonClick(e: React.SyntheticEvent, data: string): void handleButtonClick(e: React.SyntheticEvent, data: string): void
isLoading: boolean isLoading: boolean
name: string name: string
hasError: boolean
}): ReactElement { }): ReactElement {
const [field, meta] = useField(name) const [field, meta] = useField(name)
const [isButtonDisabled, setIsButtonDisabled] = useState(true) const [isButtonDisabled, setIsButtonDisabled] = useState(true)

View File

@ -5,8 +5,6 @@ import Link from 'next/link'
import get3BoxProfile from '@utils/profile' import get3BoxProfile from '@utils/profile'
import { accountTruncate } from '@utils/web3' import { accountTruncate } from '@utils/web3'
import axios from 'axios' import axios from 'axios'
import Add from './Add'
import { useWeb3 } from '@context/Web3'
import { getEnsName } from '@utils/ens' import { getEnsName } from '@utils/ens'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
@ -21,17 +19,18 @@ export default function Publisher({
minimal?: boolean minimal?: boolean
className?: string className?: string
}): ReactElement { }): ReactElement {
// const { accountId } = useWeb3()
const isMounted = useIsMounted() const isMounted = useIsMounted()
const [profile, setProfile] = useState<Profile>() const [profile, setProfile] = useState<Profile>()
const [name, setName] = useState(accountTruncate(account)) const [name, setName] = useState('')
const [accountEns, setAccountEns] = useState<string>() const [accountEns, setAccountEns] = useState<string>()
// const showAdd = account === accountId && !profile
useEffect(() => { useEffect(() => {
if (!account) return if (!account) return
// set default name on hook
// to avoid side effect (UI not updating on account's change)
setName(accountTruncate(account))
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
async function getExternalName() { async function getExternalName() {
@ -70,7 +69,6 @@ export default function Publisher({
<Link href={`/profile/${accountEns || account}`}> <Link href={`/profile/${accountEns || account}`}>
<a title="Show profile page.">{name}</a> <a title="Show profile page.">{name}</a>
</Link> </Link>
{/* {showAdd && <Add />} */}
</> </>
)} )}
</div> </div>

View File

@ -53,6 +53,7 @@ export const initialValues: FormPublishData = {
}, },
metadata: { metadata: {
nft: { name: '', symbol: '', description: '', image_data: '' }, nft: { name: '', symbol: '', description: '', image_data: '' },
transferable: true,
type: 'dataset', type: 'dataset',
name: '', name: '',
author: '', author: '',

View File

@ -29,6 +29,7 @@ export interface FormPublishData {
} }
metadata: { metadata: {
nft: NftMetadata nft: NftMetadata
transferable: boolean
type: 'dataset' | 'algorithm' type: 'dataset' | 'algorithm'
name: string name: string
description: string description: string

View File

@ -181,8 +181,7 @@ export async function transformPublishFormToDdo(
} }
], ],
nft: { nft: {
...generateNftCreateData(values?.metadata.nft), ...generateNftCreateData(values?.metadata.nft, accountId)
owner: accountId
} }
}) })
} }
@ -198,7 +197,9 @@ export async function createTokensAndPricing(
web3: Web3 web3: Web3
) { ) {
const nftCreateData: NftCreateData = generateNftCreateData( const nftCreateData: NftCreateData = generateNftCreateData(
values.metadata.nft values.metadata.nft,
accountId,
values.metadata.transferable
) )
const { appConfig } = getSiteMetadata() const { appConfig } = getSiteMetadata()
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData) LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)