merge
12
.travis.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#/usr/bin/env/sh
|
||||||
|
set -e
|
||||||
|
components="server client"
|
||||||
|
|
||||||
|
for component in $components
|
||||||
|
do
|
||||||
|
echo "Testing: $component"
|
||||||
|
cd $component
|
||||||
|
npm install
|
||||||
|
npm test
|
||||||
|
cd ..
|
||||||
|
done
|
@ -1,9 +1,8 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js: node
|
node_js:
|
||||||
|
- "11.10.1"
|
||||||
|
|
||||||
script:
|
script: "./.travis.sh"
|
||||||
- npm test
|
|
||||||
- npm run build
|
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
0
.gitignore → client/.gitignore
vendored
13
client/.travis.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: node
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm test
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
4848
package-lock.json → client/package-lock.json
generated
@ -12,51 +12,56 @@
|
|||||||
"format": "npm run format:js && npm run format:css",
|
"format": "npm run format:js && npm run format:css",
|
||||||
"lint:css": "stylelint './src/**/*.{css,scss}'",
|
"lint:css": "stylelint './src/**/*.{css,scss}'",
|
||||||
"lint:js": "eslint --ignore-path .gitignore --ignore-path .prettierignore --ext .ts,.tsx .",
|
"lint:js": "eslint --ignore-path .gitignore --ignore-path .prettierignore --ext .ts,.tsx .",
|
||||||
|
"lint:fix": "eslint --fix --ignore-path .gitignore --ignore-path .prettierignore --ext .ts,.tsx .",
|
||||||
"lint": "npm run lint:js && npm run lint:css"
|
"lint": "npm run lint:js && npm run lint:css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oceanprotocol/art": "^2.2.0",
|
"@oceanprotocol/art": "^2.2.0",
|
||||||
"@oceanprotocol/squid": "^0.3.0",
|
"@oceanprotocol/squid": "^0.4.0",
|
||||||
"@types/is-url": "^1.2.28",
|
|
||||||
"@types/react-helmet": "^5.0.8",
|
|
||||||
"@types/react-transition-group": "^2.0.15",
|
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"eslint": "^5.6.0",
|
"filesize": "^4.1.2",
|
||||||
"is-url": "^1.2.4",
|
"is-url": "^1.2.4",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"query-string": "^6.2.0",
|
"query-string": "^6.2.0",
|
||||||
"react": "^16.8.1",
|
"react": "^16.8.3",
|
||||||
"react-dom": "^16.8.1",
|
"react-dom": "^16.8.3",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.0",
|
||||||
"react-moment": "^0.8.4",
|
"react-moment": "^0.8.4",
|
||||||
|
"react-popper": "^1.3.3",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-transition-group": "^2.5.3",
|
"react-transition-group": "^2.6.0",
|
||||||
"slugify": "^1.3.4",
|
"slugify": "^1.3.4",
|
||||||
"web3": "^1.0.0-beta.43"
|
"web3": "^1.0.0-beta.48"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/classnames": "^2.2.7",
|
"@types/classnames": "^2.2.7",
|
||||||
"@types/jest": "^24.0.0",
|
"@types/filesize": "^4.0.1",
|
||||||
"@types/node": "^10.12.21",
|
"@types/is-url": "^1.2.28",
|
||||||
|
"@types/jasmine": "^3.3.9",
|
||||||
|
"@types/jest": "^24.0.9",
|
||||||
|
"@types/node": "^11.9.5",
|
||||||
"@types/query-string": "^6.2.0",
|
"@types/query-string": "^6.2.0",
|
||||||
"@types/react": "^16.8.2",
|
"@types/react": "^16.8.5",
|
||||||
"@types/react-dom": "^16.8.0",
|
"@types/react-dom": "^16.8.2",
|
||||||
|
"@types/react-helmet": "^5.0.8",
|
||||||
"@types/react-router-dom": "^4.3.1",
|
"@types/react-router-dom": "^4.3.1",
|
||||||
|
"@types/react-transition-group": "^2.0.16",
|
||||||
"@types/web3": "^1.0.18",
|
"@types/web3": "^1.0.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^1.2.0",
|
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||||
"@typescript-eslint/parser": "^1.2.0",
|
"@typescript-eslint/parser": "^1.4.2",
|
||||||
|
"eslint": "5.12.0",
|
||||||
"eslint-config-oceanprotocol": "^1.3.0",
|
"eslint-config-oceanprotocol": "^1.3.0",
|
||||||
"eslint-config-prettier": "^4.0.0",
|
"eslint-config-prettier": "^4.1.0",
|
||||||
"eslint-plugin-prettier": "^3.0.1",
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"react-scripts": "^2.1.3",
|
"react-scripts": "^2.1.5",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-bigchaindb": "^1.2.1",
|
"stylelint-config-bigchaindb": "^1.2.1",
|
||||||
"stylelint-config-css-modules": "^1.3.0",
|
"stylelint-config-css-modules": "^1.3.0",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"typescript": "3.2.4"
|
"typescript": "^3.3.3333"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.2%",
|
">0.2%",
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
|
import { Logger } from '@oceanprotocol/squid'
|
||||||
import Header from './components/Header'
|
import Header from './components/Header'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Spinner from './components/atoms/Spinner'
|
import Spinner from './components/atoms/Spinner'
|
||||||
@ -10,7 +11,14 @@ import Routes from './Routes'
|
|||||||
import './styles/global.scss'
|
import './styles/global.scss'
|
||||||
import styles from './App.module.scss'
|
import styles from './App.module.scss'
|
||||||
|
|
||||||
import { nodeHost, nodePort, nodeScheme } from './config'
|
import {
|
||||||
|
nodeHost,
|
||||||
|
nodePort,
|
||||||
|
nodeScheme,
|
||||||
|
faucetHost,
|
||||||
|
faucetPort,
|
||||||
|
faucetScheme
|
||||||
|
} from './config'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -24,6 +32,11 @@ interface AppState {
|
|||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isWeb3: boolean
|
isWeb3: boolean
|
||||||
account: string
|
account: string
|
||||||
|
balance: {
|
||||||
|
eth: number
|
||||||
|
ocn: number
|
||||||
|
}
|
||||||
|
network: string
|
||||||
web3: Web3
|
web3: Web3
|
||||||
ocean: {}
|
ocean: {}
|
||||||
startLogin: () => void
|
startLogin: () => void
|
||||||
@ -37,10 +50,40 @@ class App extends Component<{}, AppState> {
|
|||||||
this.startLoginProcess()
|
this.startLoginProcess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private requestFromFaucet = async () => {
|
||||||
|
if (this.state.account !== '') {
|
||||||
|
try {
|
||||||
|
await fetch(
|
||||||
|
`${faucetScheme}://${faucetHost}:${faucetPort}/faucet`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
address: this.state.account,
|
||||||
|
agent: 'commons-marketplace'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
Logger.log('requestFromFaucet', error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no account found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
isLogged: false,
|
isLogged: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isWeb3: false,
|
isWeb3: false,
|
||||||
|
balance: {
|
||||||
|
eth: 0,
|
||||||
|
ocn: 0
|
||||||
|
},
|
||||||
|
network: '',
|
||||||
web3: new Web3(
|
web3: new Web3(
|
||||||
new Web3.providers.HttpProvider(
|
new Web3.providers.HttpProvider(
|
||||||
`${nodeScheme}://${nodeHost}:${nodePort}`
|
`${nodeScheme}://${nodeHost}:${nodePort}`
|
||||||
@ -48,7 +91,8 @@ class App extends Component<{}, AppState> {
|
|||||||
),
|
),
|
||||||
account: '',
|
account: '',
|
||||||
ocean: {},
|
ocean: {},
|
||||||
startLogin: this.startLogin
|
startLogin: this.startLogin,
|
||||||
|
requestFromFaucet: this.requestFromFaucet
|
||||||
}
|
}
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
@ -107,7 +151,7 @@ class App extends Component<{}, AppState> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// continue with default
|
Logger.log('web3 error', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -116,8 +160,13 @@ class App extends Component<{}, AppState> {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
ocean
|
ocean
|
||||||
})
|
})
|
||||||
|
// TODO: squid-js balance retrieval fix
|
||||||
|
const accounts = await ocean.getAccounts()
|
||||||
|
const balance = await accounts[0].getBalance()
|
||||||
|
this.setState({ balance })
|
||||||
|
// TODO: squid-js expose keeper for getNetworkName
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// show loading error / unable to initialize ocean
|
Logger.log('ocean/balance error', e)
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false
|
isLoading: false
|
||||||
})
|
})
|
@ -7,6 +7,8 @@ import Home from './routes/Home'
|
|||||||
import NotFound from './routes/NotFound'
|
import NotFound from './routes/NotFound'
|
||||||
import Publish from './routes/Publish/'
|
import Publish from './routes/Publish/'
|
||||||
import Search from './routes/Search'
|
import Search from './routes/Search'
|
||||||
|
import Faucet from './routes/Faucet'
|
||||||
|
import Invoices from './routes/Invoices'
|
||||||
import Styleguide from './routes/Styleguide'
|
import Styleguide from './routes/Styleguide'
|
||||||
|
|
||||||
const Routes = () => (
|
const Routes = () => (
|
||||||
@ -17,6 +19,8 @@ const Routes = () => (
|
|||||||
<Route component={Publish} path="/publish" />
|
<Route component={Publish} path="/publish" />
|
||||||
<Route component={Search} path="/search" />
|
<Route component={Search} path="/search" />
|
||||||
<Route component={Details} path="/asset/:did" />
|
<Route component={Details} path="/asset/:did" />
|
||||||
|
<Route component={Faucet} path="/faucet" />
|
||||||
|
<Route component={Invoices} path="/invoices" />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
@ -1,9 +1,6 @@
|
|||||||
@import '../styles/variables';
|
@import '../styles/variables';
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
// background: $brand-black
|
|
||||||
// url('@oceanprotocol/art/mantaray/mantaray-back.svg') no-repeat center -6rem;
|
|
||||||
// background-size: cover;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $spacer / 2 0;
|
padding: $spacer / 2 0;
|
||||||
}
|
}
|
||||||
@ -94,3 +91,8 @@
|
|||||||
color: $brand-pink;
|
color: $brand-pink;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accountStatus {
|
||||||
|
margin-left: $spacer;
|
||||||
|
margin-bottom: .2rem;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg'
|
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg'
|
||||||
|
import AccountStatus from './molecules/AccountStatus/'
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
|
|
||||||
import menu from '../data/menu.json'
|
import menu from '../data/menu.json'
|
||||||
@ -27,6 +28,7 @@ const Header = () => (
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
<AccountStatus className={styles.accountStatus} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
@ -37,10 +37,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: rgba($brand-white, .7);
|
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: .8;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ interface ButtonProps {
|
|||||||
link?: boolean
|
link?: boolean
|
||||||
href?: string
|
href?: string
|
||||||
onClick?: any
|
onClick?: any
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Button extends PureComponent<ButtonProps, any> {
|
export default class Button extends PureComponent<ButtonProps, any> {
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
.form {
|
.form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: $brand-white;
|
||||||
|
padding: $spacer;
|
||||||
|
border: 1px solid $brand-grey-lighter;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -9,6 +13,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formMinimal {
|
||||||
|
composes: form;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.formHeader {
|
.formHeader {
|
||||||
margin-bottom: $spacer;
|
margin-bottom: $spacer;
|
||||||
}
|
}
|
@ -6,14 +6,20 @@ const Form = ({
|
|||||||
description,
|
description,
|
||||||
children,
|
children,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
minimal,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
children: any
|
children: any
|
||||||
onSubmit?: any
|
onSubmit?: any
|
||||||
|
minimal?: boolean
|
||||||
}) => (
|
}) => (
|
||||||
<form className={styles.form} onSubmit={onSubmit} {...props}>
|
<form
|
||||||
|
className={minimal ? styles.formMinimal : styles.form}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<header className={styles.formHeader}>
|
<header className={styles.formHeader}>
|
||||||
<h1 className={styles.formTitle}>{title}</h1>
|
<h1 className={styles.formTitle}>{title}</h1>
|
@ -49,7 +49,15 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InputComponent = () => {
|
public InputComponent = () => {
|
||||||
const { type, options, group, name, required, onChange } = this.props
|
const {
|
||||||
|
type,
|
||||||
|
options,
|
||||||
|
group,
|
||||||
|
name,
|
||||||
|
required,
|
||||||
|
onChange,
|
||||||
|
value
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const wrapClass = this.inputWrapClasses()
|
const wrapClass = this.inputWrapClasses()
|
||||||
|
|
||||||
@ -64,19 +72,22 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
|||||||
onFocus={this.toggleFocus}
|
onFocus={this.toggleFocus}
|
||||||
onBlur={this.toggleFocus}
|
onBlur={this.toggleFocus}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
>
|
>
|
||||||
<option value="">---</option>
|
<option value="">---</option>
|
||||||
{options &&
|
{options &&
|
||||||
options.map((option: string, index: number) => (
|
options
|
||||||
<option
|
.sort((a, b) => a.localeCompare(b))
|
||||||
key={index}
|
.map((option: string, index: number) => (
|
||||||
value={slugify(option, {
|
<option
|
||||||
lower: true
|
key={index}
|
||||||
})}
|
value={slugify(option, {
|
||||||
>
|
lower: true
|
||||||
{option}
|
})}
|
||||||
</option>
|
>
|
||||||
))}
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
@ -3,6 +3,7 @@
|
|||||||
.spinner {
|
.spinner {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-top: $spacer * $line-height;
|
||||||
margin-bottom: $spacer / 2;
|
margin-bottom: $spacer / 2;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
@ -13,7 +14,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-top: -10px;
|
margin-top: -20px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid $brand-purple;
|
border: 2px solid $brand-purple;
|
||||||
@ -24,7 +25,6 @@
|
|||||||
|
|
||||||
.spinnerMessage {
|
.spinnerMessage {
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
margin-top: $spacer / 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spinner {
|
@keyframes spinner {
|
@ -0,0 +1,33 @@
|
|||||||
|
@import '../../../styles/variables';
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default: red square
|
||||||
|
.statusIndicator {
|
||||||
|
width: $font-size-small;
|
||||||
|
height: $font-size-small;
|
||||||
|
display: block;
|
||||||
|
background: $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
// yellow triangle
|
||||||
|
.statusIndicatorCloseEnough {
|
||||||
|
composes: statusIndicator;
|
||||||
|
background: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: $font-size-small / 1.7 solid transparent;
|
||||||
|
border-right: $font-size-small / 1.7 solid transparent;
|
||||||
|
border-bottom: $font-size-small solid $yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// green circle
|
||||||
|
.statusIndicatorActive {
|
||||||
|
composes: statusIndicator;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $green;
|
||||||
|
}
|
35
client/src/components/molecules/AccountStatus/Indicator.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import { User } from '../../../context/User'
|
||||||
|
import styles from './Indicator.module.scss'
|
||||||
|
|
||||||
|
const Indicator = ({
|
||||||
|
className,
|
||||||
|
togglePopover,
|
||||||
|
forwardedRef
|
||||||
|
}: {
|
||||||
|
className?: string
|
||||||
|
togglePopover: () => void
|
||||||
|
forwardedRef: (ref: HTMLElement | null) => void
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={cx(styles.status, className)}
|
||||||
|
onMouseOver={togglePopover}
|
||||||
|
onMouseOut={togglePopover}
|
||||||
|
ref={forwardedRef}
|
||||||
|
>
|
||||||
|
<User.Consumer>
|
||||||
|
{states =>
|
||||||
|
!states.isWeb3 ? (
|
||||||
|
<span className={styles.statusIndicator} />
|
||||||
|
) : !states.isLogged ? (
|
||||||
|
<span className={styles.statusIndicatorCloseEnough} />
|
||||||
|
) : states.isLogged ? (
|
||||||
|
<span className={styles.statusIndicatorActive} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</User.Consumer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Indicator
|
@ -0,0 +1,62 @@
|
|||||||
|
@import '../../../styles/variables';
|
||||||
|
|
||||||
|
$popoverWidth: 18rem;
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
position: relative;
|
||||||
|
width: $popoverWidth;
|
||||||
|
padding: $spacer / 2;
|
||||||
|
background: $brand-black;
|
||||||
|
border-radius: .1rem;
|
||||||
|
border: .1rem solid $brand-grey-light;
|
||||||
|
box-shadow: 0 6px 16px 0 rgba($brand-black, .3);
|
||||||
|
color: $brand-grey-light;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
animation: showPopup .2s ease-in forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes showPopup {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverInfoline {
|
||||||
|
border-bottom: .05rem solid $brand-grey;
|
||||||
|
padding: $spacer / 3 0;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
width: 15rem;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
margin-left: $spacer / 2;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
59
client/src/components/molecules/AccountStatus/Popover.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { User } from '../../../context/User'
|
||||||
|
import styles from './Popover.module.scss'
|
||||||
|
|
||||||
|
const Popover = ({
|
||||||
|
forwardedRef,
|
||||||
|
style
|
||||||
|
}: {
|
||||||
|
forwardedRef: (ref: HTMLElement | null) => void
|
||||||
|
style: React.CSSProperties
|
||||||
|
}) => (
|
||||||
|
<div className={styles.popover} ref={forwardedRef} style={style}>
|
||||||
|
<User.Consumer>
|
||||||
|
{states =>
|
||||||
|
states.account &&
|
||||||
|
states.balance && (
|
||||||
|
<div className={styles.popoverInfoline}>
|
||||||
|
<span
|
||||||
|
className={styles.balance}
|
||||||
|
title={(states.balance.eth / 1e18).toFixed(10)}
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{(states.balance.eth / 1e18)
|
||||||
|
.toFixed(3)
|
||||||
|
.slice(0, -1)}
|
||||||
|
</strong>{' '}
|
||||||
|
ETH
|
||||||
|
</span>
|
||||||
|
<span className={styles.balance}>
|
||||||
|
<strong>{states.balance.ocn}</strong> OCEAN
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</User.Consumer>
|
||||||
|
|
||||||
|
<div className={styles.popoverInfoline}>
|
||||||
|
<User.Consumer>
|
||||||
|
{states =>
|
||||||
|
states.account ? (
|
||||||
|
<span className={styles.address} title={states.account}>
|
||||||
|
{states.account}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<em>No account selected</em>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</User.Consumer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.popoverInfoline}>
|
||||||
|
<User.Consumer>
|
||||||
|
{states => states.network && states.network}
|
||||||
|
</User.Consumer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Popover
|
54
client/src/components/molecules/AccountStatus/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import { Manager, Reference, Popper } from 'react-popper'
|
||||||
|
import AccountPopover from './Popover'
|
||||||
|
import AccountIndicator from './Indicator'
|
||||||
|
|
||||||
|
interface AccountStatusProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccountStatusState {
|
||||||
|
isPopoverOpen: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AccountStatus extends PureComponent<
|
||||||
|
AccountStatusProps,
|
||||||
|
AccountStatusState
|
||||||
|
> {
|
||||||
|
public state = {
|
||||||
|
isPopoverOpen: false
|
||||||
|
}
|
||||||
|
|
||||||
|
public togglePopover() {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
isPopoverOpen: !prevState.isPopoverOpen
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<Manager>
|
||||||
|
<Reference>
|
||||||
|
{({ ref }) => (
|
||||||
|
<AccountIndicator
|
||||||
|
togglePopover={() => this.togglePopover()}
|
||||||
|
className={this.props.className}
|
||||||
|
forwardedRef={ref}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Reference>
|
||||||
|
{this.state.isPopoverOpen && (
|
||||||
|
<Popper placement="auto">
|
||||||
|
{({ ref, style, placement }) => (
|
||||||
|
<AccountPopover
|
||||||
|
forwardedRef={ref}
|
||||||
|
style={style}
|
||||||
|
data-placement={placement}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
)}
|
||||||
|
</Manager>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import { User } from '../../context/User'
|
||||||
import Spinner from '../atoms/Spinner'
|
import Spinner from '../atoms/Spinner'
|
||||||
import Asset from '../molecules/Asset'
|
import Asset from '../molecules/Asset'
|
||||||
import styles from './AssetsUser.module.scss'
|
import styles from './AssetsUser.module.scss'
|
||||||
|
import { Logger } from '@oceanprotocol/squid'
|
||||||
|
|
||||||
export default class AssetsUser extends PureComponent {
|
export default class AssetsUser extends PureComponent {
|
||||||
public state = { results: [], isLoading: true }
|
public state = { results: [], isLoading: true }
|
||||||
@ -12,23 +14,29 @@ export default class AssetsUser extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async searchOcean() {
|
private async searchOcean() {
|
||||||
const queryRequest: any = {
|
this.context.ocean.keeper.didRegistry.contract.getPastEvents(
|
||||||
offset: 100,
|
'DIDAttributeRegistered',
|
||||||
page: 0,
|
{
|
||||||
query: {
|
filter: { _owner: this.context.account },
|
||||||
// TODO: query all assets published by current active account
|
fromBlock: 0,
|
||||||
$text: {
|
toBlock: 'latest'
|
||||||
$search: 'zoid'
|
},
|
||||||
|
async (error: any, events: any) => {
|
||||||
|
if (error) {
|
||||||
|
Logger.log('error retrieving', error)
|
||||||
|
this.setState({ isLoading: false })
|
||||||
|
} else {
|
||||||
|
const results = []
|
||||||
|
for (const event of events) {
|
||||||
|
const ddo = await this.context.ocean.resolveDID(
|
||||||
|
`did:op:${event.returnValues._did.substring(2)}`
|
||||||
|
)
|
||||||
|
results.push(ddo)
|
||||||
|
}
|
||||||
|
this.setState({ results, isLoading: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
try {
|
|
||||||
const assets = await this.context.ocean.searchAssets(queryRequest)
|
|
||||||
this.setState({ results: assets, isLoading: false })
|
|
||||||
} catch (error) {
|
|
||||||
this.setState({ isLoading: false })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@ -54,3 +62,5 @@ export default class AssetsUser extends PureComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetsUser.contextType = User
|
23
client/src/components/molecules/Web3message.module.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
color: $brand-grey;
|
||||||
|
padding-left: 2rem;
|
||||||
|
position: relative;
|
||||||
|
border-bottom: .1rem solid $brand-grey-lighter;
|
||||||
|
border-top: .1rem solid $brand-grey-lighter;
|
||||||
|
padding-top: $spacer / 2;
|
||||||
|
padding-bottom: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $spacer / 8;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-left: -($spacer);
|
||||||
|
margin-right: $spacer / 3;
|
||||||
|
}
|
60
client/src/components/molecules/Web3message.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import AccountStatus from './AccountStatus/'
|
||||||
|
import styles from './Web3message.module.scss'
|
||||||
|
import { User } from '../../context/User'
|
||||||
|
|
||||||
|
export default class Web3message extends PureComponent {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<User.Consumer>
|
||||||
|
{states =>
|
||||||
|
!states.isWeb3
|
||||||
|
? this.noWeb3()
|
||||||
|
: !states.isLogged
|
||||||
|
? this.unlockAccount(states)
|
||||||
|
: states.isLogged
|
||||||
|
? this.haveAccount(states.account)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</User.Consumer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public noWeb3() {
|
||||||
|
return (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<AccountStatus className={styles.status} /> No Web3 Browser. For
|
||||||
|
publishing an asset you need to{' '}
|
||||||
|
<a href="https://docs.oceanprotocol.com/tutorials/metamask-setup/">
|
||||||
|
setup MetaMask
|
||||||
|
</a>{' '}
|
||||||
|
or use any other Web3-capable plugin or browser.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public unlockAccount(states: any) {
|
||||||
|
return (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<AccountStatus className={styles.status} /> Account locked. For
|
||||||
|
publishing an asset you need to unlock your Web3 account.
|
||||||
|
<Button link onClick={states.startLogin}>
|
||||||
|
Unlock account
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public haveAccount(account: string) {
|
||||||
|
return (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<AccountStatus className={styles.status} /> Connected with
|
||||||
|
account
|
||||||
|
<code className={styles.account} title={account && account}>
|
||||||
|
{`${account && account.substring(0, 20)}...`}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
export const serviceScheme = 'http'
|
||||||
|
export const serviceHost = 'localhost'
|
||||||
|
export const servicePort = 4000
|
||||||
|
|
||||||
export const nodeScheme = 'http'
|
export const nodeScheme = 'http'
|
||||||
export const nodeHost = 'localhost'
|
export const nodeHost = 'localhost'
|
||||||
export const nodePort = 8545
|
export const nodePort = 8545
|
||||||
@ -22,4 +26,8 @@ export const threshold = 0
|
|||||||
export const password = 'node0'
|
export const password = 'node0'
|
||||||
export const address = '0x00bd138abd70e2f00903268f3db08f2d25677c9e'
|
export const address = '0x00bd138abd70e2f00903268f3db08f2d25677c9e'
|
||||||
|
|
||||||
|
export const faucetScheme = 'http'
|
||||||
|
export const faucetHost = 'localhost'
|
||||||
|
export const faucetPort = 3001
|
||||||
|
|
||||||
export const verbose = true
|
export const verbose = true
|
@ -7,7 +7,15 @@ export const User = React.createContext({
|
|||||||
account: '',
|
account: '',
|
||||||
web3: {},
|
web3: {},
|
||||||
ocean: {},
|
ocean: {},
|
||||||
|
balance: {
|
||||||
|
eth: 0,
|
||||||
|
ocn: 0
|
||||||
|
},
|
||||||
|
network: '',
|
||||||
startLogin: () => {
|
startLogin: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
|
},
|
||||||
|
requestFromFaucet: () => {
|
||||||
|
/* empty */
|
||||||
}
|
}
|
||||||
})
|
})
|
114
client/src/data/form-publish.json
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Essentials",
|
||||||
|
"description": "Start by adding a title and URLs for your data set.",
|
||||||
|
"fields": {
|
||||||
|
"name": {
|
||||||
|
"label": "Title",
|
||||||
|
"placeholder": "i.e. Almond sales data",
|
||||||
|
"type": "text",
|
||||||
|
"required": true,
|
||||||
|
"help": "Enter a concise title. You will be able to enter a more thorough description in the next step."
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"label": "Files",
|
||||||
|
"placeholder": "i.e. https://file.com/file.json",
|
||||||
|
"type": "text",
|
||||||
|
"required": true,
|
||||||
|
"help": "Provide one or multiple urls to your data set files."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Information",
|
||||||
|
"description": "Further describe and categorize your data set to help people discover it.",
|
||||||
|
"fields": {
|
||||||
|
"description": {
|
||||||
|
"label": "Description",
|
||||||
|
"description": "Add a thorough description with as much detail as possible.",
|
||||||
|
"placeholder": "i.e. Almond sales data ",
|
||||||
|
"type": "textarea",
|
||||||
|
"required": true,
|
||||||
|
"rows": 5
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"label": "Categories",
|
||||||
|
"description": "Pick a category which best fits your data set.",
|
||||||
|
"type": "select",
|
||||||
|
"required": true,
|
||||||
|
"options": [
|
||||||
|
"Image Recognition",
|
||||||
|
"Dataset Of Datasets",
|
||||||
|
"Language",
|
||||||
|
"Performing Arts",
|
||||||
|
"Visual Arts & Design",
|
||||||
|
"Philosophy",
|
||||||
|
"History",
|
||||||
|
"Theology",
|
||||||
|
"Anthropology & Archeology",
|
||||||
|
"Sociology",
|
||||||
|
"Psychology",
|
||||||
|
"Politics",
|
||||||
|
"Interdisciplinary",
|
||||||
|
"Economics & Finance",
|
||||||
|
"Demography",
|
||||||
|
"Biology",
|
||||||
|
"Chemistry",
|
||||||
|
"Physics & Energy",
|
||||||
|
"Earth & Climate",
|
||||||
|
"Space & Astronomy",
|
||||||
|
"Mathematics",
|
||||||
|
"Computer Technology",
|
||||||
|
"Engineering",
|
||||||
|
"Agriculture & Bio Engineering",
|
||||||
|
"Transportation",
|
||||||
|
"Urban Planning",
|
||||||
|
"Medicine",
|
||||||
|
"Business & Management",
|
||||||
|
"Sports & Recreation",
|
||||||
|
"Communication & Journalism",
|
||||||
|
"Other"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Authorship",
|
||||||
|
"description": "Give proper attribution for your data set.",
|
||||||
|
"fields": {
|
||||||
|
"author": {
|
||||||
|
"label": "Author",
|
||||||
|
"placeholder": "i.e. Jelly McJellyfish",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"copyrightHolder": {
|
||||||
|
"label": "Copyright Holder",
|
||||||
|
"placeholder": "i.e. Marine Institute of Jellyfish",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"label": "License",
|
||||||
|
"type": "select",
|
||||||
|
"required": true,
|
||||||
|
"options": [
|
||||||
|
"Public Domain",
|
||||||
|
"CC BY: Attribution",
|
||||||
|
"CC BY-SA: Attribution ShareAlike",
|
||||||
|
"CC BY-ND: Attribution-NoDerivs",
|
||||||
|
"CC BY-NC: Attribution-NonCommercial",
|
||||||
|
"CC BY-NC-SA: Attribution-NonCommercial-ShareAlike",
|
||||||
|
"CC BY-NC-ND: Attribution-NonCommercial-NoDerivs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Register",
|
||||||
|
"description": "Splendid, we got all the data. Now let's register your data set.",
|
||||||
|
"content": "After clicking the button below you will be asked by your wallet to sign this request."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -3,6 +3,14 @@
|
|||||||
"title": "Publish",
|
"title": "Publish",
|
||||||
"link": "/publish"
|
"link": "/publish"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Invoices",
|
||||||
|
"link": "/invoices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Faucet",
|
||||||
|
"link": "/faucet"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "About",
|
"title": "About",
|
||||||
"link": "/about"
|
"link": "/about"
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
15
client/src/routes/Faucet.module.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@import '../styles/variables';
|
||||||
|
|
||||||
|
.action {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: $spacer * 2;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: $spacer;
|
||||||
|
color: $brand-grey-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: $green;
|
||||||
|
}
|
96
client/src/routes/Faucet.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import Route from '../components/templates/Route'
|
||||||
|
import Button from '../components/atoms/Button'
|
||||||
|
import Spinner from '../components/atoms/Spinner'
|
||||||
|
import { User } from '../context/User'
|
||||||
|
import Web3message from '../components/molecules/Web3message'
|
||||||
|
import styles from './Faucet.module.scss'
|
||||||
|
|
||||||
|
interface FaucetState {
|
||||||
|
isLoading: boolean
|
||||||
|
success?: string
|
||||||
|
error?: string
|
||||||
|
eth?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Faucet extends PureComponent<{}, FaucetState> {
|
||||||
|
public state = {
|
||||||
|
isLoading: false,
|
||||||
|
success: undefined,
|
||||||
|
error: undefined,
|
||||||
|
eth: 'xx'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTokens = async (requestFromFaucet: () => void) => {
|
||||||
|
this.setState({ isLoading: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestFromFaucet()
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
success: `Successfully added ${
|
||||||
|
this.state.eth
|
||||||
|
} ETH to your account.`
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ isLoading: false, error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMarkup = () => (
|
||||||
|
<User.Consumer>
|
||||||
|
{states => (
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
onClick={() => this.getTokens(states.requestFromFaucet)}
|
||||||
|
>
|
||||||
|
Request Ether
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</User.Consumer>
|
||||||
|
)
|
||||||
|
|
||||||
|
private ActionMarkup = () => (
|
||||||
|
<div className={styles.action}>
|
||||||
|
{this.state.isLoading ? (
|
||||||
|
<Spinner message="Getting Ether..." />
|
||||||
|
) : this.state.error ? (
|
||||||
|
<div className={styles.error}>
|
||||||
|
{this.state.error}{' '}
|
||||||
|
<Button link onClick={this.reset}>
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : this.state.success ? (
|
||||||
|
<div className={styles.success}>{this.state.success}</div>
|
||||||
|
) : (
|
||||||
|
<this.RequestMarkup />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can only request Ether once every 24 hours for your address.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
private reset = () => {
|
||||||
|
this.setState({
|
||||||
|
error: undefined,
|
||||||
|
success: undefined,
|
||||||
|
isLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
title="Faucet"
|
||||||
|
description="Shower yourself with some Ether for the Ocean POA network."
|
||||||
|
>
|
||||||
|
<Web3message />
|
||||||
|
|
||||||
|
<this.ActionMarkup />
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -26,18 +26,21 @@ class Home extends Component<HomeProps, HomeState> {
|
|||||||
description={meta.description}
|
description={meta.description}
|
||||||
className={styles.home}
|
className={styles.home}
|
||||||
>
|
>
|
||||||
<Form onSubmit={this.searchAssets}>
|
<Form onSubmit={this.searchAssets} minimal>
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
name="search"
|
name="search"
|
||||||
label="Search"
|
label="Search for data sets"
|
||||||
placeholder="i.e. almond sales data"
|
placeholder="i.e. almond sales data"
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
onChange={this.inputChange}
|
onChange={this.inputChange}
|
||||||
group={<Button>Search</Button>}
|
group={
|
||||||
|
<Button primary disabled={!this.state.search}>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<AssetsUser />
|
<AssetsUser />
|
||||||
</Route>
|
</Route>
|
||||||
)
|
)
|
61
client/src/routes/Invoices.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import Route from '../components/templates/Route'
|
||||||
|
import { User } from '../context/User'
|
||||||
|
import Asset from '../components/molecules/Asset'
|
||||||
|
import { Logger } from '@oceanprotocol/squid'
|
||||||
|
import styles from './Search.module.scss'
|
||||||
|
|
||||||
|
interface InvoicesState {
|
||||||
|
results: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Invoices extends Component<{}, InvoicesState> {
|
||||||
|
public state = { results: [] }
|
||||||
|
|
||||||
|
public async componentDidMount() {
|
||||||
|
// this is currently my published assets
|
||||||
|
this.context.ocean.keeper.didRegistry.contract.getPastEvents(
|
||||||
|
'DIDAttributeRegistered',
|
||||||
|
{
|
||||||
|
filter: { _owner: this.context.account },
|
||||||
|
fromBlock: 0,
|
||||||
|
toBlock: 'latest'
|
||||||
|
},
|
||||||
|
async (error: any, events: any) => {
|
||||||
|
if (error) {
|
||||||
|
Logger.log('error retrieving', error)
|
||||||
|
} else {
|
||||||
|
const results = []
|
||||||
|
for (const event of events) {
|
||||||
|
const ddo = await this.context.ocean.resolveDID(
|
||||||
|
`did:op:${event.returnValues._did.substring(2)}`
|
||||||
|
)
|
||||||
|
results.push(ddo)
|
||||||
|
}
|
||||||
|
this.setState({ results })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderResults = () =>
|
||||||
|
this.state.results.length ? (
|
||||||
|
<div className={styles.results}>
|
||||||
|
{this.state.results.map((asset, index) => (
|
||||||
|
<Asset key={index} asset={asset} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>No invoices yet</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<Route title="Your invoices" wide>
|
||||||
|
{this.renderResults()}
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoices.contextType = User
|
@ -32,3 +32,13 @@
|
|||||||
opacity: .7;
|
opacity: .7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: $font-size-mini;
|
||||||
|
font-size: .85rem;
|
||||||
|
color: #6e7e93;
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './Item.module.scss'
|
import styles from './Item.module.scss'
|
||||||
|
import filesize from 'filesize'
|
||||||
|
|
||||||
const Item = ({ item, removeItem }: { item: any; removeItem: any }) => (
|
const Item = ({ item, removeItem }: { item: any; removeItem: any }) => (
|
||||||
<li>
|
<li>
|
||||||
<a href={item.url}>{item.url}</a>
|
<a href={item.url}>{item.url}</a>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<span>url: {item.found ? 'confirmed' : 'unconfirmed'}</span>
|
||||||
|
<span>
|
||||||
|
size:
|
||||||
|
{item.found && item.size ? filesize(item.size) : 'unknown'}
|
||||||
|
</span>
|
||||||
|
<span>type: {item.found && item.type ? item.type : 'unknown'}</span>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.remove}
|
className={styles.remove}
|
@ -6,13 +6,14 @@ import ItemForm from './ItemForm'
|
|||||||
import Item from './Item'
|
import Item from './Item'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
import { serviceHost, servicePort, serviceScheme } from '../../../config'
|
||||||
|
|
||||||
interface FilesProps {
|
interface FilesProps {
|
||||||
files: any[]
|
files: any[]
|
||||||
placeholder: string
|
placeholder: string
|
||||||
help?: string
|
help?: string
|
||||||
name: string
|
name: string
|
||||||
onChange: any
|
onChange: any
|
||||||
// resetForm: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilesStates {
|
interface FilesStates {
|
||||||
@ -30,14 +31,48 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
|||||||
this.setState({ isFormShown: !this.state.isFormShown })
|
this.setState({ isFormShown: !this.state.isFormShown })
|
||||||
}
|
}
|
||||||
|
|
||||||
public addItem = (value: string) => {
|
public addItem = async (value: string) => {
|
||||||
this.props.files.push({ url: value })
|
let res: any
|
||||||
// this.props.resetForm()
|
let file: any = { url: value, found: false }
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${serviceScheme}://${serviceHost}:${servicePort}/api/v1/urlcheck`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ url: value }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
res = await response.json()
|
||||||
|
file.size = res.result.contentLength
|
||||||
|
file.type = res.result.contentType
|
||||||
|
file.found = res.result.found
|
||||||
|
} catch (error) {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
this.props.files.push(file)
|
||||||
|
const event = {
|
||||||
|
currentTarget: {
|
||||||
|
name: 'files',
|
||||||
|
value: this.props.files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onChange(event as any)
|
||||||
this.setState({ isFormShown: !this.state.isFormShown })
|
this.setState({ isFormShown: !this.state.isFormShown })
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeItem = (index: number) => {
|
public removeItem = (index: number) => {
|
||||||
this.props.files.splice(index, 1)
|
this.props.files.splice(index, 1)
|
||||||
|
const event = {
|
||||||
|
currentTarget: {
|
||||||
|
name: 'files',
|
||||||
|
value: this.props.files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onChange(event as any)
|
||||||
|
this.forceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@ -52,7 +87,7 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
|||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
name={name}
|
name={name}
|
||||||
value={files}
|
value={JSON.stringify(files)}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
|
|
76
client/src/routes/Publish/Progress.module.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
margin-top: $spacer * 1.5;
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: inline-block;
|
||||||
|
width: 25%;
|
||||||
|
text-align: center;
|
||||||
|
color: $brand-grey-light;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 60%;
|
||||||
|
height: .1rem;
|
||||||
|
background: $brand-grey-lighter;
|
||||||
|
position: absolute;
|
||||||
|
top: 20%;
|
||||||
|
left: -30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
composes: item;
|
||||||
|
font-family: $font-family-button;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $brand-black;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
background: $brand-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background: $green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed {
|
||||||
|
composes: active;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
background: $green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-top: $spacer / 8;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $brand-grey-light;
|
||||||
|
color: $brand-white;
|
||||||
|
font-family: $font-family-button;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
}
|
32
client/src/routes/Publish/Progress.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './Progress.module.scss'
|
||||||
|
|
||||||
|
const Progress = ({
|
||||||
|
currentStep,
|
||||||
|
steps
|
||||||
|
}: {
|
||||||
|
currentStep: number
|
||||||
|
steps: any[]
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ul className={styles.progress}>
|
||||||
|
{steps.map(({ title }, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className={
|
||||||
|
currentStep === index + 1
|
||||||
|
? styles.active
|
||||||
|
: currentStep > index + 1
|
||||||
|
? styles.completed
|
||||||
|
: styles.item
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className={styles.number}>{index + 1}</span>
|
||||||
|
<span className={styles.label}>{title}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Progress
|
26
client/src/routes/Publish/Step.module.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: $font-size-h2;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: $spacer / 4;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: $spacer / 2;
|
||||||
|
|
||||||
|
button:last-child {
|
||||||
|
min-width: 12rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
164
client/src/routes/Publish/Step.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import Input from '../../components/atoms/Form/Input'
|
||||||
|
import Label from '../../components/atoms/Form/Label'
|
||||||
|
import Row from '../../components/atoms/Form/Row'
|
||||||
|
import Button from '../../components/atoms/Button'
|
||||||
|
import { User } from '../../context/User'
|
||||||
|
import Files from './Files/'
|
||||||
|
import StepRegisterContent from './StepRegisterContent'
|
||||||
|
import styles from './Step.module.scss'
|
||||||
|
|
||||||
|
interface StepProps {
|
||||||
|
currentStep: number
|
||||||
|
index: number
|
||||||
|
inputChange: any
|
||||||
|
inputToArrayChange: any
|
||||||
|
fields?: any[]
|
||||||
|
state: any
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
next: any
|
||||||
|
prev: any
|
||||||
|
totalSteps: number
|
||||||
|
tryAgain: any
|
||||||
|
toStart: any
|
||||||
|
publishedDid?: string
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Step extends PureComponent<StepProps, {}> {
|
||||||
|
public previousButton() {
|
||||||
|
const { currentStep, prev } = this.props
|
||||||
|
|
||||||
|
if (currentStep !== 1) {
|
||||||
|
return (
|
||||||
|
<Button link onClick={prev}>
|
||||||
|
← Previous
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
public nextButton() {
|
||||||
|
const { currentStep, next, totalSteps, state } = this.props
|
||||||
|
|
||||||
|
if (currentStep < totalSteps) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!state.validationStatus[currentStep].allFieldsValid
|
||||||
|
}
|
||||||
|
onClick={next}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
currentStep,
|
||||||
|
index,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
fields,
|
||||||
|
inputChange,
|
||||||
|
inputToArrayChange,
|
||||||
|
state,
|
||||||
|
totalSteps,
|
||||||
|
tryAgain,
|
||||||
|
toStart,
|
||||||
|
content
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (currentStep !== index + 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastStep = currentStep === totalSteps
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<h2 className={styles.title}>{title}</h2>
|
||||||
|
<p className={styles.description}>{description}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{fields &&
|
||||||
|
Object.entries(fields).map(([key, value]) => {
|
||||||
|
let onChange = inputChange
|
||||||
|
|
||||||
|
if (key === 'categories') {
|
||||||
|
onChange = inputToArrayChange
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'files') {
|
||||||
|
return (
|
||||||
|
<Row key={key}>
|
||||||
|
<Label htmlFor={key} required>
|
||||||
|
{value.label}
|
||||||
|
</Label>
|
||||||
|
<Files
|
||||||
|
placeholder={value.placeholder}
|
||||||
|
name={key}
|
||||||
|
help={value.help}
|
||||||
|
files={state.files}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
label={value.label}
|
||||||
|
placeholder={value.placeholder}
|
||||||
|
required={value.required}
|
||||||
|
type={value.type}
|
||||||
|
help={value.help}
|
||||||
|
options={value.options}
|
||||||
|
onChange={onChange}
|
||||||
|
rows={value.rows}
|
||||||
|
value={(state as any)[key]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{lastStep && (
|
||||||
|
<StepRegisterContent
|
||||||
|
tryAgain={tryAgain}
|
||||||
|
toStart={toStart}
|
||||||
|
state={state}
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.actions}>
|
||||||
|
{this.previousButton()}
|
||||||
|
{this.nextButton()}
|
||||||
|
|
||||||
|
{lastStep && (
|
||||||
|
<User.Consumer>
|
||||||
|
{states =>
|
||||||
|
states.isLogged ? (
|
||||||
|
<Button primary>Register asset</Button>
|
||||||
|
) : (
|
||||||
|
<Button onClick={states.startLogin}>
|
||||||
|
Register asset (login first)
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</User.Consumer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Step.contextType = User
|
@ -0,0 +1,5 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
}
|