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,13 +1,12 @@
|
||||
language: node_js
|
||||
node_js: node
|
||||
node_js:
|
||||
- "11.10.1"
|
||||
|
||||
script:
|
||||
- npm test
|
||||
- npm run build
|
||||
script: "./.travis.sh"
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- node_modules
|
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",
|
||||
"lint:css": "stylelint './src/**/*.{css,scss}'",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oceanprotocol/art": "^2.2.0",
|
||||
"@oceanprotocol/squid": "^0.3.0",
|
||||
"@types/is-url": "^1.2.28",
|
||||
"@types/react-helmet": "^5.0.8",
|
||||
"@types/react-transition-group": "^2.0.15",
|
||||
"@oceanprotocol/squid": "^0.4.0",
|
||||
"classnames": "^2.2.6",
|
||||
"eslint": "^5.6.0",
|
||||
"filesize": "^4.1.2",
|
||||
"is-url": "^1.2.4",
|
||||
"moment": "^2.24.0",
|
||||
"query-string": "^6.2.0",
|
||||
"react": "^16.8.1",
|
||||
"react-dom": "^16.8.1",
|
||||
"react": "^16.8.3",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-moment": "^0.8.4",
|
||||
"react-popper": "^1.3.3",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-transition-group": "^2.5.3",
|
||||
"react-transition-group": "^2.6.0",
|
||||
"slugify": "^1.3.4",
|
||||
"web3": "^1.0.0-beta.43"
|
||||
"web3": "^1.0.0-beta.48"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^10.12.21",
|
||||
"@types/filesize": "^4.0.1",
|
||||
"@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/react": "^16.8.2",
|
||||
"@types/react-dom": "^16.8.0",
|
||||
"@types/react": "^16.8.5",
|
||||
"@types/react-dom": "^16.8.2",
|
||||
"@types/react-helmet": "^5.0.8",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"@types/react-transition-group": "^2.0.16",
|
||||
"@types/web3": "^1.0.18",
|
||||
"@typescript-eslint/eslint-plugin": "^1.2.0",
|
||||
"@typescript-eslint/parser": "^1.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||
"@typescript-eslint/parser": "^1.4.2",
|
||||
"eslint": "5.12.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",
|
||||
"node-sass": "^4.11.0",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"react-scripts": "^2.1.3",
|
||||
"react-scripts": "^2.1.5",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-bigchaindb": "^1.2.1",
|
||||
"stylelint-config-css-modules": "^1.3.0",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"typescript": "3.2.4"
|
||||
"typescript": "^3.3.3333"
|
||||
},
|
||||
"browserslist": [
|
||||
">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 Web3 from 'web3'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
import { Logger } from '@oceanprotocol/squid'
|
||||
import Header from './components/Header'
|
||||
import Footer from './components/Footer'
|
||||
import Spinner from './components/atoms/Spinner'
|
||||
@ -10,7 +11,14 @@ import Routes from './Routes'
|
||||
import './styles/global.scss'
|
||||
import styles from './App.module.scss'
|
||||
|
||||
import { nodeHost, nodePort, nodeScheme } from './config'
|
||||
import {
|
||||
nodeHost,
|
||||
nodePort,
|
||||
nodeScheme,
|
||||
faucetHost,
|
||||
faucetPort,
|
||||
faucetScheme
|
||||
} from './config'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -24,6 +32,11 @@ interface AppState {
|
||||
isLoading: boolean
|
||||
isWeb3: boolean
|
||||
account: string
|
||||
balance: {
|
||||
eth: number
|
||||
ocn: number
|
||||
}
|
||||
network: string
|
||||
web3: Web3
|
||||
ocean: {}
|
||||
startLogin: () => void
|
||||
@ -37,10 +50,40 @@ class App extends Component<{}, AppState> {
|
||||
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 = {
|
||||
isLogged: false,
|
||||
isLoading: true,
|
||||
isWeb3: false,
|
||||
balance: {
|
||||
eth: 0,
|
||||
ocn: 0
|
||||
},
|
||||
network: '',
|
||||
web3: new Web3(
|
||||
new Web3.providers.HttpProvider(
|
||||
`${nodeScheme}://${nodeHost}:${nodePort}`
|
||||
@ -48,7 +91,8 @@ class App extends Component<{}, AppState> {
|
||||
),
|
||||
account: '',
|
||||
ocean: {},
|
||||
startLogin: this.startLogin
|
||||
startLogin: this.startLogin,
|
||||
requestFromFaucet: this.requestFromFaucet
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
@ -107,7 +151,7 @@ class App extends Component<{}, AppState> {
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// continue with default
|
||||
Logger.log('web3 error', e)
|
||||
}
|
||||
}
|
||||
try {
|
||||
@ -116,8 +160,13 @@ class App extends Component<{}, AppState> {
|
||||
isLoading: false,
|
||||
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) {
|
||||
// show loading error / unable to initialize ocean
|
||||
Logger.log('ocean/balance error', e)
|
||||
this.setState({
|
||||
isLoading: false
|
||||
})
|
@ -7,6 +7,8 @@ import Home from './routes/Home'
|
||||
import NotFound from './routes/NotFound'
|
||||
import Publish from './routes/Publish/'
|
||||
import Search from './routes/Search'
|
||||
import Faucet from './routes/Faucet'
|
||||
import Invoices from './routes/Invoices'
|
||||
import Styleguide from './routes/Styleguide'
|
||||
|
||||
const Routes = () => (
|
||||
@ -17,6 +19,8 @@ const Routes = () => (
|
||||
<Route component={Publish} path="/publish" />
|
||||
<Route component={Search} path="/search" />
|
||||
<Route component={Details} path="/asset/:did" />
|
||||
<Route component={Faucet} path="/faucet" />
|
||||
<Route component={Invoices} path="/invoices" />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
)
|
@ -1,9 +1,6 @@
|
||||
@import '../styles/variables';
|
||||
|
||||
.header {
|
||||
// background: $brand-black
|
||||
// url('@oceanprotocol/art/mantaray/mantaray-back.svg') no-repeat center -6rem;
|
||||
// background-size: cover;
|
||||
width: 100%;
|
||||
padding: $spacer / 2 0;
|
||||
}
|
||||
@ -94,3 +91,8 @@
|
||||
color: $brand-pink;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.accountStatus {
|
||||
margin-left: $spacer;
|
||||
margin-bottom: .2rem;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg'
|
||||
import AccountStatus from './molecules/AccountStatus/'
|
||||
import styles from './Header.module.scss'
|
||||
|
||||
import menu from '../data/menu.json'
|
||||
@ -27,6 +28,7 @@ const Header = () => (
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
<AccountStatus className={styles.accountStatus} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
@ -37,10 +37,9 @@
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: rgba($brand-white, .7);
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
opacity: .8;
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ interface ButtonProps {
|
||||
link?: boolean
|
||||
href?: string
|
||||
onClick?: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default class Button extends PureComponent<ButtonProps, any> {
|
@ -2,6 +2,10 @@
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
background: $brand-white;
|
||||
padding: $spacer;
|
||||
border: 1px solid $brand-grey-lighter;
|
||||
border-radius: $border-radius;
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
@ -9,6 +13,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.formMinimal {
|
||||
composes: form;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.formHeader {
|
||||
margin-bottom: $spacer;
|
||||
}
|
@ -6,14 +6,20 @@ const Form = ({
|
||||
description,
|
||||
children,
|
||||
onSubmit,
|
||||
minimal,
|
||||
...props
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
children: any
|
||||
onSubmit?: any
|
||||
minimal?: boolean
|
||||
}) => (
|
||||
<form className={styles.form} onSubmit={onSubmit} {...props}>
|
||||
<form
|
||||
className={minimal ? styles.formMinimal : styles.form}
|
||||
onSubmit={onSubmit}
|
||||
{...props}
|
||||
>
|
||||
{title && (
|
||||
<header className={styles.formHeader}>
|
||||
<h1 className={styles.formTitle}>{title}</h1>
|
@ -49,7 +49,15 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@ -64,19 +72,22 @@ export default class Input extends PureComponent<InputProps, InputState> {
|
||||
onFocus={this.toggleFocus}
|
||||
onBlur={this.toggleFocus}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
>
|
||||
<option value="">---</option>
|
||||
{options &&
|
||||
options.map((option: string, index: number) => (
|
||||
<option
|
||||
key={index}
|
||||
value={slugify(option, {
|
||||
lower: true
|
||||
})}
|
||||
>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
options
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((option: string, index: number) => (
|
||||
<option
|
||||
key={index}
|
||||
value={slugify(option, {
|
||||
lower: true
|
||||
})}
|
||||
>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
@ -3,6 +3,7 @@
|
||||
.spinner {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-top: $spacer * $line-height;
|
||||
margin-bottom: $spacer / 2;
|
||||
|
||||
&:before {
|
||||
@ -13,7 +14,7 @@
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -10px;
|
||||
margin-top: -20px;
|
||||
margin-left: -10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid $brand-purple;
|
||||
@ -24,7 +25,6 @@
|
||||
|
||||
.spinnerMessage {
|
||||
color: $brand-grey-light;
|
||||
margin-top: $spacer / 2;
|
||||
}
|
||||
|
||||
@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 { Link } from 'react-router-dom'
|
||||
import { User } from '../../context/User'
|
||||
import Spinner from '../atoms/Spinner'
|
||||
import Asset from '../molecules/Asset'
|
||||
import styles from './AssetsUser.module.scss'
|
||||
import { Logger } from '@oceanprotocol/squid'
|
||||
|
||||
export default class AssetsUser extends PureComponent {
|
||||
public state = { results: [], isLoading: true }
|
||||
@ -12,23 +14,29 @@ export default class AssetsUser extends PureComponent {
|
||||
}
|
||||
|
||||
private async searchOcean() {
|
||||
const queryRequest: any = {
|
||||
offset: 100,
|
||||
page: 0,
|
||||
query: {
|
||||
// TODO: query all assets published by current active account
|
||||
$text: {
|
||||
$search: 'zoid'
|
||||
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)
|
||||
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() {
|
||||
@ -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 nodeHost = 'localhost'
|
||||
export const nodePort = 8545
|
||||
@ -22,4 +26,8 @@ export const threshold = 0
|
||||
export const password = 'node0'
|
||||
export const address = '0x00bd138abd70e2f00903268f3db08f2d25677c9e'
|
||||
|
||||
export const faucetScheme = 'http'
|
||||
export const faucetHost = 'localhost'
|
||||
export const faucetPort = 3001
|
||||
|
||||
export const verbose = true
|
@ -7,7 +7,15 @@ export const User = React.createContext({
|
||||
account: '',
|
||||
web3: {},
|
||||
ocean: {},
|
||||
balance: {
|
||||
eth: 0,
|
||||
ocn: 0
|
||||
},
|
||||
network: '',
|
||||
startLogin: () => {
|
||||
/* 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",
|
||||
"link": "/publish"
|
||||
},
|
||||
{
|
||||
"title": "Invoices",
|
||||
"link": "/invoices"
|
||||
},
|
||||
{
|
||||
"title": "Faucet",
|
||||
"link": "/faucet"
|
||||
},
|
||||
{
|
||||
"title": "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}
|
||||
className={styles.home}
|
||||
>
|
||||
<Form onSubmit={this.searchAssets}>
|
||||
<Form onSubmit={this.searchAssets} minimal>
|
||||
<Input
|
||||
type="search"
|
||||
name="search"
|
||||
label="Search"
|
||||
label="Search for data sets"
|
||||
placeholder="i.e. almond sales data"
|
||||
value={this.state.search}
|
||||
onChange={this.inputChange}
|
||||
group={<Button>Search</Button>}
|
||||
group={
|
||||
<Button primary disabled={!this.state.search}>
|
||||
Search
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
<AssetsUser />
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: $font-size-mini;
|
||||
font-size: .85rem;
|
||||
color: #6e7e93;
|
||||
|
||||
span {
|
||||
padding-right: .5rem;
|
||||
}
|
||||
}
|
@ -1,9 +1,18 @@
|
||||
import React from 'react'
|
||||
import styles from './Item.module.scss'
|
||||
import filesize from 'filesize'
|
||||
|
||||
const Item = ({ item, removeItem }: { item: any; removeItem: any }) => (
|
||||
<li>
|
||||
<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
|
||||
type="button"
|
||||
className={styles.remove}
|
@ -6,13 +6,14 @@ import ItemForm from './ItemForm'
|
||||
import Item from './Item'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
import { serviceHost, servicePort, serviceScheme } from '../../../config'
|
||||
|
||||
interface FilesProps {
|
||||
files: any[]
|
||||
placeholder: string
|
||||
help?: string
|
||||
name: string
|
||||
onChange: any
|
||||
// resetForm: any
|
||||
}
|
||||
|
||||
interface FilesStates {
|
||||
@ -30,14 +31,48 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
||||
this.setState({ isFormShown: !this.state.isFormShown })
|
||||
}
|
||||
|
||||
public addItem = (value: string) => {
|
||||
this.props.files.push({ url: value })
|
||||
// this.props.resetForm()
|
||||
public addItem = async (value: string) => {
|
||||
let res: any
|
||||
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 })
|
||||
}
|
||||
|
||||
public removeItem = (index: number) => {
|
||||
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() {
|
||||
@ -52,7 +87,7 @@ export default class Files extends PureComponent<FilesProps, FilesStates> {
|
||||
<input
|
||||
type="hidden"
|
||||
name={name}
|
||||
value={files}
|
||||
value={JSON.stringify(files)}
|
||||
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;
|
||||
}
|