Merge pull request #28 from oceanprotocol/feature/gatsby
refactor for Gatsby, update all the things
@ -1,6 +1,7 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"env": { "es6": true, "browser": true, "node": true },
|
||||
"env": { "es6": true, "browser": true, "node": true, "jest": true },
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
@ -26,6 +27,7 @@
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"rules": {
|
||||
"react/prop-types": "off",
|
||||
"react/no-unused-prop-types": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off"
|
||||
}
|
||||
}
|
||||
|
2
.github/CODEOWNERS
vendored
@ -1 +1 @@
|
||||
* @maxieprotocol @kremalicious @pfmescher @unjapones
|
||||
* @mihaisc @kremalicious
|
5
.gitignore
vendored
@ -1,11 +1,12 @@
|
||||
node_modules
|
||||
out
|
||||
.DS_Store
|
||||
.next
|
||||
.idea
|
||||
.env
|
||||
.env.build
|
||||
coverage
|
||||
dist
|
||||
public
|
||||
.cache
|
||||
storybook-static
|
||||
public/storybook
|
||||
.artifacts
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2
|
||||
}
|
@ -1,7 +1,3 @@
|
||||
module.exports = {
|
||||
stories: [
|
||||
'../src/components/**/*.stories.tsx',
|
||||
'../src/styles/**/*.stories.tsx'
|
||||
],
|
||||
addons: []
|
||||
stories: ['../src/**/*.stories.tsx', '../tests/**/*.stories.tsx']
|
||||
}
|
||||
|
@ -1,27 +1,43 @@
|
||||
import React from 'react'
|
||||
import { addDecorator } from '@storybook/react'
|
||||
import WebFont from 'webfontloader'
|
||||
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: ['Montserrat:400,400i,600']
|
||||
}
|
||||
})
|
||||
import {
|
||||
createHistory,
|
||||
createMemorySource,
|
||||
LocationProvider
|
||||
} from '@reach/router'
|
||||
|
||||
// Import global css with custom properties once for all stories.
|
||||
// Needed because in Next.js we impoprt that file only once too,
|
||||
// in src/_app.tsx which does not get loaded by Storybook
|
||||
import '../src/styles/global.css'
|
||||
import '../src/global/styles.css'
|
||||
|
||||
// Wrapper for all stories previews
|
||||
addDecorator(storyFn => (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
width: '100%',
|
||||
padding: '2rem'
|
||||
}}
|
||||
>
|
||||
{storyFn()}
|
||||
</div>
|
||||
const history = createHistory(createMemorySource('/'))
|
||||
|
||||
addDecorator((storyFn) => (
|
||||
<LocationProvider history={history}>
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
width: '100%',
|
||||
padding: '2rem'
|
||||
}}
|
||||
>
|
||||
{storyFn()}
|
||||
</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)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// https://www.gatsbyjs.org/docs/visual-testing-with-storybook/
|
||||
|
||||
// Make CSS modules work
|
||||
// https://github.com/storybookjs/storybook/issues/4306#issuecomment-517951264
|
||||
const setCssModulesRule = rule => {
|
||||
const setCssModulesRule = (rule) => {
|
||||
const nextRule = rule
|
||||
const cssLoader = rule.use[1]
|
||||
|
||||
@ -15,8 +17,8 @@ const setCssModulesRule = rule => {
|
||||
return nextRule
|
||||
}
|
||||
|
||||
module.exports = async ({ config, mode }) => {
|
||||
const cssRules = config.module.rules.map(rule => {
|
||||
module.exports = ({ config }) => {
|
||||
const cssRules = config.module.rules.map((rule) => {
|
||||
const isCssRule = rule.test.toString().indexOf('css') !== -1
|
||||
let nextRule = rule
|
||||
|
||||
@ -28,28 +30,51 @@ module.exports = async ({ config, mode }) => {
|
||||
|
||||
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({
|
||||
test: /\.(ts|tsx)$/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
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')
|
||||
|
||||
// 'fs' fix for squid.js
|
||||
config.node = {
|
||||
fs: 'empty'
|
||||
}
|
||||
|
||||
// Handle SVGs
|
||||
// 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')) {
|
||||
const test = rule.test
|
||||
.toString()
|
||||
.replace('svg|', '')
|
||||
.replace(/\//g, '')
|
||||
const test = rule.test.toString().replace('svg|', '').replace(/\//g, '')
|
||||
return { ...rule, test: new RegExp(test) }
|
||||
} else {
|
||||
return rule
|
||||
@ -59,7 +84,19 @@ module.exports = async ({ config, mode }) => {
|
||||
// Use SVG Configuration for SVGR yourself
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack']
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
svgoConfig: {
|
||||
plugins: {
|
||||
removeViewBox: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'url-loader'
|
||||
]
|
||||
})
|
||||
|
||||
return config
|
||||
|
27
.travis.yml
@ -4,33 +4,16 @@ node_js: node
|
||||
|
||||
cache:
|
||||
npm: true
|
||||
directories:
|
||||
- .next/cache
|
||||
|
||||
before_script:
|
||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
- chmod +x ./cc-test-reporter
|
||||
- './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'
|
||||
# - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
# - chmod +x ./cc-test-reporter
|
||||
# - './cc-test-reporter before-build'
|
||||
|
||||
script:
|
||||
# will run `npm ci` automatically here
|
||||
# - ./scripts/keeper.sh
|
||||
- npm test
|
||||
- './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
|
||||
- npm run lint
|
||||
# - './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
|
||||
- npm run build
|
||||
|
||||
notifications:
|
||||
|
81
README.md
@ -2,8 +2,6 @@
|
||||
|
||||
<h1 align="center">Ocean Marketplace</h1>
|
||||
|
||||
>
|
||||
|
||||
[![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)
|
||||
[![Maintainability](https://api.codeclimate.com/v1/badges/d114f94f75e6efd2ee71/maintainability)](https://codeclimate.com/repos/5e3933869a31771fd800011c/maintainability)
|
||||
@ -15,6 +13,7 @@
|
||||
- [🤓 Resources](#-resources)
|
||||
- [🏄 Get Started](#-get-started)
|
||||
- [Local Spree components with Barge](#local-spree-components-with-barge)
|
||||
- [API](#api)
|
||||
- [🦑 Environment variables](#-environment-variables)
|
||||
- [🎨 Storybook](#-storybook)
|
||||
- [✨ Code Style](#-code-style)
|
||||
@ -27,30 +26,25 @@
|
||||
|
||||
## 🤓 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
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:oceanprotocol/dexfreight.git
|
||||
cd dexfreight
|
||||
git clone git@github.com:oceanprotocol/market.git
|
||||
cd market
|
||||
|
||||
npm install
|
||||
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:
|
||||
|
||||
```
|
||||
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
```
|
||||
To explore the generated GraphQL data structure fire up the accompanying GraphiQL IDE under
|
||||
`http://localhost:8000/__graphql`.
|
||||
|
||||
### 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/`.
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
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
|
||||
# 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](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:
|
||||
|
||||
- [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
|
||||
|
||||
> 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.
|
||||
- [node-mocks-http](https://github.com/howardabrams/node-mocks-http) for all `api/` routes
|
||||
|
||||
To run all linting and unit tests:
|
||||
|
||||
@ -164,7 +153,7 @@ npm run serve
|
||||
|
||||
## ⬆️ 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`.
|
||||
|
||||
@ -174,47 +163,47 @@ If needed, app can be deployed manually. Make sure to switch to Ocean Protocol o
|
||||
|
||||
```bash
|
||||
# first run
|
||||
now login
|
||||
now switch
|
||||
vercel login
|
||||
vercel switch
|
||||
|
||||
# deploy
|
||||
now
|
||||
vercel
|
||||
# switch alias to new deployment
|
||||
now alias
|
||||
vercel alias
|
||||
```
|
||||
|
||||
## 🏗 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)**
|
||||
|
||||
- K8 namespace: `dexfreight-nile`
|
||||
- `aquarius.nile.dexfreight.dev-ocean.com`
|
||||
- `brizo.nile.dexfreight.dev-ocean.com`
|
||||
- K8 namespace: `market-nile`
|
||||
- `aquarius.nile.market.dev-ocean.com`
|
||||
- `brizo.nile.market.dev-ocean.com`
|
||||
|
||||
Edit command with `kubectl`, e.g.:
|
||||
|
||||
```bash
|
||||
kubectl edit deployment -n dexfreight-nile aquarius
|
||||
kubectl edit deployment -n market-nile aquarius
|
||||
```
|
||||
|
||||
**Pacific (Production)**
|
||||
|
||||
- K8 namespace: `dexfreight-pacific`
|
||||
- `aquarius.pacific.dexfreight.dev-ocean.com`
|
||||
- `brizo.pacific.dexfreight.dev-ocean.com`
|
||||
- K8 namespace: `market-pacific`
|
||||
- `aquarius.pacific.market.dev-ocean.com`
|
||||
- `brizo.pacific.market.dev-ocean.com`
|
||||
|
||||
Edit command with `kubectl`, e.g.:
|
||||
|
||||
```bash
|
||||
kubectl edit deployment -n dexfreight-pacific aquarius
|
||||
kubectl edit deployment -n market-pacific aquarius
|
||||
```
|
||||
|
||||
## 🏛 License
|
||||
|
||||
```text
|
||||
Copyright 2019 Ocean Protocol Foundation Ltd.
|
||||
Copyright 2020 Ocean Protocol Foundation Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { NowRequest, NowResponse } from '@now/node'
|
||||
import axios, { AxiosResponse } from 'axios'
|
||||
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) {
|
||||
case 'POST':
|
||||
res.status(200).json(await checkUrl(req.body.url))
|
@ -1,12 +1,12 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { NowRequest, NowResponse } from '@now/node'
|
||||
import axios, { AxiosResponse } from 'axios'
|
||||
import siteConfig from '../../../site.config'
|
||||
import siteConfig from '../content/site.json'
|
||||
|
||||
async function redeploy(
|
||||
req: NextApiRequest
|
||||
req: NowRequest
|
||||
): Promise<AxiosResponse | undefined | string> {
|
||||
// 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)
|
||||
try {
|
||||
// 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) {
|
||||
case 'POST':
|
||||
res.status(200).json(await redeploy(req))
|
19
app.config.js
Normal 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
|
||||
}
|
||||
}
|
4
content/pages/history.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "History",
|
||||
"description": "Find the data sets and jobs that you previously accessed."
|
||||
}
|
4
content/pages/publish.json
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,5 @@
|
||||
import wrapPageElementWithStyles from './src/helpers/wrapPageElement'
|
||||
import wrapRootElementWithProviders from './src/helpers/wrapRootElement'
|
||||
|
||||
export const wrapPageElement = wrapPageElementWithStyles
|
||||
export const wrapRootElement = wrapRootElementWithProviders
|
@ -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
@ -1,2 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
109
next.config.js
@ -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
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
33785
package-lock.json
generated
119
package.json
@ -1,92 +1,113 @@
|
||||
{
|
||||
"name": "market",
|
||||
"name": "@oceanprotocol/market",
|
||||
"description": "Data marketplace for ocean.",
|
||||
"version": "0.0.1",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://oceanprotocol.com",
|
||||
"scripts": {
|
||||
"start": "next dev",
|
||||
"export": "next export",
|
||||
"build": "npm run storybook:build && next build",
|
||||
"serve": "next start",
|
||||
"start": "gatsby clean && gatsby develop --host 0.0.0.0",
|
||||
"build": "gatsby clean && gatsby build",
|
||||
"serve": "serve -s public/",
|
||||
"jest": "NODE_ENV=test jest -c tests/unit/jest.config.js",
|
||||
"test": "npm run lint && npm run jest",
|
||||
"test:watch": "npm run lint && npm run jest -- --watch",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .",
|
||||
"format": "prettier --ignore-path .gitignore **/**/*.{css,yml,js,jsx,ts,tsx,json} --write",
|
||||
"analyze": "ANALYZE=true next build",
|
||||
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
|
||||
"type-check": "tsc --noEmit",
|
||||
"analyze": "npm run build && source-map-explorer 'public/*.js'",
|
||||
"storybook": "start-storybook -p 4000 -c .storybook",
|
||||
"storybook:build": "build-storybook -c .storybook -o public/storybook"
|
||||
},
|
||||
"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/squid": "^2.2.0",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
"@tippyjs/react": "^4.0.2",
|
||||
"@tippyjs/react": "^4.1.0",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"axios": "^0.19.2",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.11.0",
|
||||
"date-fns": "^2.14.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"filesize": "^6.1.0",
|
||||
"is-url-superb": "^3.0.0",
|
||||
"next": "^9.3.2",
|
||||
"next-seo": "^4.4.0",
|
||||
"next-svgr": "^0.0.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"gatsby": "^2.23.22",
|
||||
"gatsby-image": "^2.4.12",
|
||||
"gatsby-plugin-manifest": "^2.4.17",
|
||||
"gatsby-plugin-react-helmet": "^3.3.9",
|
||||
"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",
|
||||
"query-string": "^6.13.1",
|
||||
"react": "^16.13.1",
|
||||
"react-data-table-component": "^6.9.2",
|
||||
"react-datepicker": "^2.14.0",
|
||||
"react-data-table-component": "^6.9.6",
|
||||
"react-datepicker": "^3.0.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-dropzone": "^11.0.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-jsonschema-form": "^1.8.1",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-paginate": "^6.3.2",
|
||||
"react-rating": "^2.0.4",
|
||||
"react-rating": "^2.0.5",
|
||||
"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",
|
||||
"slugify": "^1.4.0",
|
||||
"use-debounce": "^3.4.0",
|
||||
"slugify": "^1.4.4",
|
||||
"web3connect": "^1.0.0-beta.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.7",
|
||||
"@next/bundle-analyzer": "^9.3.0",
|
||||
"@storybook/addon-storyshots": "^5.3.17",
|
||||
"@storybook/react": "^5.3.17",
|
||||
"@testing-library/jest-dom": "^5.1.1",
|
||||
"@testing-library/react": "^10.0.1",
|
||||
"@testing-library/react-hooks": "^3.2.1",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.9.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/numeral": "0.0.26",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-datepicker": "^2.11.0",
|
||||
"@types/react-jsonschema-form": "^1.7.0",
|
||||
"@babel/core": "^7.10.3",
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@storybook/addon-actions": "^6.0.0-beta.45",
|
||||
"@storybook/addon-storyshots": "^6.0.0-beta.45",
|
||||
"@storybook/react": "^6.0.0-beta.45",
|
||||
"@svgr/webpack": "^5.4.0",
|
||||
"@testing-library/jest-dom": "^5.11.0",
|
||||
"@testing-library/react": "^10.4.4",
|
||||
"@types/jest": "^26.0.4",
|
||||
"@types/loadable__component": "^5.10.0",
|
||||
"@types/node": "^14.0.19",
|
||||
"@types/numeral": "^0.0.28",
|
||||
"@types/react": "^16.9.41",
|
||||
"@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-tabs": "^2.3.2",
|
||||
"@types/shortid": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
||||
"@typescript-eslint/parser": "^2.23.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-preset-react-app": "^9.1.1",
|
||||
"eslint": "^6.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.6.0",
|
||||
"@typescript-eslint/parser": "^3.6.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-preset-react-app": "^9.1.2",
|
||||
"electron": "^9.1.0",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^26.1.0",
|
||||
"node-mocks-http": "^1.8.1",
|
||||
"prettier": "^1.19.1",
|
||||
"react-test-renderer": "^16.12.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.8.3",
|
||||
"webfontloader": "^1.6.28"
|
||||
"prettier": "^2.0.5",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"serve": "^11.3.2",
|
||||
"source-map-explorer": "^2.4.2",
|
||||
"typescript": "^3.9.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
42
plugins/gatsby-source-ocean/gatsby-node.js
Normal 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)
|
||||
}
|
||||
}
|
3
plugins/gatsby-source-ocean/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "gatsby-source-ocean"
|
||||
}
|
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.2 KiB |
@ -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'
|
||||
}
|
||||
]
|
||||
}
|
26
src/@types/MetaData.d.ts
vendored
@ -1,15 +1,5 @@
|
||||
import { MetaData, AdditionalInformation, Curation } from '@oceanprotocol/squid'
|
||||
|
||||
declare type DeliveryType = 'files' | 'api' | 'subscription'
|
||||
|
||||
declare type Granularity =
|
||||
| 'hourly'
|
||||
| 'daily'
|
||||
| 'weekly'
|
||||
| 'monthly'
|
||||
| 'annually'
|
||||
| 'Not updated periodically'
|
||||
| ''
|
||||
import { MetaData, AdditionalInformation } from '@oceanprotocol/squid'
|
||||
import { ServiceMetadata } from '@oceanprotocol/squid/dist/node/ddo/Service'
|
||||
|
||||
export interface Sample {
|
||||
name: string
|
||||
@ -21,14 +11,20 @@ export declare type AccessType = 'Download' | 'Compute'
|
||||
export interface AdditionalInformationMarket extends AdditionalInformation {
|
||||
description: string
|
||||
links?: Sample[] // redefine existing key, cause not specific enough in Squid
|
||||
deliveryType: DeliveryType
|
||||
termsAndConditions: boolean
|
||||
dateRange?: [string, string]
|
||||
supportName?: string
|
||||
supportEmail?: string
|
||||
access: AccessType
|
||||
}
|
||||
|
||||
export interface MetaDataMarket extends MetaData {
|
||||
additionalInformation: AdditionalInformationMarket
|
||||
}
|
||||
|
||||
export interface ServiceMetaDataMarket extends ServiceMetadata {
|
||||
attributes: MetaDataMarket
|
||||
}
|
||||
|
||||
// type for assets pulled into GraphQL
|
||||
export interface OceanAsset extends MetaDataMarket {
|
||||
did: DID
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react'
|
||||
export const ReactComponent: React.FunctionComponent<React.SVGProps<
|
||||
@ -7,5 +12,4 @@ declare module '*.svg' {
|
||||
export default src
|
||||
}
|
||||
|
||||
declare type Nullable<T> = T | null
|
||||
declare module '*.md'
|
||||
declare module '*.gif'
|
5
src/@types/node_modules.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'intersection-observer'
|
||||
|
||||
declare module 'ethereum-blockies' {
|
||||
export function toDataUrl(address: string): string
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -12,10 +12,7 @@
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: calc(var(--spacer) * 2) calc(var(--spacer) / 1.5);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--layout-max-width);
|
||||
padding: calc(var(--spacer) * 2) 0;
|
||||
|
||||
/* sticky footer technique */
|
||||
flex: 1;
|
47
src/components/Layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -6,6 +6,12 @@
|
||||
border-left-width: 0.5rem;
|
||||
border-top-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,
|
||||
@ -14,32 +20,33 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-large);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: calc(var(--spacer) / 8);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 0;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
/* States */
|
||||
.error {
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
border-color: var(--rbrand-alert-ed);
|
||||
color: var(--brand-alert-red);
|
||||
}
|
||||
|
||||
.success {
|
||||
border-color: var(--green);
|
||||
color: var(--green);
|
||||
border-color: var(--brand-alert-green);
|
||||
color: var(--brand-alert-green);
|
||||
}
|
||||
|
||||
.info {
|
||||
border-color: var(--yellow);
|
||||
color: var(--yellow);
|
||||
border-color: var(--brand-alert-yellow);
|
||||
color: var(--brand-alert-yellow);
|
||||
}
|
||||
|
||||
.warning {
|
||||
border-color: var(--orange);
|
||||
color: var(--orange);
|
||||
border-color: var(--brand-alert-orange);
|
||||
color: var(--brand-alert-orange);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Alert.module.css'
|
||||
|
||||
export function Alert({
|
||||
@ -6,13 +6,13 @@ export function Alert({
|
||||
text,
|
||||
state
|
||||
}: {
|
||||
title: string
|
||||
title?: string
|
||||
text: string
|
||||
state: 'error' | 'warning' | 'info' | 'success'
|
||||
}) {
|
||||
}): ReactElement {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,14 +1,16 @@
|
||||
.box {
|
||||
display: block;
|
||||
background: var(--brand-white);
|
||||
padding: var(--spacer);
|
||||
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:focus {
|
||||
outline: 0;
|
||||
border-color: var(--brand-pink);
|
||||
/* box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); */
|
||||
/* transform: translate3d(0, -2px, 0); */
|
||||
transform: translate3d(0, -0.1rem, 0);
|
||||
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
background: var(--brand-grey-light);
|
||||
box-shadow: 0 9px 18px 0 rgba(0, 0, 0, 0.1);
|
||||
user-select: none;
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:first-child {
|
||||
@ -57,21 +56,28 @@
|
||||
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-radius: 0;
|
||||
outline: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
background: 0;
|
||||
padding: 0;
|
||||
color: var(--brand-pink);
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.link:hover {
|
||||
background: none;
|
||||
color: var(--brand-pink);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Size Modifiers */
|
||||
.small {
|
||||
|
@ -1,14 +1,57 @@
|
||||
import React from 'react'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import Button from './Button'
|
||||
import { Center } from '../../../.storybook/helpers'
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Button',
|
||||
decorators: [(storyFn: any) => <Center>{storyFn()}</Center>]
|
||||
title: 'Atoms/Button'
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
)
|
||||
|
@ -1,44 +1,53 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Link from 'next/link'
|
||||
import React, { ReactNode, FormEvent, ReactElement } from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './Button.module.css'
|
||||
|
||||
declare type ButtonProps = {
|
||||
children: string | ReactElement
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
interface ButtonProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
primary?: boolean
|
||||
link?: boolean
|
||||
href?: string
|
||||
size?: string
|
||||
onClick?: any
|
||||
onClick?: (e: FormEvent) => void
|
||||
disabled?: boolean
|
||||
to?: string
|
||||
name?: string
|
||||
size?: 'small'
|
||||
style?: 'primary' | 'ghost' | 'text'
|
||||
type?: 'submit'
|
||||
download?: boolean
|
||||
}
|
||||
|
||||
const Button = ({
|
||||
primary,
|
||||
link,
|
||||
export default function Button({
|
||||
href,
|
||||
size,
|
||||
children,
|
||||
className,
|
||||
to,
|
||||
size,
|
||||
style,
|
||||
...props
|
||||
}: ButtonProps) => {
|
||||
const classes = primary
|
||||
? `${styles.button} ${styles.primary}`
|
||||
: link
|
||||
? `${styles.button} ${styles.link}`
|
||||
: styles.button
|
||||
}: ButtonProps): ReactElement {
|
||||
const styleClasses = cx({
|
||||
button: true,
|
||||
primary: style === 'primary',
|
||||
ghost: style === 'ghost',
|
||||
text: style === 'text',
|
||||
small: size === 'small',
|
||||
[className]: className
|
||||
})
|
||||
|
||||
return href ? (
|
||||
<Link href={href}>
|
||||
<a className={`${classes} ${className}`} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
return to ? (
|
||||
<Link to={to} className={styleClasses} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
) : href ? (
|
||||
<a href={href} className={styleClasses} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
) : (
|
||||
<button className={`${classes} ${className}`} {...props}>
|
||||
<button className={styleClasses} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button
|
||||
|
12
src/components/atoms/Container.module.css
Normal 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;
|
||||
}
|
22
src/components/atoms/Container.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
.dropzone {
|
||||
padding: 22px;
|
||||
padding: var(--spacer);
|
||||
text-align: center;
|
||||
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 {
|
||||
border-color: var(--color-secondary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ export default function Dropzone({
|
||||
multiple?: boolean
|
||||
error?: string
|
||||
}) {
|
||||
const onDrop = useCallback(acceptedFiles => handleOnDrop(acceptedFiles), [
|
||||
const onDrop = useCallback((acceptedFiles) => handleOnDrop(acceptedFiles), [
|
||||
handleOnDrop
|
||||
])
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { WidgetProps } from 'react-jsonschema-form'
|
||||
import dynamic from 'next/dynamic'
|
||||
import loadable from '@loadable/component'
|
||||
import styles from './DateRangeWidget.module.css'
|
||||
import { toStringNoMS } from '../../../utils'
|
||||
|
||||
// lazy load this module, it's huge
|
||||
const LazyDatePicker = dynamic(() => import('react-datepicker'))
|
||||
const LazyDatePicker = loadable(() => import('react-datepicker'))
|
||||
|
||||
export function getWidgetValue(
|
||||
date1: Date,
|
||||
@ -75,7 +75,7 @@ export default function DateRangeWidget(props: WidgetProps) {
|
||||
<input
|
||||
id="range"
|
||||
type="checkbox"
|
||||
onChange={ev => setRange(ev.target.checked)}
|
||||
onChange={(ev) => setRange(ev.target.checked)}
|
||||
checked={range}
|
||||
/>
|
||||
<label className={styles.label} htmlFor="range">
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react'
|
||||
import { WidgetProps } from 'react-jsonschema-form'
|
||||
import styles from './TermsWidget.module.css'
|
||||
import Markdown from '../Markdown'
|
||||
import terms from '../../../../content/terms.md'
|
||||
|
||||
export default function TermsWidget(props: WidgetProps) {
|
||||
const {
|
||||
@ -21,7 +19,7 @@ export default function TermsWidget(props: WidgetProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Markdown text={terms} className={styles.terms} />
|
||||
{/* <Markdown text={terms} className={styles.terms} /> */}
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={required ? `${styles.label} ${styles.req}` : styles.label}
|
||||
@ -32,9 +30,9 @@ export default function TermsWidget(props: WidgetProps) {
|
||||
checked={typeof value === 'undefined' ? false : value}
|
||||
disabled={disabled || readonly}
|
||||
autoFocus={autofocus}
|
||||
onChange={event => onChange(event.target.checked)}
|
||||
onBlur={onBlur && (event => onBlur(id, event.target.checked))}
|
||||
onFocus={onFocus && (event => onFocus(id, event.target.checked))}
|
||||
onChange={(event) => onChange(event.target.checked)}
|
||||
onBlur={onBlur && ((event) => onBlur(id, event.target.checked))}
|
||||
onFocus={onFocus && ((event) => onFocus(id, event.target.checked))}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
|
@ -1,5 +1,5 @@
|
||||
.help {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--brand-grey-light);
|
||||
margin-top: var(--spacer) / 4;
|
||||
margin-top: calc(var(--spacer) / 6);
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
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-family: var(--font-family-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--brand-black);
|
||||
border: none;
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
background: var(--brand-white);
|
||||
padding: var(--spacer) / 3;
|
||||
padding: calc(var(--spacer) / 3);
|
||||
margin: 0;
|
||||
border-radius: var(--border-radius);
|
||||
transition: 0.2s ease-out;
|
||||
min-height: 43px;
|
||||
appearance: none;
|
||||
display: block;
|
||||
}
|
||||
.input:focus,
|
||||
.select:focus {
|
||||
border: none;
|
||||
|
||||
.input:focus {
|
||||
border-color: var(--brand-grey);
|
||||
box-shadow: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select::placeholder,
|
||||
.input::placeholder {
|
||||
font-family: var(--font-family-base);
|
||||
font-size: var(--font-size-base);
|
||||
@ -67,9 +31,7 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.select[readonly],
|
||||
.input[readonly],
|
||||
.select[disabled],
|
||||
.input[disabled] {
|
||||
background-color: var(--brand-grey-lighter);
|
||||
cursor: not-allowed;
|
||||
@ -80,35 +42,38 @@
|
||||
composes: input;
|
||||
height: 43px;
|
||||
padding-right: 3rem;
|
||||
border: 0;
|
||||
|
||||
/* custom arrow */
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
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(
|
||||
to right,
|
||||
var(--brand-pink) 1px,
|
||||
lighten(var(--brand-grey-lighter), 5%) 2px,
|
||||
lighten(var(--brand-grey-lighter), 5%)
|
||||
var(--brand-grey-lighter) 1px,
|
||||
transparent 1px,
|
||||
transparent
|
||||
);
|
||||
background-position: calc(100% - 18px) calc(1rem + 5px),
|
||||
calc(100% - 13px) calc(1rem + 5px), 100% 0;
|
||||
background-size: 5px 5px, 5px 5px, 2.5rem 3rem;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: 0;
|
||||
font-family: var(--font-family-base);
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
margin-top: var(--spacer) / 2;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin-bottom: -2%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: var(--break-point--small)) {
|
||||
@media screen and (min-width: 40rem) {
|
||||
.radioGroup {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
@ -117,13 +82,15 @@
|
||||
|
||||
.radioWrap {
|
||||
position: relative;
|
||||
padding: var(--spacer) / 2;
|
||||
padding: calc(var(--spacer) / 2);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: var(--break-point--small)) {
|
||||
@media screen and (min-width: 40rem) {
|
||||
.radioWrap {
|
||||
flex: 0 0 49%;
|
||||
}
|
||||
}
|
||||
@ -138,7 +105,7 @@
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 1.2;
|
||||
border: 2px solid var(--brand-grey-lighter);
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
border-radius: 0.2rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@ -152,13 +119,16 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inputSmall {
|
||||
/* Size modifiers */
|
||||
|
||||
.small {
|
||||
composes: input;
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 32px;
|
||||
padding: var(--spacer) / 4;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
}
|
||||
.inputSmall::placeholder {
|
||||
|
||||
.small::placeholder {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
@ -166,6 +136,8 @@
|
||||
composes: select;
|
||||
height: 32px;
|
||||
padding-right: 2rem;
|
||||
|
||||
/* custom arrow */
|
||||
background-position: calc(100% - 14px) 1rem, calc(100% - 9px) 1rem, 100% 0;
|
||||
background-size: 5px 5px, 5px 5px, 2rem 3rem;
|
||||
}
|
66
src/components/atoms/Input/InputElement.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
.label .required {
|
||||
.label {
|
||||
color: var(--brand-grey);
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-family-title);
|
||||
line-height: 1.2;
|
||||
display: block;
|
||||
margin-bottom: var(--spacer) / 6;
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.required:after {
|
||||
|
@ -11,7 +11,7 @@ const Label = ({
|
||||
htmlFor: string
|
||||
}) => (
|
||||
<label
|
||||
className={required ? styles.required : styles.label}
|
||||
className={`${styles.label} ${required && styles.required}`}
|
||||
title={required ? 'Required' : ''}
|
||||
{...props}
|
||||
>
|
||||
|
3
src/components/atoms/Input/index.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.field {
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
59
src/components/atoms/Input/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -1,57 +1,27 @@
|
||||
.loaderWrap {
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-small);
|
||||
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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 0.6em auto;
|
||||
position: relative;
|
||||
transform: translateZ(0);
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
.loaderHorizontal {
|
||||
composes: loader;
|
||||
display: inline-block;
|
||||
margin: 0 1.5em 0 0.7em;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--brand-purple);
|
||||
border-top-color: var(--brand-violet);
|
||||
animation: loader 0.6s linear infinite;
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
content: '';
|
||||
.message {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--brand-grey-light);
|
||||
display: block;
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
left: -0.6em;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
left: 0.6em;
|
||||
}
|
||||
|
||||
@keyframes load1 {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
40% {
|
||||
transform: scaleY(1.3);
|
||||
@keyframes loader {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Loader.module.css'
|
||||
|
||||
export default function Loader({
|
||||
message,
|
||||
isHorizontal
|
||||
message
|
||||
}: {
|
||||
message?: string
|
||||
isHorizontal?: boolean
|
||||
}) {
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<span
|
||||
className={isHorizontal ? styles.loaderHorizontal : styles.loader}
|
||||
/>
|
||||
{message || null}
|
||||
<span className={styles.loader} />
|
||||
{message && <span className={styles.message}>{message}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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 />
|
||||
}
|
@ -7,10 +7,10 @@
|
||||
.price span {
|
||||
font-weight: var(--font-weight-base);
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.small {
|
||||
transform: scale(0.8);
|
||||
transform: scale(0.7);
|
||||
transform-origin: left 80%;
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Web3 from 'web3'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './Price.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function Price({
|
||||
price,
|
||||
className,
|
||||
@ -10,11 +13,15 @@ export default function Price({
|
||||
price: string
|
||||
className?: string
|
||||
small?: boolean
|
||||
}) {
|
||||
const classes = small
|
||||
? `${styles.price} ${styles.small} ${className}`
|
||||
: `${styles.price} ${className}`
|
||||
}): ReactElement {
|
||||
const styleClasses = cx({
|
||||
price: true,
|
||||
small: small,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
const isFree = price === '0'
|
||||
|
||||
const displayPrice = isFree ? (
|
||||
'Free'
|
||||
) : (
|
||||
@ -23,5 +30,5 @@ export default function Price({
|
||||
</>
|
||||
)
|
||||
|
||||
return <div className={classes}>{displayPrice}</div>
|
||||
return <div className={styleClasses}>{displayPrice}</div>
|
||||
}
|
||||
|
54
src/components/atoms/Seo.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -4,25 +4,23 @@
|
||||
height: var(--font-size-small);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
background: var(--green);
|
||||
background: var(--brand-alert-green);
|
||||
}
|
||||
|
||||
/* yellow triangle */
|
||||
.warning {
|
||||
composes: status;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 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 */
|
||||
.error {
|
||||
composes: status;
|
||||
border-radius: 0;
|
||||
background: var(--red);
|
||||
background: var(--brand-alert-red);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
@ -1,13 +1,22 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './Status.module.css'
|
||||
|
||||
export default function Status({ state }: { state?: string }) {
|
||||
const classes =
|
||||
state === 'error'
|
||||
? styles.error
|
||||
: state === 'warning'
|
||||
? styles.warning
|
||||
: styles.status
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
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} />
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import React from 'react'
|
||||
import Time from '../Time'
|
||||
import Link from 'next/link'
|
||||
import { Link } from 'gatsby'
|
||||
|
||||
export default function DdoLinkCell({ id, name }: { id: any; name: any }) {
|
||||
return (
|
||||
<Link href="/asset/[did]" as={`/asset/${id}`} passHref>
|
||||
<a>{name}</a>
|
||||
</Link>
|
||||
)
|
||||
return <Link to={`/asset/${id}`}>{name}</Link>
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Link } from 'gatsby'
|
||||
import shortid from 'shortid'
|
||||
import slugify from 'slugify'
|
||||
import styles from './Tags.module.css'
|
||||
@ -20,10 +20,8 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
|
||||
return noLinks ? (
|
||||
<span className={styles.tag}>{cleanTag}</span>
|
||||
) : (
|
||||
<Link href={`/search?tags=${tag}`}>
|
||||
<a className={styles.tag} title={cleanTag}>
|
||||
{cleanTag}
|
||||
</a>
|
||||
<Link to={`/search?tags=${tag}`} className={styles.tag} title={cleanTag}>
|
||||
{cleanTag}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@ -44,7 +42,7 @@ const Tags: React.FC<TagsProps> = ({
|
||||
return (
|
||||
<div className={classes}>
|
||||
{tags &&
|
||||
tags.map(tag => (
|
||||
tags.map((tag) => (
|
||||
<Tag tag={tag} noLinks={noLinks} key={shortid.generate()} />
|
||||
))}
|
||||
{shouldShowMore && (
|
||||
|
@ -7,7 +7,7 @@
|
||||
composes: box from '../atoms/Box.module.css';
|
||||
font-size: var(--font-size-small);
|
||||
height: 100%;
|
||||
color: var(--brand-grey-dark);
|
||||
color: var(--color-secondary);
|
||||
position: relative;
|
||||
/* for sticking footer to bottom */
|
||||
display: flex;
|
||||
@ -18,59 +18,32 @@
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
|
||||
/* for sticking footer to bottom */
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-h4);
|
||||
color: var(--color-primary);
|
||||
margin-bottom: calc(var(--spacer) / 6);
|
||||
}
|
||||
|
||||
.tags > * {
|
||||
font-size: var(--font-size-mini);
|
||||
font-size: var(--font-size-large);
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.foot {
|
||||
color: var(--color-secondary);
|
||||
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 {
|
||||
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 {
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-mini);
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: var(--brand-black);
|
||||
background: var(--brand-grey-lighter);
|
||||
padding: 0.1px 0.5px 0.1px 0.5px;
|
||||
border-radius: 2px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
}
|
||||
|
@ -1,73 +1,55 @@
|
||||
import React from 'react'
|
||||
import { DDO } from '@oceanprotocol/squid'
|
||||
import Link from 'next/link'
|
||||
import { Link } from 'gatsby'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import {
|
||||
AdditionalInformationMarket,
|
||||
MetaDataMarket
|
||||
} from '../../@types/MetaData'
|
||||
import { findServiceByType } from '../../utils'
|
||||
import { MetaDataMarket } from '../../@types/MetaData'
|
||||
import Tags from '../atoms/Tags'
|
||||
import Price from '../atoms/Price'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
import Rating from '../atoms/Rating'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: Partial<DDO>
|
||||
did: string
|
||||
metadata: MetaDataMarket
|
||||
}
|
||||
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
const { attributes } = findServiceByType(ddo, 'metadata')
|
||||
const { name, price } = attributes.main
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
||||
did,
|
||||
metadata
|
||||
}: AssetTeaserProps) => {
|
||||
const { name, price } = metadata.main
|
||||
|
||||
let description
|
||||
let copyrightHolder
|
||||
let tags
|
||||
let categories
|
||||
let access
|
||||
const {
|
||||
description,
|
||||
copyrightHolder,
|
||||
tags,
|
||||
categories,
|
||||
access
|
||||
} = metadata.additionalInformation
|
||||
|
||||
if (attributes && attributes.additionalInformation) {
|
||||
;({
|
||||
description,
|
||||
copyrightHolder,
|
||||
tags,
|
||||
categories,
|
||||
access
|
||||
} = attributes.additionalInformation as AdditionalInformationMarket)
|
||||
}
|
||||
|
||||
const { curation } = attributes as MetaDataMarket
|
||||
const { curation } = metadata
|
||||
|
||||
return (
|
||||
<article className={styles.teaser}>
|
||||
<Link href="/asset/[did]" as={`/asset/${ddo.id}`}>
|
||||
<a className={styles.link}>
|
||||
<h1 className={styles.title}>{name}</h1>
|
||||
{access === 'Compute' && (
|
||||
<div className={styles.accessLabel}>{access}</div>
|
||||
<Link to={`/asset/${did}`} className={styles.link}>
|
||||
<h1 className={styles.title}>{name}</h1>
|
||||
{access === 'Compute' && (
|
||||
<div className={styles.accessLabel}>{access}</div>
|
||||
)}
|
||||
<Rating curation={curation} readonly />
|
||||
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{description || ''}
|
||||
</Dotdotdot>
|
||||
|
||||
{tags && (
|
||||
<Tags className={styles.tags} items={tags} max={3} noLinks />
|
||||
)}
|
||||
<Rating curation={curation} readonly />
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{description || ''}
|
||||
</Dotdotdot>
|
||||
|
||||
{tags && (
|
||||
<Tags className={styles.tags} items={tags} max={3} noLinks />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className={styles.foot}>
|
||||
{categories && <p className={styles.type}>{categories[0]}</p>}
|
||||
|
||||
<Price price={price} className={styles.price} />
|
||||
|
||||
<p className={styles.copyright}>
|
||||
Provided by <strong>{copyrightHolder}</strong>
|
||||
</p>
|
||||
</footer>
|
||||
</a>
|
||||
<footer className={styles.foot}>
|
||||
<Price price={price} small />
|
||||
</footer>
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { DDO, Ocean } from '@oceanprotocol/squid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { findServiceByType, redeploy } from '../../utils'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import { DDO } from '@oceanprotocol/squid'
|
||||
import { redeploy } from '../../utils'
|
||||
import Button from '../atoms/Button'
|
||||
import BaseDialog from '../atoms/BaseDialog'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
@ -15,19 +15,17 @@ const content = [
|
||||
|
||||
export default function DeleteAction({ ddo }: { ddo: DDO }) {
|
||||
const { ocean, accountId } = useOcean()
|
||||
|
||||
const navigate = useNavigate()
|
||||
const isOwner = ddo.publicKey[0].owner === accountId
|
||||
|
||||
const router = useRouter()
|
||||
const [isModal, setIsModal] = useState(false)
|
||||
const [status, setStatus] = useState(0) // 0-confirmation, 1-deleting, 2-success, 3-error
|
||||
const { attributes } = findServiceByType(ddo, 'metadata')
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
|
||||
useEffect(() => {
|
||||
let tId: number
|
||||
if (status === 2) {
|
||||
tId = window.setTimeout(() => {
|
||||
router.push(`/explore`)
|
||||
navigate(`/`)
|
||||
}, 1000)
|
||||
}
|
||||
return () => {
|
||||
|
@ -13,7 +13,7 @@ export const FieldTemplate = ({
|
||||
rawErrors,
|
||||
children
|
||||
}: FieldTemplateProps) => {
|
||||
const noLabel = id !== noLabelFields.filter(f => id === f)[0]
|
||||
const noLabel = id !== noLabelFields.filter((f) => id === f)[0]
|
||||
return (
|
||||
<section
|
||||
key={id}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import isUrl from 'is-url-superb'
|
||||
import Loader from '../../../atoms/Loader'
|
||||
import Button from '../../../atoms/Button'
|
||||
@ -11,12 +11,12 @@ const FileInput = ({
|
||||
children,
|
||||
i
|
||||
}: {
|
||||
children: any
|
||||
children: ReactNode
|
||||
i: number
|
||||
formData: string[]
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
}) => (
|
||||
}): ReactElement => (
|
||||
<>
|
||||
{children}
|
||||
{formData[i] && (
|
||||
|
@ -105,7 +105,7 @@ export default function Form({
|
||||
transformErrors={transformErrors}
|
||||
>
|
||||
<div>
|
||||
<Button disabled={buttonDisabled} primary>
|
||||
<Button disabled={buttonDisabled} style="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ComputeItem } from '@oceanprotocol/react'
|
||||
import BaseDialog from '../atoms/BaseDialog'
|
||||
import { findServiceByType } from '../../utils'
|
||||
import styles from './JobDetailsDialog.module.css'
|
||||
import MetaItem from '../templates/AssetDetails/MetaItem'
|
||||
import MetaItem from '../organisms/AssetContent/MetaItem'
|
||||
import Time from '../atoms/Time'
|
||||
import shortid from 'shortid'
|
||||
import Link from 'next/link'
|
||||
import { Link } from 'gatsby'
|
||||
|
||||
export default function JobDetailsDialog({
|
||||
computeItem,
|
||||
@ -16,10 +15,10 @@ export default function JobDetailsDialog({
|
||||
computeItem: ComputeItem | undefined
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
}): ReactElement {
|
||||
if (!computeItem) return null
|
||||
|
||||
const { attributes } = findServiceByType(computeItem.ddo, 'metadata')
|
||||
const { attributes } = computeItem.ddo.findServiceByType('metadata')
|
||||
const { name } = attributes.main
|
||||
const {
|
||||
dateCreated,
|
||||
@ -46,8 +45,8 @@ export default function JobDetailsDialog({
|
||||
<MetaItem
|
||||
title="Results"
|
||||
content={resultsUrls.map((url: string) => (
|
||||
<Link href={url} key={shortid.generate()} passHref>
|
||||
<a>{url}</a>
|
||||
<Link to={url} key={shortid.generate()}>
|
||||
{url}
|
||||
</Link>
|
||||
))}
|
||||
/>
|
||||
@ -55,24 +54,12 @@ export default function JobDetailsDialog({
|
||||
{algorithmLogUrl && (
|
||||
<MetaItem
|
||||
title="Algorithm Log"
|
||||
content={
|
||||
<Link href={algorithmLogUrl} key={shortid.generate()} passHref>
|
||||
<a>{algorithmLogUrl}</a>
|
||||
</Link>
|
||||
}
|
||||
content={<Link to={algorithmLogUrl}>{algorithmLogUrl}</Link>}
|
||||
/>
|
||||
)}
|
||||
<MetaItem
|
||||
title="Data Set"
|
||||
content={
|
||||
<Link
|
||||
href="/asset/[did]"
|
||||
as={`/asset/${computeItem.ddo.id}`}
|
||||
passHref
|
||||
>
|
||||
<a>{name}</a>
|
||||
</Link>
|
||||
}
|
||||
content={<Link to={`/asset/${computeItem.ddo.id}`}>{name}</Link>}
|
||||
/>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
@ -1,11 +1,91 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.navigation button,
|
||||
.link {
|
||||
display: block;
|
||||
padding: calc(var(--spacer) / 2);
|
||||
margin-left: var(--spacer);
|
||||
text-transform: uppercase;
|
||||
color: var(--brand-grey);
|
||||
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,
|
||||
@ -14,14 +94,12 @@
|
||||
color: var(--brand-pink);
|
||||
}
|
||||
|
||||
.link.active {
|
||||
.link[aria-current],
|
||||
.link[aria-current]:hover,
|
||||
.link[aria-current]:focus {
|
||||
color: var(--brand-pink);
|
||||
}
|
||||
|
||||
.link:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.link:last-child {
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { menu } from '../../../site.config'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import { useLocation } from '@reach/router'
|
||||
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 = {
|
||||
name: string
|
||||
@ -10,25 +13,42 @@ declare type MenuItem = {
|
||||
}
|
||||
|
||||
function MenuLink({ item }: { item: MenuItem }) {
|
||||
const router = useRouter()
|
||||
const location = useLocation()
|
||||
|
||||
const classes =
|
||||
router && router.pathname === item.link
|
||||
location && location.pathname === item.link
|
||||
? `${styles.link} ${styles.active}`
|
||||
: styles.link
|
||||
|
||||
return (
|
||||
<Link key={item.name} href={item.link}>
|
||||
<a className={classes}>{item.name}</a>
|
||||
<Link key={item.name} to={item.link} className={classes}>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Menu() {
|
||||
export default function Menu(): ReactElement {
|
||||
const { menu, siteTitle } = useSiteMetadata()
|
||||
|
||||
return (
|
||||
<nav className={styles.menu}>
|
||||
{menu.map((item: MenuItem) => (
|
||||
<MenuLink key={item.name} item={item} />
|
||||
))}
|
||||
<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) => (
|
||||
<li key={item.name}>
|
||||
<MenuLink item={item} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Container>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-h2);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -1,15 +1,23 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import styles from './PageHeader.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function PageHeader({
|
||||
title,
|
||||
description
|
||||
}: {
|
||||
title: string
|
||||
description?: string
|
||||
}) {
|
||||
center?: boolean
|
||||
}): ReactElement {
|
||||
const styleClasses = cx({
|
||||
header: true
|
||||
})
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<header className={styleClasses}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
{description && <p className={styles.description}>{description}</p>}
|
||||
</header>
|
||||
|
@ -39,8 +39,8 @@ export default function Pagination({
|
||||
// adapt based on media query match
|
||||
marginPagesDisplayed={smallViewport ? 0 : 1}
|
||||
pageRangeDisplayed={smallViewport ? 3 : 6}
|
||||
onPageChange={data => onPageChange(data.selected)}
|
||||
hrefBuilder={pageIndex => hrefBuilder(pageIndex)}
|
||||
onPageChange={(data) => onPageChange(data.selected)}
|
||||
hrefBuilder={(pageIndex) => hrefBuilder(pageIndex)}
|
||||
disableInitialCallback
|
||||
previousLabel="←"
|
||||
nextLabel="→"
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import Form from '../../molecules/Form/index'
|
||||
import {
|
||||
PublishFormSchema,
|
||||
@ -11,7 +12,6 @@ import { MetaDataMarket } from '../../../@types/MetaData'
|
||||
import { File, MetaData } from '@oceanprotocol/squid'
|
||||
import { isBrowser, toStringNoMS } from '../../../utils'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useRouter } from 'next/router'
|
||||
import styles from './PublishForm.module.css'
|
||||
import utils from 'web3-utils'
|
||||
import AssetModel from '../../../models/Asset'
|
||||
@ -21,8 +21,6 @@ import {
|
||||
ServiceCompute
|
||||
} from '@oceanprotocol/squid/dist/node/ddo/Service'
|
||||
|
||||
declare type PublishFormProps = {}
|
||||
|
||||
const FILES_DATA_LOCAL_STORAGE_KEY = 'filesData'
|
||||
const PUBLISH_FORM_LOCAL_STORAGE_KEY = 'publishForm'
|
||||
|
||||
@ -80,8 +78,6 @@ export function transformPublishFormToMetadata(
|
||||
copyrightHolder: holder,
|
||||
tags: keywords?.split(','),
|
||||
termsAndConditions,
|
||||
supportName,
|
||||
supportEmail,
|
||||
access: access || 'Download'
|
||||
},
|
||||
// ------- curation -------
|
||||
@ -101,11 +97,11 @@ export function transformPublishFormToMetadata(
|
||||
return metadata
|
||||
}
|
||||
|
||||
const PublishForm: React.FC<PublishFormProps> = () => {
|
||||
const PublishForm: React.FC<any> = () => {
|
||||
const [buttonDisabled, setButtonDisabled] = useState(false)
|
||||
const { web3Connect } = useWeb3()
|
||||
const { ocean, account } = useOcean()
|
||||
const router = useRouter()
|
||||
const navigate = useNavigate()
|
||||
const [data, updateData] = useStoredValue(
|
||||
PUBLISH_FORM_LOCAL_STORAGE_KEY,
|
||||
publishFormData
|
||||
@ -172,7 +168,7 @@ const PublishForm: React.FC<PublishFormProps> = () => {
|
||||
className: styles.success
|
||||
})
|
||||
toast.dismiss(submittingToast)
|
||||
router.push(`/asset/${asset.id}`)
|
||||
navigate(`/asset/${asset.id}`)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
|
@ -2,7 +2,15 @@
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
.inputGroup .input {
|
||||
.inputGroup > div {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.inputGroup label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inputGroup input {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
@ -12,44 +20,23 @@
|
||||
border-top-right-radius: 0;
|
||||
margin-top: -1px;
|
||||
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:focus,
|
||||
.inputGroup .input:focus + button:hover,
|
||||
.inputGroup .input:focus + button:focus {
|
||||
.inputGroup input:focus + button:hover,
|
||||
.inputGroup input:focus + button:focus {
|
||||
background: var(--brand-gradient);
|
||||
transform: 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) {
|
||||
.inputGroup {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inputGroup .input {
|
||||
.inputGroup input {
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
@ -64,14 +51,3 @@
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
composes: input from './Form/FieldTemplate.module.css';
|
||||
}
|
||||
|
||||
.large {
|
||||
composes: large from './Form/FieldTemplate.module.css';
|
||||
}
|
||||
|
||||
.filters {
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect, ChangeEvent, FormEvent } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useState, ChangeEvent, FormEvent, ReactElement } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import styles from './SearchBar.module.css'
|
||||
import Loader from '../atoms/Loader'
|
||||
import Button from '../atoms/Button'
|
||||
import Input from '../atoms/Input'
|
||||
|
||||
export default function SearchBar({
|
||||
placeholder,
|
||||
@ -14,8 +15,8 @@ export default function SearchBar({
|
||||
initialValue?: string
|
||||
filters?: boolean
|
||||
large?: true
|
||||
}) {
|
||||
const router = useRouter()
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const [value, setValue] = useState(initialValue || '')
|
||||
const [searchStarted, setSearchStarted] = useState(false)
|
||||
|
||||
@ -29,29 +30,18 @@ export default function SearchBar({
|
||||
if (value === '') return
|
||||
|
||||
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 (
|
||||
<form className={styles.form}>
|
||||
<div className={styles.inputGroup}>
|
||||
<input
|
||||
<Input
|
||||
type="search"
|
||||
className={large ? `${styles.input} ${styles.large}` : styles.input}
|
||||
name="search"
|
||||
placeholder={placeholder || 'What are you looking for?'}
|
||||
value={value}
|
||||
onChange={e => handleChange(e)}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<Button onClick={(e: FormEvent<HTMLButtonElement>) => startSearch(e)}>
|
||||
|
@ -1,7 +1,3 @@
|
||||
.input {
|
||||
composes: input from './Form/FieldTemplate.module.css';
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import SearchFilterSection from '../atoms/SearchFilterSection'
|
||||
import usePriceQueryParams from '../../hooks/usePriceQueryParams'
|
||||
|
||||
import styles from './SearchPriceFilter.module.css'
|
||||
import Input from '../atoms/Input'
|
||||
|
||||
export declare type PriceInputProps = {
|
||||
label: string
|
||||
@ -16,20 +17,16 @@ export const PriceInput = ({
|
||||
value,
|
||||
onChange,
|
||||
text
|
||||
}: PriceInputProps) => {
|
||||
}: PriceInputProps): ReactElement => {
|
||||
return (
|
||||
<label htmlFor={label} className={styles.label}>
|
||||
<input
|
||||
id={label}
|
||||
name={label}
|
||||
type="number"
|
||||
min="0"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={styles.input}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</label>
|
||||
<Input
|
||||
name={label}
|
||||
label={text}
|
||||
type="number"
|
||||
min="0"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -42,13 +39,13 @@ export const SearchPriceFilter = () => {
|
||||
<PriceInput
|
||||
label="minPrice"
|
||||
value={min}
|
||||
onChange={ev => setMin(ev.target.value)}
|
||||
onChange={(ev) => setMin(ev.target.value)}
|
||||
text="Min price"
|
||||
/>
|
||||
<PriceInput
|
||||
label="maxPrice"
|
||||
value={max}
|
||||
onChange={ev => setMax(ev.target.value)}
|
||||
onChange={(ev) => setMax(ev.target.value)}
|
||||
text="Max price"
|
||||
/>
|
||||
</div>
|
||||
|
58
src/components/molecules/Wallet/Account.module.css
Normal 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;
|
||||
}
|
59
src/components/molecules/Wallet/Account.tsx
Normal 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
|
53
src/components/molecules/Wallet/Details.module.css
Normal 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;
|
||||
}
|
37
src/components/molecules/Wallet/Details.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -1,27 +1,17 @@
|
||||
.feedback {
|
||||
font-size: var(--font-size-small);
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
padding-left: calc(var(--spacer) / 1.5);
|
||||
padding-left: var(--spacer);
|
||||
padding-top: calc(var(--spacer) / 2);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
border-top: 1px solid var(--brand-grey-lighter);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.statuscontainer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.statuscontainer h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.walletcontainer {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.feedback i {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -30,7 +20,6 @@
|
||||
}
|
||||
|
||||
.error {
|
||||
font-style: italic;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: 0;
|
@ -1,13 +1,7 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Status from '../../atoms/Status'
|
||||
import Wallet from './Wallet'
|
||||
import styles from './index.module.css'
|
||||
import {
|
||||
useWeb3,
|
||||
useOcean,
|
||||
InjectedProviderStatus,
|
||||
OceanConnectionStatus
|
||||
} from '@oceanprotocol/react'
|
||||
import styles from './Feedback.module.css'
|
||||
import { useWeb3, useOcean } from '@oceanprotocol/react'
|
||||
|
||||
export declare type Web3Error = {
|
||||
status: 'error' | 'warning' | 'success'
|
||||
@ -19,9 +13,9 @@ export default function Web3Feedback({
|
||||
isBalanceInsufficient
|
||||
}: {
|
||||
isBalanceInsufficient?: boolean
|
||||
}) {
|
||||
}): ReactElement {
|
||||
const { ethProviderStatus } = useWeb3()
|
||||
const { status, balanceInOcean } = useOcean()
|
||||
const { status } = useOcean()
|
||||
const isEthProviderAbsent = ethProviderStatus === -1
|
||||
const isEthProviderDisconnected = ethProviderStatus === 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.'
|
||||
: 'Something went wrong.'
|
||||
|
||||
return (
|
||||
return !hasSuccess ? (
|
||||
<section className={styles.feedback}>
|
||||
<div className={styles.statuscontainer}>
|
||||
<Status state={state} aria-hidden />
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
{!hasSuccess && <p className={styles.error}>{message}</p>}
|
||||
</div>
|
||||
{!isEthProviderAbsent && (
|
||||
<div className={styles.walletcontainer}>
|
||||
<Wallet balanceOcean={balanceInOcean} />
|
||||
</div>
|
||||
)}
|
||||
<Status state={state} aria-hidden />
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<p className={styles.error}>{message}</p>
|
||||
</section>
|
||||
)
|
||||
) : null
|
||||
}
|
0
src/components/molecules/Wallet/index.module.css
Normal file
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Web3Feedback from '.'
|
||||
import Web3Feedback from './Feedback'
|
||||
import web3Mock from '../../../../tests/unit/__mocks__/web3'
|
||||
import web3ProviderMock, {
|
||||
context
|
62
src/components/molecules/Wallet/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|