diff --git a/content/pages/edit.json b/content/pages/edit.json index 989be0e34..0928a3721 100644 --- a/content/pages/edit.json +++ b/content/pages/edit.json @@ -19,6 +19,15 @@ "type": "textarea", "rows": 10, "required": 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 } ] } diff --git a/content/pages/publish.json b/content/pages/publish.json index 27ef135eb..8da56c3a0 100644 --- a/content/pages/publish.json +++ b/content/pages/publish.json @@ -42,6 +42,15 @@ "options": ["Download"], "required": 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", diff --git a/src/@types/Form.d.ts b/src/@types/Form.d.ts index 4a88b4128..107289ca0 100644 --- a/src/@types/Form.d.ts +++ b/src/@types/Form.d.ts @@ -3,6 +3,7 @@ export interface FormFieldProps { name: string type?: string options?: string[] + sortOptions?: boolean required?: boolean help?: string placeholder?: string diff --git a/src/@types/MetaData.d.ts b/src/@types/MetaData.d.ts index 1b4718bfd..6e3a48dea 100644 --- a/src/@types/MetaData.d.ts +++ b/src/@types/MetaData.d.ts @@ -30,6 +30,7 @@ export interface MetadataPublishForm { description: string files: string | File[] author: string + timeout: string dataTokenOptions: DataTokenOptions access: 'Download' | 'Compute' | string termsAndConditions: boolean diff --git a/src/components/organisms/AssetActions/Consume.module.css b/src/components/organisms/AssetActions/Consume.module.css index 56f289c9a..01979ae81 100644 --- a/src/components/organisms/AssetActions/Consume.module.css +++ b/src/components/organisms/AssetActions/Consume.module.css @@ -21,6 +21,10 @@ composes: help from './index.module.css'; } +.help:not(:empty) { + margin-top: calc(var(--spacer) / 2); +} + .feedback { width: 100%; } diff --git a/src/components/organisms/AssetActions/Consume.tsx b/src/components/organisms/AssetActions/Consume.tsx index 3bf67dc6d..42b4b4bdb 100644 --- a/src/components/organisms/AssetActions/Consume.tsx +++ b/src/components/organisms/AssetActions/Consume.tsx @@ -11,6 +11,27 @@ import { useOcean, useConsume, usePricing } from '@oceanprotocol/react' import { useSiteMetadata } from '../../../hooks/useSiteMetadata' import checkPreviousOrder from '../../../utils/checkPreviousOrder' import { useAsset } from '../../../providers/Asset' +import { secondsToString } from '../../../utils/metadata' + +function getHelpText( + token: { + dtBalance: string + dtSymbol: string + }, + hasDatatoken: boolean, + hasPreviousOrder: boolean, + timeout: string +) { + const { dtBalance, dtSymbol } = token + const assetTimeout = timeout === 'Forever' ? '' : ` for ${timeout}` + const text = hasPreviousOrder + ? `You bought this data set already allowing you to download it without paying again${assetTimeout}.` + : hasDatatoken + ? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.` + : `For using this data set, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.` + + return text +} export default function Consume({ ddo, @@ -35,6 +56,12 @@ export default function Consume({ const [isDisabled, setIsDisabled] = useState(true) const [hasDatatoken, setHasDatatoken] = useState(false) const [isConsumable, setIsConsumable] = useState(true) + const [assetTimeout, setAssetTimeout] = useState('') + + useEffect(() => { + const { timeout } = ddo.findServiceByType('access').attributes.main + setAssetTimeout(secondsToString(timeout)) + }, [ddo]) useEffect(() => { if (!price) return @@ -72,6 +99,7 @@ export default function Consume({ if (!ocean || !accountId) return async function checkOrders() { + // HEADS UP! checkPreviousOrder() also checks for expiration of possible set timeout. const orderId = await checkPreviousOrder(ocean, accountId, ddo, 'access') setPreviousOrderId(orderId) setHasPreviousOrder(!!orderId) @@ -104,20 +132,20 @@ export default function Consume({ ) : ( <> - {hasDatatoken && ( -
- You own {dtBalance} {ddo.dataTokenInfo.symbol} allowing you to use - this data set without paying again. -
- )} - {(!hasDatatoken || !hasPreviousOrder) && ( -
- For using this data set, you will buy 1 {ddo.dataTokenInfo.symbol}{' '} - and immediately spend it back to the publisher and pool. -
- )} +
+ {getHelpText( + { dtBalance, dtSymbol: ddo.dataTokenInfo.symbol }, + hasDatatoken, + hasPreviousOrder, + assetTimeout + )} +
)} diff --git a/src/components/organisms/AssetActions/Edit/FormEditMetadata.tsx b/src/components/organisms/AssetActions/Edit/FormEditMetadata.tsx index 342c5276e..238b4cd24 100644 --- a/src/components/organisms/AssetActions/Edit/FormEditMetadata.tsx +++ b/src/components/organisms/AssetActions/Edit/FormEditMetadata.tsx @@ -6,13 +6,52 @@ import Input from '../../../atoms/Input' import { useOcean } from '@oceanprotocol/react' import { FormFieldProps } from '../../../../@types/Form' import { MetadataPublishForm } from '../../../../@types/MetaData' +import { checkIfTimeoutInPredefinedValues } from '../../../../utils/metadata' + +function handleTimeoutCustomOption( + data: FormFieldProps[], + values: Partial +) { + const timeoutFieldContent = data.filter( + (field) => field.name === 'timeout' + )[0] + const timeoutInputIndex = data.findIndex( + (element) => element.name === 'timeout' + ) + if ( + data[timeoutInputIndex].options.length < 6 && + !checkIfTimeoutInPredefinedValues( + values.timeout, + timeoutFieldContent.options + ) + ) { + data[timeoutInputIndex].options.push(values.timeout) + } else if ( + data[timeoutInputIndex].options.length === 6 && + checkIfTimeoutInPredefinedValues( + values.timeout, + timeoutFieldContent.options + ) + ) { + data[timeoutInputIndex].options.pop() + } else if ( + data[timeoutInputIndex].options.length === 6 && + data[timeoutInputIndex].options[5] !== values.timeout + ) { + data[timeoutInputIndex].options[5] = values.timeout + } +} export default function FormEditMetadata({ data, - setShowEdit + setShowEdit, + setTimeoutStringValue, + values }: { data: FormFieldProps[] setShowEdit: (show: boolean) => void + setTimeoutStringValue: (value: string) => void + values: Partial }): ReactElement { const { ocean, accountId } = useOcean() const { @@ -31,6 +70,11 @@ export default function FormEditMetadata({ setFieldValue(field.name, e.target.value) } + // This component is handled by Formik so it's not rendered like a "normal" react component, + // so handleTimeoutCustomOption is called only once. + // https://github.com/oceanprotocol/market/pull/324#discussion_r561132310 + if (data && values) handleTimeoutCustomOption(data, values) + return (
{data.map((field: FormFieldProps) => ( @@ -45,7 +89,11 @@ export default function FormEditMetadata({ ))}