1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-14 09:14:52 +01:00

Merge pull request #32 from oceanprotocol/feature/publish

Get publish to work
This commit is contained in:
Matthias Kretschmann 2020-07-17 17:20:14 +02:00 committed by GitHub
commit 8ab2e72970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1212 additions and 997 deletions

View File

@ -13,3 +13,4 @@ GATSBY_FACTORY_ADDRESS='0xxxx'
#GATSBY_PROVIDER_URI='https://provider.rinkeby.v3.dev-ocean.com'
#GATSBY_FACTORY_ADDRESS='0xB9d406D24B310A7D821D0b782a36909e8c925471'
#GATSBY_OCEAN_TOKEN_ADDRESS='0x8967BCF84170c91B0d24D4302C2376283b0B3a07'
#GATSBY_MARKET_ADDRESS='xxx'

View File

@ -1,72 +0,0 @@
import { NowRequest, NowResponse } from '@now/node'
import axios, { AxiosResponse } from 'axios'
import { IncomingHttpHeaders } from 'http'
interface ResponseResult {
contentLength?: string
contentType?: string
}
export interface FileResponse {
status: string
message?: string
result?: ResponseResult
}
function successResult(headers: IncomingHttpHeaders): FileResponse {
const contentType =
headers['content-type'] && headers['content-type'].split(';')[0]
let contentLength = headers['content-length'] && headers['content-length']
// sometimes servers send content-range header,
// try to use it if content-length is not present
if (headers['content-range'] && !headers['content-length']) {
const size = headers['content-range'].split('/')[1]
contentLength = size
}
const result: ResponseResult = {
contentLength,
contentType
}
return { status: 'success', result }
}
async function checkUrl(url: string): Promise<FileResponse> {
if (!url) {
return { status: 'error', message: 'missing url' }
}
try {
const response: AxiosResponse = await axios({
method: 'HEAD',
url,
headers: { Range: 'bytes=0-' }
})
const { headers, status } = response
const successResponses =
status.toString().startsWith('2') || status.toString().startsWith('416')
if (!response && !successResponses) {
return { status: 'error', message: 'Unknown Error' }
}
const result = successResult(headers)
return result
} catch (error) {
return { status: 'error', message: error.message }
}
}
export default async (req: NowRequest, res: NowResponse) => {
switch (req.method) {
case 'POST':
res.status(200).json(await checkUrl(req.body.url))
break
default:
res.setHeader('Allow', ['POST'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}

View File

@ -20,5 +20,8 @@ module.exports = {
// Main, Rinkeby, Kovan
// networks: [1, 4, 42],
networks: [4],
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx'
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
marketAddress:
process.env.GATSBY_MARKET_ADDRESS ||
'0x36A7f3383A63279cDaF4DfC0F3ABc07d90252C6b'
}

View File

@ -11,6 +11,13 @@
"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).",
"type": "textarea",
"required": true
},
{
"name": "files",
"label": "Files",
@ -19,28 +26,22 @@
"type": "files",
"required": true
},
{
"name": "description",
"label": "Description",
"help": "Add a thorough description with as much detail as possible.",
"type": "textarea",
"required": true
},
{
"name": "price",
"label": "Price",
"help": "Set your price in Ocean Tokens.",
"type": "price",
"min": 1,
"required": true
},
{
"name": "access",
"label": "Access Type",
"help": "Choose how you want your files to be accessible for the specified price.",
"type": "select",
"options": ["Download", "Compute"],
"required": true
},
{
"name": "cost",
"label": "Price",
"help": "Set your price for accessing this data set in Ocean Tokens.",
"type": "price",
"min": 1,
"required": true
},
{
"name": "author",
"label": "Author",
@ -84,7 +85,7 @@
{
"name": "termsAndConditions",
"label": "Terms & Conditions",
"type": "checkbox",
"type": "terms",
"options": ["I agree to these Terms and Conditions"],
"required": true
}

67
content/pages/terms.md Normal file
View File

@ -0,0 +1,67 @@
---
title: Terms and Conditions
description: Thanks for using our product and services.
---
By using our products and services you are agreeing to the terms. Please read them carefully.
_Latest Revision: February 20, 2020_
## Definitions
- _Users_ mean buyers and sellers of data in the marketplace.
- _Data seller or provider_ means individual or an institution offering to sell the data in the marketplace.
- _Data buyer or recipient_ means individual or an institution offering to buy the data from the marketplace.
- _Marketplace_ means the web-based logistics data marketplace via which users can sell and buy logistics data.
## Your agreement
By using this Marketplace, you agree to be bound by, and to comply with, these Terms and Conditions. If you do not agree to these Terms and Conditions, please do not use it.
## Toxicity
You shall not use the Marketplace to advertise, sell, or exchange any data, products or services relating to illegal or illicit activities, including, without limitation, pornographic products or services, illegal drug products or services, or illegal weapons.
## Data Sharing
The data recipient or buyer will not release data to a third party without prior approval from the data provider or seller. The data recipient will not share, publish, or otherwise release any findings or conclusions derived from analysis of data obtained from the data provider or data seller without prior approval from the data seller. BigChainDB is not responsible and guarantee quality of data provided by the sellers.
## Delivery of the Data
BigchainDB does not host in its premises the data being offered or already offered in the Marketplace by the data provider. If the provider selects IPFS as a medium to host data, then the data is stored in distributed data storage which BigchainDB has control over or the ability to manage.
## Right to Remove Published Metadata
BigchainDB holds the right to remove published data if found in violation of these terms and conditions. We also reserve the right to remove published data that are deemed out of scope of the Marketplace. That means data published by the sellers has to be logistics data and consistent with the data categories mentioned in the Marketplace.
## Confidentiality
The Recipient shall: (a) protect the Confidential Information of the Provider with at least the same degree of care with which it protects its own confidential or proprietary information, but not less than a reasonable degree of care, and (b) instruct its employees and all other parties who are authorized to have access to the Providers Confidential Information of the restrictions contained in this Agreement. Each Recipient shall limit access to the Providers Confidential Information to its own employees, agents, contractors, , and consultants strictly with a "need to know"; provided, however, that such parties have executed an agreement with the Recipient with confidentiality provisions at least as restrictive as those contained herein. The parties hereby undertake to ensure the individual compliance of such employees, agents, contractors, and consultants with the terms hereof and shall be responsible for any actions of such employees, agents, contractors, and consultants. The Recipient shall, as soon as reasonably practical after discovery, report to the Provider any unauthorized use of, disclosure of or access to the Providers Confidential Information, subject to any reasonable restrictions placed on the timing of such notice by a law enforcement or regulatory agency investigating the incident; and take all reasonable measures to prevent any further unauthorized disclosure or access.
## Indemnification
Users of the Marketplace shall defend, indemnify and hold harmless BigChainDB from and against any and all claims, demands, judgments, liability, damages, losses, costs, and expenses, including reasonable attorneys' fees, arising out of or resulting from the Users or its Client's or Third Party Service Provider's misuse or unauthorized use of the Marketplace . In addition, both Data Provider and Data Recipients will hold each other harmless from and against any and all claims, demands, judgements, liability, damages, losses, costs, and expenses as a result of using Providers data.
## Warranty Disclaimer / Limitation of Liability
The Logistics Data Marketplace (referred to as Marketplace from here on) may be subject to transcription and transmission errors, accordingly, the Marketplace is provided on an "as is," "as available" basis. Any use or reliance upon the Marketplace by Users shall be at its own risk. EXCEPT AS SET FORTH IN THIS SECTION, NEITHER BIGCHAINDB NOR THE DATA PROVIDER MAKES ANY WARRANTIES, EXPRESS OR IMPLIED, HEREUNDER WITH RESPECT TO THE SERVICES, DATA, OR THE MEDIA ON WHICH THE DATA IS PROVIDED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF ACCURACY, COMPLETENESS, CURRENTNESS, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. BIGCHAINDB AND THE DATA PROVIDERS AGGREGATE LIABILITY TO DATA RECIPIENT OR ANY THIRD PARTY, WHETHER FOR NEGLIGENCE, BREACH OF WARRANTY, OR ANY OTHER CAUSE OF ACTION, SHALL BE LIMITED TO THE PRICE PAID FOR THE PRODUCT OR SERVICES TO WHICH THE INCIDENT RELATES. IN NO EVENT SHALL BIGCHAINDB OR DATA PROVIDER BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, WHETHER OR NOT FORESEEABLE AND HOWEVER ARISING, INCLUDING BUT NOT LIMITED TO LOST INCOME OR LOST REVENUE, WHETHER BASED IN CONTRACT, TORT OR ANY OTHER THEORY.
## Audit / Non-Compliance
BigchainDB may monitor your use of the Marketplace. BigchainDB reserves the right, in its sole discretion, to immediately suspend your use of the Marketplace in the event of any suspected or actual violation of the terms of this Agreement. In the event an audit reveals that you are not in compliance with the terms and conditions of this Agreement, you shall be responsible for the costs of the audit, as well as any and all damages resulting from such non-compliance including, without limitation, any special, incidental, indirect, or consequential damages whatsoever (including punitive damages and damages for loss of goodwill).
## Regulations
Users shall comply with all Federal and State laws and regulations governing the confidentiality of the information that is the subject of this Agreement.
## Intended Use
BigchainDB reserves the right to review and pre-approve the Users intended use of the Marketplace.
## Force Majeure
BigchainDB shall not be liable for any losses arising out of the delay or interruption of its performance of the Marketplace due to any act of God, act of governmental authority, act of public enemy, war, riot, flood, civil commotion, insurrection, severe weather conditions, or any other cause beyond the reasonable control of the party.
By accessing and using this Marketplace, you accept and agree to be bound by the terms and conditions of this agreement. In addition, when using the Marketplace, you shall be subject to any posted guidelines or rules applicable to using the services. Any participation in this Marketplace will constitute acceptance of this agreement. If you do not agree to abide by the above, please do not use the Marketplace.
These terms and conditions are subject to change.

View File

@ -1,64 +0,0 @@
# Terms and Conditions
Thanks for using our product and services. By using our products and services you are agreeing to the terms. Please read them carefully.
_Latest Revision: February 20, 2020_
## Definitions
“Users” mean buyers and sellers of data in the marketplace.
“Data seller or provider” means individual or an institution offering to sell the data in the marketplace.
“Data buyer or recipient” means individual or an institution offering to buy the data from the marketplace.
“Marketplace” means the web-based logistics data marketplace via which users can sell and buy logistics data.
## Your agreement
By using this Marketplace, you agree to be bound by, and to comply with, these Terms and Conditions. If you do not agree to these Terms and Conditions, please do not use it.
## Toxicity
You shall not use the Marketplace to advertise, sell, or exchange any data, products or services relating to illegal or illicit activities, including, without limitation, pornographic products or services, illegal drug products or services, or illegal weapons.
## Data Sharing
The data recipient or buyer will not release data to a third party without prior approval from the data provider or seller. The data recipient will not share, publish, or otherwise release any findings or conclusions derived from analysis of data obtained from the data provider or data seller without prior approval from the data seller. Neither dexFreight nor BigChainDB is responsible and guarantee quality of data provided by the sellers.
## Delivery of the Data
BigchainDB/dexFreight does not host in its premises the data being offered or already offered in the Marketplace by the data provider. If the provider selects IPFS as a medium to host data, then the data is stored in distributed data storage which neither dexFreight nor BigchainDB has control over or the ability to manage.
## Right to Remove Published Metadata
BigchainDB/dexFreight holds the right to remove published data if found in violation of these terms and conditions. We also reserve the right to remove published data that are deemed out of scope of the Marketplace. That means data published by the sellers has to be logistics data and consistent with the data categories mentioned in the Marketplace.
## Confidentiality
The Recipient shall: (a) protect the Confidential Information of the Provider with at least the same degree of care with which it protects its own confidential or proprietary information, but not less than a reasonable degree of care, and (b) instruct its employees and all other parties who are authorized to have access to the Providers Confidential Information of the restrictions contained in this Agreement. Each Recipient shall limit access to the Providers Confidential Information to its own employees, agents, contractors, , and consultants strictly with a "need to know"; provided, however, that such parties have executed an agreement with the Recipient with confidentiality provisions at least as restrictive as those contained herein. The parties hereby undertake to ensure the individual compliance of such employees, agents, contractors, and consultants with the terms hereof and shall be responsible for any actions of such employees, agents, contractors, and consultants. The Recipient shall, as soon as reasonably practical after discovery, report to the Provider any unauthorized use of, disclosure of or access to the Providers Confidential Information, subject to any reasonable restrictions placed on the timing of such notice by a law enforcement or regulatory agency investigating the incident; and take all reasonable measures to prevent any further unauthorized disclosure or access.
## Indemnification
Users of the Marketplace shall defend, indemnify and hold harmless dexFreight and BigChainDB from and against any and all claims, demands, judgments, liability, damages, losses, costs, and expenses, including reasonable attorneys' fees, arising out of or resulting from the Users or its Client's or Third Party Service Provider's misuse or unauthorized use of the Marketplace . In addition, both Data Provider and Data Recipients will hold each other harmless from and against any and all claims, demands, judgements, liability, damages, losses, costs, and expenses as a result of using Providers data.
## Warranty Disclaimer / Limitation of Liability
The Logistics Data Marketplace (referred to as Marketplace from here on) may be subject to transcription and transmission errors, accordingly, the Marketplace is provided on an "as is," "as available" basis. Any use or reliance upon the Marketplace by Users shall be at its own risk. EXCEPT AS SET FORTH IN THIS SECTION, NEITHER BigchainDB/dexFreight NOR THE DATA PROVIDER MAKES ANY WARRANTIES, EXPRESS OR IMPLIED, HEREUNDER WITH RESPECT TO THE SERVICES, DATA, OR THE MEDIA ON WHICH THE DATA IS PROVIDED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF ACCURACY, COMPLETENESS, CURRENTNESS, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. BigchainDB/dexFreight AND THE DATA PROVIDERS AGGREGATE LIABILITY TO DATA RECIPIENT OR ANY THIRD PARTY, WHETHER FOR NEGLIGENCE, BREACH OF WARRANTY, OR ANY OTHER CAUSE OF ACTION, SHALL BE LIMITED TO THE PRICE PAID FOR THE PRODUCT OR SERVICES TO WHICH THE INCIDENT RELATES. IN NO EVENT SHALL the BigchainDB/dexFreight OR DATA PROVIDER BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, WHETHER OR NOT FORESEEABLE AND HOWEVER ARISING, INCLUDING BUT NOT LIMITED TO LOST INCOME OR LOST REVENUE, WHETHER BASED IN CONTRACT, TORT OR ANY OTHER THEORY.
## Audit / Non-Compliance
BigchainDB/dexFreight may monitor your use of the Marketplace. BigchainDB/dexFreight reserves the right, in its sole discretion, to immediately suspend your use of the Marketplace in the event of any suspected or actual violation of the terms of this Agreement. In the event an audit reveals that you are not in compliance with the terms and conditions of this Agreement, you shall be responsible for the costs of the audit, as well as any and all damages resulting from such non-compliance including, without limitation, any special, incidental, indirect, or consequential damages whatsoever (including punitive damages and damages for loss of goodwill).
## Regulations
Users shall comply with all Federal and State laws and regulations governing the confidentiality of the information that is the subject of this Agreement.
## Intended Use
BigchainDB/dexFreight reserves the right to review and pre-approve the Users intended use of the Marketplace.
## Force Majeure
BigchainDB/dexFreight shall not be liable for any losses arising out of the delay or interruption of its performance of the Marketplace due to any act of God, act of governmental authority, act of public enemy, war, riot, flood, civil commotion, insurrection, severe weather conditions, or any other cause beyond the reasonable control of the party.
By accessing and using this Marketplace, you accept and agree to be bound by the terms and conditions of this agreement. In addition, when using the Marketplace, you shall be subject to any posted guidelines or rules applicable to using the services. Any participation in this Marketplace will constitute acceptance of this agreement. If you do not agree to abide by the above, please do not use the Marketplace.
These terms and conditions are subject to change.

View File

@ -1,12 +1,12 @@
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
// 'fs' fix for squid.js
fs: 'empty'
},
// fix for 'got'/'swarm-js' dependency
externals: ['got']
})
const createFields = require('./gatsby/createFields')
const createMarkdownPages = require('./gatsby/createMarkdownPages')
exports.onCreateNode = ({ node, actions, getNode }) => {
createFields(node, actions, getNode)
}
exports.createPages = async ({ graphql, actions }) => {
await createMarkdownPages(graphql, actions)
}
exports.onCreatePage = async ({ page, actions }) => {
@ -22,3 +22,14 @@ exports.onCreatePage = async ({ page, actions }) => {
createPage(page)
}
}
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
// 'fs' fix for squid.js
fs: 'empty'
},
// fix for 'got'/'swarm-js' dependency
externals: ['got']
})
}

