mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Set, edit, and display timeout (#324)
* added timeout to publish asset * add timeout to edit asses(wip) * added timout to edit metadata form * fixed wrong constant name * fix options autosorting Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * Fixed autosorting in edit form * Added "1 day" to timeout options * Changed ternary operators to switch * Feature/asset timeout (#325) * Compute asset timeout * Code styled * Deleted unused import * Display timeout for buy/download * Switch case for timeout values * Moved mapping function to /utils/metadata * display timeout option not matching defined ones, map seconds to string * handle update with no predefined timeout value, add weeks to map method * Display timeout on button * consume button text logic change * whoops, revert wrong change * small millisecondsToStr refactor * copy tweaks * template literal logic restore * keep tweaking help text logic * abstract into method * change whole condition logic * tweak hasDatatoken/hasPreviousOrder combination condition * Unified seconds to string conversion methods * getHelpText tweaks, small refactor * copy editing, limit hardcoded timeout list * fix mixup of map & filter * use Timeout as label and be done with it Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro> Co-authored-by: claudiaHash <49017601+claudiaHash@users.noreply.github.com> Co-authored-by: Matthias Kretschmann <m@kretschmann.io> Co-authored-by: Claudia Holhos <clawww1996@gmail.com>
This commit is contained in:
parent
7b854e09dd
commit
a2fe2fdee0
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
1
src/@types/Form.d.ts
vendored
1
src/@types/Form.d.ts
vendored
@ -3,6 +3,7 @@ export interface FormFieldProps {
|
||||
name: string
|
||||
type?: string
|
||||
options?: string[]
|
||||
sortOptions?: boolean
|
||||
required?: boolean
|
||||
help?: string
|
||||
placeholder?: string
|
||||
|
1
src/@types/MetaData.d.ts
vendored
1
src/@types/MetaData.d.ts
vendored
@ -30,6 +30,7 @@ export interface MetadataPublishForm {
|
||||
description: string
|
||||
files: string | File[]
|
||||
author: string
|
||||
timeout: string
|
||||
dataTokenOptions: DataTokenOptions
|
||||
access: 'Download' | 'Compute' | string
|
||||
termsAndConditions: boolean
|
||||
|
@ -21,6 +21,10 @@
|
||||
composes: help from './index.module.css';
|
||||
}
|
||||
|
||||
.help:not(:empty) {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.feedback {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -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({
|
||||
) : (
|
||||
<>
|
||||
<Button style="primary" onClick={handleConsume} disabled={isDisabled}>
|
||||
{hasDatatoken || hasPreviousOrder ? 'Download' : 'Buy'}
|
||||
{hasPreviousOrder
|
||||
? 'Download'
|
||||
: `Buy ${
|
||||
assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`
|
||||
}`}
|
||||
</Button>
|
||||
{hasDatatoken && (
|
||||
<div className={styles.help}>
|
||||
You own {dtBalance} {ddo.dataTokenInfo.symbol} allowing you to use
|
||||
this data set without paying again.
|
||||
</div>
|
||||
)}
|
||||
{(!hasDatatoken || !hasPreviousOrder) && (
|
||||
<div className={styles.help}>
|
||||
For using this data set, you will buy 1 {ddo.dataTokenInfo.symbol}{' '}
|
||||
and immediately spend it back to the publisher and pool.
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.help}>
|
||||
{getHelpText(
|
||||
{ dtBalance, dtSymbol: ddo.dataTokenInfo.symbol },
|
||||
hasDatatoken,
|
||||
hasPreviousOrder,
|
||||
assetTimeout
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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<MetadataPublishForm>
|
||||
) {
|
||||
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<MetadataPublishForm>
|
||||
}): 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 (
|
||||
<Form className={styles.form}>
|
||||
{data.map((field: FormFieldProps) => (
|
||||
@ -45,7 +89,11 @@ export default function FormEditMetadata({
|
||||
))}
|
||||
|
||||
<footer className={styles.actions}>
|
||||
<Button style="primary" disabled={!ocean || !accountId || !isValid}>
|
||||
<Button
|
||||
style="primary"
|
||||
disabled={!ocean || !accountId || !isValid}
|
||||
onClick={() => setTimeoutStringValue(values.timeout)}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
|
@ -12,6 +12,7 @@ import MetadataPreview from '../../../molecules/MetadataPreview'
|
||||
import Debug from './Debug'
|
||||
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||
import FormEditMetadata from './FormEditMetadata'
|
||||
import { mapTimeoutStringToSeconds } from '../../../../utils/metadata'
|
||||
import styles from './index.module.css'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import MetadataFeedback from '../../../molecules/MetadataFeedback'
|
||||
@ -35,6 +36,7 @@ const contentQuery = graphql`
|
||||
help
|
||||
type
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
rows
|
||||
}
|
||||
@ -59,6 +61,7 @@ export default function Edit({
|
||||
const { metadata, ddo, refreshDdo } = useAsset()
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [timeoutStringValue, setTimeoutStringValue] = useState<string>()
|
||||
|
||||
const hasFeedback = error || success
|
||||
|
||||
@ -68,24 +71,46 @@ export default function Edit({
|
||||
) {
|
||||
try {
|
||||
// Construct new DDO with new values
|
||||
const newDdo = await ocean.assets.editMetadata(ddo, {
|
||||
const ddoEditedMetdata = await ocean.assets.editMetadata(ddo, {
|
||||
title: values.name,
|
||||
description: values.description
|
||||
})
|
||||
|
||||
// Update DDO on-chain
|
||||
const tx = await ocean.assets.updateMetadata(newDdo, accountId)
|
||||
if (!ddoEditedMetdata) {
|
||||
setError(content.form.error)
|
||||
Logger.error(content.form.error)
|
||||
return
|
||||
}
|
||||
let ddoEditedTimeout = ddoEditedMetdata
|
||||
if (timeoutStringValue !== values.timeout) {
|
||||
const service = ddoEditedMetdata.findServiceByType('access')
|
||||
const timeout = mapTimeoutStringToSeconds(values.timeout)
|
||||
ddoEditedTimeout = await ocean.assets.editServiceTimeout(
|
||||
ddoEditedMetdata,
|
||||
service.index,
|
||||
timeout
|
||||
)
|
||||
}
|
||||
|
||||
// Edit failed
|
||||
if (!newDdo || !tx) {
|
||||
if (!ddoEditedTimeout) {
|
||||
setError(content.form.error)
|
||||
Logger.error(content.form.error)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit succeeded
|
||||
setSuccess(content.form.success)
|
||||
resetForm()
|
||||
const storedddo = await ocean.assets.updateMetadata(
|
||||
ddoEditedTimeout,
|
||||
accountId
|
||||
)
|
||||
if (!storedddo) {
|
||||
setError(content.form.error)
|
||||
Logger.error(content.form.error)
|
||||
return
|
||||
} else {
|
||||
// Edit succeeded
|
||||
setSuccess(content.form.success)
|
||||
resetForm()
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
setError(error.message)
|
||||
@ -94,7 +119,10 @@ export default function Edit({
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={getInitialValues(metadata)}
|
||||
initialValues={getInitialValues(
|
||||
metadata,
|
||||
ddo.findServiceByType('access').attributes.main.timeout
|
||||
)}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
// move user's focus to top of screen
|
||||
@ -103,7 +131,7 @@ export default function Edit({
|
||||
await handleSubmit(values, resetForm)
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, values }) =>
|
||||
{({ isSubmitting, values, initialValues }) =>
|
||||
isSubmitting || hasFeedback ? (
|
||||
<MetadataFeedback
|
||||
title="Updating Data Set"
|
||||
@ -125,6 +153,8 @@ export default function Edit({
|
||||
<FormEditMetadata
|
||||
data={content.form.data}
|
||||
setShowEdit={setShowEdit}
|
||||
setTimeoutStringValue={setTimeoutStringValue}
|
||||
values={initialValues}
|
||||
/>
|
||||
|
||||
<aside>
|
||||
|
@ -6,7 +6,10 @@ import FormPublish from './FormPublish'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import { FormContent } from '../../../@types/Form'
|
||||
import { initialValues, validationSchema } from '../../../models/FormPublish'
|
||||
import { transformPublishFormToMetadata } from '../../../utils/metadata'
|
||||
import {
|
||||
transformPublishFormToMetadata,
|
||||
mapTimeoutStringToSeconds
|
||||
} from '../../../utils/metadata'
|
||||
import MetadataPreview from '../../molecules/MetadataPreview'
|
||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
@ -37,6 +40,8 @@ export default function PublishPage({
|
||||
resetForm: () => void
|
||||
): Promise<void> {
|
||||
const metadata = transformPublishFormToMetadata(values)
|
||||
const timeout = mapTimeoutStringToSeconds(values.timeout)
|
||||
|
||||
const serviceType = values.access === 'Download' ? 'access' : 'compute'
|
||||
|
||||
try {
|
||||
@ -50,7 +55,8 @@ export default function PublishPage({
|
||||
const ddo = await publish(
|
||||
(metadata as unknown) as Metadata,
|
||||
serviceType,
|
||||
values.dataTokenOptions
|
||||
values.dataTokenOptions,
|
||||
timeout
|
||||
)
|
||||
|
||||
// Publish failed
|
||||
|
@ -31,6 +31,7 @@
|
||||
--border-color: #e2e2e2;
|
||||
--box-shadow-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
|
||||
--font-family-base: 'Sharp Sans', -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
--font-family-heading: 'Sharp Sans Display', -apple-system, BlinkMacSystemFont,
|
||||
|
@ -1,18 +1,22 @@
|
||||
import { MetadataMarket, MetadataPublishForm } from '../@types/MetaData'
|
||||
import { secondsToString } from '../utils/metadata'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
name: Yup.string()
|
||||
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||
.required('Required'),
|
||||
description: Yup.string().required('Required').min(10)
|
||||
description: Yup.string().required('Required').min(10),
|
||||
timeout: Yup.string().required('Required')
|
||||
})
|
||||
|
||||
export function getInitialValues(
|
||||
metadata: MetadataMarket
|
||||
metadata: MetadataMarket,
|
||||
timeout: number
|
||||
): Partial<MetadataPublishForm> {
|
||||
return {
|
||||
name: metadata.main.name,
|
||||
description: metadata.additionalInformation?.description
|
||||
description: metadata.additionalInformation.description,
|
||||
timeout: secondsToString(timeout)
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ export const validationSchema: Yup.SchemaOf<MetadataPublishForm> = Yup.object()
|
||||
.required('Required'),
|
||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
description: Yup.string().min(10).required('Required'),
|
||||
timeout: Yup.string().required('Required'),
|
||||
access: Yup.string()
|
||||
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
@ -37,6 +38,8 @@ export const initialValues: Partial<MetadataPublishForm> = {
|
||||
},
|
||||
files: '',
|
||||
description: '',
|
||||
timeout: 'Forever',
|
||||
access: '',
|
||||
termsAndConditions: false
|
||||
termsAndConditions: false,
|
||||
tags: ''
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ export const contentQuery = graphql`
|
||||
help
|
||||
type
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
}
|
||||
success
|
||||
|
@ -5,7 +5,7 @@ export default async function checkPreviousOrder(
|
||||
accountId: string,
|
||||
ddo: DDO,
|
||||
serviceType: ServiceType
|
||||
) {
|
||||
): Promise<string> {
|
||||
if (!ocean) return
|
||||
|
||||
const service = ddo.findServiceByType(serviceType)
|
||||
|
@ -10,6 +10,62 @@ export function transformTags(value: string): string[] {
|
||||
return transformedTags
|
||||
}
|
||||
|
||||
export function mapTimeoutStringToSeconds(timeout: string): number {
|
||||
switch (timeout) {
|
||||
case 'Forever':
|
||||
return 0
|
||||
case '1 day':
|
||||
return 86400
|
||||
case '1 week':
|
||||
return 604800
|
||||
case '1 month':
|
||||
return 2630000
|
||||
case '1 year':
|
||||
return 31556952
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function numberEnding(number: number): string {
|
||||
return number > 1 ? 's' : ''
|
||||
}
|
||||
|
||||
export function secondsToString(numberOfSeconds: number): string {
|
||||
if (numberOfSeconds === 0) return 'Forever'
|
||||
|
||||
const years = Math.floor(numberOfSeconds / 31536000)
|
||||
const weeks = Math.floor((numberOfSeconds %= 31536000) / 604800)
|
||||
const days = Math.floor((numberOfSeconds %= 604800) / 86400)
|
||||
const hours = Math.floor((numberOfSeconds %= 86400) / 3600)
|
||||
const minutes = Math.floor((numberOfSeconds %= 3600) / 60)
|
||||
const seconds = numberOfSeconds % 60
|
||||
|
||||
return years
|
||||
? `${years} year${numberEnding(years)}`
|
||||
: weeks
|
||||
? `${weeks} week${numberEnding(weeks)}`
|
||||
: days
|
||||
? `${days} day${numberEnding(days)}`
|
||||
: hours
|
||||
? `${hours} hour${numberEnding(hours)}`
|
||||
: minutes
|
||||
? `${minutes} minute${numberEnding(minutes)}`
|
||||
: seconds
|
||||
? `${seconds} second${numberEnding(seconds)}`
|
||||
: 'less than a second'
|
||||
}
|
||||
|
||||
export function checkIfTimeoutInPredefinedValues(
|
||||
timeout: string,
|
||||
timeoutOptions: string[]
|
||||
): boolean {
|
||||
if (timeoutOptions.indexOf(timeout) > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function transformPublishFormToMetadata(
|
||||
{
|
||||
name,
|
||||
|
@ -8,6 +8,7 @@ const testFormData: MetadataPublishForm = {
|
||||
symbol: ''
|
||||
},
|
||||
name: '',
|
||||
timeout: '',
|
||||
description: 'description',
|
||||
termsAndConditions: true,
|
||||
access: 'Download'
|
||||
|
Loading…
Reference in New Issue
Block a user