1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-15 01:34:57 +01:00

Merge pull request #28 from oceanprotocol/feature/gatsby

refactor for Gatsby, update all the things
This commit is contained in:
Matthias Kretschmann 2020-07-09 13:59:04 +02:00 committed by GitHub
commit e8ebb88782
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
209 changed files with 23867 additions and 15730 deletions

View File

@ -1,6 +1,7 @@
{ {
"parser": "babel-eslint",
"extends": ["eslint:recommended", "prettier"], "extends": ["eslint:recommended", "prettier"],
"env": { "es6": true, "browser": true, "node": true }, "env": { "es6": true, "browser": true, "node": true, "jest": true },
"settings": { "settings": {
"react": { "react": {
"version": "detect" "version": "detect"
@ -26,6 +27,7 @@
"plugins": ["@typescript-eslint", "prettier"], "plugins": ["@typescript-eslint", "prettier"],
"rules": { "rules": {
"react/prop-types": "off", "react/prop-types": "off",
"react/no-unused-prop-types": "off",
"@typescript-eslint/explicit-function-return-type": "off" "@typescript-eslint/explicit-function-return-type": "off"
} }
} }

2
.github/CODEOWNERS vendored
View File

@ -1 +1 @@
* @maxieprotocol @kremalicious @pfmescher @unjapones * @mihaisc @kremalicious

5
.gitignore vendored
View File

@ -1,11 +1,12 @@
node_modules node_modules
out out
.DS_Store .DS_Store
.next
.idea .idea
.env .env
.env.build
coverage coverage
dist
public
.cache
storybook-static storybook-static
public/storybook public/storybook
.artifacts .artifacts

View File

@ -1,7 +1,3 @@
module.exports = { module.exports = {
stories: [ stories: ['../src/**/*.stories.tsx', '../tests/**/*.stories.tsx']
'../src/components/**/*.stories.tsx',
'../src/styles/**/*.stories.tsx'
],
addons: []
} }

View File

@ -1,20 +1,19 @@
import React from 'react' import React from 'react'
import { addDecorator } from '@storybook/react' import { addDecorator } from '@storybook/react'
import WebFont from 'webfontloader' import {
createHistory,
WebFont.load({ createMemorySource,
google: { LocationProvider
families: ['Montserrat:400,400i,600'] } from '@reach/router'
}
})
// Import global css with custom properties once for all stories. // Import global css with custom properties once for all stories.
// Needed because in Next.js we impoprt that file only once too, import '../src/global/styles.css'
// in src/_app.tsx which does not get loaded by Storybook
import '../src/styles/global.css'
// Wrapper for all stories previews // Wrapper for all stories previews
addDecorator(storyFn => ( const history = createHistory(createMemorySource('/'))
addDecorator((storyFn) => (
<LocationProvider history={history}>
<div <div
style={{ style={{
minHeight: '100vh', minHeight: '100vh',
@ -24,4 +23,21 @@ addDecorator(storyFn => (
> >
{storyFn()} {storyFn()}
</div> </div>
</LocationProvider>
)) ))
// Gatsby's Link overrides:
// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
global.___loader = {
enqueue: () => {},
hovering: () => {}
}
// Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
global.__PATH_PREFIX__ = ''
global.__BASE_PATH__ = ''
// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn't inside a storybook
window.___navigate = (pathname) => {
action('NavigateTo:')(pathname)
}

View File

@ -1,6 +1,8 @@
// https://www.gatsbyjs.org/docs/visual-testing-with-storybook/
// Make CSS modules work // Make CSS modules work
// https://github.com/storybookjs/storybook/issues/4306#issuecomment-517951264 // https://github.com/storybookjs/storybook/issues/4306#issuecomment-517951264
const setCssModulesRule = rule => { const setCssModulesRule = (rule) => {
const nextRule = rule const nextRule = rule
const cssLoader = rule.use[1] const cssLoader = rule.use[1]
@ -15,8 +17,8 @@ const setCssModulesRule = rule => {
return nextRule return nextRule
} }
module.exports = async ({ config, mode }) => { module.exports = ({ config }) => {
const cssRules = config.module.rules.map(rule => { const cssRules = config.module.rules.map((rule) => {
const isCssRule = rule.test.toString().indexOf('css') !== -1 const isCssRule = rule.test.toString().indexOf('css') !== -1
let nextRule = rule let nextRule = rule
@ -28,28 +30,51 @@ module.exports = async ({ config, mode }) => {
config.module.rules = cssRules config.module.rules = cssRules
// Transpile Gatsby module because Gatsby includes un-transpiled ES6 code.
config.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/]
// use installed babel-loader which is v8.0-beta (which is meant to work with @babel/core@7)
config.module.rules[0].use[0].loader = require.resolve('babel-loader')
// use @babel/preset-react for JSX and env (instead of staged presets)
config.module.rules[0].use[0].options.presets = [
require.resolve('@babel/preset-react'),
require.resolve('@babel/preset-env')
]
config.module.rules[0].use[0].options.plugins = [
// use @babel/plugin-proposal-class-properties for class arrow functions
require.resolve('@babel/plugin-proposal-class-properties'),
// use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook
require.resolve('babel-plugin-remove-graphql-queries')
]
// Prefer Gatsby ES6 entrypoint (module) over commonjs (main) entrypoint
config.resolve.mainFields = ['browser', 'module', 'main']
// Handle TypeScript
config.module.rules.push({ config.module.rules.push({
test: /\.(ts|tsx)$/, test: /\.(ts|tsx)$/,
loader: require.resolve('babel-loader'), loader: require.resolve('babel-loader'),
options: { options: {
presets: [['react-app', { flow: false, typescript: true }]] presets: [['react-app', { flow: false, typescript: true }]],
plugins: [
require.resolve('@babel/plugin-proposal-class-properties'),
// use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook
require.resolve('babel-plugin-remove-graphql-queries')
]
} }
}) })
config.resolve.extensions.push('.ts', '.tsx') config.resolve.extensions.push('.ts', '.tsx')
// 'fs' fix for squid.js
config.node = { config.node = {
fs: 'empty' fs: 'empty'
} }
// Handle SVGs // Handle SVGs
// Don't use Storybook's default SVG Configuration // Don't use Storybook's default SVG Configuration
config.module.rules = config.module.rules.map(rule => { config.module.rules = config.module.rules.map((rule) => {
if (rule.test.toString().includes('svg')) { if (rule.test.toString().includes('svg')) {
const test = rule.test const test = rule.test.toString().replace('svg|', '').replace(/\//g, '')
.toString()
.replace('svg|', '')
.replace(/\//g, '')
return { ...rule, test: new RegExp(test) } return { ...rule, test: new RegExp(test) }
} else { } else {
return rule return rule
@ -59,7 +84,19 @@ module.exports = async ({ config, mode }) => {
// Use SVG Configuration for SVGR yourself // Use SVG Configuration for SVGR yourself
config.module.rules.push({ config.module.rules.push({
test: /\.svg$/, test: /\.svg$/,
use: ['@svgr/webpack'] use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: {
removeViewBox: false
}
}
}
},
'url-loader'
]
}) })
return config return config

View File

@ -4,33 +4,16 @@ node_js: node
cache: cache:
npm: true npm: true
directories:
- .next/cache
before_script: before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter # - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter # - chmod +x ./cc-test-reporter
- './cc-test-reporter before-build' # - './cc-test-reporter before-build'
# startup Barge with local Spree network
# - git clone https://github.com/oceanprotocol/barge
# - cd barge
# - export AQUARIUS_VERSION=v1.0.7
# - export BRIZO_VERSION=v0.9.3
# - export KEEPER_VERSION=v0.13.2
# - export EVENTS_HANDLER_VERSION=v0.4.5
# - export KEEPER_OWNER_ROLE_ADDRESS="0xe2DD09d719Da89e5a3D0F2549c7E24566e947260"
# - rm -rf "${HOME}/.ocean/keeper-contracts/artifacts"
# - bash -x start_ocean.sh --no-commons --no-dashboard 2>&1 > start_ocean.log &
# - cd ..
- cp .env.example .env && cp .env.example .env.build
# overwrite AQUARIUS_URI from above .env files, which default to Spree
- export AQUARIUS_URI='https://aquarius.pacific.market.dev-ocean.com'
script: script:
# will run `npm ci` automatically here # will run `npm ci` automatically here
# - ./scripts/keeper.sh - npm run lint
- npm test # - './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
- './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
- npm run build - npm run build
notifications: notifications:

View File

@ -2,8 +2,6 @@
<h1 align="center">Ocean Marketplace</h1> <h1 align="center">Ocean Marketplace</h1>
>
[![Build Status](https://travis-ci.com/oceanprotocol/market.svg?branch=master)](https://travis-ci.com/oceanprotocol/market) [![Build Status](https://travis-ci.com/oceanprotocol/market.svg?branch=master)](https://travis-ci.com/oceanprotocol/market)
[![Now deployment](https://flat.badgen.net/badge/now/auto-deployment/21c4dd?icon=now)](https://zeit.co/oceanprotocol/market) [![Now deployment](https://flat.badgen.net/badge/now/auto-deployment/21c4dd?icon=now)](https://zeit.co/oceanprotocol/market)
[![Maintainability](https://api.codeclimate.com/v1/badges/d114f94f75e6efd2ee71/maintainability)](https://codeclimate.com/repos/5e3933869a31771fd800011c/maintainability) [![Maintainability](https://api.codeclimate.com/v1/badges/d114f94f75e6efd2ee71/maintainability)](https://codeclimate.com/repos/5e3933869a31771fd800011c/maintainability)
@ -15,6 +13,7 @@
- [🤓 Resources](#-resources) - [🤓 Resources](#-resources)
- [🏄 Get Started](#-get-started) - [🏄 Get Started](#-get-started)
- [Local Spree components with Barge](#local-spree-components-with-barge) - [Local Spree components with Barge](#local-spree-components-with-barge)
- [API](#api)
- [🦑 Environment variables](#-environment-variables) - [🦑 Environment variables](#-environment-variables)
- [🎨 Storybook](#-storybook) - [🎨 Storybook](#-storybook)
- [✨ Code Style](#-code-style) - [✨ Code Style](#-code-style)
@ -27,30 +26,25 @@
## 🤓 Resources ## 🤓 Resources
- [UI Design: Figma Mock Up](https://www.figma.com/file/K38ZsQjzndyp2YFJCLxIN7/dexFreight-Marketplace)
- [Planning: ZenHub Board](https://app.zenhub.com/workspaces/dexfreight-marketplace-5e2f201751116794cf4f2e75/board?repos=236508929)
## 🏄 Get Started ## 🏄 Get Started
The app is a React app built with [Next.js](https://nextjs.org) + TypeScript + CSS modules and will connect to Ocean components in Pacific by default. The app is a React app built with [Gatsby.js](https://www.gatsbyjs.org) + TypeScript + CSS modules and will connect to Ocean components in Pacific by default.
To start local development: To start local development:
```bash ```bash
git clone git@github.com:oceanprotocol/dexfreight.git git clone git@github.com:oceanprotocol/market.git
cd dexfreight cd market
npm install npm install
npm start npm start
``` ```
This will launch the app under [localhost:3000](http://localhost:3000). This will start the development server under
`http://localhost:8000`.
Depending on your configuration, you might have to increase the amount of `inotify` watchers: To explore the generated GraphQL data structure fire up the accompanying GraphiQL IDE under
`http://localhost:8000/__graphql`.
```
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
```
### Local Spree components with Barge ### Local Spree components with Barge
@ -72,33 +66,30 @@ This will take some time on first start, and at the end you need to copy the gen
The script will wait for all contracts to be generated in the `keeper-contracts` Docker container, then will copy the artifacts in place into `node_modules/@oceanprotocol/keeper-contracts/artifacts/`. The script will wait for all contracts to be generated in the `keeper-contracts` Docker container, then will copy the artifacts in place into `node_modules/@oceanprotocol/keeper-contracts/artifacts/`.
Finally, set environment variables to use those local connections in `.env` & `.env.build` in the app: Finally, set environment variables to use those local connections in `.env` in the app:
```bash ```bash
# modify env variables, Spree is enabled by default when using those files # modify env variables, Spree is enabled by default when using those files
cp .env.example .env && cp .env.example .env.build cp .env.example .env
``` ```
### API
Files under `/api` are isolated and not part of Gatsby.
- [API Documentation](api/)
## 🦑 Environment variables ## 🦑 Environment variables
The `./src/config/ocean.ts` file is setup to prioritize environment variables for setting each Ocean component endpoint. By setting environment variables, you can easily switch between Ocean networks the app connects to, without directly modifying `./src/config/ocean.ts`. The `app.config.js` file is setup to prioritize environment variables for setting each Ocean component endpoint. By setting environment variables, you can easily switch between Ocean networks the app connects to, without directly modifying `app.config.js`.
For local development, you can use a `.env` & `.env.build` file: For local development, you can use a `.env` file:
```bash ```bash
# modify env variables, Spree is enabled by default when using those files # modify env variables, Spree is enabled by default when using those files
cp .env.example .env && cp .env.example .env.build cp .env.example .env
``` ```
For a Now deployment, all environment variables defining the Ocean component endpoints need to be added with `now secrets` to `oceanprotocol` org based on the `@` variable names defined in `now.json`, e.g.:
```bash
now switch
now secrets add aquarius_uri https://aquarius.pacific.dexfreight.dev-ocean.com
```
Adding the env vars like that will provide them during both, build & run time.
## 🎨 Storybook ## 🎨 Storybook
[Storybook](https://storybook.js.org) is set up for this project and is used for UI development of components. Stories are created inside `src/components/` alongside each component in the form of `ComponentName.stories.tsx`. [Storybook](https://storybook.js.org) is set up for this project and is used for UI development of components. Stories are created inside `src/components/` alongside each component in the form of `ComponentName.stories.tsx`.
@ -130,9 +121,7 @@ npm run format
Test suite for unit tests is setup with [Jest](https://jestjs.io) as a test runner and: Test suite for unit tests is setup with [Jest](https://jestjs.io) as a test runner and:
- [react-testing-library](https://github.com/kentcdodds/react-testing-library) for all React components - [react-testing-library](https://github.com/kentcdodds/react-testing-library) for all React components
- [node-mocks-http](https://github.com/howardabrams/node-mocks-http) for all `src/pages/api/` routes - [node-mocks-http](https://github.com/howardabrams/node-mocks-http) for all `api/` routes
> Note: fully testing Next.js API routes should be part of integration tests. There are [various problems](https://spectrum.chat/next-js/general/api-routes-unit-testing~aa868f97-3a7d-45fe-97e5-3f0408f0022d) with fully testing them so a proper unit test suite for them should be setup.
To run all linting and unit tests: To run all linting and unit tests:
@ -164,7 +153,7 @@ npm run serve
## ⬆️ Deployment ## ⬆️ Deployment
Every branch or Pull Request is automatically deployed by [Now](https://zeit.co/now) with their GitHub integration. A link to a deployment will appear under each Pull Request. Every branch or Pull Request is automatically deployed by [Vercel](https://vercel.com) with their GitHub integration. A link to a deployment will appear under each Pull Request.
The latest deployment of the `master` branch is automatically aliased to `xxx`. The latest deployment of the `master` branch is automatically aliased to `xxx`.
@ -174,47 +163,47 @@ If needed, app can be deployed manually. Make sure to switch to Ocean Protocol o
```bash ```bash
# first run # first run
now login vercel login
now switch vercel switch
# deploy # deploy
now vercel
# switch alias to new deployment # switch alias to new deployment
now alias vercel alias
``` ```
## 🏗 Ocean Protocol Infrastructure ## 🏗 Ocean Protocol Infrastructure
The following Aquarius & Brizo instances specifically for dexFreight marketplace are deployed in Ocean Protocol's AWS K8: The following Aquarius & Brizo instances specifically for marketplace are deployed in Ocean Protocol's AWS K8:
**Nile (Staging)** **Nile (Staging)**
- K8 namespace: `dexfreight-nile` - K8 namespace: `market-nile`
- `aquarius.nile.dexfreight.dev-ocean.com` - `aquarius.nile.market.dev-ocean.com`
- `brizo.nile.dexfreight.dev-ocean.com` - `brizo.nile.market.dev-ocean.com`
Edit command with `kubectl`, e.g.: Edit command with `kubectl`, e.g.:
```bash ```bash
kubectl edit deployment -n dexfreight-nile aquarius kubectl edit deployment -n market-nile aquarius
``` ```
**Pacific (Production)** **Pacific (Production)**
- K8 namespace: `dexfreight-pacific` - K8 namespace: `market-pacific`
- `aquarius.pacific.dexfreight.dev-ocean.com` - `aquarius.pacific.market.dev-ocean.com`
- `brizo.pacific.dexfreight.dev-ocean.com` - `brizo.pacific.market.dev-ocean.com`
Edit command with `kubectl`, e.g.: Edit command with `kubectl`, e.g.:
```bash ```bash
kubectl edit deployment -n dexfreight-pacific aquarius kubectl edit deployment -n market-pacific aquarius
``` ```
## 🏛 License ## 🏛 License
```text ```text
Copyright 2019 Ocean Protocol Foundation Ltd. Copyright 2020 Ocean Protocol Foundation Ltd.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
import { NextApiRequest, NextApiResponse } from 'next' import { NowRequest, NowResponse } from '@now/node'
import axios, { AxiosResponse } from 'axios' import axios, { AxiosResponse } from 'axios'
import { IncomingHttpHeaders } from 'http' import { IncomingHttpHeaders } from 'http'
@ -60,7 +60,7 @@ async function checkUrl(url: string): Promise<FileResponse> {
} }
} }
export default async (req: NextApiRequest, res: NextApiResponse) => { export default async (req: NowRequest, res: NowResponse) => {
switch (req.method) { switch (req.method) {
case 'POST': case 'POST':
res.status(200).json(await checkUrl(req.body.url)) res.status(200).json(await checkUrl(req.body.url))

View File

@ -1,12 +1,12 @@
import { NextApiRequest, NextApiResponse } from 'next' import { NowRequest, NowResponse } from '@now/node'
import axios, { AxiosResponse } from 'axios' import axios, { AxiosResponse } from 'axios'
import siteConfig from '../../../site.config' import siteConfig from '../content/site.json'
async function redeploy( async function redeploy(
req: NextApiRequest req: NowRequest
): Promise<AxiosResponse | undefined | string> { ): Promise<AxiosResponse | undefined | string> {
// Cancel if we are not on live // Cancel if we are not on live
if (req.headers.host !== siteConfig.url) return '' if (req.headers.host !== siteConfig.site.siteUrl) return ''
console.log('not canceled', req) console.log('not canceled', req)
try { try {
// Trigger new `master` deployment with Deploy Hook // Trigger new `master` deployment with Deploy Hook
@ -19,7 +19,7 @@ async function redeploy(
} }
} }
export default async (req: NextApiRequest, res: NextApiResponse) => { export default async (req: NowRequest, res: NowResponse) => {
switch (req.method) { switch (req.method) {
case 'POST': case 'POST':
res.status(200).json(await redeploy(req)) res.status(200).json(await redeploy(req))

19
app.config.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
oceanConfig: {
nodeUri: process.env.NODE_URI || 'https://pacific.oceanprotocol.com',
aquariusUri:
process.env.AQUARIUS_URI ||
'https://aquarius.marketplace.oceanprotocol.com',
brizoUri:
process.env.BRIZO_URI || 'https://brizo.marketplace.oceanprotocol.com',
brizoAddress:
process.env.BRIZO_ADDRESS || '0x00c6A0BC5cD0078d6Cd0b659E8061B404cfa5704',
secretStoreUri:
process.env.SECRET_STORE_URI || 'https://secret-store.oceanprotocol.com',
faucetUri: process.env.FAUCET_URI || 'https://faucet.oceanprotocol.com',
ratingUri:
process.env.RATING_URI ||
'https://rating.pacific.marketplace.dev-ocean.com',
verbose: 3
}
}

View File

@ -0,0 +1,4 @@
{
"title": "History",
"description": "Find the data sets and jobs that you previously accessed."
}

View File

@ -0,0 +1,4 @@
{
"title": "Publish Data",
"description": "Highlight the important features of your data set to make it more discoverable and catch the interest of data consumers."
}

20
content/site.json Normal file
View File

@ -0,0 +1,20 @@
{
"site": {
"siteTitle": "Ocean Market",
"siteTagline": "A marketplace to find and publish open data sets in the Ocean Network.",
"siteUrl": "https://market.oceanprotocol.now.sh",
"siteIcon": "node_modules/@oceanprotocol/art/logo/favicon-white.png",
"siteImage": "../src/images/share.png",
"copyright": "All Rights Reserved. Powered by [Ocean Protocol](https://oceanprotocol.com)",
"menu": [
{
"name": "Publish",
"link": "/publish"
},
{
"name": "History",
"link": "/history"
}
]
}
}

10
gatsby-browser.js Normal file
View File

@ -0,0 +1,10 @@
import wrapPageElementWithStyles from './src/helpers/wrapPageElement'
import wrapRootElementWithProviders from './src/helpers/wrapRootElement'
export const wrapPageElement = wrapPageElementWithStyles
export const wrapRootElement = wrapRootElementWithProviders
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
if (typeof window.IntersectionObserver === 'undefined') {
import('intersection-observer')
}

74
gatsby-config.js Normal file
View File

@ -0,0 +1,74 @@
require('dotenv').config()
const siteContent = require('./content/site.json')
const { oceanConfig } = require('./app.config')
module.exports = {
siteMetadata: {
...siteContent.site
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'content',
path: `${__dirname}/content`
}
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: `${__dirname}/src/images`
}
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'art',
path: `${__dirname}/node_modules/@oceanprotocol/art/`
}
},
{
resolve: 'gatsby-source-ocean',
options: {
aquariusUri: oceanConfig.aquariusUri
}
},
{
resolve: 'gatsby-plugin-sharp',
options: {
defaultQuality: 80
}
},
'gatsby-transformer-sharp',
'gatsby-transformer-json',
'gatsby-transformer-remark',
{
resolve: 'gatsby-plugin-svgr',
options: {
icon: false,
svgoConfig: {
plugins: [{ removeViewBox: false }]
}
}
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-remove-trailing-slashes',
{
// https://www.gatsbyjs.org/packages/gatsby-plugin-manifest/#using-with-gatsby-plugin-offline
resolve: 'gatsby-plugin-manifest',
options: {
name: siteContent.site.siteTitle,
short_name: siteContent.site.siteTitle,
start_url: '/',
background_color: '#ffffff',
theme_color: '#141414',
icon: siteContent.site.siteIcon,
display: 'standalone',
cache_busting_mode: 'none'
}
},
'gatsby-plugin-webpack-size'
]
}

78
gatsby-node.js Normal file
View File

@ -0,0 +1,78 @@
const path = require('path')
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
// 'fs' fix for squid.js
fs: 'empty'
}
})
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
// Create pages for all assets
const assetDetailsTemplate = path.resolve(
'src/components/templates/AssetDetails.tsx'
)
const result = await graphql(`
query {
allOceanAsset {
edges {
node {
did
main {
type
name
dateCreated
author
license
price
datePublished
files {
contentType
index
}
}
additionalInformation {
description
deliveryType
termsAndConditions
access
}
}
}
}
}
`)
if (result.errors) {
throw result.errors
}
await result.data.allOceanAsset.edges.forEach(({ node }) => {
const path = `/asset/${node.did}`
createPage({
path,
component: assetDetailsTemplate,
context: { did: node.did }
})
})
}
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
const handleClientSideOnly = page.path.match(/^\/asset/)
if (handleClientSideOnly) {
page.matchPath = '/asset/*'
// Update the page.
createPage(page)
}
}

5
gatsby-ssr.js Normal file
View File

@ -0,0 +1,5 @@
import wrapPageElementWithStyles from './src/helpers/wrapPageElement'
import wrapRootElementWithProviders from './src/helpers/wrapRootElement'
export const wrapPageElement = wrapPageElementWithStyles
export const wrapRootElement = wrapRootElementWithProviders

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noImplicitAny": true,
"sourceMap": true,
"target": "es5"
}
}

2
next-env.d.ts vendored
View File

@ -1,2 +0,0 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

View File

@ -1,109 +0,0 @@
const webpack = require('webpack')
require('dotenv').config()
// Returns environment variables as an object
const env = Object.keys(process.env).reduce((acc, curr) => {
acc[`process.env.${curr}`] = JSON.stringify(process.env[curr])
return acc
}, {})
const withSvgr = (nextConfig = {}) => ({
webpack(config, options) {
config.module.rules.push({
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
icon: true
}
}
]
})
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config
}
})
// eslint-disable-next-line no-unused-vars
const withFsFix = (nextConfig = {}) => ({
webpack(config, options) {
// Fixes npm packages that depend on `fs` module
// https://github.com/zeit/next.js/issues/7755#issuecomment-508633125
// or https://github.com/zeit/next.js/issues/7755
if (!options.isServer) {
config.node = {
fs: 'empty'
}
}
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config
}
})
const withGlobalConstants = (nextConfig = {}) => ({
webpack(config, options) {
// Allows to create global constants which can be configured at compile
// time (in this case they are the environment variables)
config.plugins.push(new webpack.DefinePlugin(env))
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config
}
})
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
const withMarkdown = (nextConfig = {}) => ({
webpack(config, options) {
config.module.rules.push({
test: /\.md$/,
loader: 'raw-loader'
})
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config
}
})
module.exports = withBundleAnalyzer(
withSvgr(
withFsFix(
withMarkdown(
withGlobalConstants({
exportPathMap: (defaultPathMap, { dev }) => {
// In dev environment return defaultPathMas as it is
if (dev) {
return defaultPathMap
}
// pages we know about beforehand
const paths = {
'/': { page: '/' },
'/publish': { page: '/publish' },
'/explore': { page: '/explore' }
}
return paths
}
})
)
)
)
)

33421
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +1,113 @@
{ {
"name": "market", "name": "@oceanprotocol/market",
"description": "Data marketplace for ocean.", "description": "Data marketplace for ocean.",
"version": "0.0.1", "version": "0.0.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"homepage": "https://oceanprotocol.com",
"scripts": { "scripts": {
"start": "next dev", "start": "gatsby clean && gatsby develop --host 0.0.0.0",
"export": "next export", "build": "gatsby clean && gatsby build",
"build": "npm run storybook:build && next build", "serve": "serve -s public/",
"serve": "next start",
"jest": "NODE_ENV=test jest -c tests/unit/jest.config.js", "jest": "NODE_ENV=test jest -c tests/unit/jest.config.js",
"test": "npm run lint && npm run jest", "test": "npm run lint && npm run jest",
"test:watch": "npm run lint && npm run jest -- --watch", "test:watch": "npm run lint && npm run jest -- --watch",
"lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .", "lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .",
"format": "prettier --ignore-path .gitignore **/**/*.{css,yml,js,jsx,ts,tsx,json} --write", "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
"analyze": "ANALYZE=true next build", "type-check": "tsc --noEmit",
"analyze": "npm run build && source-map-explorer 'public/*.js'",
"storybook": "start-storybook -p 4000 -c .storybook", "storybook": "start-storybook -p 4000 -c .storybook",
"storybook:build": "build-storybook -c .storybook -o public/storybook" "storybook:build": "build-storybook -c .storybook -o public/storybook"
}, },
"dependencies": { "dependencies": {
"@oceanprotocol/art": "^2.2.0", "@loadable/component": "^5.13.1",
"@now/node": "^1.7.1",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/react": "0.0.11", "@oceanprotocol/react": "0.0.11",
"@oceanprotocol/squid": "^2.2.0", "@oceanprotocol/squid": "^2.2.0",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0", "@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.0.2", "@tippyjs/react": "^4.1.0",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"axios": "^0.19.2", "axios": "^0.19.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"date-fns": "^2.11.0", "date-fns": "^2.14.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"filesize": "^6.1.0", "filesize": "^6.1.0",
"is-url-superb": "^3.0.0", "gatsby": "^2.23.22",
"next": "^9.3.2", "gatsby-image": "^2.4.12",
"next-seo": "^4.4.0", "gatsby-plugin-manifest": "^2.4.17",
"next-svgr": "^0.0.2", "gatsby-plugin-react-helmet": "^3.3.9",
"nprogress": "^0.2.0", "gatsby-plugin-remove-trailing-slashes": "^2.3.10",
"gatsby-plugin-sharp": "^2.6.18",
"gatsby-plugin-svgr": "^2.0.2",
"gatsby-plugin-webpack-size": "^1.0.0",
"gatsby-source-filesystem": "^2.3.18",
"gatsby-source-graphql": "^2.6.1",
"gatsby-transformer-json": "^2.4.10",
"gatsby-transformer-remark": "^2.8.23",
"gatsby-transformer-sharp": "^2.5.10",
"intersection-observer": "^0.11.0",
"is-url-superb": "^4.0.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"query-string": "^6.13.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-data-table-component": "^6.9.2", "react-data-table-component": "^6.9.6",
"react-datepicker": "^2.14.0", "react-datepicker": "^3.0.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",
"react-dropzone": "^11.0.1", "react-dropzone": "^11.0.1",
"react-helmet": "^6.1.0",
"react-jsonschema-form": "^1.8.1", "react-jsonschema-form": "^1.8.1",
"react-markdown": "^4.3.1", "react-markdown": "^4.3.1",
"react-paginate": "^6.3.2", "react-paginate": "^6.3.2",
"react-rating": "^2.0.4", "react-rating": "^2.0.5",
"react-responsive-modal": "^5.0.2", "react-responsive-modal": "^5.0.2",
"react-toastify": "^5.5.0", "react-spring": "^8.0.27",
"react-tabs": "^3.1.1",
"react-toastify": "^6.0.8",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"slugify": "^1.4.0", "slugify": "^1.4.4",
"use-debounce": "^3.4.0",
"web3connect": "^1.0.0-beta.33" "web3connect": "^1.0.0-beta.33"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.7", "@babel/core": "^7.10.3",
"@next/bundle-analyzer": "^9.3.0", "@babel/preset-typescript": "^7.10.1",
"@storybook/addon-storyshots": "^5.3.17", "@storybook/addon-actions": "^6.0.0-beta.45",
"@storybook/react": "^5.3.17", "@storybook/addon-storyshots": "^6.0.0-beta.45",
"@testing-library/jest-dom": "^5.1.1", "@storybook/react": "^6.0.0-beta.45",
"@testing-library/react": "^10.0.1", "@svgr/webpack": "^5.4.0",
"@testing-library/react-hooks": "^3.2.1", "@testing-library/jest-dom": "^5.11.0",
"@types/jest": "^25.1.4", "@testing-library/react": "^10.4.4",
"@types/node": "^13.9.1", "@types/jest": "^26.0.4",
"@types/nprogress": "^0.2.0", "@types/loadable__component": "^5.10.0",
"@types/numeral": "0.0.26", "@types/node": "^14.0.19",
"@types/react": "^16.9.23", "@types/numeral": "^0.0.28",
"@types/react-datepicker": "^2.11.0", "@types/react": "^16.9.41",
"@types/react-jsonschema-form": "^1.7.0", "@types/react-datepicker": "^3.0.2",
"@types/react-helmet": "^6.0.0",
"@types/react-jsonschema-form": "^1.7.3",
"@types/react-paginate": "^6.2.1", "@types/react-paginate": "^6.2.1",
"@types/react-tabs": "^2.3.2",
"@types/shortid": "0.0.29", "@types/shortid": "0.0.29",
"@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^2.23.0", "@typescript-eslint/parser": "^3.6.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.1", "babel-preset-react-app": "^9.1.2",
"eslint": "^6.8.0", "electron": "^9.1.0",
"eslint": "^7.4.0",
"eslint-config-oceanprotocol": "^1.5.0", "eslint-config-oceanprotocol": "^1.5.0",
"eslint-config-prettier": "^6.10.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.2", "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.19.0", "eslint-plugin-react": "^7.20.3",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^25.1.0", "jest": "^26.1.0",
"node-mocks-http": "^1.8.1", "node-mocks-http": "^1.8.1",
"prettier": "^1.19.1", "prettier": "^2.0.5",
"react-test-renderer": "^16.12.0", "react-test-renderer": "^16.13.1",
"ts-jest": "^25.2.1", "serve": "^11.3.2",
"typescript": "^3.8.3", "source-map-explorer": "^2.4.2",
"webfontloader": "^1.6.28" "typescript": "^3.9.6"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,42 @@
const axios = require('axios')
exports.sourceNodes = async (
{ actions, createNodeId, createContentDigest },
{ aquariusUri }
) => {
const { createNode } = actions
// Query for all assets to use in creating pages.
const result = await axios(`${aquariusUri}/api/v1/aquarius/assets`)
for (let i = 0; i < result.data.ids.length; i++) {
const did = result.data.ids[i]
const metadataResult = await axios(
`${aquariusUri}/api/v1/aquarius/assets/metadata/${did}`
)
const metadata = {
did,
...metadataResult.data.attributes
}
const nodeMeta = {
id: createNodeId(did),
parent: null,
children: [],
internal: {
type: 'OceanAsset',
contentDigest: createContentDigest(metadata),
description: `All data sets queried from ${aquariusUri}`
}
}
const node = {
...metadata,
...nodeMeta
}
await createNode(node)
}
}

View File

@ -0,0 +1,3 @@
{
"name": "gatsby-source-ocean"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,35 +0,0 @@
module.exports = {
title: 'Ocean Market',
description: `A marketplace to find and publish open data sets in the Ocean Network.`,
url: 'https://market.oceanprotocol.now.sh/',
copyright:
'All Rights Reserved. Powered by [Ocean Protocol](https://oceanprotocol.com)',
refundPolicy: [
'Data can be challenged within 2 days after purchase.',
'The marketplace decides if you are eligible for refund.'
],
assetTerms: [
{
name: 'Personal Identifiable Information',
value: 'This offer contains no personal data'
},
{
name: 'Regions where data can be used',
value: 'Worldwide'
}
],
menu: [
{
name: 'Explore',
link: '/explore'
},
{
name: 'Publish',
link: '/publish'
},
{
name: 'Transactions',
link: '/transactions'
}
]
}

View File

@ -1,15 +1,5 @@
import { MetaData, AdditionalInformation, Curation } from '@oceanprotocol/squid' import { MetaData, AdditionalInformation } from '@oceanprotocol/squid'
import { ServiceMetadata } from '@oceanprotocol/squid/dist/node/ddo/Service'
declare type DeliveryType = 'files' | 'api' | 'subscription'
declare type Granularity =
| 'hourly'
| 'daily'
| 'weekly'
| 'monthly'
| 'annually'
| 'Not updated periodically'
| ''
export interface Sample { export interface Sample {
name: string name: string
@ -21,14 +11,20 @@ export declare type AccessType = 'Download' | 'Compute'
export interface AdditionalInformationMarket extends AdditionalInformation { export interface AdditionalInformationMarket extends AdditionalInformation {
description: string description: string
links?: Sample[] // redefine existing key, cause not specific enough in Squid links?: Sample[] // redefine existing key, cause not specific enough in Squid
deliveryType: DeliveryType
termsAndConditions: boolean termsAndConditions: boolean
dateRange?: [string, string] dateRange?: [string, string]
supportName?: string
supportEmail?: string
access: AccessType access: AccessType
} }
export interface MetaDataMarket extends MetaData { export interface MetaDataMarket extends MetaData {
additionalInformation: AdditionalInformationMarket additionalInformation: AdditionalInformationMarket
} }
export interface ServiceMetaDataMarket extends ServiceMetadata {
attributes: MetaDataMarket
}
// type for assets pulled into GraphQL
export interface OceanAsset extends MetaDataMarket {
did: DID
}

View File

@ -1,3 +1,8 @@
declare module '*.module.css' {
const classes: { [key: string]: string }
export default classes
}
declare module '*.svg' { declare module '*.svg' {
import * as React from 'react' import * as React from 'react'
export const ReactComponent: React.FunctionComponent<React.SVGProps< export const ReactComponent: React.FunctionComponent<React.SVGProps<
@ -7,5 +12,4 @@ declare module '*.svg' {
export default src export default src
} }
declare type Nullable<T> = T | null declare module '*.gif'
declare module '*.md'

5
src/@types/node_modules.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module 'intersection-observer'
declare module 'ethereum-blockies' {
export function toDataUrl(address: string): string
}

View File

@ -1,40 +0,0 @@
import React, { ReactNode } from 'react'
import Head from 'next/head'
import { NextSeo } from 'next-seo'
import styles from './Layout.module.css'
import Header from './components/organisms/Header'
import Footer from './components/organisms/Footer'
import PageHeader from './components/molecules/PageHeader'
export default function Layout({
children,
title,
description,
noPageHeader
}: {
children: ReactNode
title?: string
description?: string
noPageHeader?: boolean
}) {
return (
<div className={styles.app}>
<Head>
<link rel="icon" href="/icons/icon-96x96.png" />
<link rel="apple-touch-icon" href="icons/icon-256x256.png" />
<meta name="theme-color" content="#ca2935" />
</Head>
<NextSeo title={title} description={description} />
<Header />
<main className={styles.main}>
{title && !noPageHeader && (
<PageHeader title={title} description={description} />
)}
{children}
</main>
<Footer />
</div>
)
}

View File

@ -12,10 +12,7 @@
} }
.main { .main {
padding: calc(var(--spacer) * 2) calc(var(--spacer) / 1.5); padding: calc(var(--spacer) * 2) 0;
margin-left: auto;
margin-right: auto;
max-width: var(--layout-max-width);
/* sticky footer technique */ /* sticky footer technique */
flex: 1; flex: 1;

47
src/components/Layout.tsx Normal file
View File

@ -0,0 +1,47 @@
import React, { ReactNode, ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import Header from './organisms/Header'
import Footer from './organisms/Footer'
import PageHeader from './molecules/PageHeader'
import styles from './Layout.module.css'
import Seo from './atoms/Seo'
import Container from './atoms/Container'
export interface LayoutProps {
children: ReactNode
title: string
uri: string
description?: string
noPageHeader?: boolean
}
export default function Layout({
children,
title,
uri,
description,
noPageHeader
}: LayoutProps): ReactElement {
return (
<div className={styles.app}>
<Helmet>
<link rel="icon" href="/icons/icon-96x96.png" />
<link rel="apple-touch-icon" href="icons/icon-256x256.png" />
<meta name="theme-color" content="#ca2935" />
</Helmet>
<Seo title={title} description={description} uri={uri} />
<Header />
<main className={styles.main}>
<Container>
{title && !noPageHeader && (
<PageHeader title={title} description={description} />
)}
{children}
</Container>
</main>
<Footer />
</div>
)
}

View File

@ -6,6 +6,12 @@
border-left-width: 0.5rem; border-left-width: 0.5rem;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
}
.alert + .alert {
margin-top: calc(var(--spacer) / 2);
} }
.alert, .alert,
@ -14,32 +20,33 @@
} }
.title { .title {
font-size: var(--font-size-large); font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 2); margin-bottom: calc(var(--spacer) / 8);
color: inherit; color: inherit;
} }
.text { .text {
margin-bottom: 0; margin-bottom: 0;
font-size: var(--font-size-small);
} }
/* States */ /* States */
.error { .error {
border-color: var(--red); border-color: var(--rbrand-alert-ed);
color: var(--red); color: var(--brand-alert-red);
} }
.success { .success {
border-color: var(--green); border-color: var(--brand-alert-green);
color: var(--green); color: var(--brand-alert-green);
} }
.info { .info {
border-color: var(--yellow); border-color: var(--brand-alert-yellow);
color: var(--yellow); color: var(--brand-alert-yellow);
} }
.warning { .warning {
border-color: var(--orange); border-color: var(--brand-alert-orange);
color: var(--orange); color: var(--brand-alert-orange);
} }

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { ReactElement } from 'react'
import styles from './Alert.module.css' import styles from './Alert.module.css'
export function Alert({ export function Alert({
@ -6,13 +6,13 @@ export function Alert({
text, text,
state state
}: { }: {
title: string title?: string
text: string text: string
state: 'error' | 'warning' | 'info' | 'success' state: 'error' | 'warning' | 'info' | 'success'
}) { }): ReactElement {
return ( return (
<div className={`${styles.alert} ${styles[state]}`}> <div className={`${styles.alert} ${styles[state]}`}>
<h3 className={styles.title}>{title}</h3> {title && <h3 className={styles.title}>{title}</h3>}
<p className={styles.text}>{text}</p> <p className={styles.text}>{text}</p>
</div> </div>
) )

View File

@ -1,14 +1,16 @@
.box { .box {
display: block;
background: var(--brand-white); background: var(--brand-white);
padding: var(--spacer);
border-radius: var(--border-radius); border-radius: var(--border-radius);
border: 1px solid var(--brand-grey-light); border: 1px solid var(--brand-grey-lighter);
box-shadow: 0 6px 15px 0 rgba(0, 0, 0, 0.05);
overflow: hidden;
padding: var(--spacer);
} }
a.box:hover, a.box:hover,
a.box:focus { a.box:focus {
outline: 0; outline: 0;
border-color: var(--brand-pink); transform: translate3d(0, -0.1rem, 0);
/* box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); */ box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.07);
/* transform: translate3d(0, -2px, 0); */
} }

View File

@ -16,8 +16,7 @@
background: var(--brand-grey-light); background: var(--brand-grey-light);
box-shadow: 0 9px 18px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 9px 18px 0 rgba(0, 0, 0, 0.1);
user-select: none; user-select: none;
margin-left: calc(var(--spacer) / 4); text-align: center;
margin-right: calc(var(--spacer) / 4);
} }
.button:first-child { .button:first-child {
@ -57,21 +56,28 @@
background: var(--brand-gradient); background: var(--brand-gradient);
} }
.link { .ghost,
.ghost:hover,
.ghost:focus,
.ghost:active {
color: var(--brand-grey);
background: var(--brand-white);
}
.text,
.text:hover,
.text:focus,
.text:active {
border: 0; border: 0;
border-radius: 0;
outline: 0; outline: 0;
padding: 0;
display: inline-block; display: inline-block;
background: 0; background: 0;
padding: 0;
color: var(--brand-pink); color: var(--brand-pink);
box-shadow: none; box-shadow: none;
cursor: pointer; cursor: pointer;
} }
.link:hover {
background: none;
color: var(--brand-pink);
box-shadow: none;
}
/* Size Modifiers */ /* Size Modifiers */
.small { .small {

View File

@ -1,14 +1,57 @@
import React from 'react' import React from 'react'
import { action } from '@storybook/addon-actions'
import Button from './Button' import Button from './Button'
import { Center } from '../../../.storybook/helpers'
export default { export default {
title: 'Atoms/Button', title: 'Atoms/Button'
decorators: [(storyFn: any) => <Center>{storyFn()}</Center>]
} }
export const Normal = () => <Button>Hello Button</Button> export const Default = () => (
<>
<Button onClick={action('clicked')}>Hello Button</Button>
<br />
<br />
<Button size="small" onClick={action('clicked')}>
Hello Button
</Button>
</>
)
export const Primary = () => <Button primary>Hello Button</Button> export const Primary = () => (
<>
<Button style="primary" onClick={action('clicked')}>
Hello Button
</Button>
<br />
<br />
<Button style="primary" size="small" onClick={action('clicked')}>
Hello Button
</Button>
</>
)
export const Link = () => <Button link>Hello Button</Button> export const Ghost = () => (
<>
<Button style="ghost" onClick={action('clicked')}>
Hello Button
</Button>
<br />
<br />
<Button style="ghost" size="small" onClick={action('clicked')}>
Hello Button
</Button>
</>
)
export const Text = () => (
<>
<Button style="text" onClick={action('clicked')}>
Hello Button
</Button>
<br />
<br />
<Button style="text" size="small" onClick={action('clicked')}>
Hello Button
</Button>
</>
)

View File

@ -1,44 +1,53 @@
import React, { ReactElement } from 'react' import React, { ReactNode, FormEvent, ReactElement } from 'react'
import Link from 'next/link' import { Link } from 'gatsby'
import classNames from 'classnames/bind'
import styles from './Button.module.css' import styles from './Button.module.css'
declare type ButtonProps = { const cx = classNames.bind(styles)
children: string | ReactElement
interface ButtonProps {
children: ReactNode
className?: string className?: string
primary?: boolean
link?: boolean
href?: string href?: string
size?: string onClick?: (e: FormEvent) => void
onClick?: any
disabled?: boolean disabled?: boolean
to?: string
name?: string
size?: 'small'
style?: 'primary' | 'ghost' | 'text'
type?: 'submit'
download?: boolean
} }
const Button = ({ export default function Button({
primary,
link,
href, href,
size,
children, children,
className, className,
to,
size,
style,
...props ...props
}: ButtonProps) => { }: ButtonProps): ReactElement {
const classes = primary const styleClasses = cx({
? `${styles.button} ${styles.primary}` button: true,
: link primary: style === 'primary',
? `${styles.button} ${styles.link}` ghost: style === 'ghost',
: styles.button text: style === 'text',
small: size === 'small',
[className]: className
})
return href ? ( return to ? (
<Link href={href}> <Link to={to} className={styleClasses} {...props}>
<a className={`${classes} ${className}`} {...props}> {children}
</Link>
) : href ? (
<a href={href} className={styleClasses} {...props}>
{children} {children}
</a> </a>
</Link>
) : ( ) : (
<button className={`${classes} ${className}`} {...props}> <button className={styleClasses} {...props}>
{children} {children}
</button> </button>
) )
} }
export default Button

View File

@ -0,0 +1,12 @@
.container {
max-width: var(--break-point--large);
margin-left: auto;
margin-right: auto;
padding-left: calc(var(--spacer) / 1.2);
padding-right: calc(var(--spacer) / 1.2);
width: 100%;
}
.container.narrow {
max-width: 42rem;
}

View File

@ -0,0 +1,22 @@
import React, { ReactElement, ReactNode } from 'react'
import styles from './Container.module.css'
export default function Container({
children,
narrow,
className
}: {
children: ReactNode
narrow?: boolean
className?: string
}): ReactElement {
return (
<div
className={`${styles.container} ${narrow && styles.narrow} ${
className && className
}`}
>
{children}
</div>
)
}

View File

@ -1,12 +1,16 @@
.dropzone { .dropzone {
padding: 22px; padding: var(--spacer);
text-align: center; text-align: center;
color: var(--color-secondary); color: var(--color-secondary);
border: 2px dashed var(--color-primary); border: 0.1rem dashed var(--color-secondary);
font-size: var(--font-size-small);
border-radius: var(--border-radius);
} }
.dragover { .dragover {
border-color: var(--color-secondary); border-color: var(--color-primary);
} }
.disabled { .disabled {
} }

View File

@ -14,7 +14,7 @@ export default function Dropzone({
multiple?: boolean multiple?: boolean
error?: string error?: string
}) { }) {
const onDrop = useCallback(acceptedFiles => handleOnDrop(acceptedFiles), [ const onDrop = useCallback((acceptedFiles) => handleOnDrop(acceptedFiles), [
handleOnDrop handleOnDrop
]) ])

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { WidgetProps } from 'react-jsonschema-form' import { WidgetProps } from 'react-jsonschema-form'
import dynamic from 'next/dynamic' import loadable from '@loadable/component'
import styles from './DateRangeWidget.module.css' import styles from './DateRangeWidget.module.css'
import { toStringNoMS } from '../../../utils' import { toStringNoMS } from '../../../utils'
// lazy load this module, it's huge // lazy load this module, it's huge
const LazyDatePicker = dynamic(() => import('react-datepicker')) const LazyDatePicker = loadable(() => import('react-datepicker'))
export function getWidgetValue( export function getWidgetValue(
date1: Date, date1: Date,
@ -75,7 +75,7 @@ export default function DateRangeWidget(props: WidgetProps) {
<input <input
id="range" id="range"
type="checkbox" type="checkbox"
onChange={ev => setRange(ev.target.checked)} onChange={(ev) => setRange(ev.target.checked)}
checked={range} checked={range}
/> />
<label className={styles.label} htmlFor="range"> <label className={styles.label} htmlFor="range">

View File

@ -1,8 +1,6 @@
import React from 'react' import React from 'react'
import { WidgetProps } from 'react-jsonschema-form' import { WidgetProps } from 'react-jsonschema-form'
import styles from './TermsWidget.module.css' import styles from './TermsWidget.module.css'
import Markdown from '../Markdown'
import terms from '../../../../content/terms.md'
export default function TermsWidget(props: WidgetProps) { export default function TermsWidget(props: WidgetProps) {
const { const {
@ -21,7 +19,7 @@ export default function TermsWidget(props: WidgetProps) {
return ( return (
<> <>
<Markdown text={terms} className={styles.terms} /> {/* <Markdown text={terms} className={styles.terms} /> */}
<label <label
htmlFor={id} htmlFor={id}
className={required ? `${styles.label} ${styles.req}` : styles.label} className={required ? `${styles.label} ${styles.req}` : styles.label}
@ -32,9 +30,9 @@ export default function TermsWidget(props: WidgetProps) {
checked={typeof value === 'undefined' ? false : value} checked={typeof value === 'undefined' ? false : value}
disabled={disabled || readonly} disabled={disabled || readonly}
autoFocus={autofocus} autoFocus={autofocus}
onChange={event => onChange(event.target.checked)} onChange={(event) => onChange(event.target.checked)}
onBlur={onBlur && (event => onBlur(id, event.target.checked))} onBlur={onBlur && ((event) => onBlur(id, event.target.checked))}
onFocus={onFocus && (event => onFocus(id, event.target.checked))} onFocus={onFocus && ((event) => onFocus(id, event.target.checked))}
/> />
<span>{label}</span> <span>{label}</span>
</label> </label>

View File

@ -1,5 +1,5 @@
.help { .help {
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--brand-grey-light); color: var(--brand-grey-light);
margin-top: var(--spacer) / 4; margin-top: calc(var(--spacer) / 6);
} }

View File

@ -1,213 +0,0 @@
import cx from 'classnames'
import React, { PureComponent, FormEvent, ChangeEvent } from 'react'
import slugify from '@sindresorhus/slugify'
import DatePicker from 'react-datepicker'
import Help from './Help'
import Label from './Label'
import Row from './Row'
import InputGroup from './InputGroup'
import styles from './Input.module.css'
interface InputProps {
name: string
label: string
placeholder?: string
required?: boolean
help?: string
tag?: string
type?: string
options?: string[]
additionalComponent?: any
value?: string
onChange?(
event:
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
rows?: number
group?: any
multiple?: boolean
pattern?: string
}
interface InputState {
isFocused: boolean
dateCreated?: Date
}
export default class Input extends PureComponent<InputProps, InputState> {
public state: InputState = {
isFocused: false,
dateCreated: new Date()
}
public inputWrapClasses() {
if (this.props.type === 'search') {
return styles.inputWrapSearch
} else if (this.props.type === 'search' && this.state.isFocused) {
return cx(styles.inputWrapSearch, styles.isFocused)
} else if (this.state.isFocused && this.props.type !== 'search') {
return cx(styles.inputWrap, styles.isFocused)
} else {
return styles.inputWrap
}
}
public handleFocus = () => {
this.setState({ isFocused: !this.state.isFocused })
}
private handleDateChange = (date: Date) => {
this.setState({ dateCreated: date })
const event = {
currentTarget: {
name: 'dateCreated',
value: date
}
}
this.props.onChange!(event as any) // eslint-disable-line @typescript-eslint/no-non-null-assertion
}
public InputComponent = () => {
const { type, options, group, name, required, onChange, value } = this.props
const wrapClass = this.inputWrapClasses()
switch (type) {
case 'select':
return (
<div className={wrapClass}>
<select
id={name}
className={styles.select}
name={name}
required={required}
onFocus={this.handleFocus}
onBlur={this.handleFocus}
onChange={onChange}
value={value}
>
<option value="">---</option>
{options &&
options
.sort((a, b) => a.localeCompare(b))
.map((option: string, index: number) => (
<option key={index} value={option}>
{option}
</option>
))}
</select>
</div>
)
case 'textarea':
return (
<div className={wrapClass}>
<textarea
id={name}
className={styles.input}
onFocus={this.handleFocus}
onBlur={this.handleFocus}
{...this.props}
/>
</div>
)
case 'radio':
case 'checkbox':
return (
<div className={styles.radioGroup}>
{options &&
options.map((option: string, index: number) => (
<div className={styles.radioWrap} key={index}>
<input
className={styles.radio}
id={slugify(option)}
type={type}
name={name}
value={slugify(option)}
/>
<label
className={styles.radioLabel}
htmlFor={slugify(option)}
>
{option}
</label>
</div>
))}
</div>
)
case 'date':
return (
<div className={wrapClass}>
<DatePicker
selected={this.state.dateCreated}
onChange={this.handleDateChange}
className={styles.input}
onFocus={this.handleFocus}
onBlur={this.handleFocus}
id={name}
name={name}
/>
</div>
)
default:
return (
<div className={wrapClass}>
{group ? (
<InputGroup>
<input
id={name}
type={type || 'text'}
className={styles.input}
onFocus={this.handleFocus}
onBlur={this.handleFocus}
{...this.props}
/>
{group}
</InputGroup>
) : (
<input
id={name}
type={type || 'text'}
className={styles.input}
onFocus={this.handleFocus}
onBlur={this.handleFocus}
{...this.props}
/>
)}
{/* {type === 'search' && <SearchIcon />} */}
</div>
)
}
}
public render() {
const {
name,
label,
required,
help,
additionalComponent,
multiple
} = this.props
return (
<Row>
<Label htmlFor={name} required={required}>
{label}
</Label>
<this.InputComponent />
{help && <Help>{help}</Help>}
{multiple && 'hello'}
{additionalComponent && additionalComponent}
</Row>
)
}
}

View File

@ -1,63 +1,27 @@
.inputWrap,
.inputWrapSearch {
background: var(--brand-gradient);
border-radius: var(--border-radius);
padding: 2px;
display: flex;
position: relative;
}
.inputWrap .isFocused,
.inputWrapSearch .isFocused {
background: var(--brand-black);
}
.inputWrap > div,
.inputWrap > div > div,
.inputWrapSearch > div,
.inputWrapSearch > div > div {
width: 100%;
}
.inputWrapSearch,
.input { .input {
padding-left: var(--spacer) * 1.5;
}
.inputWrapSearch svg {
position: absolute;
left: var(--spacer) / 2;
width: 1.25rem;
height: 1.25rem;
top: 50%;
margin-top: -0.6rem;
fill: rgba(var(--brand-grey-light), 0.7);
}
.input,
.select {
font-size: var(--font-size-base); font-size: var(--font-size-base);
font-family: var(--font-family-base); font-family: var(--font-family-base);
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
color: var(--brand-black); color: var(--brand-black);
border: none; border: 1px solid var(--brand-grey-lighter);
box-shadow: none; box-shadow: none;
width: 100%; width: 100%;
background: var(--brand-white); background: var(--brand-white);
padding: var(--spacer) / 3; padding: calc(var(--spacer) / 3);
margin: 0; margin: 0;
border-radius: var(--border-radius); border-radius: var(--border-radius);
transition: 0.2s ease-out; transition: 0.2s ease-out;
min-height: 43px; min-height: 43px;
appearance: none; appearance: none;
display: block;
} }
.input:focus,
.select:focus { .input:focus {
border: none; border-color: var(--brand-grey);
box-shadow: none; box-shadow: none;
outline: 0; outline: 0;
} }
.select::placeholder,
.input::placeholder { .input::placeholder {
font-family: var(--font-family-base); font-family: var(--font-family-base);
font-size: var(--font-size-base); font-size: var(--font-size-base);
@ -67,9 +31,7 @@
opacity: 0.7; opacity: 0.7;
} }
.select[readonly],
.input[readonly], .input[readonly],
.select[disabled],
.input[disabled] { .input[disabled] {
background-color: var(--brand-grey-lighter); background-color: var(--brand-grey-lighter);
cursor: not-allowed; cursor: not-allowed;
@ -80,35 +42,38 @@
composes: input; composes: input;
height: 43px; height: 43px;
padding-right: 3rem; padding-right: 3rem;
border: 0;
/* custom arrow */
background-image: linear-gradient( background-image: linear-gradient(
45deg, 45deg,
transparent 50%, transparent 50%,
var(--brand-purple 50%) var(--brand-purple) 50%
), ),
linear-gradient(135deg, var(--brand-purple) 50%, transparent 50%), linear-gradient(135deg, var(--brand-grey) 50%, transparent 50%),
linear-gradient( linear-gradient(
to right, to right,
var(--brand-pink) 1px, var(--brand-grey-lighter) 1px,
lighten(var(--brand-grey-lighter), 5%) 2px, transparent 1px,
lighten(var(--brand-grey-lighter), 5%) transparent
); );
background-position: calc(100% - 18px) calc(1rem + 5px), background-position: calc(100% - 18px) calc(1rem + 5px),
calc(100% - 13px) calc(1rem + 5px), 100% 0; calc(100% - 13px) calc(1rem + 5px), 100% 0;
background-size: 5px 5px, 5px 5px, 2.5rem 3rem; background-size: 5px 5px, 5px 5px, 2.5rem 3rem;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.select:focus { .select:focus {
outline: 0; outline: 0;
font-family: var(--font-family-base); font-family: var(--font-family-base);
} }
.radioGroup { .radioGroup {
margin-top: var(--spacer) / 2; margin-top: calc(var(--spacer) / 2);
margin-bottom: -2%; margin-bottom: -2%;
}
@media screen and (min-width: var(--break-point--small)) { @media screen and (min-width: 40rem) {
.radioGroup {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
@ -117,13 +82,15 @@
.radioWrap { .radioWrap {
position: relative; position: relative;
padding: var(--spacer) / 2; padding: calc(var(--spacer) / 2);
text-align: center; text-align: center;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 2%; margin-bottom: 2%;
}
@media screen and (min-width: var(--break-point--small)) { @media screen and (min-width: 40rem) {
.radioWrap {
flex: 0 0 49%; flex: 0 0 49%;
} }
} }
@ -138,7 +105,7 @@
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
font-size: var(--font-size-small); font-size: var(--font-size-small);
line-height: 1.2; line-height: 1.2;
border: 2px solid var(--brand-grey-lighter); border: 1px solid var(--brand-grey-lighter);
border-radius: 0.2rem; border-radius: 0.2rem;
position: absolute; position: absolute;
left: 0; left: 0;
@ -152,13 +119,16 @@
align-items: center; align-items: center;
} }
.inputSmall { /* Size modifiers */
.small {
composes: input; composes: input;
font-size: var(--font-size-small); font-size: var(--font-size-small);
min-height: 32px; min-height: 32px;
padding: var(--spacer) / 4; padding: calc(var(--spacer) / 4);
} }
.inputSmall::placeholder {
.small::placeholder {
font-size: var(--font-size-small); font-size: var(--font-size-small);
} }
@ -166,6 +136,8 @@
composes: select; composes: select;
height: 32px; height: 32px;
padding-right: 2rem; padding-right: 2rem;
/* custom arrow */
background-position: calc(100% - 14px) 1rem, calc(100% - 9px) 1rem, 100% 0; background-position: calc(100% - 14px) 1rem, calc(100% - 9px) 1rem, 100% 0;
background-size: 5px 5px, 5px 5px, 2rem 3rem; background-size: 5px 5px, 5px 5px, 2rem 3rem;
} }

View File

@ -0,0 +1,66 @@
import React from 'react'
import slugify from '@sindresorhus/slugify'
import styles from './InputElement.module.css'
import { InputProps } from '.'
export default function InputElement(props: InputProps) {
const { type, options, rows, name } = props
switch (type) {
case 'select':
return (
<select id={name} className={styles.select} name={name} {...props}>
<option value="">---</option>
{options &&
options
.sort((a: string, b: string) => a.localeCompare(b))
.map((option: string, index: number) => (
<option key={index} value={option}>
{option}
</option>
))}
</select>
)
case 'textarea':
return (
<textarea
id={name}
className={styles.input}
rows={rows}
name={name}
{...props}
/>
)
case 'radio':
case 'checkbox':
return (
<div className={styles.radioGroup}>
{options &&
options.map((option: string, index: number) => (
<div className={styles.radioWrap} key={index}>
<input
className={styles.radio}
id={slugify(option)}
type={type}
name={name}
{...props}
/>
<label className={styles.radioLabel} htmlFor={slugify(option)}>
{option}
</label>
</div>
))}
</div>
)
default:
return (
<input
id={name}
type={type || 'text'}
className={styles.input}
name={name}
{...props}
/>
)
}
}

View File

@ -1,10 +1,10 @@
.label .required { .label {
color: var(--brand-grey); color: var(--brand-grey);
font-size: var(--font-size-base); font-size: var(--font-size-base);
font-family: var(--font-family-title); font-family: var(--font-family-title);
line-height: 1.2; line-height: 1.2;
display: block; display: block;
margin-bottom: var(--spacer) / 6; margin-bottom: calc(var(--spacer) / 4);
} }
.required:after { .required:after {

View File

@ -11,7 +11,7 @@ const Label = ({
htmlFor: string htmlFor: string
}) => ( }) => (
<label <label
className={required ? styles.required : styles.label} className={`${styles.label} ${required && styles.required}`}
title={required ? 'Required' : ''} title={required ? 'Required' : ''}
{...props} {...props}
> >

View File

@ -0,0 +1,3 @@
.field {
margin-bottom: var(--spacer);
}

View File

@ -0,0 +1,59 @@
import React, { FormEvent, ChangeEvent, ReactElement } from 'react'
import InputElement from './InputElement'
import Help from './Help'
import Label from './Label'
import styles from './index.module.css'
export interface InputProps {
name: string
label?: string
placeholder?: string
required?: boolean
help?: string
tag?: string
type?: string
options?: string[]
additionalComponent?: ReactElement
value?: string
onChange?(
e:
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
rows?: number
multiple?: boolean
pattern?: string
min?: string
disabled?: boolean
field?: {
name: string
value: string
onChange: () => void
onBlur: () => void
}
}
export default function Input(props: InputProps) {
const {
required,
name,
label,
help,
additionalComponent,
field
} = props as Partial<InputProps>
return (
<div className={styles.field}>
<Label htmlFor={name} required={required}>
{label}
</Label>
<InputElement {...field} {...props} />
{help && <Help>{help}</Help>}
{additionalComponent && additionalComponent}
</div>
)
}

View File

@ -1,57 +1,27 @@
.loaderWrap { .loaderWrap {
display: inline-block; display: flex;
font-size: var(--font-size-small); align-items: center;
color: var(--brand-grey);
}
.loader,
.loader:before,
.loader:after {
background: var(--brand-grey);
border-radius: var(--border-radius);
animation: load1 0.7s infinite ease-in-out;
font-size: var(--font-size-base);
width: 0.3em;
height: 1em;
display: block;
} }
.loader { .loader {
margin: 0.6em auto; display: block;
position: relative; width: 20px;
transform: translateZ(0); height: 20px;
animation-delay: -0.16s; border-radius: 50%;
} border: 2px solid var(--brand-purple);
.loaderHorizontal { border-top-color: var(--brand-violet);
composes: loader; animation: loader 0.6s linear infinite;
display: inline-block;
margin: 0 1.5em 0 0.7em;
vertical-align: middle;
} }
.loader:before, .message {
.loader:after { font-size: var(--font-size-small);
position: absolute; color: var(--brand-grey-light);
top: 0; display: block;
content: ''; margin-left: calc(var(--spacer) / 4);
} }
.loader:before { @keyframes loader {
left: -0.6em; to {
animation-delay: -0.32s; transform: rotate(360deg);
}
.loader:after {
left: 0.6em;
}
@keyframes load1 {
0%,
80%,
100% {
transform: scaleY(0.5);
}
40% {
transform: scaleY(1.3);
} }
} }

View File

@ -1,19 +1,15 @@
import React from 'react' import React, { ReactElement } from 'react'
import styles from './Loader.module.css' import styles from './Loader.module.css'
export default function Loader({ export default function Loader({
message, message
isHorizontal
}: { }: {
message?: string message?: string
isHorizontal?: boolean }): ReactElement {
}) {
return ( return (
<div className={styles.loaderWrap}> <div className={styles.loaderWrap}>
<span <span className={styles.loader} />
className={isHorizontal ? styles.loaderHorizontal : styles.loader} {message && <span className={styles.message}>{message}</span>}
/>
{message || null}
</div> </div>
) )
} }

View File

@ -1,24 +0,0 @@
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: var(--color-primary);
position: fixed;
z-index: 99;
top: 0;
left: 0;
width: 100%;
height: 0.2rem;
}
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px var(--color-primary), 0 0 5px var(--color-primary);
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}

View File

@ -1,49 +0,0 @@
import React, { useEffect } from 'react'
import NProgress, { NProgressOptions } from 'nprogress'
import Router from 'next/router'
//
// Component loosely taken from, but highly refactored
// https://github.com/sergiodxa/next-nprogress/blob/master/src/component.js
//
declare type NProgressContainerProps = {
showAfterMs?: number
options?: NProgressOptions
}
export default function NProgressContainer({
showAfterMs = 300,
options
}: NProgressContainerProps) {
let timer: NodeJS.Timeout
function routeChangeStart() {
clearTimeout(timer)
timer = setTimeout(NProgress.start, showAfterMs)
}
function routeChangeEnd() {
clearTimeout(timer)
NProgress.done()
}
useEffect(() => {
if (options) {
NProgress.configure(options)
}
Router.events.on('routeChangeStart', routeChangeStart)
Router.events.on('routeChangeComplete', routeChangeEnd)
Router.events.on('routeChangeError', routeChangeEnd)
return () => {
clearTimeout(timer)
Router.events.off('routeChangeStart', routeChangeStart)
Router.events.off('routeChangeComplete', routeChangeEnd)
Router.events.off('routeChangeError', routeChangeEnd)
}
}, [])
return <div />
}

View File

@ -7,10 +7,10 @@
.price span { .price span {
font-weight: var(--font-weight-base); font-weight: var(--font-weight-base);
color: var(--color-secondary); color: var(--color-secondary);
font-size: var(--font-size-small); font-size: var(--font-size-base);
} }
.small { .small {
transform: scale(0.8); transform: scale(0.7);
transform-origin: left 80%; transform-origin: left 80%;
} }

View File

@ -1,7 +1,10 @@
import React from 'react' import React, { ReactElement } from 'react'
import Web3 from 'web3' import Web3 from 'web3'
import classNames from 'classnames/bind'
import styles from './Price.module.css' import styles from './Price.module.css'
const cx = classNames.bind(styles)
export default function Price({ export default function Price({
price, price,
className, className,
@ -10,11 +13,15 @@ export default function Price({
price: string price: string
className?: string className?: string
small?: boolean small?: boolean
}) { }): ReactElement {
const classes = small const styleClasses = cx({
? `${styles.price} ${styles.small} ${className}` price: true,
: `${styles.price} ${className}` small: small,
[className]: className
})
const isFree = price === '0' const isFree = price === '0'
const displayPrice = isFree ? ( const displayPrice = isFree ? (
'Free' 'Free'
) : ( ) : (
@ -23,5 +30,5 @@ export default function Price({
</> </>
) )
return <div className={classes}>{displayPrice}</div> return <div className={styleClasses}>{displayPrice}</div>
} }

View File

@ -0,0 +1,54 @@
import React, { ReactElement } from 'react'
import { Helmet } from 'react-helmet'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
export default function Seo({
title,
description,
uri
}: {
title?: string
description?: string
uri: string
}): ReactElement {
const { siteTitle, siteTagline, siteUrl, siteImage } = useSiteMetadata()
// Remove trailing slash from all URLs
const canonical = `${siteUrl}${uri}`.replace(/\/$/, '')
return (
<Helmet
defaultTitle={`${siteTitle}${siteTagline}`}
titleTemplate="%s — Ocean Protocol"
title={title}
>
<html lang="en" />
{typeof window !== 'undefined' &&
window.location &&
window.location.hostname !== 'oceanprotocol.com' && (
<meta name="robots" content="noindex,nofollow" />
)}
<link rel="canonical" href={canonical} />
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={uri} />
<meta
name="image"
content={`${siteUrl}${siteImage.childImageSharp.original.src}`}
/>
<meta
property="og:image"
content={`${siteUrl}${siteImage.childImageSharp.original.src}`}
/>
<meta property="og:site_name" content={siteTitle} />
<meta name="twitter:creator" content="@oceanprotocol" />
<meta name="twitter:card" content="summary_large_image" />
</Helmet>
)
}

View File

@ -4,25 +4,23 @@
height: var(--font-size-small); height: var(--font-size-small);
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
background: var(--green); background: var(--brand-alert-green);
} }
/* yellow triangle */ /* yellow triangle */
.warning { .warning {
composes: status;
border-radius: 0; border-radius: 0;
background: none; background: none;
width: 0; width: 0;
height: 0; height: 0;
border-left: calc(var(--font-size-small) / 1.7) solid transparent; border-left: calc(var(--font-size-small) / 1.7) solid transparent;
border-right: calc(var(--font-size-small) / 1.7) solid transparent; border-right: calc(var(--font-size-small) / 1.7) solid transparent;
border-bottom: var(--font-size-small) solid var(--orange); border-bottom: var(--font-size-small) solid var(--brand-alert-yellow);
} }
/* red square */ /* red square */
.error { .error {
composes: status;
border-radius: 0; border-radius: 0;
background: var(--red); background: var(--brand-alert-red);
text-transform: capitalize; text-transform: capitalize;
} }

View File

@ -1,13 +1,22 @@
import React from 'react' import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import styles from './Status.module.css' import styles from './Status.module.css'
export default function Status({ state }: { state?: string }) { const cx = classNames.bind(styles)
const classes =
state === 'error'
? styles.error
: state === 'warning'
? styles.warning
: styles.status
return <i className={classes} /> export default function Status({
state,
className
}: {
state?: string
className?: string
}): ReactElement {
const styleClasses = cx({
status: true,
warning: state === 'warning',
error: state === 'error',
[className]: className
})
return <i className={styleClasses} />
} }

View File

@ -1,11 +1,6 @@
import React from 'react' import React from 'react'
import Time from '../Time' import { Link } from 'gatsby'
import Link from 'next/link'
export default function DdoLinkCell({ id, name }: { id: any; name: any }) { export default function DdoLinkCell({ id, name }: { id: any; name: any }) {
return ( return <Link to={`/asset/${id}`}>{name}</Link>
<Link href="/asset/[did]" as={`/asset/${id}`} passHref>
<a>{name}</a>
</Link>
)
} }

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import Link from 'next/link' import { Link } from 'gatsby'
import shortid from 'shortid' import shortid from 'shortid'
import slugify from 'slugify' import slugify from 'slugify'
import styles from './Tags.module.css' import styles from './Tags.module.css'
@ -20,10 +20,8 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
return noLinks ? ( return noLinks ? (
<span className={styles.tag}>{cleanTag}</span> <span className={styles.tag}>{cleanTag}</span>
) : ( ) : (
<Link href={`/search?tags=${tag}`}> <Link to={`/search?tags=${tag}`} className={styles.tag} title={cleanTag}>
<a className={styles.tag} title={cleanTag}>
{cleanTag} {cleanTag}
</a>
</Link> </Link>
) )
} }
@ -44,7 +42,7 @@ const Tags: React.FC<TagsProps> = ({
return ( return (
<div className={classes}> <div className={classes}>
{tags && {tags &&
tags.map(tag => ( tags.map((tag) => (
<Tag tag={tag} noLinks={noLinks} key={shortid.generate()} /> <Tag tag={tag} noLinks={noLinks} key={shortid.generate()} />
))} ))}
{shouldShowMore && ( {shouldShowMore && (

View File

@ -7,7 +7,7 @@
composes: box from '../atoms/Box.module.css'; composes: box from '../atoms/Box.module.css';
font-size: var(--font-size-small); font-size: var(--font-size-small);
height: 100%; height: 100%;
color: var(--brand-grey-dark); color: var(--color-secondary);
position: relative; position: relative;
/* for sticking footer to bottom */ /* for sticking footer to bottom */
display: flex; display: flex;
@ -18,59 +18,32 @@
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
margin-top: calc(var(--spacer) / 2);
/* for sticking footer to bottom */ /* for sticking footer to bottom */
flex: 1; flex: 1;
} }
.title { .title {
font-size: var(--font-size-h4); font-size: var(--font-size-large);
color: var(--color-primary); margin-bottom: calc(var(--spacer) / 4);
margin-bottom: calc(var(--spacer) / 6);
}
.tags > * {
font-size: var(--font-size-mini);
} }
.foot { .foot {
color: var(--color-secondary);
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
margin-top: calc(var(--spacer) / 1.5);
border-top: 1px solid var(--brand-grey-light);
padding-top: calc(var(--spacer) / 3);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
} }
.foot p { .foot p {
margin: 0; margin: 0;
} }
.price {
text-align: right;
}
p.copyright {
width: 100%;
font-weight: var(--font-weight-base);
margin-bottom: 0;
font-size: var(--font-size-mini);
text-align: center;
margin-top: calc(var(--spacer) / 3);
}
.accessLabel { .accessLabel {
font-size: var(--font-size-small); font-size: var(--font-size-mini);
width: auto; width: auto;
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
color: var(--brand-black); color: var(--brand-black);
background: var(--brand-grey-lighter); background: var(--brand-grey-lighter);
padding: 0.1px 0.5px 0.1px 0.5px; padding: 0.2rem 0.5rem;
border-radius: 2px; border-bottom-left-radius: var(--border-radius);
} }

View File

@ -1,47 +1,36 @@
import React from 'react' import React from 'react'
import { DDO } from '@oceanprotocol/squid' import { Link } from 'gatsby'
import Link from 'next/link'
import Dotdotdot from 'react-dotdotdot' import Dotdotdot from 'react-dotdotdot'
import { import { MetaDataMarket } from '../../@types/MetaData'
AdditionalInformationMarket,
MetaDataMarket
} from '../../@types/MetaData'
import { findServiceByType } from '../../utils'
import Tags from '../atoms/Tags' import Tags from '../atoms/Tags'
import Price from '../atoms/Price' import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import Rating from '../atoms/Rating' import Rating from '../atoms/Rating'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
ddo: Partial<DDO> did: string
metadata: MetaDataMarket
} }
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => { const AssetTeaser: React.FC<AssetTeaserProps> = ({
const { attributes } = findServiceByType(ddo, 'metadata') did,
const { name, price } = attributes.main metadata
}: AssetTeaserProps) => {
const { name, price } = metadata.main
let description const {
let copyrightHolder
let tags
let categories
let access
if (attributes && attributes.additionalInformation) {
;({
description, description,
copyrightHolder, copyrightHolder,
tags, tags,
categories, categories,
access access
} = attributes.additionalInformation as AdditionalInformationMarket) } = metadata.additionalInformation
}
const { curation } = attributes as MetaDataMarket const { curation } = metadata
return ( return (
<article className={styles.teaser}> <article className={styles.teaser}>
<Link href="/asset/[did]" as={`/asset/${ddo.id}`}> <Link to={`/asset/${did}`} className={styles.link}>
<a className={styles.link}>
<h1 className={styles.title}>{name}</h1> <h1 className={styles.title}>{name}</h1>
{access === 'Compute' && ( {access === 'Compute' && (
<div className={styles.accessLabel}>{access}</div> <div className={styles.accessLabel}>{access}</div>
@ -59,15 +48,8 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
</div> </div>
<footer className={styles.foot}> <footer className={styles.foot}>
{categories && <p className={styles.type}>{categories[0]}</p>} <Price price={price} small />
<Price price={price} className={styles.price} />
<p className={styles.copyright}>
Provided by <strong>{copyrightHolder}</strong>
</p>
</footer> </footer>
</a>
</Link> </Link>
</article> </article>
) )

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { DDO, Ocean } from '@oceanprotocol/squid' import { useNavigate } from '@reach/router'
import { useRouter } from 'next/router' import { DDO } from '@oceanprotocol/squid'
import { findServiceByType, redeploy } from '../../utils' import { redeploy } from '../../utils'
import Button from '../atoms/Button' import Button from '../atoms/Button'
import BaseDialog from '../atoms/BaseDialog' import BaseDialog from '../atoms/BaseDialog'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
@ -15,19 +15,17 @@ const content = [
export default function DeleteAction({ ddo }: { ddo: DDO }) { export default function DeleteAction({ ddo }: { ddo: DDO }) {
const { ocean, accountId } = useOcean() const { ocean, accountId } = useOcean()
const navigate = useNavigate()
const isOwner = ddo.publicKey[0].owner === accountId const isOwner = ddo.publicKey[0].owner === accountId
const router = useRouter()
const [isModal, setIsModal] = useState(false) const [isModal, setIsModal] = useState(false)
const [status, setStatus] = useState(0) // 0-confirmation, 1-deleting, 2-success, 3-error const [status, setStatus] = useState(0) // 0-confirmation, 1-deleting, 2-success, 3-error
const { attributes } = findServiceByType(ddo, 'metadata') const { attributes } = ddo.findServiceByType('metadata')
useEffect(() => { useEffect(() => {
let tId: number let tId: number
if (status === 2) { if (status === 2) {
tId = window.setTimeout(() => { tId = window.setTimeout(() => {
router.push(`/explore`) navigate(`/`)
}, 1000) }, 1000)
} }
return () => { return () => {

View File

@ -13,7 +13,7 @@ export const FieldTemplate = ({
rawErrors, rawErrors,
children children
}: FieldTemplateProps) => { }: FieldTemplateProps) => {
const noLabel = id !== noLabelFields.filter(f => id === f)[0] const noLabel = id !== noLabelFields.filter((f) => id === f)[0]
return ( return (
<section <section
key={id} key={id}

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { ReactElement, ReactNode } from 'react'
import isUrl from 'is-url-superb' import isUrl from 'is-url-superb'
import Loader from '../../../atoms/Loader' import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button' import Button from '../../../atoms/Button'
@ -11,12 +11,12 @@ const FileInput = ({
children, children,
i i
}: { }: {
children: any children: ReactNode
i: number i: number
formData: string[] formData: string[]
handleButtonClick(e: React.SyntheticEvent, data: string): void handleButtonClick(e: React.SyntheticEvent, data: string): void
isLoading: boolean isLoading: boolean
}) => ( }): ReactElement => (
<> <>
{children} {children}
{formData[i] && ( {formData[i] && (

View File

@ -105,7 +105,7 @@ export default function Form({
transformErrors={transformErrors} transformErrors={transformErrors}
> >
<div> <div>
<Button disabled={buttonDisabled} primary> <Button disabled={buttonDisabled} style="primary">
Submit Submit
</Button> </Button>
</div> </div>

View File

@ -1,12 +1,11 @@
import React from 'react' import React, { ReactElement } from 'react'
import { ComputeItem } from '@oceanprotocol/react' import { ComputeItem } from '@oceanprotocol/react'
import BaseDialog from '../atoms/BaseDialog' import BaseDialog from '../atoms/BaseDialog'
import { findServiceByType } from '../../utils'
import styles from './JobDetailsDialog.module.css' import styles from './JobDetailsDialog.module.css'
import MetaItem from '../templates/AssetDetails/MetaItem' import MetaItem from '../organisms/AssetContent/MetaItem'
import Time from '../atoms/Time' import Time from '../atoms/Time'
import shortid from 'shortid' import shortid from 'shortid'
import Link from 'next/link' import { Link } from 'gatsby'
export default function JobDetailsDialog({ export default function JobDetailsDialog({
computeItem, computeItem,
@ -16,10 +15,10 @@ export default function JobDetailsDialog({
computeItem: ComputeItem | undefined computeItem: ComputeItem | undefined
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
}) { }): ReactElement {
if (!computeItem) return null if (!computeItem) return null
const { attributes } = findServiceByType(computeItem.ddo, 'metadata') const { attributes } = computeItem.ddo.findServiceByType('metadata')
const { name } = attributes.main const { name } = attributes.main
const { const {
dateCreated, dateCreated,
@ -46,8 +45,8 @@ export default function JobDetailsDialog({
<MetaItem <MetaItem
title="Results" title="Results"
content={resultsUrls.map((url: string) => ( content={resultsUrls.map((url: string) => (
<Link href={url} key={shortid.generate()} passHref> <Link to={url} key={shortid.generate()}>
<a>{url}</a> {url}
</Link> </Link>
))} ))}
/> />
@ -55,24 +54,12 @@ export default function JobDetailsDialog({
{algorithmLogUrl && ( {algorithmLogUrl && (
<MetaItem <MetaItem
title="Algorithm Log" title="Algorithm Log"
content={ content={<Link to={algorithmLogUrl}>{algorithmLogUrl}</Link>}
<Link href={algorithmLogUrl} key={shortid.generate()} passHref>
<a>{algorithmLogUrl}</a>
</Link>
}
/> />
)} )}
<MetaItem <MetaItem
title="Data Set" title="Data Set"
content={ content={<Link to={`/asset/${computeItem.ddo.id}`}>{name}</Link>}
<Link
href="/asset/[did]"
as={`/asset/${computeItem.ddo.id}`}
passHref
>
<a>{name}</a>
</Link>
}
/> />
</div> </div>
</BaseDialog> </BaseDialog>

View File

@ -1,11 +1,91 @@
.menu { .menu {
width: 100%;
} }
.link { .menu > div {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
}
.logoUnit {
display: flex;
align-items: center;
}
.logoUnit svg {
fill: var(--color-primary);
width: 4rem;
height: 4rem;
margin-left: -0.5rem;
}
.logoUnit path {
fill: var(--brand-black);
}
.logoUnit:hover path {
fill: var(--brand-pink);
}
.title {
display: none;
}
@media screen and (min-width: 40rem) {
.title {
margin: 0;
display: block;
color: var(--color-secondary);
font-size: var(--font-size-base);
}
.logoUnit svg {
margin-top: -0.4rem;
}
}
@media screen and (min-width: 60rem) {
.title {
font-size: var(--font-size-large);
}
}
.navigation {
white-space: nowrap;
overflow-y: hidden;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.navigation::-webkit-scrollbar,
.navigation::-moz-scrollbar {
display: none;
}
.navigation li {
display: inline-block; display: inline-block;
}
.navigation button,
.link {
display: block;
padding: calc(var(--spacer) / 2);
margin-left: var(--spacer);
text-transform: uppercase;
color: var(--brand-grey); color: var(--brand-grey);
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
margin: calc(var(--spacer) / 4) var(--spacer); font-size: var(--font-size-small);
position: relative;
z-index: 1;
}
.navigation button {
text-transform: none;
padding-top: calc(var(--spacer) / 4);
padding-bottom: calc(var(--spacer) / 4);
} }
.link:hover, .link:hover,
@ -14,14 +94,12 @@
color: var(--brand-pink); color: var(--brand-pink);
} }
.link.active { .link[aria-current],
.link[aria-current]:hover,
.link[aria-current]:focus {
color: var(--brand-pink); color: var(--brand-pink);
} }
.link:first-child {
margin-left: 0;
}
.link:last-child { .link:last-child {
margin-right: 0; padding-right: 0;
} }

View File

@ -1,8 +1,11 @@
import React from 'react' import React, { ReactElement } from 'react'
import Link from 'next/link' import { Link } from 'gatsby'
import { useRouter } from 'next/router' import { useLocation } from '@reach/router'
import { menu } from '../../../site.config'
import styles from './Menu.module.css' import styles from './Menu.module.css'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
import Wallet from './Wallet'
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg'
import Container from '../atoms/Container'
declare type MenuItem = { declare type MenuItem = {
name: string name: string
@ -10,25 +13,42 @@ declare type MenuItem = {
} }
function MenuLink({ item }: { item: MenuItem }) { function MenuLink({ item }: { item: MenuItem }) {
const router = useRouter() const location = useLocation()
const classes = const classes =
router && router.pathname === item.link location && location.pathname === item.link
? `${styles.link} ${styles.active}` ? `${styles.link} ${styles.active}`
: styles.link : styles.link
return ( return (
<Link key={item.name} href={item.link}> <Link key={item.name} to={item.link} className={classes}>
<a className={classes}>{item.name}</a> {item.name}
</Link> </Link>
) )
} }
export default function Menu() { export default function Menu(): ReactElement {
const { menu, siteTitle } = useSiteMetadata()
return ( return (
<nav className={styles.menu}> <nav className={styles.menu}>
<Container>
<Link to="/" className={styles.logoUnit}>
<Logo />
<h1 className={styles.title}>{siteTitle}</h1>
</Link>
<ul className={styles.navigation}>
<li>
<Wallet />
</li>
{menu.map((item: MenuItem) => ( {menu.map((item: MenuItem) => (
<MenuLink key={item.name} item={item} /> <li key={item.name}>
<MenuLink item={item} />
</li>
))} ))}
</ul>
</Container>
</nav> </nav>
) )
} }

View File

@ -4,6 +4,7 @@
} }
.title { .title {
font-size: var(--font-size-h2);
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -1,15 +1,23 @@
import React from 'react' import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import styles from './PageHeader.module.css' import styles from './PageHeader.module.css'
const cx = classNames.bind(styles)
export default function PageHeader({ export default function PageHeader({
title, title,
description description
}: { }: {
title: string title: string
description?: string description?: string
}) { center?: boolean
}): ReactElement {
const styleClasses = cx({
header: true
})
return ( return (
<header className={styles.header}> <header className={styleClasses}>
<h1 className={styles.title}>{title}</h1> <h1 className={styles.title}>{title}</h1>
{description && <p className={styles.description}>{description}</p>} {description && <p className={styles.description}>{description}</p>}
</header> </header>

View File

@ -39,8 +39,8 @@ export default function Pagination({
// adapt based on media query match // adapt based on media query match
marginPagesDisplayed={smallViewport ? 0 : 1} marginPagesDisplayed={smallViewport ? 0 : 1}
pageRangeDisplayed={smallViewport ? 3 : 6} pageRangeDisplayed={smallViewport ? 3 : 6}
onPageChange={data => onPageChange(data.selected)} onPageChange={(data) => onPageChange(data.selected)}
hrefBuilder={pageIndex => hrefBuilder(pageIndex)} hrefBuilder={(pageIndex) => hrefBuilder(pageIndex)}
disableInitialCallback disableInitialCallback
previousLabel="←" previousLabel="←"
nextLabel="→" nextLabel="→"

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useNavigate } from '@reach/router'
import Form from '../../molecules/Form/index' import Form from '../../molecules/Form/index'
import { import {
PublishFormSchema, PublishFormSchema,
@ -11,7 +12,6 @@ import { MetaDataMarket } from '../../../@types/MetaData'
import { File, MetaData } from '@oceanprotocol/squid' import { File, MetaData } from '@oceanprotocol/squid'
import { isBrowser, toStringNoMS } from '../../../utils' import { isBrowser, toStringNoMS } from '../../../utils'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import styles from './PublishForm.module.css' import styles from './PublishForm.module.css'
import utils from 'web3-utils' import utils from 'web3-utils'
import AssetModel from '../../../models/Asset' import AssetModel from '../../../models/Asset'
@ -21,8 +21,6 @@ import {
ServiceCompute ServiceCompute
} from '@oceanprotocol/squid/dist/node/ddo/Service' } from '@oceanprotocol/squid/dist/node/ddo/Service'
declare type PublishFormProps = {}
const FILES_DATA_LOCAL_STORAGE_KEY = 'filesData' const FILES_DATA_LOCAL_STORAGE_KEY = 'filesData'
const PUBLISH_FORM_LOCAL_STORAGE_KEY = 'publishForm' const PUBLISH_FORM_LOCAL_STORAGE_KEY = 'publishForm'
@ -80,8 +78,6 @@ export function transformPublishFormToMetadata(
copyrightHolder: holder, copyrightHolder: holder,
tags: keywords?.split(','), tags: keywords?.split(','),
termsAndConditions, termsAndConditions,
supportName,
supportEmail,
access: access || 'Download' access: access || 'Download'
}, },
// ------- curation ------- // ------- curation -------
@ -101,11 +97,11 @@ export function transformPublishFormToMetadata(
return metadata return metadata
} }
const PublishForm: React.FC<PublishFormProps> = () => { const PublishForm: React.FC<any> = () => {
const [buttonDisabled, setButtonDisabled] = useState(false) const [buttonDisabled, setButtonDisabled] = useState(false)
const { web3Connect } = useWeb3() const { web3Connect } = useWeb3()
const { ocean, account } = useOcean() const { ocean, account } = useOcean()
const router = useRouter() const navigate = useNavigate()
const [data, updateData] = useStoredValue( const [data, updateData] = useStoredValue(
PUBLISH_FORM_LOCAL_STORAGE_KEY, PUBLISH_FORM_LOCAL_STORAGE_KEY,
publishFormData publishFormData
@ -172,7 +168,7 @@ const PublishForm: React.FC<PublishFormProps> = () => {
className: styles.success className: styles.success
}) })
toast.dismiss(submittingToast) toast.dismiss(submittingToast)
router.push(`/asset/${asset.id}`) navigate(`/asset/${asset.id}`)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} finally { } finally {

View File

@ -2,7 +2,15 @@
margin-bottom: var(--spacer); margin-bottom: var(--spacer);
} }
.inputGroup .input { .inputGroup > div {
margin: 0;
}
.inputGroup label {
display: none;
}
.inputGroup input {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
@ -12,44 +20,23 @@
border-top-right-radius: 0; border-top-right-radius: 0;
margin-top: -1px; margin-top: -1px;
width: 100%; width: 100%;
border-width: 1px;
text-transform: uppercase;
color: var(--brand-grey-lighter);
background: var(--brand-gradient);
box-shadow: none;
} }
.inputGroup button:hover, .inputGroup button:hover,
.inputGroup button:focus, .inputGroup button:focus,
.inputGroup .input:focus + button:hover, .inputGroup input:focus + button:hover,
.inputGroup .input:focus + button:focus { .inputGroup input:focus + button:focus {
background: var(--brand-gradient); background: var(--brand-gradient);
transform: none; transform: none;
box-shadow: none; box-shadow: none;
} }
.inputGroup .input:focus + button {
color: var(--brand-white);
}
/* .inputGroup button:hover,
.inputGroup button:focus,
.inputGroup .input:focus + button {
color: var(--brand-white);
background-color: var(--brand-pink);
}
.inputGroup .input:focus + button {
border-color: var(--brand-pink);
background-color: var(--brand-pink);
} */
@media screen and (min-width: 30rem) { @media screen and (min-width: 30rem) {
.inputGroup { .inputGroup {
display: flex; display: flex;
} }
.inputGroup .input { .inputGroup input {
border-bottom-left-radius: var(--border-radius); border-bottom-left-radius: var(--border-radius);
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
@ -64,14 +51,3 @@
width: auto; width: auto;
} }
} }
.input {
composes: input from './Form/FieldTemplate.module.css';
}
.large {
composes: large from './Form/FieldTemplate.module.css';
}
.filters {
}

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect, ChangeEvent, FormEvent } from 'react' import React, { useState, ChangeEvent, FormEvent, ReactElement } from 'react'
import { useRouter } from 'next/router' import { useNavigate } from '@reach/router'
import styles from './SearchBar.module.css' import styles from './SearchBar.module.css'
import Loader from '../atoms/Loader' import Loader from '../atoms/Loader'
import Button from '../atoms/Button' import Button from '../atoms/Button'
import Input from '../atoms/Input'
export default function SearchBar({ export default function SearchBar({
placeholder, placeholder,
@ -14,8 +15,8 @@ export default function SearchBar({
initialValue?: string initialValue?: string
filters?: boolean filters?: boolean
large?: true large?: true
}) { }): ReactElement {
const router = useRouter() const navigate = useNavigate()
const [value, setValue] = useState(initialValue || '') const [value, setValue] = useState(initialValue || '')
const [searchStarted, setSearchStarted] = useState(false) const [searchStarted, setSearchStarted] = useState(false)
@ -29,29 +30,18 @@ export default function SearchBar({
if (value === '') return if (value === '') return
setSearchStarted(true) setSearchStarted(true)
router.push(`/search?text=${value}`) navigate(`/search?text=${value}`)
} }
useEffect(() => {
// fix for storybook
if (!router) return
router.events.on('routeChangeComplete', () => setSearchStarted(false))
return () => {
router.events.off('routeChangeComplete', () => setSearchStarted(false))
}
}, [])
return ( return (
<form className={styles.form}> <form className={styles.form}>
<div className={styles.inputGroup}> <div className={styles.inputGroup}>
<input <Input
type="search" type="search"
className={large ? `${styles.input} ${styles.large}` : styles.input} name="search"
placeholder={placeholder || 'What are you looking for?'} placeholder={placeholder || 'What are you looking for?'}
value={value} value={value}
onChange={e => handleChange(e)} onChange={handleChange}
required required
/> />
<Button onClick={(e: FormEvent<HTMLButtonElement>) => startSearch(e)}> <Button onClick={(e: FormEvent<HTMLButtonElement>) => startSearch(e)}>

View File

@ -1,7 +1,3 @@
.input {
composes: input from './Form/FieldTemplate.module.css';
}
.layout { .layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,8 +1,9 @@
import React, { ChangeEvent } from 'react' import React, { ChangeEvent, ReactElement } from 'react'
import SearchFilterSection from '../atoms/SearchFilterSection' import SearchFilterSection from '../atoms/SearchFilterSection'
import usePriceQueryParams from '../../hooks/usePriceQueryParams' import usePriceQueryParams from '../../hooks/usePriceQueryParams'
import styles from './SearchPriceFilter.module.css' import styles from './SearchPriceFilter.module.css'
import Input from '../atoms/Input'
export declare type PriceInputProps = { export declare type PriceInputProps = {
label: string label: string
@ -16,20 +17,16 @@ export const PriceInput = ({
value, value,
onChange, onChange,
text text
}: PriceInputProps) => { }: PriceInputProps): ReactElement => {
return ( return (
<label htmlFor={label} className={styles.label}> <Input
<input
id={label}
name={label} name={label}
label={text}
type="number" type="number"
min="0" min="0"
value={value} value={value}
onChange={onChange} onChange={onChange}
className={styles.input}
/> />
<span>{text}</span>
</label>
) )
} }
@ -42,13 +39,13 @@ export const SearchPriceFilter = () => {
<PriceInput <PriceInput
label="minPrice" label="minPrice"
value={min} value={min}
onChange={ev => setMin(ev.target.value)} onChange={(ev) => setMin(ev.target.value)}
text="Min price" text="Min price"
/> />
<PriceInput <PriceInput
label="maxPrice" label="maxPrice"
value={max} value={max}
onChange={ev => setMax(ev.target.value)} onChange={(ev) => setMax(ev.target.value)}
text="Max price" text="Max price"
/> />
</div> </div>

View File

@ -0,0 +1,58 @@
.button {
font-family: var(--font-family-base);
font-weight: var(--font-weight-bold);
font-size: var(--font-size-small);
text-transform: uppercase;
border: 1px solid var(--brand-grey-lighter);
border-radius: var(--border-radius);
padding: calc(var(--spacer) / 4);
white-space: nowrap;
background: none;
color: var(--brand-grey);
margin: 0;
transition: border 0.2s ease-out;
cursor: pointer;
}
.button:hover,
.button:focus {
transform: none;
outline: 0;
border-color: var(--brand-grey-light);
}
.blockies {
width: var(--font-size-large);
height: var(--font-size-large);
border-radius: 50%;
overflow: hidden;
display: inline-block;
vertical-align: middle;
margin-right: calc(var(--spacer) / 6);
}
.address {
text-transform: none;
border-right: 1px solid var(--brand-grey-lighter);
padding-right: calc(var(--spacer) / 4);
}
.button svg {
width: 1em;
height: 1em;
fill: var(--brand-grey-lighter);
display: inline-block;
vertical-align: middle;
margin-left: calc(var(--spacer) / 4);
transition: transform 0.2s ease-out;
}
.wallet[aria-expanded='true'] .button svg {
transform: rotate(180deg);
}
.status {
margin-left: calc(var(--spacer) / 4);
position: relative;
top: 1px;
}

View File

@ -0,0 +1,59 @@
import React from 'react'
import styles from './Account.module.css'
import { useWeb3, useOcean } from '@oceanprotocol/react'
import { toDataUrl } from 'ethereum-blockies'
import { ReactComponent as Caret } from '../../../images/caret.svg'
import Status from '../../atoms/Status'
function accountTruncate(account: string) {
const middle = account.substring(6, 38)
const truncated = account.replace(middle, '…')
return truncated
}
const Blockies = ({ account }: { account: string | undefined }) => {
if (!account) return null
const blockies = toDataUrl(account)
return (
<img
className={styles.blockies}
src={blockies}
alt="Blockies"
aria-hidden="true"
/>
)
}
// Forward ref for Tippy.js
// eslint-disable-next-line
const Account = React.forwardRef((props, ref: any) => {
const { account, web3Connect, ethProviderStatus } = useWeb3()
const { status } = useOcean()
const hasSuccess = ethProviderStatus === 1 && status === 1
return account ? (
<button className={styles.button} aria-label="Account" ref={ref}>
<Blockies account={account} />
<span className={styles.address} title={account}>
{accountTruncate(account)}
</span>
{!hasSuccess && (
<Status className={styles.status} state="warning" aria-hidden />
)}
<Caret aria-hidden="true" />
</button>
) : (
<button
className={styles.button}
onClick={() => web3Connect.connect()}
// Need the `ref` here although we do not want
// the Tippy to show in this state.
ref={ref}
>
Activate Wallet
</button>
)
})
export default Account

View File

@ -0,0 +1,53 @@
.details {
composes: box from '../../atoms/Box.module.css';
padding: calc(var(--spacer) / 2) !important;
min-width: 20rem;
max-width: 25rem;
}
.details > ul {
margin-bottom: calc(var(--spacer) / 4);
}
.balance {
color: var(--color-secondary);
white-space: nowrap;
font-size: var(--font-size-small);
}
.balance span {
font-size: var(--font-size-base);
font-weight: var(--font-weight-bold);
display: inline-block;
margin-left: 0.1rem;
}
.arrow,
.arrow::before {
position: absolute;
width: 1.2rem;
height: 1.2rem;
z-index: -1;
}
.arrow::before {
content: '';
transform: rotate(45deg);
background: var(--brand-grey-lighter);
}
.details[data-placement*='top'] > .arrow {
bottom: -4px;
}
.details[data-placement*='bottom'] > .arrow {
top: -4px;
}
.details[data-placement*='left'] > .arrow {
right: -4px;
}
.details[data-placement*='right'] > .arrow {
left: -4px;
}

View File

@ -0,0 +1,37 @@
import React, { ReactElement } from 'react'
import Button from '../../atoms/Button'
import styles from './Details.module.css'
import { useWeb3, useOcean } from '@oceanprotocol/react'
import Web3Feedback from './Feedback'
import { formatNumber } from '../../../utils'
export default function Details({ attrs }: { attrs: any }): ReactElement {
const { balance, web3Connect } = useWeb3()
const { balanceInOcean } = useOcean()
const ethBalanceText = formatNumber(Number(balance))
const oceanBalanceText = formatNumber(Number(balanceInOcean))
return (
<div className={styles.details} {...attrs}>
<ul>
<li className={styles.balance}>
OCEAN <span>{oceanBalanceText}</span>
</li>
<li className={styles.balance}>
ETH <span>{ethBalanceText}</span>
</li>
<li>
<Button
style="text"
size="small"
onClick={() => web3Connect.toggleModal()}
>
Switch Wallet
</Button>
</li>
</ul>
<Web3Feedback />
<div className={styles.arrow} data-popper-arrow />
</div>
)
}

View File

@ -1,27 +1,17 @@
.feedback { .feedback {
font-size: var(--font-size-small); font-size: var(--font-size-small);
display: flex; padding-left: var(--spacer);
max-width: 100%; padding-top: calc(var(--spacer) / 2);
padding-left: calc(var(--spacer) / 1.5); margin-top: calc(var(--spacer) / 2);
border-top: 1px solid var(--brand-grey-lighter);
position: relative; position: relative;
width: 100%; width: 100%;
} }
.statuscontainer {
flex-grow: 1;
}
.statuscontainer h3 {
margin-top: 0;
}
.walletcontainer {
width: 35%;
}
.feedback i { .feedback i {
position: absolute; position: absolute;
left: 0; left: 0;
top: 4px; top: calc(var(--spacer) / 2);
} }
.title { .title {
@ -30,7 +20,6 @@
} }
.error { .error {
font-style: italic;
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--color-secondary); color: var(--color-secondary);
margin-bottom: 0; margin-bottom: 0;

View File

@ -1,13 +1,7 @@
import React from 'react' import React, { ReactElement } from 'react'
import Status from '../../atoms/Status' import Status from '../../atoms/Status'
import Wallet from './Wallet' import styles from './Feedback.module.css'
import styles from './index.module.css' import { useWeb3, useOcean } from '@oceanprotocol/react'
import {
useWeb3,
useOcean,
InjectedProviderStatus,
OceanConnectionStatus
} from '@oceanprotocol/react'
export declare type Web3Error = { export declare type Web3Error = {
status: 'error' | 'warning' | 'success' status: 'error' | 'warning' | 'success'
@ -19,9 +13,9 @@ export default function Web3Feedback({
isBalanceInsufficient isBalanceInsufficient
}: { }: {
isBalanceInsufficient?: boolean isBalanceInsufficient?: boolean
}) { }): ReactElement {
const { ethProviderStatus } = useWeb3() const { ethProviderStatus } = useWeb3()
const { status, balanceInOcean } = useOcean() const { status } = useOcean()
const isEthProviderAbsent = ethProviderStatus === -1 const isEthProviderAbsent = ethProviderStatus === -1
const isEthProviderDisconnected = ethProviderStatus === 0 const isEthProviderDisconnected = ethProviderStatus === 0
const isOceanDisconnected = status === 0 const isOceanDisconnected = status === 0
@ -60,18 +54,11 @@ export default function Web3Feedback({
? 'You do not have enough OCEAN in your wallet to purchase this asset.' ? 'You do not have enough OCEAN in your wallet to purchase this asset.'
: 'Something went wrong.' : 'Something went wrong.'
return ( return !hasSuccess ? (
<section className={styles.feedback}> <section className={styles.feedback}>
<div className={styles.statuscontainer}>
<Status state={state} aria-hidden /> <Status state={state} aria-hidden />
<h3 className={styles.title}>{title}</h3> <h3 className={styles.title}>{title}</h3>
{!hasSuccess && <p className={styles.error}>{message}</p>} <p className={styles.error}>{message}</p>
</div>
{!isEthProviderAbsent && (
<div className={styles.walletcontainer}>
<Wallet balanceOcean={balanceInOcean} />
</div>
)}
</section> </section>
) ) : null
} }

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import Web3Feedback from '.' import Web3Feedback from './Feedback'
import web3Mock from '../../../../tests/unit/__mocks__/web3' import web3Mock from '../../../../tests/unit/__mocks__/web3'
import web3ProviderMock, { import web3ProviderMock, {
context context

View File

@ -0,0 +1,62 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring'
import { useWeb3 } from '@oceanprotocol/react'
import Account from './Account'
import Details from './Details'
import styles from './index.module.css'
const Tippy = loadable(() => import('@tippyjs/react/headless'))
const animation = {
config: { tension: 400, friction: 20 },
from: { transform: 'scale(0.5) translateY(-3rem)' },
to: { transform: 'scale(1) translateY(0)' }
}
export default function Wallet(): ReactElement {
const { account, ethProviderStatus } = useWeb3()
const [props, setSpring] = useSpring(() => animation.from)
function onMount() {
setSpring({
transform: 'scale(1) translateY(0)',
onRest: (): void => null,
config: animation.config
})
}
function onHide({ unmount }: { unmount: any }) {
setSpring({
...animation.from,
onRest: unmount,
config: { ...animation.config, clamp: true }
})
}
const isEthProviderAbsent = ethProviderStatus === -1
if (isEthProviderAbsent) return null
return (
<Tippy
interactive
interactiveBorder={30}
trigger="click focus"
render={(attrs: any) => (
<animated.div style={props}>
<Details attrs={attrs} />
</animated.div>
)}
appendTo={
typeof document !== 'undefined' && document.querySelector('body')
}
animation
onMount={onMount}
onHide={onHide}
disabled={!account}
fallback={<Account />}
>
<Account />
</Tippy>
)
}

View File

@ -1,18 +0,0 @@
.wallet {
text-align: right;
}
.address {
font-family: var(--font-family-monospace);
}
.balance {
color: var(--color-secondary);
white-space: nowrap;
}
.balance span {
font-weight: var(--font-weight-bold);
display: inline-block;
margin-left: 0.2rem;
}

View File

@ -1,35 +0,0 @@
import React from 'react'
import styles from './Wallet.module.css'
import Button from '../../atoms/Button'
import { formatNumber } from '../../../utils'
import { useWeb3 } from '@oceanprotocol/react'
const Wallet = ({ balanceOcean }: { balanceOcean: string }) => {
const { account, balance, web3Connect } = useWeb3()
const ethBalanceText = formatNumber(Number(balance))
const oceanBalanceText = formatNumber(Number(balanceOcean))
return (
<div className={styles.wallet}>
{account ? (
<ul>
<li className={styles.address} title={account}>
{`${account.substring(0, 12)}...`}
</li>
<li className={styles.balance} title="OCEAN">
<span>{oceanBalanceText}</span>
</li>
<li className={styles.balance} title="ETH">
Ξ <span>{ethBalanceText}</span>
</li>
</ul>
) : (
<Button link onClick={() => web3Connect.connect()}>
Activate Wallet
</Button>
)}
</div>
)
}
export default Wallet

View File

@ -0,0 +1,27 @@
.compute {
display: flex;
flex-wrap: wrap;
}
.jobButtonWrapper {
margin-top: var(--spacer);
}
.feedback {
width: 100%;
}
.compute button {
margin-left: var(--spacer);
}
.compute button:first {
margin-left: 0px;
}
.info {
margin-top: var(--spacer);
display: flex;
flex-direction: column;
width: 100%;
}

Some files were not shown because too many files have changed in this diff Show More