27
gatsby/createFields.js Normal file
View File

@ -0,0 +1,27 @@
const { createFilePath } = require('gatsby-source-filesystem')
function createMarkdownFields(node, actions, getNode) {
const { createNodeField } = actions
// Automatically create slugs for specific node types,
// relative to ./content/pages/
const { type } = node.internal
if (type === 'MarkdownRemark') {
// Create a slug from the file path & name
const slug = createFilePath({
node,
getNode,
basePath: 'pages/',
trailingSlash: false
})
createNodeField({
name: 'slug',
node,
value: slug
})
}
}
module.exports = createMarkdownFields

View File

@ -0,0 +1,42 @@
const path = require('path')
async function createMarkdownPages(graphql, actions) {
const { createPage } = actions
const markdownPageTemplate = path.resolve(
'./src/components/templates/PageMarkdown.tsx'
)
// Grab all markdown files with a frontmatter title defined
const markdownResult = await graphql(`
{
allMarkdownRemark(filter: { frontmatter: { title: { ne: "" } } }) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
if (markdownResult.errors) {
throw markdownResult.errors
}
// Create markdown pages.
const markdownPages = markdownResult.data.allMarkdownRemark.edges
markdownPages.forEach((page) => {
createPage({
path: page.node.fields.slug,
component: markdownPageTemplate,
context: {
slug: page.node.fields.slug
}
})
})
}
module.exports = createMarkdownPages

1180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
"@loadable/component": "^5.13.1",
"@now/node": "^1.7.2",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/react": "^0.0.13",
"@oceanprotocol/react": "^0.0.15",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.1.0",
@ -34,6 +34,7 @@
"ethereum-blockies": "github:MyEtherWallet/blockies",
"filesize": "^6.1.0",
"formik": "^2.1.5",
"formik-persist": "^1.1.0",
"gatsby": "^2.24.3",
"gatsby-image": "^2.4.13",
"gatsby-plugin-manifest": "^2.4.18",
@ -72,9 +73,9 @@
"devDependencies": {
"@babel/core": "^7.10.5",
"@babel/preset-typescript": "^7.10.1",
"@storybook/addon-actions": "^6.0.0-rc.5",
"@storybook/addon-storyshots": "^6.0.0-rc.5",
"@storybook/react": "^6.0.0-rc.5",
"@storybook/addon-actions": "^6.0.0-rc.8",
"@storybook/addon-storyshots": "^6.0.0-rc.8",
"@storybook/react": "^6.0.0-rc.8",
"@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.11.1",
"@testing-library/react": "^10.4.7",
@ -104,7 +105,7 @@
"prettier": "^2.0.5",
"serve": "^11.3.2",
"source-map-explorer": "^2.4.2",
"typescript": "^3.9.6"
"typescript": "^3.9.7"
},
"repository": {
"type": "git",

View File

@ -22,8 +22,8 @@ export interface MetadataPublishForm {
files: string | File[]
author: string
license: string
price: string
access: string
cost: string
access: 'Download' | 'Compute' | string
termsAndConditions: boolean
// ---- optional fields ----
copyrightHolder?: string

View File

@ -1,4 +1,5 @@
.file {
font-size: var(--font-size-small);
background: var(--color-secondary);
background-size: 100%;
padding: var(--spacer) calc(var(--spacer) / 2);
@ -10,7 +11,6 @@
}
.file li {
font-size: var(--font-size-small);
color: var(--brand-white);
}
@ -18,3 +18,9 @@
font-size: var(--font-size-mini);
opacity: 0.75;
}
.file.small {
font-size: var(--font-size-mini);
height: 5.75rem;
width: 4.5rem;
}

View File

@ -1,14 +1,31 @@
import React, { ReactElement } from 'react'
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
import filesize from 'filesize'
import classNames from 'classnames/bind'
import cleanupContentType from '../../utils/cleanupContentType'
import styles from './File.module.css'
export default function File({ file }: { file: FileMetadata }): ReactElement {
const cx = classNames.bind(styles)
export default function File({
file,
className,
small
}: {
file: FileMetadata
className?: string
small?: boolean
}): ReactElement {
if (!file) return null
const styleClasses = cx({
file: true,
small: small,
[className]: className
})
return (
<ul className={styles.file}>
<ul className={styleClasses}>
{file.contentType || file.contentLength ? (
<>
<li>{cleanupContentType(file.contentType)}</li>

View File

@ -1,8 +1,9 @@
import React from 'react'
import React, { ReactElement } from 'react'
import styles from './Help.module.css'
import Markdown from '../Markdown'
const FormHelp = ({ children }: { children: string }) => (
<div className={styles.help}>{children}</div>
const FormHelp = ({ children }: { children: string }): ReactElement => (
<Markdown className={styles.help} text={children} />
)
export default FormHelp

View File

@ -2,7 +2,8 @@ import React, { ReactElement } from 'react'
import slugify from '@sindresorhus/slugify'
import styles from './InputElement.module.css'
import { InputProps } from '.'
import FilesInput from '../../molecules/FilesInput'
import FilesInput from '../../molecules/FormFields/FilesInput'
import Terms from '../../molecules/FormFields/Terms'
export default function InputElement(props: InputProps): ReactElement {
const { type, options, rows, name } = props
@ -55,6 +56,8 @@ export default function InputElement(props: InputProps): ReactElement {
)
case 'files':
return <FilesInput name={name} {...props} />
case 'terms':
return <Terms name={name} {...props} />
default:
return (
<input

View File

@ -3,6 +3,11 @@
position: relative;
}
.field[data-is-submitting] {
pointer-events: none;
opacity: 0.4;
}
.field .field {
margin-bottom: calc(var(--spacer) / 2);
}

View File

@ -3,7 +3,8 @@ import InputElement from './InputElement'
import Help from './Help'
import Label from './Label'
import styles from './index.module.css'
import { ErrorMessage } from 'formik'
import { ErrorMessage, FormikState, FieldProps, FieldInputProps } from 'formik'
import { MetadataPublishForm } from '../../../@types/Metadata'
export interface InputProps {
name: string
@ -28,19 +29,18 @@ export interface InputProps {
pattern?: string
min?: string
disabled?: boolean
field?: {
name: string
value: string
onChange: () => void
onBlur: () => void
}
field?: any
form?: any
}
export default function Input(props: Partial<InputProps>): ReactElement {
const { required, name, label, help, additionalComponent, field } = props
return (
<div className={styles.field}>
<div
className={styles.field}
data-is-submitting={props.form && props.form.isSubmitting ? true : null}
>
<Label htmlFor={name} required={required}>
{label}
</Label>

View File

@ -1,3 +0,0 @@
.input {
composes: input from '../../atoms/Input/InputElement.module.css';
}

View File

@ -1,7 +1,7 @@
import React, { ReactElement } from 'react'
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
import { prettySize } from '../../../utils'
import cleanupContentType from '../../../utils/cleanupContentType'
import { prettySize } from '../../../../utils'
import cleanupContentType from '../../../../utils/cleanupContentType'
import styles from './Info.module.css'
export default function FileInfo({
@ -9,7 +9,7 @@ export default function FileInfo({
removeItem
}: {
file: FileMetadata
removeItem(): void
removeItem?(): void
}): ReactElement {
return (
<div className={styles.info}>
@ -19,9 +19,11 @@ export default function FileInfo({
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
</ul>
<button className={styles.removeButton} onClick={() => removeItem()}>
&times;
</button>
{removeItem && (
<button className={styles.removeButton} onClick={() => removeItem()}>
&times;
</button>
)}
</div>
)
}

View File

@ -0,0 +1,3 @@
.input {
composes: input from '../../../atoms/Input/InputElement.module.css';
}

View File

@ -1,12 +1,11 @@
import React, { ReactElement } from 'react'
import isUrl from 'is-url-superb'
import Button from '../../atoms/Button'
import Button from '../../../atoms/Button'
import { useField } from 'formik'
import Loader from '../../atoms/Loader'
import InputElement from '../../atoms/Input/InputElement'
import { InputProps } from '../../atoms/Input'
import Loader from '../../../atoms/Loader'
import { InputProps } from '../../../atoms/Input'
import styles from './Input.module.css'
import InputGroup from '../../atoms/Input/InputGroup'
import InputGroup from '../../../atoms/Input/InputGroup'
export default function FileInput({
handleButtonClick,

View File

@ -3,8 +3,8 @@ import { useField } from 'formik'
import { toast } from 'react-toastify'
import FileInfo from './Info'
import FileInput from './Input'
import { getFileInfo } from '../../../utils'
import { InputProps } from '../../atoms/Input'
import { getFileInfo } from '../../../../utils'
import { InputProps } from '../../../atoms/Input'
interface Values {
url: string

View File

@ -0,0 +1,25 @@
.terms {
composes: content from '../../templates/PageMarkdown.module.css';
padding: calc(var(--spacer) / 2);
border: 1px solid var(--brand-grey-lighter);
background-color: var(--brand-grey-dimmed);
border-radius: var(--border-radius);
margin-bottom: calc(var(--spacer) / 2);
font-size: var(--font-size-small);
max-height: 250px;
/* smooth overflow scrolling for pre-iOS 13 */
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.terms h1 {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 2);
}
.terms h2 {
font-size: var(--font-size-small);
margin-bottom: calc(var(--spacer) / 2);
}

View File

@ -0,0 +1,27 @@
import React, { ReactElement } from 'react'
import { InputProps } from '../../atoms/Input'
import InputElement from '../../atoms/Input/InputElement'
import styles from './Terms.module.css'
import { graphql, useStaticQuery } from 'gatsby'
const query = graphql`
query TermsQuery {
terms: markdownRemark(fields: { slug: { eq: "/terms" } }) {
html
}
}
`
export default function Terms(props: InputProps): ReactElement {
const data = useStaticQuery(query)
return (
<>
<div
className={styles.terms}
dangerouslySetInnerHTML={{ __html: data.terms.html }}
/>
<InputElement {...props} type="checkbox" />
</>
)
}

View File

@ -0,0 +1,31 @@
.preview {
font-size: var(--font-size-small);
margin-bottom: var(--spacer);
}
.preview header {
margin-bottom: var(--spacer);
}
.metaFull {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
}
.preview h2 {
font-size: var(--font-size-h3);
margin-bottom: calc(var(--spacer) / 2);
}
.preview h3 {
margin-bottom: calc(var(--spacer) / 12);
}
.preview [class*='MetaItem-module--metaItem'] {
margin-bottom: calc(var(--spacer) / 2);
}
.file {
margin-bottom: calc(var(--spacer) / 2);
}

View File

@ -0,0 +1,50 @@
import React, { ReactElement } from 'react'
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
import Markdown from '../../atoms/Markdown'
import Tags from '../../atoms/Tags'
import MetaItem from '../../organisms/AssetContent/MetaItem'
import styles from './Preview.module.css'
import File from '../../atoms/File'
import { MetadataPublishForm } from '../../../@types/Metadata'
export default function Preview({
values
}: {
values: MetadataPublishForm
}): ReactElement {
return (
<div className={styles.preview}>
<header>
{values.name && <h2>{values.name}</h2>}
{values.description && <Markdown text={values.description} />}
{values.files && values.files.length && (
<File
file={values.files[0] as FileMetadata}
className={styles.file}
small
/>
)}
{values.tags && <Tags items={values.tags.split(',')} />}
</header>
<div className={styles.metaFull}>
{Object.entries(values)
.filter(
([key, value]) =>
!(
key.includes('name') ||
key.includes('description') ||
key.includes('tags') ||
key.includes('files') ||
key.includes('termsAndConditions') ||
value === undefined ||
value === ''
)
)
.map(([key, value]) => (
<MetaItem key={key} title={key} content={value} />
))}
</div>
</div>
)
}

View File

@ -1,52 +1,12 @@
import React, { ReactElement } from 'react'
import * as Yup from 'yup'
import { toStringNoMS } from '../../../utils'
import { toast } from 'react-toastify'
import styles from './PublishForm.module.css'
import { useOcean } from '@oceanprotocol/react'
import {
Service,
ServiceCompute
} from '@oceanprotocol/lib/dist/node/ddo/interfaces/Service'
import { Formik, Form as FormFormik, Field } from 'formik'
import { useOcean, usePublish } from '@oceanprotocol/react'
import { useFormikContext, Form, Field } from 'formik'
import Input from '../../atoms/Input'
import Button from '../../atoms/Button'
import { transformPublishFormToMetadata } from './utils'
import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetadataPublishForm } from '../../../@types/Metadata'
import AssetModel from '../../../models/Asset'
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
const validationSchema = Yup.object().shape<MetadataPublishForm>({
// ---- required fields ----
name: Yup.string().required('Required'),
author: Yup.string().required('Required'),
price: Yup.string().required('Required'),
files: Yup.array<FileMetadata>().required('Required').nullable(),
description: Yup.string().required('Required'),
license: Yup.string().required('Required'),
access: Yup.string().min(4).required('Required'),
termsAndConditions: Yup.boolean().required('Required'),
// ---- optional fields ----
copyrightHolder: Yup.string(),
tags: Yup.string(),
links: Yup.object<FileMetadata[]>()
})
const initialValues: MetadataPublishForm = {
name: undefined,
author: undefined,
price: undefined,
files: undefined,
description: undefined,
license: undefined,
access: undefined,
termsAndConditions: undefined,
copyrightHolder: undefined,
tags: undefined,
links: undefined
}
import { Persist } from 'formik-persist'
import Loader from '../../atoms/Loader'
export default function PublishForm({
content
@ -54,94 +14,30 @@ export default function PublishForm({
content: FormContent
}): ReactElement {
const { ocean, account } = useOcean()
async function handleSubmit(values: MetadataPublishForm) {
const submittingToast = toast.info('submitting asset')
console.log(`
Collected form values:
----------------------
${values}
`)
const metadata = transformPublishFormToMetadata(values)
console.log(`
Transformed metadata values:
----------------------
${metadata}
`)
// if services array stays empty, the default access service
// will be created by squid-js
// let services: Service[] = []
// if (metadata.additionalInformation.access === 'Compute') {
// const computeService: ServiceCompute = await ocean.compute.createComputeServiceAttributes(
// account,
// metadata.main.price,
// // Note: a hack without consequences.
// // Will make metadata.main.datePublished (automatically created by MetadataStore)
// // go out of sync with this service.main.datePublished.
// toStringNoMS(new Date(Date.now()))
// )
// services = [computeService]
// }
// try {
// const asset = await ocean.assets.create(
// (metadata as unknown) as Metadata,
// account,
// services
// )
// // TODO: Reset the form to initial values
// // User feedback and redirect
// toast.success('asset created successfully', {
// className: styles.success
// })
// toast.dismiss(submittingToast)
// // navigate(`/asset/${asset.id}`)
// } catch (e) {
// console.error(e.message)
// }
}
const { publishStepText, isLoading } = usePublish()
const { status, setStatus, isValid } = useFormikContext()
return (
<Formik
initialValues={initialValues}
initialStatus="empty"
validationSchema={validationSchema}
onSubmit={async (values, { setSubmitting }) => {
await handleSubmit(values)
setSubmitting(false)
}}
<Form
className={styles.form}
onChange={() => status === 'empty' && setStatus(null)}
>
{({ isSubmitting, isValid, status, setStatus }) => (
<FormFormik
className={styles.form}
onChange={() => status === 'empty' && setStatus(null)}
>
{content.data.map((field: FormFieldProps) => (
<Field key={field.name} {...field} component={Input} />
))}
{content.data.map((field: FormFieldProps) => (
<Field key={field.name} {...field} component={Input} />
))}
<Button
style="primary"
type="submit"
disabled={
!ocean ||
!account ||
isSubmitting ||
!isValid ||
status === 'empty'
}
>
Submit
</Button>
</FormFormik>
{isLoading ? (
<Loader message={publishStepText} />
) : (
<Button
style="primary"
type="submit"
disabled={!ocean || !account || !isValid || status === 'empty'}
>
Submit
</Button>
)}
</Formik>
<Persist name="ocean-publish-form" />
</Form>
)
}

View File

@ -12,6 +12,6 @@
.sticky {
position: sticky;
top: var(--spacer);
top: calc(var(--spacer) / 2);
}
}

View File

@ -1,22 +1,91 @@
import React, { ReactElement } from 'react'
import PublishForm from './PublishForm'
import { useNavigate } from '@reach/router'
import { toast } from 'react-toastify'
import { Formik } from 'formik'
import { usePublish } from '@oceanprotocol/react'
import styles from './index.module.css'
import PublishForm from './PublishForm'
import Web3Feedback from '../../molecules/Wallet/Feedback'
import { FormContent } from '../../../@types/Form'
import { initialValues, validationSchema } from './validation'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import { MetadataPublishForm } from '../../../@types/Metadata'
import { transformPublishFormToMetadata } from './utils'
import Preview from './Preview'
export default function PublishPage({
content
}: {
content: { form: FormContent }
}): ReactElement {
const { publish, publishError } = usePublish()
const navigate = useNavigate()
const { marketAddress } = useSiteMetadata()
async function handleSubmit(values: MetadataPublishForm): Promise<void> {
console.log(`
Collected form values:
----------------------
${JSON.stringify(values)}
`)
const metadata = transformPublishFormToMetadata(values)
const tokensToMint = '4' // how to know this?
const serviceType = values.access === 'Download' ? 'access' : 'compute'
console.log(`
Transformed metadata values:
----------------------
${JSON.stringify(metadata)}
Cost: 1
Tokens to mint: ${tokensToMint}
`)
try {
const ddo = await publish(metadata as any, tokensToMint, marketAddress, [
{ serviceType, cost: '1' }
])
if (publishError) {
toast.error(publishError)
return null
}
// User feedback and redirect to new asset detail page
ddo && toast.success('Asset created successfully.')
// TODO: reset form state and make sure persistant form in localStorage is cleared
navigate(`/asset/${ddo.id}`)
} catch (error) {
console.error(error.message)
toast.error(error.message)
}
}
return (
<article className={styles.grid}>
<PublishForm content={content.form} />
<aside>
<div className={styles.sticky}>
<Web3Feedback />
</div>
</aside>
<Formik
initialValues={initialValues}
initialStatus="empty"
validationSchema={validationSchema}
onSubmit={async (values, { setSubmitting }) => {
await handleSubmit(values)
setSubmitting(false)
}}
>
{({ values }) => (
<>
<PublishForm content={content.form} />
<aside>
<div className={styles.sticky}>
<Preview values={values} />
<Web3Feedback />
</div>
</aside>
</>
)}
</Formik>
</article>
)
}

View File

@ -1,7 +1,6 @@
import { MetadataMarket, MetadataPublishForm } from '../../../@types/Metadata'
import { toStringNoMS } from '../../../utils'
import AssetModel from '../../../models/Asset'
import web3Utils from 'web3-utils'
export function transformPublishFormToMetadata(
data: MetadataPublishForm
@ -10,7 +9,6 @@ export function transformPublishFormToMetadata(
const {
name,
price,
author,
license,
description,
@ -26,7 +24,6 @@ export function transformPublishFormToMetadata(
main: {
...AssetModel.main,
name,
price: `${web3Utils.toWei(price.toString())}`,
author,
dateCreated: currentTime,
datePublished: currentTime,

View File

@ -0,0 +1,36 @@
import { MetadataPublishForm } from '../../../@types/Metadata'
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
import * as Yup from 'yup'
export const validationSchema = Yup.object().shape<MetadataPublishForm>({
// ---- required fields ----
name: Yup.string().required('Required'),
author: Yup.string().required('Required'),
cost: Yup.string().required('Required'),
files: Yup.array<FileMetadata>().required('Required').nullable(),
description: Yup.string().required('Required'),
license: Yup.string().required('Required'),
access: Yup.string()
.matches(/Compute|Download/g)
.required('Required'),
termsAndConditions: Yup.boolean().required('Required'),
// ---- optional fields ----
copyrightHolder: Yup.string(),
tags: Yup.string(),
links: Yup.object<FileMetadata[]>()
})
export const initialValues: MetadataPublishForm = {
name: undefined,
author: undefined,
cost: undefined,
files: undefined,
description: undefined,
license: undefined,
access: undefined,
termsAndConditions: undefined,
copyrightHolder: undefined,
tags: undefined,
links: undefined
}

View File

@ -0,0 +1,121 @@
.teaser {
margin-bottom: calc(var(--spacer) * 2);
margin-top: -4rem;
border-radius: var(--border-radius);
overflow: hidden;
}
.content {
/* handling long text, like URLs */
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
}
.content table {
overflow-wrap: normal;
word-wrap: normal;
word-break: normal;
}
.content h1,
.content h2 {
font-size: var(--font-size-h3);
}
.content h3 {
font-size: var(--font-size-h4);
}
.content h4 {
font-size: var(--font-size-h5);
}
.content h5 {
font-size: var(--font-size-base);
}
.content ul,
.content ol {
margin: 0;
margin-bottom: var(--spacer);
padding-left: 1.5rem;
}
.content ul {
list-style: none;
}
.content ul li {
position: relative;
display: block;
}
.content ul li:before {
content: '▪';
top: -2px;
position: absolute;
left: -1.5rem;
color: var(--brand-grey-light);
user-select: none;
}
.content li + li {
margin-top: calc(var(--spacer) / 8);
}
.content li ul,
.content li ol,
.content li p {
margin-bottom: 0;
margin-top: calc(var(--spacer) / 8);
}
.content hr {
display: block;
margin: calc(var(--spacer) * 2) auto;
max-width: 20%;
border: 0;
border-top: 2px solid var(--brand-black);
}
.content figure {
margin-bottom: var(--spacer);
}
.content figcaption {
text-align: center;
color: var(--brand-grey-light);
margin-top: calc(var(--spacer) / 4);
font-size: var(--font-size-small);
}
.content blockquote {
font-style: italic;
font-size: var(--font-size-large);
padding-left: calc(var(--spacer) / 2);
position: relative;
margin-top: calc(var(--spacer) * 1.5);
margin-bottom: calc(var(--spacer) * 1.5);
}
.content blockquote::before {
content: '‟';
font-size: 400%;
position: absolute;
left: -3rem;
top: -2rem;
color: var(--brand-grey-light);
}
.content :global(.anchor) {
opacity: 0;
color: var(--brand-grey-light);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
margin-top: 0.2rem;
}
.content :global([id]:hover .anchor) {
opacity: 1;
}

View File

@ -0,0 +1,41 @@
import React, { ReactElement } from 'react'
import { graphql, PageProps } from 'gatsby'
import Layout from '../Layout'
import styles from './PageMarkdown.module.css'
import Container from '../atoms/Container'
export default function PageTemplateMarkdown(props: PageProps): ReactElement {
const { html, frontmatter } = (props.data as any).markdownRemark
const { title, description } = frontmatter
return (
<Layout
title={title}
description={description}
uri={props.uri}
headerCenter
>
<Container narrow>
<div
className={styles.content}
dangerouslySetInnerHTML={{ __html: html }}
/>
</Container>
</Layout>
)
}
export const pageQuery = graphql`
query PageMarkdownBySlug($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
description
}
fields {
slug
}
}
}
`

View File

@ -16,6 +16,7 @@ const query = graphql`
appConfig {
infuraProjectId
networks
marketAddress
oceanConfig {
factoryAddress
metadataStoreUri

View File

@ -9,7 +9,6 @@ const AssetModel: MetadataMarket = {
dateCreated: '',
author: '',
license: '',
price: '0',
files: []
},
additionalInformation: {

View File

@ -54,7 +54,7 @@ export function toStringNoMS(date: Date): string {
export async function getFileInfo(url: string): Promise<FileMetadata> {
const response: AxiosResponse = await axios({
method: 'POST',
url: '/api/file',
url: 'https://fileinfo.oceanprotocol.com',
data: { url }
})
@ -67,12 +67,12 @@ export async function getFileInfo(url: string): Promise<FileMetadata> {
return {
contentLength,
contentType: contentType || '', // need to do that cause squid.js File interface requires contentType
contentType: contentType || '', // need to do that cause lib-js File interface requires contentType
url
}
}
export async function fetchData(url: string): Promise<Axios> {
export async function fetchData(url: string): Promise<any> {
try {
const response = await axios(url)