1
0
mirror of https://github.com/oceanprotocol/commons.git synced 2023-03-15 18:03:00 +01:00
This commit is contained in:
Jernej Pregelj 2019-03-20 15:27:00 +01:00
commit 7555ced58b
125 changed files with 11112 additions and 2900 deletions

12
.travis.sh Executable file
View 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

View File

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

View File

13
client/.travis.yml Normal file
View File

@ -0,0 +1,13 @@
language: node_js
node_js: node
script:
- npm test
- npm run build
notifications:
email: false
cache:
directories:
- node_modules

File diff suppressed because it is too large Load Diff

View File

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

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View 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

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

View File

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

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

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

View File

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

View File

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

View 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."
}
]
}

View File

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

View File

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 932 B

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

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

View File

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

View 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

View File

@ -32,3 +32,13 @@
opacity: .7; opacity: .7;
} }
} }
.details {
font-size: $font-size-mini;
font-size: .85rem;
color: #6e7e93;
span {
padding-right: .5rem;
}
}

View File

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

View File

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

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

View 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

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

View 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

View File

@ -0,0 +1,5 @@
@import '../../styles/variables';
.message {
margin-bottom: $spacer;
}

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