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:
commit
8ab2e72970
@ -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'
|
72
api/file.ts
72
api/file.ts
@ -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`)
|
||||
}
|
||||
}
|
@ -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'
|
||||
}
|
||||
|
@ -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
67
content/pages/terms.md
Normal 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 Provider’s Confidential Information of the restrictions contained in this Agreement. Each Recipient shall limit access to the Provider’s 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 Provider’s 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 User’s 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 Provider’s 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 User’s 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.
|
@ -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 Provider’s Confidential Information of the restrictions contained in this Agreement. Each Recipient shall limit access to the Provider’s 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 Provider’s 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 User’s 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 Provider’s 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 User’s 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.
|
@ -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
27
gatsby/createFields.js
Normal 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
|
42
gatsby/createMarkdownPages.js
Normal file
42
gatsby/createMarkdownPages.js
Normal 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
1180
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -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",
|
||||
|
4
src/@types/MetaData.d.ts
vendored
4
src/@types/MetaData.d.ts
vendored
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -3,6 +3,11 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.field[data-is-submitting] {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.field .field {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,3 +0,0 @@
|
||||
.input {
|
||||
composes: input from '../../atoms/Input/InputElement.module.css';
|
||||
}
|
@ -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()}>
|
||||
×
|
||||
</button>
|
||||
{removeItem && (
|
||||
<button className={styles.removeButton} onClick={() => removeItem()}>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.input {
|
||||
composes: input from '../../../atoms/Input/InputElement.module.css';
|
||||
}
|
@ -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,
|
@ -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
|
25
src/components/molecules/FormFields/Terms.module.css
Normal file
25
src/components/molecules/FormFields/Terms.module.css
Normal 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);
|
||||
}
|
27
src/components/molecules/FormFields/Terms.tsx
Normal file
27
src/components/molecules/FormFields/Terms.tsx
Normal 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" />
|
||||
</>
|
||||
)
|
||||
}
|
31
src/components/pages/Publish/Preview.module.css
Normal file
31
src/components/pages/Publish/Preview.module.css
Normal 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);
|
||||
}
|
50
src/components/pages/Publish/Preview.tsx
Normal file
50
src/components/pages/Publish/Preview.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -12,6 +12,6 @@
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: var(--spacer);
|
||||
top: calc(var(--spacer) / 2);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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,
|
||||
|
36
src/components/pages/Publish/validation.ts
Normal file
36
src/components/pages/Publish/validation.ts
Normal 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
|
||||
}
|
121
src/components/templates/PageMarkdown.module.css
Normal file
121
src/components/templates/PageMarkdown.module.css
Normal 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;
|
||||
}
|
41
src/components/templates/PageMarkdown.tsx
Normal file
41
src/components/templates/PageMarkdown.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@ -16,6 +16,7 @@ const query = graphql`
|
||||
appConfig {
|
||||
infuraProjectId
|
||||
networks
|
||||
marketAddress
|
||||
oceanConfig {
|
||||
factoryAddress
|
||||
metadataStoreUri
|
||||
|
@ -9,7 +9,6 @@ const AssetModel: MetadataMarket = {
|
||||
dateCreated: '',
|
||||
author: '',
|
||||
license: '',
|
||||
price: '0',
|
||||
files: []
|
||||
},
|
||||
additionalInformation: {
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user