diff --git a/content/publish/form.json b/content/publish/form.json index 97429af19..316eecde9 100644 --- a/content/publish/form.json +++ b/content/publish/form.json @@ -1,6 +1,6 @@ { "metadata": { - "title": "Enter details", + "title": "Metadata", "fields": [ { "name": "name", @@ -38,7 +38,7 @@ ] }, "services": { - "title": "Create services", + "title": "Access", "fields": [ { "name": "dataTokenOptions", @@ -87,13 +87,12 @@ "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 + "placeholder": "e.g. https://provider.polygon.oceanprotocol.com/" } ] }, "pricing": { - "title": "Create pricing schema", + "title": "Pricing", "fields": [ { "name": "dummy content, as content is defined under 'create' key in ../price.json" diff --git a/src/@types/Form.d.ts b/src/@types/Form.d.ts index 30c10f8fc..0f4407282 100644 --- a/src/@types/Form.d.ts +++ b/src/@types/Form.d.ts @@ -1,29 +1,23 @@ -import { AssetSelectionAsset } from '@shared/Form/FormFields/AssetSelection' - -// declaring into global scope to be able to use this as -// ambiant types despite the above imports -declare global { - interface FormFieldProps { - label: string - name: string - type?: string - options?: string[] | AssetSelectionAsset[] - sortOptions?: boolean - required?: boolean - multiple?: boolean - disabled?: boolean - help?: string - placeholder?: string - pattern?: string - min?: string - disclaimer?: string - disclaimerValues?: string[] - advanced?: boolean - } - - interface FormStepContent { - title: string - description?: string - fields: FormFieldProps[] - } +interface FormFieldContent { + label: string + name: string + type?: string + options?: string[] + sortOptions?: boolean + required?: boolean + multiple?: boolean + disabled?: boolean + help?: string + placeholder?: string + pattern?: string + min?: string + disclaimer?: string + disclaimerValues?: string[] + advanced?: boolean +} + +interface FormStepContent { + title: string + description?: string + fields: FormFieldContent[] } diff --git a/src/@types/aquarius/MetaData.d.ts b/src/@types/aquarius/MetaData.d.ts index 1e017e5b3..af6ec5022 100644 --- a/src/@types/aquarius/MetaData.d.ts +++ b/src/@types/aquarius/MetaData.d.ts @@ -7,6 +7,11 @@ import { // declaring into global scope to be able to use this as // ambiant types despite the above imports declare global { + interface DdoMarket { + metadata: any + services: any[] + } + interface AdditionalInformationMarket extends AdditionalInformation { termsAndConditions: boolean } diff --git a/src/@utils/ddo.ts b/src/@utils/ddo.ts new file mode 100644 index 000000000..ac03cecfa --- /dev/null +++ b/src/@utils/ddo.ts @@ -0,0 +1,97 @@ +import axios from 'axios' +import { toast } from 'react-toastify' +import isUrl from 'is-url-superb' +import slugify from 'slugify' +import { MetadataAlgorithm, Logger } from '@oceanprotocol/lib' + +export function dateToStringNoMS(date: Date): string { + return date.toISOString().replace(/\.[0-9]{3}Z/, 'Z') +} + +export function transformTags(value: string): string[] { + const originalTags = value?.split(',') + const transformedTags = originalTags?.map((tag) => slugify(tag).toLowerCase()) + 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 months = Math.floor((numberOfSeconds %= 31536000) / 2630000) + 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)}` + : months + ? `${months} month${numberEnding(months)}` + : 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 getAlgorithmComponent( + image: string, + containerTag: string, + entrypoint: string, + algorithmLanguage: string +): MetadataAlgorithm { + return { + language: algorithmLanguage, + format: 'docker-image', + version: '0.1', + container: { + entrypoint: entrypoint, + image: image, + tag: containerTag + } + } +} + +export function getAlgorithmFileExtension(fileUrl: string): string { + const splitedFileUrl = fileUrl.split('.') + return splitedFileUrl[splitedFileUrl.length - 1] +} diff --git a/src/@utils/docker.ts b/src/@utils/docker.ts new file mode 100644 index 000000000..edd918c3d --- /dev/null +++ b/src/@utils/docker.ts @@ -0,0 +1,63 @@ +import { Logger } from '@oceanprotocol/lib' +import axios from 'axios' +import isUrl from 'is-url-superb' +import { toast } from 'react-toastify' + +async function isDockerHubImageValid( + image: string, + tag: string +): Promise { + try { + const response = await axios.post( + `https://dockerhub-proxy.oceanprotocol.com`, + { image, tag } + ) + if ( + !response || + response.status !== 200 || + response.data.status !== 'success' + ) { + toast.error( + 'Could not fetch docker hub image info. Please check image name and tag and try again' + ) + return false + } + + return true + } catch (error) { + Logger.error(error.message) + toast.error( + 'Could not fetch docker hub image info. Please check image name and tag and try again' + ) + return false + } +} + +async function is3rdPartyImageValid(imageURL: string): Promise { + try { + const response = await axios.head(imageURL) + if (!response || response.status !== 200) { + toast.error( + 'Could not fetch docker image info. Please check URL and try again' + ) + return false + } + return true + } catch (error) { + Logger.error(error.message) + toast.error( + 'Could not fetch docker image info. Please check URL and try again' + ) + return false + } +} + +export async function validateDockerImage( + dockerImage: string, + tag: string +): Promise { + const isValid = isUrl(dockerImage) + ? await is3rdPartyImageValid(dockerImage) + : await isDockerHubImageValid(dockerImage, tag) + return isValid +} diff --git a/src/@utils/metadata.ts b/src/@utils/metadata.ts deleted file mode 100644 index 4ce2e3adc..000000000 --- a/src/@utils/metadata.ts +++ /dev/null @@ -1,237 +0,0 @@ -import axios from 'axios' -import { toast } from 'react-toastify' -import isUrl from 'is-url-superb' -import slugify from 'slugify' -import { DDO, MetadataAlgorithm, Logger } from '@oceanprotocol/lib' -import { FormPublishData } from '../components/Publish/_types' - -export function dateToStringNoMS(date: Date): string { - return date.toISOString().replace(/\.[0-9]{3}Z/, 'Z') -} - -export function transformTags(value: string): string[] { - const originalTags = value?.split(',') - const transformedTags = originalTags?.map((tag) => slugify(tag).toLowerCase()) - 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 months = Math.floor((numberOfSeconds %= 31536000) / 2630000) - 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)}` - : months - ? `${months} month${numberEnding(months)}` - : 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 -} - -function getAlgorithmComponent( - image: string, - containerTag: string, - entrypoint: string, - algorithmLanguace: string -): MetadataAlgorithm { - return { - language: algorithmLanguace, - format: 'docker-image', - version: '0.1', - container: { - entrypoint: entrypoint, - image: image, - tag: containerTag - } - } -} - -function getAlgorithmFileExtension(fileUrl: string): string { - const splitedFileUrl = fileUrl.split('.') - return splitedFileUrl[splitedFileUrl.length - 1] -} - -// export function transformPublishFormToMetadata( -// { -// name, -// author, -// description, -// tags, -// links, -// termsAndConditions, -// files -// }: Partial, -// ddo?: DDO -// ): MetadataMarket { -// const currentTime = dateToStringNoMS(new Date()) - -// const metadata: MetadataMarket = { -// main: { -// name, -// author, -// dateCreated: ddo ? ddo.created : currentTime, -// datePublished: '', -// files: typeof files !== 'string' && files, -// license: 'https://market.oceanprotocol.com/terms' -// }, -// additionalInformation: { -// description, -// tags: transformTags(tags), -// links: typeof links !== 'string' ? links : [], -// termsAndConditions -// } -// } - -// return metadata -// } - -// async function isDockerHubImageValid( -// image: string, -// tag: string -// ): Promise { -// try { -// const response = await axios.post( -// `https://dockerhub-proxy.oceanprotocol.com`, -// { -// image, -// tag -// } -// ) -// if ( -// !response || -// response.status !== 200 || -// response.data.status !== 'success' -// ) { -// toast.error( -// 'Could not fetch docker hub image info. Please check image name and tag and try again' -// ) -// return false -// } - -// return true -// } catch (error) { -// Logger.error(error.message) -// toast.error( -// 'Could not fetch docker hub image info. Please check image name and tag and try again' -// ) -// return false -// } -// } - -async function is3rdPartyImageValid(imageURL: string): Promise { - try { - const response = await axios.head(imageURL) - if (!response || response.status !== 200) { - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } - return true - } catch (error) { - Logger.error(error.message) - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } -} - -// export async function validateDockerImage( -// dockerImage: string, -// tag: string -// ): Promise { -// const isValid = isUrl(dockerImage) -// ? await is3rdPartyImageValid(dockerImage) -// : await isDockerHubImageValid(dockerImage, tag) -// return isValid -// } - -// export function transformPublishAlgorithmFormToMetadata( -// { -// name, -// author, -// description, -// tags, -// image, -// containerTag, -// entrypoint, -// termsAndConditions, -// files -// }: Partial, -// ddo?: DDO -// ): MetadataMarket { -// const currentTime = dateToStringNoMS(new Date()) -// const fileUrl = typeof files !== 'string' && files[0].url -// const algorithmLanguage = getAlgorithmFileExtension(fileUrl) -// const algorithm = getAlgorithmComponent( -// image, -// containerTag, -// entrypoint, -// algorithmLanguage -// ) -// const metadata: MetadataMarket = { -// main: { -// name, -// type: 'algorithm', -// author, -// dateCreated: ddo ? ddo.created : currentTime, -// files: typeof files !== 'string' && files, -// license: 'https://market.oceanprotocol.com/terms', -// algorithm -// }, -// additionalInformation: { -// description, -// tags: transformTags(tags), -// termsAndConditions -// } -// } - -// return metadata -// } diff --git a/src/components/@shared/Form/FormFields/AdvancedSettings.tsx b/src/components/@shared/Form/FormFields/AdvancedSettings.tsx index be54bdc6e..20eab111d 100644 --- a/src/components/@shared/Form/FormFields/AdvancedSettings.tsx +++ b/src/components/@shared/Form/FormFields/AdvancedSettings.tsx @@ -8,7 +8,7 @@ export default function AdvancedSettings(prop: { content: FormStepContent handleFieldChange: ( e: ChangeEvent, - field: FormFieldProps + field: FormFieldContent ) => void }): ReactElement { const [showAdvancedSettings, setShowAdvancedSettings] = @@ -30,7 +30,7 @@ export default function AdvancedSettings(prop: { {showAdvancedSettings && prop.content.fields.map( - (field: FormFieldProps) => + (field: FormFieldContent) => field.advanced === true && ( handleChange(event)} + // onChange={(event) => handleChange(event)} {...props} disabled={disabled} value={value.name} diff --git a/src/components/@shared/Form/FormFields/Datatoken/index.tsx b/src/components/@shared/Form/FormFields/Datatoken/index.tsx index bb4280f1b..470ac71bf 100644 --- a/src/components/@shared/Form/FormFields/Datatoken/index.tsx +++ b/src/components/@shared/Form/FormFields/Datatoken/index.tsx @@ -15,10 +15,10 @@ export default function Datatoken(props: InputProps): ReactElement { // Generate new DT name & symbol on first mount useEffect(() => { - if (field.value.name !== '') return + if (field.value?.name !== '') return generateName() - }, [field.value.name]) + }, [field.value?.name]) return (
diff --git a/src/components/@shared/Form/FormFields/Terms.tsx b/src/components/@shared/Form/FormFields/Terms.tsx index 58b9ae33a..2a6d437da 100644 --- a/src/components/@shared/Form/FormFields/Terms.tsx +++ b/src/components/@shared/Form/FormFields/Terms.tsx @@ -6,7 +6,7 @@ import styles from './Terms.module.css' export default function Terms(props: InputProps): ReactElement { const termsProps: InputProps = { ...props, - defaultChecked: props.value.toString() === 'true' + defaultChecked: props.value?.toString() === 'true' } return ( diff --git a/src/components/@shared/Form/Input/index.tsx b/src/components/@shared/Form/Input/index.tsx index 0809ee421..1f6f3f761 100644 --- a/src/components/@shared/Form/Input/index.tsx +++ b/src/components/@shared/Form/Input/index.tsx @@ -1,11 +1,4 @@ -import React, { - FormEvent, - ChangeEvent, - ReactElement, - ReactNode, - useEffect, - useState -} from 'react' +import React, { ReactElement, ReactNode, useEffect, useState } from 'react' import InputElement from './InputElement' import Label from './Label' import styles from './index.module.css' @@ -17,8 +10,7 @@ import Markdown from '@shared/Markdown' const cx = classNames.bind(styles) -export interface InputProps { - name: string +export interface InputProps extends FieldInputProps { label?: string | ReactNode placeholder?: string required?: boolean @@ -28,23 +20,7 @@ export interface InputProps { options?: string[] sortOptions?: boolean additionalComponent?: ReactElement - value?: string - onChange?( - e: - | FormEvent - | ChangeEvent - | ChangeEvent - | ChangeEvent - ): void - onKeyPress?( - e: - | React.KeyboardEvent - | React.KeyboardEvent - | React.KeyboardEvent - | React.KeyboardEvent - ): void rows?: number - multiple?: boolean pattern?: string min?: string max?: string @@ -58,7 +34,6 @@ export interface InputProps { defaultChecked?: boolean size?: 'mini' | 'small' | 'large' | 'default' className?: string - checked?: boolean disclaimer?: string disclaimerValues?: string[] } diff --git a/src/components/Asset/AssetContent/AlgorithmDatasetsListForCompute.module.css b/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.module.css similarity index 100% rename from src/components/Asset/AssetContent/AlgorithmDatasetsListForCompute.module.css rename to src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.module.css diff --git a/src/components/Asset/AssetContent/AlgorithmDatasetsListForCompute.tsx b/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx similarity index 100% rename from src/components/Asset/AssetContent/AlgorithmDatasetsListForCompute.tsx rename to src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx diff --git a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx index 91214579c..e7ca97d70 100644 --- a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx @@ -127,7 +127,7 @@ export default function FormStartCompute({ return (
- {content.form.data.map((field: FormFieldProps) => ( + {content.form.data.map((field: FormFieldContent) => ( void }): ReactElement { @@ -64,7 +64,7 @@ export default function FormEditComputeDataset({ return (

{title}

- {data.map((field: FormFieldProps) => ( + {data.map((field: InputProps) => ( // ) { // const timeoutFieldContent = data.filter( @@ -48,7 +48,7 @@ export default function FormEditMetadata({ showPrice, isComputeDataset }: { - data: FormFieldProps[] + data: InputProps[] setShowEdit: (show: boolean) => void setTimeoutStringValue: (value: string) => void values: Partial @@ -65,7 +65,7 @@ export default function FormEditMetadata({ // Workaround for default `validateOnChange` not kicking in function handleFieldChange( e: ChangeEvent, - field: FormFieldProps + field: InputProps ) { validateField(field.name) setFieldValue(field.name, e.target.value) @@ -89,7 +89,7 @@ export default function FormEditMetadata({ return ( {data.map( - (field: FormFieldProps) => + (field: InputProps) => (!showPrice && field.name === 'price') || ( , -// field: FormFieldProps +// field: InputProps // ) { // const value = // field.type === 'checkbox' || field.type === 'terms' @@ -148,7 +148,7 @@ import React, { // // {content.data.map( -// (field: FormFieldProps) => +// (field: InputProps) => // field.advanced !== true && // ((field.name !== 'entrypoint' && // field.name !== 'image' && diff --git a/src/components/Publish/FormPublish/Metadata/index.tsx b/src/components/Publish/FormPublish/Metadata/index.tsx index abb3a6fd9..adda032d9 100644 --- a/src/components/Publish/FormPublish/Metadata/index.tsx +++ b/src/components/Publish/FormPublish/Metadata/index.tsx @@ -2,18 +2,45 @@ import Input from '@shared/Form/Input' import { Field } from 'formik' import React, { ReactElement } from 'react' import content from '../../../../../content/publish/form.json' +import { getFieldContent } from '../../_utils' export default function MetadataFields(): ReactElement { return ( <> - {content.metadata.fields.map((field: FormFieldProps) => ( + + + + + + + {/* {content.metadata.fields.map((field: FormFieldContent) => ( - ))} + ))} */} ) } diff --git a/src/components/Publish/FormPublish/Preview/index.tsx b/src/components/Publish/FormPublish/Preview/index.tsx index bb46ab726..10c23534e 100644 --- a/src/components/Publish/FormPublish/Preview/index.tsx +++ b/src/components/Publish/FormPublish/Preview/index.tsx @@ -5,7 +5,7 @@ import Tags from '@shared/atoms/Tags' import MetaItem from '../../../Asset/AssetContent/MetaItem' import FileIcon from '@shared/FileIcon' import Button from '@shared/atoms/Button' -import { transformTags } from '@utils/metadata' +import { transformTags } from '@utils/ddo' import NetworkName from '@shared/NetworkName' import { useWeb3 } from '@context/Web3' import styles from './MetadataPreview.module.css' diff --git a/src/components/Publish/FormPublish/Services/index.tsx b/src/components/Publish/FormPublish/Services/index.tsx index f4747742e..c10d309d1 100644 --- a/src/components/Publish/FormPublish/Services/index.tsx +++ b/src/components/Publish/FormPublish/Services/index.tsx @@ -4,6 +4,7 @@ import React, { ReactElement } from 'react' import IconDownload from '@images/download.svg' import IconCompute from '@images/compute.svg' import content from '../../../../../content/publish/form.json' +import { getFieldContent } from '../../_utils' const accessTypeOptions = [ { @@ -21,8 +22,45 @@ const accessTypeOptions = [ export default function ServicesFields(): ReactElement { return ( <> - {content.services.fields.map( - (field: FormFieldProps) => + + + + + + + + + {/* {content.services.fields.map( + (field: FormFieldContent) => field.advanced !== true && ( ) - )} + )} */} ) } diff --git a/src/components/Publish/_types.ts b/src/components/Publish/_types.ts index 4a742d262..c0fbbd329 100644 --- a/src/components/Publish/_types.ts +++ b/src/components/Publish/_types.ts @@ -1,5 +1,5 @@ import { DataTokenOptions } from '@hooks/usePublish' -import { EditableMetadataLinks } from '@oceanprotocol/lib' +import { EditableMetadataLinks, File } from '@oceanprotocol/lib' export interface FormPublishService { files: string | File[] @@ -7,6 +7,9 @@ export interface FormPublishService { timeout: string dataTokenOptions: DataTokenOptions access: 'Download' | 'Compute' | string + image?: string + containerTag?: string + entrypoint?: string providerUri?: string } diff --git a/src/components/Publish/_utils.ts b/src/components/Publish/_utils.ts new file mode 100644 index 000000000..de0561318 --- /dev/null +++ b/src/components/Publish/_utils.ts @@ -0,0 +1,56 @@ +import { + dateToStringNoMS, + transformTags, + getAlgorithmComponent, + getAlgorithmFileExtension +} from '@utils/ddo' +import { FormPublishData } from './_types' + +export function getFieldContent( + fieldName: string, + fields: FormFieldContent[] +): FormFieldContent { + return fields.filter((field: FormFieldContent) => field.name === fieldName)[0] +} + +export function transformPublishFormToDdo( + data: Partial, + ddo?: DdoMarket +): DdoMarket { + const currentTime = dateToStringNoMS(new Date()) + const { type } = data + const { name, description, tags, author, termsAndConditions } = data.metadata + const { files, links, image, containerTag, entrypoint, providerUri } = + data.services[0] + + const fileUrl = typeof files !== 'string' && files[0].url + const algorithmLanguage = getAlgorithmFileExtension(fileUrl) + const algorithm = getAlgorithmComponent( + image, + containerTag, + entrypoint, + algorithmLanguage + ) + + const service = { + files: typeof files !== 'string' && files, + links: typeof links !== 'string' ? links : [], + ...(type === 'algorithm' && { ...algorithm }) + } + + const newDdo: DdoMarket = { + metadata: { + name, + description, + tags: transformTags(tags), + author, + dateCreated: ddo ? ddo.metadata.dateCreated : currentTime, + datePublished: '', + termsAndConditions, + license: 'https://market.oceanprotocol.com/terms' + }, + services: [service] + } + + return newDdo +} diff --git a/src/components/Publish/index.tsx b/src/components/Publish/index.tsx index 2e332bbc9..c047da456 100644 --- a/src/components/Publish/index.tsx +++ b/src/components/Publish/index.tsx @@ -2,15 +2,12 @@ import React, { ReactElement, useState, useEffect } from 'react' import { Formik, FormikState } from 'formik' import { usePublish } from '@hooks/usePublish' import { initialValues, validationSchema } from './_constants' -// import { -// transformPublishFormToMetadata, -// mapTimeoutStringToSeconds, -// validateDockerImage -// } from '@utils/metadata' +import { validateDockerImage } from '@utils/docker' import { Logger, Metadata } from '@oceanprotocol/lib' import { useAccountPurgatory } from '@hooks/useAccountPurgatory' import { useWeb3 } from '@context/Web3' import { FormPublishData } from './_types' +import { transformPublishFormToDdo } from './_utils' import PageHeader from '@shared/Page/PageHeader' import Title from './Title' import styles from './index.module.css'