1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge pull request #28 from oceanprotocol/feature/gatsby

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

View File

@ -1,6 +1,7 @@
{
"parser": "babel-eslint",
"extends": ["eslint:recommended", "prettier"],
"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
View File

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

5
.gitignore vendored
View File

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

View File

@ -1,6 +1,6 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 2
}
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 2
}

View File

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

View File

@ -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)
}

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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))

View File

@ -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
View File

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

View File

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

View File

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

20
content/site.json Normal file
View File

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

10
gatsby-browser.js Normal file
View File

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

74
gatsby-config.js Normal file
View File

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

78
gatsby-node.js Normal file
View File

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

5
gatsby-ssr.js Normal file
View File

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

View File

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

2
next-env.d.ts vendored
View File

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

View File

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

33785
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +1,113 @@
{
"name": "market",
"name": "@oceanprotocol/market",
"description": "Data marketplace for ocean.",
"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",

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

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

View File

@ -1,15 +1,5 @@
import { MetaData, AdditionalInformation, Curation } from '@oceanprotocol/squid'
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
}

View File

@ -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
View File

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

View File

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

View File

@ -12,10 +12,7 @@
}
.main {
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
View File

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

View File

@ -6,6 +6,12 @@
border-left-width: 0.5rem;
border-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);
}

View File

@ -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>
)

View File

@ -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);
}

View File

@ -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 {

View File

@ -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>
</>
)

View File

@ -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

View File

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

View File

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

View File

@ -1,12 +1,16 @@
.dropzone {
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 {
}

View File

@ -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
])

View File

@ -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">

View File

@ -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>

View File

@ -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);
}

View File

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

View File

@ -1,63 +1,27 @@
.inputWrap,
.inputWrapSearch {
background: var(--brand-gradient);
border-radius: var(--border-radius);
padding: 2px;
display: flex;
position: relative;
}
.inputWrap .isFocused,
.inputWrapSearch .isFocused {
background: var(--brand-black);
}
.inputWrap > div,
.inputWrap > div > div,
.inputWrapSearch > div,
.inputWrapSearch > div > div {
width: 100%;
}
.inputWrapSearch,
.input {
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;
}

View File

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

View File

@ -1,10 +1,10 @@
.label .required {
.label {
color: var(--brand-grey);
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 {

View File

@ -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}
>

View File

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

View File

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

View File

@ -1,57 +1,27 @@
.loaderWrap {
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);
}
}

View File

@ -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>
)
}

View File

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

View File

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

View File

@ -7,10 +7,10 @@
.price span {
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%;
}

View File

@ -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>
}

View File

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

View File

@ -4,25 +4,23 @@
height: var(--font-size-small);
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;
}

View File

@ -1,13 +1,22 @@
import React from 'react'
import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import styles from './Status.module.css'
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} />
}

View File

@ -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>
}

View File

@ -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 && (

View File

@ -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);
}

View File

@ -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>
)

View File

@ -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 () => {

View File

@ -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}

View File

@ -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] && (

View File

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

View File

@ -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>

View File

@ -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;
}

View File

@ -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>
)
}

View File

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

View File

@ -1,15 +1,23 @@
import React from 'react'
import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import styles from './PageHeader.module.css'
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>

View File

@ -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="→"

View File

@ -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 {

View File

@ -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 {
}

View File

@ -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)}>

View File

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

View File

@ -1,8 +1,9 @@
import React, { ChangeEvent } from 'react'
import React, { ChangeEvent, ReactElement } from 'react'
import SearchFilterSection from '../atoms/SearchFilterSection'
import 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>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,17 @@
.feedback {
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;

View File

@ -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
}

View 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

View File

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

View File

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

View File

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

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