mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
new publish form data setup
This commit is contained in:
parent
70470a9459
commit
99453623d2
@ -1,89 +0,0 @@
|
||||
{
|
||||
"title": "Publish a Data Set",
|
||||
"data": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Title",
|
||||
"placeholder": "e.g. Shapes of Desert Plants",
|
||||
"help": "Enter a concise title.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics). You can change the description at any time. If you provide personal data, please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"type": "textarea",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please enter the URL to your data set file and click \"ADD FILE\" to validate the data. This URL will be stored encrypted after publishing. For a compute data set, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size.",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "links",
|
||||
"label": "Sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"help": "Please enter the URL to a sample of your data set file and click \"ADD FILE\" to validate the data. This file should reveal the data structure of your data set, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing.",
|
||||
"type": "files"
|
||||
},
|
||||
{
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true,
|
||||
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
||||
"disclaimerValues": ["Download"]
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
|
||||
"type": "select",
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken Name & Symbol",
|
||||
"type": "datatoken",
|
||||
"help": "The datatoken for this data set will be created with this name & symbol.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your data set. You are welcome to use a pseudonym, and you can change your author name at any time. Please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
"type": "terms",
|
||||
"options": ["I agree to these Terms and Conditions"],
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"success": "Asset Created!"
|
||||
}
|
98
content/publish/form.json
Normal file
98
content/publish/form.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Enter details",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Title",
|
||||
"placeholder": "e.g. Shapes of Desert Plants",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics). You can change the description at any time. If you provide personal data, please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"type": "textarea",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please enter the URL to your data set file and click \"ADD FILE\" to validate the data. This URL will be stored encrypted after publishing. For a compute data set, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size.",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "links",
|
||||
"label": "Sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"help": "Please enter the URL to a sample of your data set file and click \"ADD FILE\" to validate the data. This file should reveal the data structure of your data set, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing.",
|
||||
"type": "files"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your data set. You are welcome to use a pseudonym, and you can change your author name at any time. Please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
"type": "terms",
|
||||
"options": ["I agree to these Terms and Conditions"],
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"title": "Create services",
|
||||
"fields": [
|
||||
{
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true,
|
||||
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
||||
"disclaimerValues": ["Download"]
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
|
||||
"type": "select",
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken Name & Symbol",
|
||||
"type": "datatoken",
|
||||
"help": "The datatoken for this data set will be created with this name & symbol.",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Create pricing schema",
|
||||
"fields": [{ "name": "dummy content" }]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "Publish",
|
||||
"description": "Highlight the important features of your data set or algorithm to make it more discoverable and catch the interest of data consumers.",
|
||||
"warning": "Given the beta status, publishing on Ropsten or Rinkeby first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms).",
|
||||
"warning": "Publishing into a test network first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms).",
|
||||
"tooltipNetwork": "Assets are published into the network your wallet is connected to. Switch your wallet's network to publish into another one."
|
||||
}
|
@ -16,9 +16,15 @@ function createTypes(actions) {
|
||||
desc: String!
|
||||
cookieName: String!
|
||||
}
|
||||
type PublishJsonData implements Node {
|
||||
type PublishJson implements Node {
|
||||
metadata: Extensions
|
||||
services: Extensions
|
||||
pricing: Extensions
|
||||
}
|
||||
type Extensions {
|
||||
disclaimer: String
|
||||
disclaimerValues: [String!]
|
||||
advanced: Boolean
|
||||
}
|
||||
`
|
||||
createTypes(typeDefs)
|
||||
|
5
src/@types/Form.d.ts
vendored
5
src/@types/Form.d.ts
vendored
@ -21,10 +21,9 @@ declare global {
|
||||
advanced?: boolean
|
||||
}
|
||||
|
||||
interface FormContent {
|
||||
interface FormStepContent {
|
||||
title: string
|
||||
description?: string
|
||||
success: string
|
||||
data: FormFieldProps[]
|
||||
fields: FormFieldProps[]
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Field } from 'formik'
|
||||
import styles from './AdvancedSettings.module.css'
|
||||
|
||||
export default function AdvancedSettings(prop: {
|
||||
content: FormContent
|
||||
content: FormStepContent
|
||||
handleFieldChange: (
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
field: FormFieldProps
|
||||
|
@ -16,7 +16,7 @@ export default function Terms(props: InputProps): ReactElement {
|
||||
const data = useStaticQuery(query)
|
||||
const termsProps: InputProps = {
|
||||
...props,
|
||||
defaultChecked: props.value.toString() === 'true'
|
||||
defaultChecked: props?.value?.toString() === 'true'
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,8 @@ import styles from './index.module.css'
|
||||
import { ErrorMessage, FieldInputProps } from 'formik'
|
||||
import classNames from 'classnames/bind'
|
||||
import Disclaimer from './Disclaimer'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import Markdown from '@shared/atoms/Markdown'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
@ -97,7 +99,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
||||
data-is-submitting={props.form?.isSubmitting ? true : null}
|
||||
>
|
||||
<Label htmlFor={props.name} required={props.required}>
|
||||
{label}
|
||||
{label} {help && <Tooltip content={<Markdown text={help} />} />}
|
||||
</Label>
|
||||
<InputElement size={size} {...field} {...props} />
|
||||
|
||||
@ -107,7 +109,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{help && <Help>{help}</Help>}
|
||||
{/* {help && <Help>{help}</Help>} */}
|
||||
|
||||
{disclaimer && (
|
||||
<Disclaimer visible={disclaimerVisible}>{disclaimer}</Disclaimer>
|
||||
|
@ -7,6 +7,7 @@
|
||||
font-size: var(--font-size-h3);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
|
@ -10,7 +10,7 @@ export default function PageHeader({
|
||||
description,
|
||||
center
|
||||
}: {
|
||||
title: string
|
||||
title: ReactElement
|
||||
description?: string
|
||||
center?: boolean
|
||||
}): ReactElement {
|
||||
|
@ -4,7 +4,6 @@ import MetaFull from './MetaFull'
|
||||
import MetaSecondary from './MetaSecondary'
|
||||
import AssetActions from '../AssetActions'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import Pricing from '../../Publish/Pricing'
|
||||
import Bookmark from './Bookmark'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
@ -31,7 +30,6 @@ export default function AssetContent(): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const { owner, isInPurgatory, purgatoryData, isAssetNetwork } = useAsset()
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const [showEdit, setShowEdit] = useState<boolean>()
|
||||
const [isComputeType, setIsComputeType] = useState<boolean>(false)
|
||||
const [showEditCompute, setShowEditCompute] = useState<boolean>()
|
||||
@ -43,7 +41,6 @@ export default function AssetContent(): ReactElement {
|
||||
|
||||
const isOwner = accountId.toLowerCase() === owner.toLowerCase()
|
||||
setIsOwner(isOwner)
|
||||
setShowPricing(isOwner && price.type === '')
|
||||
setIsComputeType(Boolean(ddo.findServiceByType('compute')))
|
||||
}, [accountId, price, owner, ddo])
|
||||
|
||||
@ -70,7 +67,6 @@ export default function AssetContent(): ReactElement {
|
||||
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
{showPricing && <Pricing ddo={ddo} />}
|
||||
<div className={styles.content}>
|
||||
<MetaMain />
|
||||
<Bookmark did={ddo.id} />
|
||||
|
@ -10,23 +10,23 @@ export default function Debug({
|
||||
values: Partial<FormPublishData>
|
||||
}): ReactElement {
|
||||
const ddo = {
|
||||
'@context': 'https://w3id.org/did/v1',
|
||||
dataTokenInfo: {
|
||||
...values.dataTokenOptions
|
||||
},
|
||||
service: [
|
||||
{
|
||||
index: 0,
|
||||
type: 'metadata',
|
||||
attributes: { ...transformPublishFormToMetadata(values) }
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
type: values.access,
|
||||
serviceEndpoint: values.providerUri,
|
||||
attributes: {}
|
||||
}
|
||||
]
|
||||
'@context': 'https://w3id.org/did/v1'
|
||||
// dataTokenInfo: {
|
||||
// ...values.dataTokenOptions
|
||||
// },
|
||||
// service: [
|
||||
// {
|
||||
// index: 0,
|
||||
// type: 'metadata',
|
||||
// attributes: { ...transformPublishFormToMetadata(values) }
|
||||
// },
|
||||
// {
|
||||
// index: 1,
|
||||
// type: values.access,
|
||||
// serviceEndpoint: values.providerUri,
|
||||
// attributes: {}
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,7 +0,0 @@
|
||||
.form {
|
||||
composes: box from '../../atoms/Box.module.css';
|
||||
margin-bottom: var(--spacer);
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
import React, {
|
||||
ReactElement,
|
||||
useEffect,
|
||||
FormEvent,
|
||||
ChangeEvent,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useFormikContext, Field, Form, FormikContextType } from 'formik'
|
||||
import Input from '@shared/Form/Input'
|
||||
import Download from '@images/download.svg'
|
||||
import Compute from '@images/compute.svg'
|
||||
import FormTitle from './FormTitle'
|
||||
import FormActions from './FormActions'
|
||||
import AdvancedSettings from '@shared/Form/FormFields/AdvancedSettings'
|
||||
import { FormPublishData } from './_types'
|
||||
import styles from './FormPublish.module.css'
|
||||
import { initialValues } from './_constants'
|
||||
import content from '../../../content/pages/publish/form-dataset.json'
|
||||
|
||||
export default function FormPublish(): ReactElement {
|
||||
const {
|
||||
status,
|
||||
setStatus,
|
||||
isValid,
|
||||
values,
|
||||
setErrors,
|
||||
setTouched,
|
||||
resetForm,
|
||||
validateField,
|
||||
setFieldValue
|
||||
}: FormikContextType<FormPublishData> = useFormikContext()
|
||||
|
||||
const [computeTypeSelected, setComputeTypeSelected] = useState<boolean>(false)
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
setTouched({})
|
||||
|
||||
// setSubmitting(false)
|
||||
}, [setErrors, setTouched])
|
||||
|
||||
const accessTypeOptions = [
|
||||
{
|
||||
name: 'Download',
|
||||
title: 'Download',
|
||||
icon: <Download />
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
title: 'Compute',
|
||||
icon: <Compute />
|
||||
}
|
||||
]
|
||||
|
||||
const computeTypeOptions = ['1 day', '1 week', '1 month', '1 year']
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
// function handleFieldChange(
|
||||
// e: ChangeEvent<HTMLInputElement>,
|
||||
// field: FormFieldProps
|
||||
// ) {
|
||||
// const value =
|
||||
// field.type === 'terms' ? !JSON.parse(e.target.value) : e.target.value
|
||||
|
||||
// if (field.name === 'access' && value === 'Compute') {
|
||||
// setComputeTypeSelected(true)
|
||||
// if (values.timeout === 'Forever')
|
||||
// setFieldValue('timeout', computeTypeOptions[0])
|
||||
// } else {
|
||||
// if (field.name === 'access' && value === 'Download') {
|
||||
// setComputeTypeSelected(false)
|
||||
// }
|
||||
// }
|
||||
|
||||
// validateField(field.name)
|
||||
// setFieldValue(field.name, value)
|
||||
// }
|
||||
|
||||
const resetFormAndClearStorage = (e: FormEvent<Element>) => {
|
||||
e.preventDefault()
|
||||
resetForm({
|
||||
values: initialValues as FormPublishData,
|
||||
status: 'empty'
|
||||
})
|
||||
setStatus('empty')
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
className={styles.form}
|
||||
// do we need this?
|
||||
onChange={() => status === 'empty' && setStatus(null)}
|
||||
>
|
||||
<FormTitle title={content.title} />
|
||||
|
||||
{content.data.map(
|
||||
(field: FormFieldProps) =>
|
||||
field.advanced !== true && (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? accessTypeOptions
|
||||
: field.name === 'timeout' && computeTypeSelected === true
|
||||
? computeTypeOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
// onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
// handleFieldChange(e, field)
|
||||
// }
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||
/>
|
||||
</Form>
|
||||
)
|
||||
}
|
@ -2,6 +2,8 @@ import React, { FormEvent, ReactElement } from 'react'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import styles from './FormActions.module.css'
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import { FormPublishData } from '../_types'
|
||||
|
||||
export default function FormActions({
|
||||
isValid,
|
||||
@ -11,22 +13,22 @@ export default function FormActions({
|
||||
resetFormAndClearStorage: (e: FormEvent<Element>) => void
|
||||
}): ReactElement {
|
||||
const { ocean, account } = useOcean()
|
||||
const { status }: FormikContextType<FormPublishData> = useFormikContext()
|
||||
|
||||
return (
|
||||
<footer className={styles.actions}>
|
||||
<Button
|
||||
style="primary"
|
||||
type="submit"
|
||||
disabled={!ocean || !account || !isValid || status === 'empty'}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
{status !== 'empty' && (
|
||||
<Button style="text" size="small" onClick={resetFormAndClearStorage}>
|
||||
Reset Form
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
style="primary"
|
||||
disabled={!ocean || !account || !isValid || status === 'empty'}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</footer>
|
||||
)
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Coin.module.css'
|
||||
import InputElement from '@shared/Form/Input/InputElement'
|
||||
import Logo from '@images/logo.svg'
|
||||
@ -46,7 +45,7 @@ export default function Coin({
|
||||
{...field}
|
||||
/>
|
||||
{datatokenOptions?.symbol === 'OCEAN' && (
|
||||
<Conversion price={field.value} className={stylesIndex.conversion} />
|
||||
<Conversion price={field.value} />
|
||||
)}
|
||||
<Error meta={meta} />
|
||||
</div>
|
@ -1,7 +1,3 @@
|
||||
.dynamic {
|
||||
composes: content from './index.module.css';
|
||||
}
|
||||
|
||||
.wallet {
|
||||
display: flex;
|
||||
align-items: center;
|
@ -7,27 +7,21 @@ import Wallet from '../../../Header/Wallet'
|
||||
import Coin from './Coin'
|
||||
import styles from './Dynamic.module.css'
|
||||
import Fees from './Fees'
|
||||
import stylesIndex from './index.module.css'
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Price from './Price'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { FormPublishData } from '../../_types'
|
||||
|
||||
export default function Dynamic({
|
||||
ddo,
|
||||
content
|
||||
}: {
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
export default function Dynamic({ content }: { content: any }): ReactElement {
|
||||
const { networkId, balance } = useWeb3()
|
||||
const { account } = useOcean()
|
||||
const [firstPrice, setFirstPrice] = useState<string>()
|
||||
|
||||
// Connect with form
|
||||
const { values }: FormikContextType<PriceOptions> = useFormikContext()
|
||||
const { values }: FormikContextType<FormPublishData> = useFormikContext()
|
||||
const { dataTokenOptions } = values.services[0]
|
||||
|
||||
const {
|
||||
price,
|
||||
@ -36,7 +30,7 @@ export default function Dynamic({
|
||||
swapFee,
|
||||
dtAmount,
|
||||
oceanAmount
|
||||
} = values
|
||||
} = values.pricing
|
||||
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
@ -69,8 +63,8 @@ export default function Dynamic({
|
||||
}, [price, networkId, account, balance])
|
||||
|
||||
return (
|
||||
<div className={styles.dynamic}>
|
||||
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
|
||||
<>
|
||||
<FormHelp>{content.info}</FormHelp>
|
||||
|
||||
<aside className={styles.wallet}>
|
||||
{balance?.ocean && (
|
||||
@ -88,7 +82,7 @@ export default function Dynamic({
|
||||
Price <Tooltip content={content.tooltips.poolInfo} />
|
||||
</h4>
|
||||
|
||||
<Price ddo={ddo} firstPrice={firstPrice} />
|
||||
<Price firstPrice={firstPrice} />
|
||||
|
||||
<h4 className={styles.title}>
|
||||
Datatoken Liquidity Pool <Tooltip content={content.tooltips.poolInfo} />
|
||||
@ -103,8 +97,8 @@ export default function Dynamic({
|
||||
<Coin
|
||||
name="dtAmount"
|
||||
datatokenOptions={{
|
||||
symbol: ddo.dataTokenInfo.symbol,
|
||||
name: ddo.dataTokenInfo.name
|
||||
symbol: dataTokenOptions.symbol,
|
||||
name: dataTokenOptions.name
|
||||
}}
|
||||
weight={`${Number(weightOnDataToken) * 10}%`}
|
||||
readOnly
|
||||
@ -123,6 +117,6 @@ export default function Dynamic({
|
||||
<Alert text={error} state="error" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
14
src/components/Publish/FormPublish/Pricing/Fixed.tsx
Normal file
14
src/components/Publish/FormPublish/Pricing/Fixed.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import FormHelp from '@shared/Form/Input/Help'
|
||||
import Price from './Price'
|
||||
import Fees from './Fees'
|
||||
|
||||
export default function Fixed({ content }: { content: any }): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<FormHelp>{content.info}</FormHelp>
|
||||
<Price />
|
||||
<Fees tooltips={content.tooltips} pricingType="fixed" />
|
||||
</>
|
||||
)
|
||||
}
|
12
src/components/Publish/FormPublish/Pricing/Free.tsx
Normal file
12
src/components/Publish/FormPublish/Pricing/Free.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import FormHelp from '@shared/Form/Input/Help'
|
||||
import Price from './Price'
|
||||
|
||||
export default function Free({ content }: { content: any }): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<FormHelp>{content.info}</FormHelp>
|
||||
<Price free />
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,40 +1,23 @@
|
||||
import Conversion from '@shared/Price/Conversion'
|
||||
import { useField } from 'formik'
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Input from '@shared/Form/Input'
|
||||
import Error from './Error'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import usePricing from '@hooks/usePricing'
|
||||
import styles from './Price.module.css'
|
||||
import { FormPublishData } from '../../_types'
|
||||
|
||||
export default function Price({
|
||||
ddo,
|
||||
firstPrice,
|
||||
free
|
||||
}: {
|
||||
ddo: DDO
|
||||
firstPrice?: string
|
||||
free?: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField('price')
|
||||
const { getDTName, getDTSymbol } = usePricing()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
const [dtName, setDtName] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
async function setDatatokenSymbol(ddo: DDO) {
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
setDtSymbol(dtSymbol)
|
||||
}
|
||||
async function setDatatokenName(ddo: DDO) {
|
||||
const dtName = await getDTName(ddo)
|
||||
setDtName(dtName)
|
||||
}
|
||||
setDatatokenSymbol(ddo)
|
||||
setDatatokenName(ddo)
|
||||
}, [])
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
const { dataTokenOptions } = values.services[0]
|
||||
|
||||
return (
|
||||
<div className={styles.price}>
|
||||
@ -62,7 +45,7 @@ export default function Price({
|
||||
</div>
|
||||
<div className={styles.datatoken}>
|
||||
<h4>
|
||||
= <strong>1</strong> {dtSymbol}{' '}
|
||||
= <strong>1</strong> {dataTokenOptions.symbol}{' '}
|
||||
<Conversion price={field.value} className={styles.conversion} />
|
||||
</h4>
|
||||
</div>
|
152
src/components/Publish/FormPublish/Pricing/index.tsx
Normal file
152
src/components/Publish/FormPublish/Pricing/index.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import Tabs from '@shared/atoms/Tabs'
|
||||
import { isValidNumber } from '@utils/numbers'
|
||||
import Decimal from 'decimal.js'
|
||||
import { FormPublishData } from '../../_types'
|
||||
import Dynamic from './Dynamic'
|
||||
import Fixed from './Fixed'
|
||||
import Free from './Free'
|
||||
|
||||
const query = graphql`
|
||||
query PricingQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childContentJson {
|
||||
create {
|
||||
empty {
|
||||
title
|
||||
info
|
||||
action {
|
||||
name
|
||||
help
|
||||
}
|
||||
}
|
||||
fixed {
|
||||
title
|
||||
info
|
||||
tooltips {
|
||||
communityFee
|
||||
marketplaceFee
|
||||
}
|
||||
}
|
||||
dynamic {
|
||||
title
|
||||
info
|
||||
tooltips {
|
||||
poolInfo
|
||||
swapFee
|
||||
communityFee
|
||||
marketplaceFee
|
||||
}
|
||||
}
|
||||
free {
|
||||
title
|
||||
info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Pricing(): ReactElement {
|
||||
// Get content
|
||||
const data = useStaticQuery(query)
|
||||
const content = data.content.edges[0].node.childContentJson.create
|
||||
|
||||
const { appConfig } = useSiteMetadata()
|
||||
|
||||
// Connect with main publish form
|
||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||
const { pricing } = values
|
||||
const { price, oceanAmount, weightOnOcean, weightOnDataToken, type } = pricing
|
||||
|
||||
// Switch type value upon tab change
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
setFieldValue('pricing.type', type)
|
||||
type === 'fixed' && setFieldValue('pricing.dtAmount', 1000)
|
||||
type === 'free' && price < 1 && setFieldValue('pricing.price', 1)
|
||||
}
|
||||
|
||||
// Always update everything when price value changes
|
||||
useEffect(() => {
|
||||
if (type === 'fixed') return
|
||||
const dtAmount =
|
||||
isValidNumber(oceanAmount) &&
|
||||
isValidNumber(weightOnOcean) &&
|
||||
isValidNumber(price) &&
|
||||
isValidNumber(weightOnDataToken)
|
||||
? new Decimal(oceanAmount)
|
||||
.dividedBy(new Decimal(weightOnOcean))
|
||||
.dividedBy(new Decimal(price))
|
||||
.mul(new Decimal(weightOnDataToken))
|
||||
: 0
|
||||
|
||||
setFieldValue('pricing.dtAmount', dtAmount)
|
||||
}, [price, oceanAmount, weightOnOcean, weightOnDataToken, type])
|
||||
|
||||
const tabs = [
|
||||
appConfig.allowFixedPricing === 'true'
|
||||
? {
|
||||
title: content.fixed.title,
|
||||
content: <Fixed content={content.fixed} />
|
||||
}
|
||||
: undefined,
|
||||
appConfig.allowDynamicPricing === 'true'
|
||||
? {
|
||||
title: content.dynamic.title,
|
||||
content: <Dynamic content={content.dynamic} />
|
||||
}
|
||||
: undefined,
|
||||
appConfig.allowFreePricing === 'true'
|
||||
? {
|
||||
title: content.free.title,
|
||||
content: <Free content={content.free} />
|
||||
}
|
||||
: undefined
|
||||
].filter((tab) => tab !== undefined)
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
items={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={type === 'fixed' ? 0 : 1}
|
||||
/>
|
||||
)
|
||||
|
||||
// async function handleCreatePricing(values: PriceOptions) {
|
||||
// try {
|
||||
// const priceOptions = {
|
||||
// ...values,
|
||||
// // swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||
// swapFee: `${values.swapFee / 100}`
|
||||
// }
|
||||
|
||||
// const tx = await createPricing(priceOptions, ddo)
|
||||
|
||||
// // Pricing failed
|
||||
// if (!tx || pricingError) {
|
||||
// toast.error(pricingError || 'Price creation failed.')
|
||||
// Logger.error(pricingError || 'Price creation failed.')
|
||||
// return
|
||||
// }
|
||||
|
||||
// // Pricing succeeded
|
||||
// setSuccess(
|
||||
// `🎉 Successfully created a ${values.type} price. 🎉 Reload the page to get all updates.`
|
||||
// )
|
||||
// Logger.log(`Transaction: ${tx}`)
|
||||
// } catch (error) {
|
||||
// toast.error(error.message)
|
||||
// Logger.error(error.message)
|
||||
// }
|
||||
// }
|
||||
}
|
4
src/components/Publish/FormPublish/index.module.css
Normal file
4
src/components/Publish/FormPublish/index.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.form {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
224
src/components/Publish/FormPublish/index.tsx
Normal file
224
src/components/Publish/FormPublish/index.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
import React, {
|
||||
ReactElement,
|
||||
useEffect,
|
||||
FormEvent,
|
||||
ChangeEvent,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useStaticQuery, graphql } from 'gatsby'
|
||||
import { useFormikContext, Field, Form, FormikContextType } from 'formik'
|
||||
import Input from '@shared/Form/Input'
|
||||
import { ReactComponent as Download } from '@images/download.svg'
|
||||
import { ReactComponent as Compute } from '@images/compute.svg'
|
||||
import FormActions from './FormActions'
|
||||
import AdvancedSettings from '@shared/Form/FormFields/AdvancedSettings'
|
||||
import { FormPublishData } from '../_types'
|
||||
import styles from './index.module.css'
|
||||
import { initialValues } from '../_constants'
|
||||
import Tabs from '@shared/atoms/Tabs'
|
||||
import Pricing from './Pricing'
|
||||
import Debug from '../Debug'
|
||||
|
||||
const query = graphql`
|
||||
query {
|
||||
content: publishJson {
|
||||
metadata {
|
||||
title
|
||||
fields {
|
||||
name
|
||||
placeholder
|
||||
label
|
||||
help
|
||||
type
|
||||
required
|
||||
options
|
||||
disclaimer
|
||||
disclaimerValues
|
||||
advanced
|
||||
}
|
||||
}
|
||||
services {
|
||||
title
|
||||
fields {
|
||||
name
|
||||
placeholder
|
||||
label
|
||||
help
|
||||
type
|
||||
required
|
||||
options
|
||||
disclaimer
|
||||
disclaimerValues
|
||||
advanced
|
||||
}
|
||||
}
|
||||
pricing {
|
||||
title
|
||||
fields {
|
||||
name
|
||||
placeholder
|
||||
label
|
||||
help
|
||||
type
|
||||
required
|
||||
options
|
||||
disclaimer
|
||||
disclaimerValues
|
||||
advanced
|
||||
}
|
||||
}
|
||||
warning
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const accessTypeOptions = [
|
||||
{
|
||||
name: 'Download',
|
||||
title: 'Download',
|
||||
icon: <Download />
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
title: 'Compute',
|
||||
icon: <Compute />
|
||||
}
|
||||
]
|
||||
|
||||
export default function FormPublish(): ReactElement {
|
||||
const { content } = useStaticQuery(query)
|
||||
|
||||
const {
|
||||
setStatus,
|
||||
isValid,
|
||||
values,
|
||||
setErrors,
|
||||
setTouched,
|
||||
resetForm,
|
||||
validateField,
|
||||
setFieldValue
|
||||
}: FormikContextType<FormPublishData> = useFormikContext()
|
||||
|
||||
const [computeTypeSelected, setComputeTypeSelected] = useState<boolean>(false)
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
setTouched({})
|
||||
|
||||
// setSubmitting(false)
|
||||
}, [setErrors, setTouched])
|
||||
|
||||
const computeTypeOptions = ['1 day', '1 week', '1 month', '1 year']
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
function handleFieldChange(
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
field: FormFieldProps
|
||||
) {
|
||||
const value =
|
||||
field.type === 'terms' ? !JSON.parse(e.target.value) : e.target.value
|
||||
|
||||
if (field.name === 'access' && value === 'Compute') {
|
||||
setComputeTypeSelected(true)
|
||||
if (values.timeout === 'Forever')
|
||||
setFieldValue('timeout', computeTypeOptions[0])
|
||||
} else {
|
||||
if (field.name === 'access' && value === 'Download') {
|
||||
setComputeTypeSelected(false)
|
||||
}
|
||||
}
|
||||
|
||||
validateField(field.name)
|
||||
setFieldValue(field.name, value)
|
||||
}
|
||||
|
||||
const resetFormAndClearStorage = (e: FormEvent<Element>) => {
|
||||
e.preventDefault()
|
||||
resetForm({
|
||||
values: initialValues as FormPublishData,
|
||||
status: 'empty'
|
||||
})
|
||||
setStatus('empty')
|
||||
}
|
||||
|
||||
function getStepContentFields(contentStep: FormStepContent) {
|
||||
return contentStep.fields.map(
|
||||
(field: FormFieldProps) =>
|
||||
field.advanced !== true && (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? accessTypeOptions
|
||||
: field.name === 'timeout' && computeTypeSelected === true
|
||||
? computeTypeOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: content.metadata.title,
|
||||
content: (
|
||||
<>
|
||||
{getStepContentFields(content.metadata)}
|
||||
<AdvancedSettings
|
||||
content={content.metadata}
|
||||
handleFieldChange={handleFieldChange}
|
||||
/>
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: content.services.title,
|
||||
content: (
|
||||
<>
|
||||
{getStepContentFields(content.services)}
|
||||
<AdvancedSettings
|
||||
content={content.services}
|
||||
handleFieldChange={handleFieldChange}
|
||||
/>
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: content.pricing.title,
|
||||
content: (
|
||||
<>
|
||||
<Pricing />
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form className={styles.form}>
|
||||
<Tabs items={tabs} />
|
||||
</Form>
|
||||
<Debug values={values} />
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
.title {
|
||||
font-size: var(--font-size-h4);
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.network {
|
||||
color: var(--font-color-heading);
|
||||
margin-left: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.network svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: -0.25em;
|
||||
fill: var(--color-secondary);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
width: 0.75em;
|
||||
height: 0.75em;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
.feedback {
|
||||
width: 100%;
|
||||
min-height: 20vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import SuccessConfetti from '@shared/SuccessConfetti'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Feedback.module.css'
|
||||
import Button from '@shared/atoms/Button'
|
||||
|
||||
export default function Feedback({
|
||||
success,
|
||||
pricingStepText
|
||||
}: {
|
||||
success: string
|
||||
pricingStepText: string
|
||||
}): ReactElement {
|
||||
const SuccessAction = () => (
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
className={styles.action}
|
||||
onClick={() => window?.location.reload()}
|
||||
>
|
||||
Reload Page
|
||||
</Button>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.feedback}>
|
||||
{success ? (
|
||||
<SuccessConfetti success={success} action={<SuccessAction />} />
|
||||
) : (
|
||||
<Loader message={pricingStepText} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.fixed {
|
||||
composes: content from './index.module.css';
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Fixed.module.css'
|
||||
import FormHelp from '@shared/Form/Input/Help'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Price from './Price'
|
||||
import Fees from './Fees'
|
||||
|
||||
export default function Fixed({
|
||||
ddo,
|
||||
content
|
||||
}: {
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.fixed}>
|
||||
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
|
||||
<Price ddo={ddo} />
|
||||
<Fees tooltips={content.tooltips} pricingType="fixed" />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.free {
|
||||
composes: content from './index.module.css';
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Free.module.css'
|
||||
import FormHelp from '@shared/Form/Input/Help'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Price from './Price'
|
||||
|
||||
export default function Free({
|
||||
ddo,
|
||||
content
|
||||
}: {
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.free}>
|
||||
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
|
||||
<Price ddo={ddo} free />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content label {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.content input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content [class*='error'] {
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.conversion {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: calc(var(--spacer) / 6);
|
||||
}
|
||||
|
||||
.help {
|
||||
text-align: center;
|
||||
margin-bottom: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.actionsHelp {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
padding-left: var(--spacer);
|
||||
padding-right: var(--spacer);
|
||||
}
|
||||
|
||||
.free {
|
||||
text-align: center;
|
||||
margin-bottom: calc(var(--spacer) / 1.5);
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import Tabs from '@shared/atoms/Tabs'
|
||||
import Fixed from './Fixed'
|
||||
import Dynamic from './Dynamic'
|
||||
import Free from './Free'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import FormHelp from '@shared/Form/Input/Help'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
|
||||
import { isValidNumber } from '@utils/numbers'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export default function FormPricing({
|
||||
ddo,
|
||||
setShowPricing,
|
||||
content
|
||||
}: {
|
||||
ddo: DDO
|
||||
setShowPricing: (value: boolean) => void
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
|
||||
// Connect with form
|
||||
const { values, setFieldValue, submitForm } = useFormikContext()
|
||||
const { price, oceanAmount, weightOnOcean, weightOnDataToken, type } =
|
||||
values as PriceOptions
|
||||
|
||||
// Switch type value upon tab change
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
setFieldValue('type', type)
|
||||
type === 'fixed' && setFieldValue('dtAmount', 1000)
|
||||
type === 'free' && price < 1 && setFieldValue('price', 1)
|
||||
}
|
||||
|
||||
// Always update everything when price value changes
|
||||
useEffect(() => {
|
||||
if (type === 'fixed') return
|
||||
const dtAmount =
|
||||
isValidNumber(oceanAmount) &&
|
||||
isValidNumber(weightOnOcean) &&
|
||||
isValidNumber(price) &&
|
||||
isValidNumber(weightOnDataToken)
|
||||
? new Decimal(oceanAmount)
|
||||
.dividedBy(new Decimal(weightOnOcean))
|
||||
.dividedBy(new Decimal(price))
|
||||
.mul(new Decimal(weightOnDataToken))
|
||||
: 0
|
||||
|
||||
setFieldValue('dtAmount', dtAmount)
|
||||
}, [price, oceanAmount, weightOnOcean, weightOnDataToken, type])
|
||||
|
||||
const tabs = [
|
||||
appConfig.allowFixedPricing === 'true'
|
||||
? {
|
||||
title: content.fixed.title,
|
||||
content: <Fixed content={content.fixed} ddo={ddo} />
|
||||
}
|
||||
: undefined,
|
||||
appConfig.allowDynamicPricing === 'true'
|
||||
? {
|
||||
title: content.dynamic.title,
|
||||
content: <Dynamic content={content.dynamic} ddo={ddo} />
|
||||
}
|
||||
: undefined,
|
||||
appConfig.allowFreePricing === 'true'
|
||||
? {
|
||||
title: content.free.title,
|
||||
content: <Free content={content.free} ddo={ddo} />
|
||||
}
|
||||
: undefined
|
||||
].filter((tab) => tab !== undefined)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
items={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={type === 'fixed' ? 0 : 1}
|
||||
/>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Button style="primary" onClick={() => submitForm()}>
|
||||
{content.empty.action.name}
|
||||
</Button>
|
||||
<Button style="text" size="small" onClick={() => setShowPricing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<FormHelp className={styles.actionsHelp}>
|
||||
{content.empty.action.help}
|
||||
</FormHelp>
|
||||
</div>
|
||||
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(values, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import { allowDynamicPricing, allowFixedPricing } from '../../../../app.config'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export const validationSchema: Yup.SchemaOf<PriceOptions> = Yup.object().shape({
|
||||
price: Yup.number()
|
||||
.min(1, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
dtAmount: Yup.number()
|
||||
.min(9, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
oceanAmount: Yup.number()
|
||||
.min(21, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic|free/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
weightOnOcean: Yup.string().required('Required'),
|
||||
swapFee: Yup.number()
|
||||
.min(0.1, (param) => `Must be more or equal to ${param.min}`)
|
||||
.max(10, 'Maximum is 10%')
|
||||
.required('Required')
|
||||
.nullable()
|
||||
})
|
||||
|
||||
export const initialValues: PriceOptions = {
|
||||
price: 1,
|
||||
type:
|
||||
allowDynamicPricing === 'true'
|
||||
? 'dynamic'
|
||||
: allowFixedPricing === 'true'
|
||||
? 'fixed'
|
||||
: 'free',
|
||||
dtAmount: allowDynamicPricing === 'true' ? 9 : 1000,
|
||||
oceanAmount: 21,
|
||||
weightOnOcean: '7', // 70% on OCEAN
|
||||
weightOnDataToken: '3', // 30% on datatoken
|
||||
swapFee: 0.1 // in %
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
.pricing {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
padding: 0;
|
||||
padding-bottom: var(--spacer);
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.pricing [class*='alert'] {
|
||||
margin: var(--spacer);
|
||||
margin-bottom: 0;
|
||||
}
|
16
src/components/Publish/Title.module.css
Normal file
16
src/components/Publish/Title.module.css
Normal file
@ -0,0 +1,16 @@
|
||||
.network {
|
||||
color: var(--font-color-heading);
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.network svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: -0.25em;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
}
|
43
src/components/Publish/Title.tsx
Normal file
43
src/components/Publish/Title.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import NetworkName from '@shared/NetworkName'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import styles from './Title.module.css'
|
||||
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
const query = graphql`
|
||||
query {
|
||||
content: allFile(filter: { relativePath: { eq: "publish/index.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childPublishJson {
|
||||
title
|
||||
tooltipNetwork
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Title(): ReactElement {
|
||||
const data = useStaticQuery(query)
|
||||
const content = data.content.edges[0].node.childPublishJson
|
||||
const { networkId } = useWeb3()
|
||||
|
||||
return (
|
||||
<>
|
||||
{content.title}{' '}
|
||||
{networkId && (
|
||||
<>
|
||||
into <NetworkName networkId={networkId} className={styles.network} />
|
||||
<Tooltip
|
||||
content={content.tooltipNetwork}
|
||||
className={styles.tooltip}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,101 +1,149 @@
|
||||
import { File as FileMetadata } from '@oceanprotocol/lib'
|
||||
import * as Yup from 'yup'
|
||||
import { allowDynamicPricing, allowFixedPricing } from '../../../app.config'
|
||||
import { FormPublishData } from './_types'
|
||||
|
||||
export const initialValues: Partial<FormPublishData> = {
|
||||
metadata: {
|
||||
name: '',
|
||||
author: '',
|
||||
description: '',
|
||||
termsAndConditions: false,
|
||||
tags: ''
|
||||
},
|
||||
services: [
|
||||
{
|
||||
files: '',
|
||||
links: '',
|
||||
dataTokenOptions: { name: '', symbol: '' },
|
||||
timeout: 'Forever',
|
||||
access: '',
|
||||
providerUri: ''
|
||||
}
|
||||
],
|
||||
pricing: {
|
||||
price: 1,
|
||||
type:
|
||||
allowDynamicPricing === 'true'
|
||||
? 'dynamic'
|
||||
: allowFixedPricing === 'true'
|
||||
? 'fixed'
|
||||
: 'free',
|
||||
dtAmount: allowDynamicPricing === 'true' ? 9 : 1000,
|
||||
oceanAmount: 21,
|
||||
weightOnOcean: '7', // 70% on OCEAN
|
||||
weightOnDataToken: '3', // 30% on datatoken
|
||||
swapFee: 0.1 // in %
|
||||
}
|
||||
}
|
||||
|
||||
const validationMetadata = {
|
||||
name: Yup.string()
|
||||
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||
.required('Required'),
|
||||
description: Yup.string().min(10).required('Required'),
|
||||
author: Yup.string().required('Required'),
|
||||
tags: Yup.string().nullable(),
|
||||
termsAndConditions: Yup.boolean().required('Required')
|
||||
}
|
||||
|
||||
const validationService = {
|
||||
files: Yup.array<FileMetadata>()
|
||||
.required('Enter a valid URL and click "ADD FILE"')
|
||||
.nullable(),
|
||||
links: Yup.array<FileMetadata[]>().nullable(),
|
||||
dataTokenOptions: Yup.object()
|
||||
.shape({
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.required('Required'),
|
||||
timeout: Yup.string().required('Required'),
|
||||
access: Yup.string()
|
||||
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
providerUri: Yup.string().url().nullable()
|
||||
}
|
||||
|
||||
const validationPricing = {
|
||||
price: Yup.number()
|
||||
.min(1, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
dtAmount: Yup.number()
|
||||
.min(9, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
oceanAmount: Yup.number()
|
||||
.min(21, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic|free/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
weightOnOcean: Yup.string().required('Required'),
|
||||
swapFee: Yup.number()
|
||||
.min(0.1, (param) => `Must be more or equal to ${param.min}`)
|
||||
.max(10, 'Maximum is 10%')
|
||||
.required('Required')
|
||||
.nullable()
|
||||
}
|
||||
|
||||
export const validationSchema: Yup.SchemaOf<FormPublishData> = Yup.object()
|
||||
.shape({
|
||||
// ---- required fields ----
|
||||
name: Yup.string()
|
||||
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||
.required('Required'),
|
||||
author: Yup.string().required('Required'),
|
||||
dataTokenOptions: Yup.object()
|
||||
.shape({
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.required('Required'),
|
||||
files: Yup.array<FileMetadata>()
|
||||
.required('Enter a valid URL and click "ADD FILE"')
|
||||
.nullable(),
|
||||
description: Yup.string().min(10).required('Required'),
|
||||
timeout: Yup.string().required('Required'),
|
||||
access: Yup.string()
|
||||
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
termsAndConditions: Yup.boolean().required('Required'),
|
||||
// ---- optional fields ----
|
||||
tags: Yup.string().nullable(),
|
||||
links: Yup.array<FileMetadata[]>().nullable(),
|
||||
providerUri: Yup.string().url().nullable()
|
||||
metadata: Yup.object().shape(validationMetadata),
|
||||
services: Yup.array().of(Yup.object().shape(validationService)),
|
||||
pricing: Yup.object().shape(validationPricing)
|
||||
})
|
||||
.defined()
|
||||
|
||||
export const initialValues: Partial<FormPublishData> = {
|
||||
name: '',
|
||||
author: '',
|
||||
dataTokenOptions: {
|
||||
name: '',
|
||||
symbol: ''
|
||||
},
|
||||
files: '',
|
||||
description: '',
|
||||
timeout: 'Forever',
|
||||
access: '',
|
||||
termsAndConditions: false,
|
||||
tags: '',
|
||||
providerUri: ''
|
||||
}
|
||||
// export const validationSchemaAlgo: Yup.SchemaOf<MetadataPublishFormAlgorithm> =
|
||||
// Yup.object()
|
||||
// .shape({
|
||||
// // ---- required fields ----
|
||||
// name: Yup.string()
|
||||
// .min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||
// .required('Required'),
|
||||
// description: Yup.string().min(10).required('Required'),
|
||||
// files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
// timeout: Yup.string().required('Required'),
|
||||
// dataTokenOptions: Yup.object()
|
||||
// .shape({
|
||||
// name: Yup.string(),
|
||||
// symbol: Yup.string()
|
||||
// })
|
||||
// .required('Required'),
|
||||
// dockerImage: Yup.string()
|
||||
// .matches(/node:latest|python:latest|custom image/g, {
|
||||
// excludeEmptyString: true
|
||||
// })
|
||||
// .required('Required'),
|
||||
// image: Yup.string().required('Required'),
|
||||
// containerTag: Yup.string().required('Required'),
|
||||
// entrypoint: Yup.string().required('Required'),
|
||||
// author: Yup.string().required('Required'),
|
||||
// termsAndConditions: Yup.boolean().required('Required'),
|
||||
// // ---- optional fields ----
|
||||
// algorithmPrivacy: Yup.boolean().nullable(),
|
||||
// tags: Yup.string().nullable(),
|
||||
// links: Yup.array<FileMetadata[]>().nullable()
|
||||
// })
|
||||
// .defined()
|
||||
|
||||
export const validationSchemaAlgo: Yup.SchemaOf<MetadataPublishFormAlgorithm> =
|
||||
Yup.object()
|
||||
.shape({
|
||||
// ---- required fields ----
|
||||
name: Yup.string()
|
||||
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||
.required('Required'),
|
||||
description: Yup.string().min(10).required('Required'),
|
||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
timeout: Yup.string().required('Required'),
|
||||
dataTokenOptions: Yup.object()
|
||||
.shape({
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.required('Required'),
|
||||
dockerImage: Yup.string()
|
||||
.matches(/node:latest|python:latest|custom image/g, {
|
||||
excludeEmptyString: true
|
||||
})
|
||||
.required('Required'),
|
||||
image: Yup.string().required('Required'),
|
||||
containerTag: Yup.string().required('Required'),
|
||||
entrypoint: Yup.string().required('Required'),
|
||||
author: Yup.string().required('Required'),
|
||||
termsAndConditions: Yup.boolean().required('Required'),
|
||||
// ---- optional fields ----
|
||||
algorithmPrivacy: Yup.boolean().nullable(),
|
||||
tags: Yup.string().nullable(),
|
||||
links: Yup.array<FileMetadata[]>().nullable()
|
||||
})
|
||||
.defined()
|
||||
|
||||
export const initialValuesAlgo: Partial<MetadataPublishFormAlgorithm> = {
|
||||
name: '',
|
||||
author: '',
|
||||
dataTokenOptions: {
|
||||
name: '',
|
||||
symbol: ''
|
||||
},
|
||||
dockerImage: 'node:latest',
|
||||
image: 'node',
|
||||
containerTag: 'latest',
|
||||
entrypoint: 'node $ALGO',
|
||||
files: '',
|
||||
description: '',
|
||||
algorithmPrivacy: false,
|
||||
termsAndConditions: false,
|
||||
tags: '',
|
||||
timeout: 'Forever',
|
||||
providerUri: ''
|
||||
}
|
||||
// export const initialValuesAlgo: Partial<MetadataPublishFormAlgorithm> = {
|
||||
// name: '',
|
||||
// author: '',
|
||||
// dataTokenOptions: {
|
||||
// name: '',
|
||||
// symbol: ''
|
||||
// },
|
||||
// dockerImage: 'node:latest',
|
||||
// image: 'node',
|
||||
// containerTag: 'latest',
|
||||
// entrypoint: 'node $ALGO',
|
||||
// files: '',
|
||||
// description: '',
|
||||
// algorithmPrivacy: false,
|
||||
// termsAndConditions: false,
|
||||
// tags: '',
|
||||
// timeout: 'Forever',
|
||||
// providerUri: ''
|
||||
// }
|
||||
|
@ -2,9 +2,12 @@ import { DataTokenOptions } from '@hooks/usePublish'
|
||||
import { EditableMetadataLinks } from '@oceanprotocol/lib'
|
||||
|
||||
export interface FormPublishService {
|
||||
files: string | File[]
|
||||
links?: string | EditableMetadataLinks[]
|
||||
timeout: string
|
||||
dataTokenOptions: DataTokenOptions
|
||||
access: 'Download' | 'Compute' | string
|
||||
providerUri?: string
|
||||
}
|
||||
|
||||
export interface FormPublishData {
|
||||
@ -12,12 +15,9 @@ export interface FormPublishData {
|
||||
metadata: {
|
||||
name: string
|
||||
description: string
|
||||
files: string | File[]
|
||||
author: string
|
||||
termsAndConditions: boolean
|
||||
tags?: string
|
||||
links?: string | EditableMetadataLinks[]
|
||||
providerUri?: string
|
||||
}
|
||||
services: FormPublishService[]
|
||||
pricing: PriceOptions
|
||||
|
@ -2,7 +2,6 @@ import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import Permission from '@shared/Permission'
|
||||
import { Formik, FormikState } from 'formik'
|
||||
import { usePublish } from '@hooks/usePublish'
|
||||
import styles from './index.module.css'
|
||||
import { initialValues, validationSchema } from './_constants'
|
||||
// import {
|
||||
// transformPublishFormToMetadata,
|
||||
@ -13,13 +12,17 @@ import { Logger, Metadata } from '@oceanprotocol/lib'
|
||||
import { useAccountPurgatory } from '@hooks/useAccountPurgatory'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { FormPublishData } from './_types'
|
||||
import PageHeader from '@shared/Page/PageHeader'
|
||||
import Title from './Title'
|
||||
import styles from './index.module.css'
|
||||
import FormPublish from './FormPublish'
|
||||
|
||||
const formName = 'ocean-publish-form'
|
||||
|
||||
export default function PublishPage({
|
||||
content
|
||||
}: {
|
||||
content: { warning: string }
|
||||
content: { title: string; description: string; warning: string }
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||
@ -78,21 +81,19 @@ export default function PublishPage({
|
||||
// }
|
||||
// }
|
||||
|
||||
return isInPurgatory && purgatoryData ? null : (
|
||||
<Permission eventType="publish">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
initialStatus="empty"
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
// kick off publishing
|
||||
// await handleSubmit(values, resetForm)
|
||||
}}
|
||||
>
|
||||
{({ values }) => {
|
||||
return <>Hello</>
|
||||
}}
|
||||
</Formik>
|
||||
</Permission>
|
||||
{isInPurgatory && purgatoryData ? null : (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
initialStatus="empty"
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
// kick off publishing
|
||||
// await handleSubmit(values, resetForm)
|
||||
}}
|
||||
>
|
||||
<FormPublish />
|
||||
</Formik>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -15,4 +15,4 @@ export default function PagePublish(): ReactElement {
|
||||
</Page>
|
||||
</OceanProvider>
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user