mirror of
https://github.com/oceanprotocol/commons.git
synced 2023-03-15 18:03:00 +01:00
Merge pull request #176 from oceanprotocol/feature/wallets
Wallet selection
This commit is contained in:
commit
599e1834cc
@ -60,7 +60,7 @@ To make use of all the functionality, you need to connect to an Ocean network.
|
|||||||
|
|
||||||
By default, the client will connect to Ocean components running within [Ocean's Pacific network](https://docs.oceanprotocol.com/concepts/pacific-network/) remotely.
|
By default, the client will connect to Ocean components running within [Ocean's Pacific network](https://docs.oceanprotocol.com/concepts/pacific-network/) remotely.
|
||||||
|
|
||||||
With your MetaMask, connect to the Pacific network. To do this:
|
By default, the client uses a burner wallet connected to the correct network automatically. If you choose to use MetaMask, you need to connect to the Pacific network. To do this:
|
||||||
|
|
||||||
1. select Custom RPC in the network dropdown in MetaMask
|
1. select Custom RPC in the network dropdown in MetaMask
|
||||||
2. under New Network, enter `https://pacific.oceanprotocol.com` as the custom RPC URL
|
2. under New Network, enter `https://pacific.oceanprotocol.com` as the custom RPC URL
|
||||||
|
8
client/__mocks__/market-mock.ts
Normal file
8
client/__mocks__/market-mock.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const marketMock = {
|
||||||
|
totalAssets: 1000,
|
||||||
|
categories: ['category'],
|
||||||
|
network: 'Pacific',
|
||||||
|
networkMatch: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export { marketMock }
|
@ -3,30 +3,32 @@ import oceanMock from './ocean-mock'
|
|||||||
const userMock = {
|
const userMock = {
|
||||||
isLogged: false,
|
isLogged: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isWeb3: false,
|
isBurner: false,
|
||||||
isOceanNetwork: false,
|
isWeb3Capable: false,
|
||||||
account: '',
|
account: '',
|
||||||
web3: {},
|
web3: {},
|
||||||
...oceanMock,
|
...oceanMock,
|
||||||
balance: { eth: 0, ocn: 0 },
|
balance: { eth: 0, ocn: 0 },
|
||||||
network: '',
|
network: '',
|
||||||
requestFromFaucet: jest.fn(),
|
requestFromFaucet: jest.fn(),
|
||||||
unlockAccounts: jest.fn(),
|
loginMetamask: jest.fn(),
|
||||||
|
loginBurnerWallet: jest.fn(),
|
||||||
message: ''
|
message: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMockConnected = {
|
const userMockConnected = {
|
||||||
isLogged: true,
|
isLogged: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isWeb3: true,
|
isBurner: false,
|
||||||
isOceanNetwork: true,
|
isWeb3Capable: true,
|
||||||
account: '0xxxxxx',
|
account: '0xxxxxx',
|
||||||
web3: {},
|
web3: {},
|
||||||
...oceanMock,
|
...oceanMock,
|
||||||
balance: { eth: 0, ocn: 0 },
|
balance: { eth: 0, ocn: 0 },
|
||||||
network: '',
|
network: '',
|
||||||
requestFromFaucet: jest.fn(),
|
requestFromFaucet: jest.fn(),
|
||||||
unlockAccounts: jest.fn(),
|
loginMetamask: jest.fn(),
|
||||||
|
loginBurnerWallet: jest.fn(),
|
||||||
message: ''
|
message: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1431
client/package-lock.json
generated
1431
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,10 +13,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oceanprotocol/art": "^2.2.0",
|
"@oceanprotocol/art": "^2.2.0",
|
||||||
"@oceanprotocol/squid": "0.6.2",
|
"@oceanprotocol/squid": "^0.6.4",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@sindresorhus/slugify": "^0.9.1",
|
"@sindresorhus/slugify": "^0.9.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"bip39": "^3.0.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||||
"filesize": "^4.1.2",
|
"filesize": "^4.1.2",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"react-popper": "^1.3.3",
|
"react-popper": "^1.3.3",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"react-transition-group": "^4.1.1",
|
"react-transition-group": "^4.1.1",
|
||||||
|
"truffle-hdwallet-provider": "^1.0.13",
|
||||||
"web3": "1.0.0-beta.37"
|
"web3": "1.0.0-beta.37"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
1
client/src/@types/truffle-hdwallet-provider'/index.d.ts
vendored
Normal file
1
client/src/@types/truffle-hdwallet-provider'/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'truffle-hdwallet-provider'
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Route, Switch } from 'react-router-dom'
|
import { Route, Switch } from 'react-router-dom'
|
||||||
import withTracker from './hoc/withTracker'
|
|
||||||
|
|
||||||
import About from './routes/About'
|
import About from './routes/About'
|
||||||
import Home from './routes/Home'
|
import Home from './routes/Home'
|
||||||
@ -17,17 +16,17 @@ import Channel from './components/templates/Channel'
|
|||||||
|
|
||||||
const Routes = () => (
|
const Routes = () => (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route component={withTracker(Home)} exact path="/" />
|
<Route component={Home} exact path="/" />
|
||||||
<Route component={withTracker(Styleguide)} path="/styleguide" />
|
<Route component={Styleguide} path="/styleguide" />
|
||||||
<Route component={withTracker(About)} path="/about" />
|
<Route component={About} path="/about" />
|
||||||
<Route component={withTracker(Publish)} path="/publish" />
|
<Route component={Publish} path="/publish" />
|
||||||
<Route component={withTracker(Search)} path="/search" />
|
<Route component={Search} path="/search" />
|
||||||
<Route component={withTracker(Asset)} path="/asset/:did" />
|
<Route component={Asset} path="/asset/:did" />
|
||||||
<Route component={withTracker(Faucet)} path="/faucet" />
|
<Route component={Faucet} path="/faucet" />
|
||||||
<Route component={withTracker(History)} path="/history" />
|
<Route component={History} path="/history" />
|
||||||
<Route component={withTracker(Channels)} exact path="/channels" />
|
<Route component={Channels} exact path="/channels" />
|
||||||
<Route component={withTracker(Channel)} path="/channels/:channel" />
|
<Route component={Channel} path="/channels/:channel" />
|
||||||
<Route component={withTracker(NotFound)} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,16 +2,80 @@
|
|||||||
|
|
||||||
.account {
|
.account {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
> div {
|
> div:first-of-type {
|
||||||
|
flex: 0 0 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accountId {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-family: $font-family-monospace;
|
font-family: $font-family-monospace;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unlock {
|
||||||
|
font-size: $font-size-small !important; // stylelint-disable-line
|
||||||
|
margin-left: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accountType {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: calc(1.5rem + #{$spacer / 3});
|
||||||
|
font-size: $font-size-small;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $brand-grey-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
background: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: inline-block;
|
||||||
|
fill: currentColor;
|
||||||
|
margin-right: $spacer / 8;
|
||||||
|
transition: .2s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seedphrase {
|
||||||
|
margin-top: $spacer / 2;
|
||||||
|
margin-left: calc(1.5rem + #{$spacer / 4});
|
||||||
|
margin-right: calc(1.5rem + #{$spacer / 4});
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: $spacer / 2 $spacer;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
background: $body-background;
|
||||||
|
border: 1px solid $brand-grey-lighter;
|
||||||
|
margin-bottom: $spacer / 4;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.seedphraseHelp {
|
||||||
|
color: $brand-grey-light;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockies {
|
.blockies {
|
||||||
@ -21,4 +85,5 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: $spacer / 3;
|
margin-right: $spacer / 3;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
border: 1px solid $brand-grey-lighter;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,61 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
import { toDataUrl } from 'ethereum-blockies'
|
import { toDataUrl } from 'ethereum-blockies'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
|
import { User } from '../../context'
|
||||||
|
import { userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
|
||||||
describe('Account', () => {
|
describe('Account', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(<Account account={'0xxxxxxxxxxxxxxx'} />)
|
const { container } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{ ...userMockConnected, account: '0xxxxxxxxxxxxxxx' }}
|
||||||
|
>
|
||||||
|
<Account />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('outputs empty state without account', () => {
|
it('outputs empty state without account', () => {
|
||||||
const { container } = render(<Account account={''} />)
|
const { container, getByText } = render(
|
||||||
|
<User.Provider value={{ ...userMockConnected, account: '' }}>
|
||||||
|
<Account />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
expect(container.firstChild).toHaveTextContent('No account selected')
|
expect(container.firstChild).toHaveTextContent('No account selected')
|
||||||
|
fireEvent.click(getByText('Unlock Account'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('outputs blockie img', () => {
|
it('outputs blockie img', () => {
|
||||||
const account = '0xxxxxxxxxxxxxxx'
|
const account = '0xxxxxxxxxxxxxxx'
|
||||||
const blockies = toDataUrl(account)
|
const blockies = toDataUrl(account)
|
||||||
|
|
||||||
const { container } = render(<Account account={account} />)
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMockConnected, account }}>
|
||||||
|
<Account />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
expect(container.querySelector('.blockies')).toBeInTheDocument()
|
expect(container.querySelector('.blockies')).toBeInTheDocument()
|
||||||
expect(container.querySelector('.blockies')).toHaveAttribute(
|
expect(container.querySelector('.blockies')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
blockies
|
blockies
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Account info can be toggled', () => {
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<User.Provider
|
||||||
|
value={{
|
||||||
|
...userMockConnected,
|
||||||
|
isBurner: true,
|
||||||
|
account: '0xxxxxxxxxxxxxxx'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Account />
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
fireEvent.click(getByText('Burner Wallet'))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,19 +1,89 @@
|
|||||||
import React from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
import Dotdotdot from 'react-dotdotdot'
|
||||||
import { toDataUrl } from 'ethereum-blockies'
|
import { toDataUrl } from 'ethereum-blockies'
|
||||||
import styles from './Account.module.scss'
|
import styles from './Account.module.scss'
|
||||||
|
import WalletSelector from '../organisms/WalletSelector'
|
||||||
|
import content from '../../data/web3message.json'
|
||||||
|
import { ReactComponent as Caret } from '../../img/caret.svg'
|
||||||
|
import { User } from '../../context'
|
||||||
|
import Button from './Button'
|
||||||
|
|
||||||
const Account = ({ account }: { account: string }) => {
|
export default class Account extends PureComponent<
|
||||||
const blockies = account && toDataUrl(account)
|
{},
|
||||||
|
{ isAccountInfoOpen: boolean }
|
||||||
|
> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
return account && blockies ? (
|
public state = {
|
||||||
<div className={styles.account}>
|
isAccountInfoOpen: false
|
||||||
<img className={styles.blockies} src={blockies} alt="Blockies" />
|
|
||||||
<Dotdotdot clamp={2}>{account}</Dotdotdot>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<em>No account selected</em>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Account
|
private toggleAccountInfo() {
|
||||||
|
this.setState({ isAccountInfoOpen: !this.state.isAccountInfoOpen })
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { account, isBurner, loginMetamask, isWeb3Capable } = this.context
|
||||||
|
const { isAccountInfoOpen } = this.state
|
||||||
|
const seedphrase = localStorage.getItem('seedphrase') as string
|
||||||
|
const blockies = account && toDataUrl(account)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.account}>
|
||||||
|
{account ? (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
className={styles.blockies}
|
||||||
|
src={blockies}
|
||||||
|
alt="Blockies"
|
||||||
|
/>
|
||||||
|
<Dotdotdot className={styles.accountId} clamp={2}>
|
||||||
|
{account}
|
||||||
|
</Dotdotdot>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={styles.blockies} />
|
||||||
|
<em className={styles.noAccount}>
|
||||||
|
No account selected
|
||||||
|
</em>
|
||||||
|
<Button
|
||||||
|
link
|
||||||
|
className={styles.unlock}
|
||||||
|
onClick={() => loginMetamask()}
|
||||||
|
>
|
||||||
|
Unlock Account
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.accountType}>
|
||||||
|
{isBurner ? (
|
||||||
|
<button
|
||||||
|
className={styles.toggle}
|
||||||
|
onClick={() => this.toggleAccountInfo()}
|
||||||
|
title="Show More Account Info"
|
||||||
|
>
|
||||||
|
<Caret
|
||||||
|
className={isAccountInfoOpen ? styles.open : ''}
|
||||||
|
/>{' '}
|
||||||
|
Burner Wallet
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
'MetaMask'
|
||||||
|
)}
|
||||||
|
{isWeb3Capable && <WalletSelector />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isBurner && isAccountInfoOpen && (
|
||||||
|
<div className={styles.seedphrase}>
|
||||||
|
<code>{seedphrase}</code>
|
||||||
|
<p className={styles.seedphraseHelp}>
|
||||||
|
{content.seedphrase}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,7 +32,11 @@ const Modal = ({
|
|||||||
overlayClassName={styles.modalOverlay}
|
overlayClassName={styles.modalOverlay}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<button className={styles.close} onClick={toggleModal}>
|
<button
|
||||||
|
className={styles.close}
|
||||||
|
onClick={toggleModal}
|
||||||
|
data-testid="closeModal"
|
||||||
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import { User } from '../../../context'
|
import { User, Market } from '../../../context'
|
||||||
import styles from './Indicator.module.scss'
|
import styles from './Indicator.module.scss'
|
||||||
|
|
||||||
const Indicator = ({
|
const Indicator = ({
|
||||||
@ -19,15 +19,19 @@ const Indicator = ({
|
|||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
>
|
>
|
||||||
<User.Consumer>
|
<User.Consumer>
|
||||||
{states =>
|
{user => (
|
||||||
!states.isWeb3 ? (
|
<Market.Consumer>
|
||||||
<span className={styles.statusIndicator} />
|
{market =>
|
||||||
) : !states.isLogged || !states.isOceanNetwork ? (
|
!user.isLogged || !market.networkMatch ? (
|
||||||
<span className={styles.statusIndicatorCloseEnough} />
|
<span
|
||||||
) : states.isLogged ? (
|
className={styles.statusIndicatorCloseEnough}
|
||||||
|
/>
|
||||||
|
) : user.isLogged ? (
|
||||||
<span className={styles.statusIndicatorActive} />
|
<span className={styles.statusIndicatorActive} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
</Market.Consumer>
|
||||||
|
)}
|
||||||
</User.Consumer>
|
</User.Consumer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -40,16 +40,26 @@ $popoverWidth: 18rem;
|
|||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable */
|
||||||
button {
|
button {
|
||||||
font-size: $font-size-small;
|
svg,
|
||||||
|
&[data-action] {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* stylelint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $brand-grey-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import React from 'react'
|
|||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Popover from './Popover'
|
import Popover from './Popover'
|
||||||
import { userMock, userMockConnected } from '../../../../__mocks__/user-mock'
|
import { userMock, userMockConnected } from '../../../../__mocks__/user-mock'
|
||||||
import { User } from '../../../context'
|
import { marketMock } from '../../../../__mocks__/market-mock'
|
||||||
|
import { User, Market } from '../../../context'
|
||||||
|
|
||||||
describe('Popover', () => {
|
describe('Popover', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
@ -25,12 +26,14 @@ describe('Popover', () => {
|
|||||||
|
|
||||||
it('renders correct network', () => {
|
it('renders correct network', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={{ ...userMockConnected, network: 'Nile' }}>
|
<User.Provider value={{ ...userMockConnected, network: 'Pacific' }}>
|
||||||
|
<Market.Provider value={{ ...marketMock }}>
|
||||||
<Popover forwardedRef={() => null} style={{}} />
|
<Popover forwardedRef={() => null} style={{}} />
|
||||||
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
expect(container.firstChild).toHaveTextContent('Connected to Nile')
|
expect(container.firstChild).toHaveTextContent('Connected to Pacific')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders with wrong network', () => {
|
it('renders with wrong network', () => {
|
||||||
@ -38,7 +41,6 @@ describe('Popover', () => {
|
|||||||
<User.Provider
|
<User.Provider
|
||||||
value={{
|
value={{
|
||||||
...userMockConnected,
|
...userMockConnected,
|
||||||
isOceanNetwork: false,
|
|
||||||
network: '1'
|
network: '1'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Account from '../../atoms/Account'
|
import Account from '../../atoms/Account'
|
||||||
import { User } from '../../../context'
|
import { User, Market } from '../../../context'
|
||||||
import styles from './Popover.module.scss'
|
import styles from './Popover.module.scss'
|
||||||
|
|
||||||
export default class Popover extends PureComponent<{
|
export default class Popover extends PureComponent<{
|
||||||
forwardedRef: (ref: HTMLElement | null) => void
|
forwardedRef?: (ref: HTMLElement | null) => void
|
||||||
style: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
}> {
|
}> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const { account, balance, network } = this.context
|
||||||
account,
|
|
||||||
balance,
|
|
||||||
network,
|
|
||||||
isWeb3,
|
|
||||||
isOceanNetwork
|
|
||||||
} = this.context
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -22,15 +18,10 @@ export default class Popover extends PureComponent<{
|
|||||||
ref={this.props.forwardedRef}
|
ref={this.props.forwardedRef}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
>
|
>
|
||||||
{!isWeb3 ? (
|
{
|
||||||
<div className={styles.popoverInfoline}>
|
|
||||||
No Web3 detected. Use a browser with MetaMask installed
|
|
||||||
to publish assets.
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<div className={styles.popoverInfoline}>
|
<div className={styles.popoverInfoline}>
|
||||||
<Account account={account} />
|
<Account />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{account && balance && (
|
{account && balance && (
|
||||||
@ -52,16 +43,28 @@ export default class Popover extends PureComponent<{
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<div className={styles.popoverInfoline}>
|
<div className={styles.popoverInfoline}>
|
||||||
{network && !isOceanNetwork
|
{network && !market.networkMatch
|
||||||
? 'Please connect to Custom RPC\n https://pacific.oceanprotocol.com'
|
? `Please connect to Custom RPC
|
||||||
: network && `Connected to ${network} network`}
|
${
|
||||||
|
market.network === 'Pacific'
|
||||||
|
? 'https://pacific.oceanprotocol.com'
|
||||||
|
: market.network === 'Nile'
|
||||||
|
? 'https://nile.dev-ocean.com'
|
||||||
|
: market.network === 'Duero'
|
||||||
|
? 'https://duero.dev-ocean.com'
|
||||||
|
: 'http://localhost:8545'
|
||||||
|
}`
|
||||||
|
: network &&
|
||||||
|
`Connected to ${network} network`}
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Popover.contextType = User
|
|
||||||
|
@ -10,9 +10,7 @@ describe('AccountStatus', () => {
|
|||||||
|
|
||||||
it('togglePopover fires', () => {
|
it('togglePopover fires', () => {
|
||||||
const { container } = render(<AccountStatus />)
|
const { container } = render(<AccountStatus />)
|
||||||
|
const indicator = container.querySelector('.status')
|
||||||
const indicator = container.querySelector('.statusIndicator')
|
|
||||||
|
|
||||||
indicator && fireEvent.mouseOver(indicator)
|
indicator && fireEvent.mouseOver(indicator)
|
||||||
expect(container.querySelector('.popover')).toBeInTheDocument()
|
expect(container.querySelector('.popover')).toBeInTheDocument()
|
||||||
indicator && fireEvent.mouseOut(indicator)
|
indicator && fireEvent.mouseOut(indicator)
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
.element {
|
.element {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 1.5;
|
||||||
margin-right: $spacer / 2;
|
margin-right: $spacer / 1.5;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: $spacer / 4 $spacer / 2;
|
padding: $spacer / 4 $spacer / 2 $spacer / 4 $spacer * 1.3;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -26,6 +26,17 @@ describe('VersionTableContracts', () => {
|
|||||||
/submarine.duero.dev-ocean/
|
/submarine.duero.dev-ocean/
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<VersionTableContracts
|
||||||
|
contracts={{ hello: 'hello', hello2: 'hello2' }}
|
||||||
|
network="nile"
|
||||||
|
keeperVersion="6.6.6"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
expect(container.querySelector('tr:last-child a').href).toMatch(
|
||||||
|
/submarine.nile.dev-ocean/
|
||||||
|
)
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<VersionTableContracts
|
<VersionTableContracts
|
||||||
contracts={{ hello: 'hello', hello2: 'hello2' }}
|
contracts={{ hello: 'hello', hello2: 'hello2' }}
|
||||||
@ -34,7 +45,7 @@ describe('VersionTableContracts', () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(container.querySelector('tr:last-child a').href).toMatch(
|
expect(container.querySelector('tr:last-child a').href).toMatch(
|
||||||
/submarine.pacific.dev-ocean/
|
/submarine.oceanprotocol/
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -53,13 +53,11 @@ export const VersionTableContracts = ({
|
|||||||
// sort alphabetically
|
// sort alphabetically
|
||||||
.sort((a, b) => a.localeCompare(b))
|
.sort((a, b) => a.localeCompare(b))
|
||||||
.map(key => {
|
.map(key => {
|
||||||
const submarineLink = `https://submarine${
|
const submarineLink = `https://submarine.${
|
||||||
network === 'duero'
|
network === 'pacific'
|
||||||
? '.duero'
|
? 'oceanprotocol'
|
||||||
: network === 'pacific'
|
: `${network}.dev-ocean`
|
||||||
? '.pacific'
|
}.com/address/${contracts[key]}`
|
||||||
: ''
|
|
||||||
}.dev-ocean.com/address/${contracts[key]}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={key}>
|
<tr key={key}>
|
||||||
|
@ -11,5 +11,13 @@
|
|||||||
margin-top: -.1rem;
|
margin-top: -.1rem;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $brand-grey-light;
|
|
||||||
|
svg {
|
||||||
|
fill: $brand-grey-light;
|
||||||
|
transition: .2s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import slugify from '@sindresorhus/slugify'
|
|||||||
import styles from './VersionTableRow.module.scss'
|
import styles from './VersionTableRow.module.scss'
|
||||||
import { VersionTableContracts, VersionTableCommons } from './VersionTable'
|
import { VersionTableContracts, VersionTableCommons } from './VersionTable'
|
||||||
import VersionNumber from './VersionNumber'
|
import VersionNumber from './VersionNumber'
|
||||||
|
import { ReactComponent as Caret } from '../../../img/caret.svg'
|
||||||
|
|
||||||
const VersionTableRow = ({ value }: { value: any }) => {
|
const VersionTableRow = ({ value }: { value: any }) => {
|
||||||
const collapseStyles = {
|
const collapseStyles = {
|
||||||
@ -26,11 +27,7 @@ const VersionTableRow = ({ value }: { value: any }) => {
|
|||||||
<td>
|
<td>
|
||||||
{(value.name === 'Commons' || value.contracts) && (
|
{(value.name === 'Commons' || value.contracts) && (
|
||||||
<button className={styles.handle} {...getToggleProps()}>
|
<button className={styles.handle} {...getToggleProps()}>
|
||||||
{isOpen ? (
|
<Caret className={isOpen ? styles.open : ''} />
|
||||||
<span>▼</span>
|
|
||||||
) : (
|
|
||||||
<span>►</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<a
|
<a
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
@import '../../../styles/variables';
|
@import '../../../styles/variables';
|
||||||
|
|
||||||
.versions {
|
|
||||||
margin-top: $spacer * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.versionsTitle {
|
.versionsTitle {
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
margin-bottom: $spacer / 2;
|
margin-bottom: $spacer / 2;
|
||||||
|
margin-top: $spacer * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.versionsMinimal {
|
.versionsMinimal {
|
||||||
|
@ -9,19 +9,14 @@ import { version } from '../../../../package.json'
|
|||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
import { nodeUri, faucetUri } from '../../../config'
|
import { nodeUri, faucetUri } from '../../../config'
|
||||||
import { User } from '../../../context'
|
import { User, Market } from '../../../context'
|
||||||
|
|
||||||
import VersionTable from './VersionTable'
|
import VersionTable from './VersionTable'
|
||||||
import VersionStatus from './VersionStatus'
|
import VersionStatus from './VersionStatus'
|
||||||
|
|
||||||
// construct values which are not part of any response
|
|
||||||
export const commonsVersion =
|
|
||||||
process.env.NODE_ENV === 'production' ? version : `${version}-dev`
|
|
||||||
const commonsNetwork = new URL(nodeUri).hostname.split('.')[0]
|
|
||||||
const faucetNetwork = new URL(faucetUri).hostname.split('.')[1]
|
|
||||||
|
|
||||||
interface VersionNumbersProps {
|
interface VersionNumbersProps {
|
||||||
minimal?: boolean
|
minimal?: boolean
|
||||||
|
account: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VersionNumbersState extends OceanPlatformVersions {
|
export interface VersionNumbersState extends OceanPlatformVersions {
|
||||||
@ -44,12 +39,20 @@ export default class VersionNumbers extends PureComponent<
|
|||||||
> {
|
> {
|
||||||
public static contextType = User
|
public static contextType = User
|
||||||
|
|
||||||
|
// construct values which are not part of any response
|
||||||
|
public commonsVersion =
|
||||||
|
process.env.NODE_ENV === 'production' ? version : `${version}-dev`
|
||||||
|
public commonsNetwork = new URL(nodeUri).hostname.split('.')[0]
|
||||||
|
public faucetNetwork = faucetUri.includes('dev-ocean')
|
||||||
|
? new URL(faucetUri).hostname.split('.')[1]
|
||||||
|
: 'Pacific'
|
||||||
|
|
||||||
// define a minimal default state to fill UI
|
// define a minimal default state to fill UI
|
||||||
public state: VersionNumbersState = {
|
public state: VersionNumbersState = {
|
||||||
commons: {
|
commons: {
|
||||||
name: 'Commons',
|
name: 'Commons',
|
||||||
network: commonsNetwork,
|
network: this.commonsNetwork,
|
||||||
version: commonsVersion
|
version: this.commonsVersion
|
||||||
},
|
},
|
||||||
squid: {
|
squid: {
|
||||||
name: 'Squid-js',
|
name: 'Squid-js',
|
||||||
@ -66,7 +69,7 @@ export default class VersionNumbers extends PureComponent<
|
|||||||
faucet: {
|
faucet: {
|
||||||
name: 'Faucet',
|
name: 'Faucet',
|
||||||
version: '',
|
version: '',
|
||||||
network: faucetNetwork,
|
network: this.faucetNetwork,
|
||||||
status: OceanPlatformTechStatus.Loading
|
status: OceanPlatformTechStatus.Loading
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
@ -79,11 +82,20 @@ export default class VersionNumbers extends PureComponent<
|
|||||||
// for canceling axios requests
|
// for canceling axios requests
|
||||||
public signal = axios.CancelToken.source()
|
public signal = axios.CancelToken.source()
|
||||||
|
|
||||||
public async componentDidMount() {
|
public componentDidMount() {
|
||||||
this.getOceanVersions()
|
this.getOceanVersions()
|
||||||
this.getFaucetVersion()
|
this.getFaucetVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: any) {
|
||||||
|
// Workaround: Using account prop instead of getting it from
|
||||||
|
// context to be able to compare. Cause there is no `prevContext`.
|
||||||
|
if (prevProps.account !== this.props.account) {
|
||||||
|
this.getOceanVersions()
|
||||||
|
this.getFaucetVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.signal.cancel()
|
this.signal.cancel()
|
||||||
}
|
}
|
||||||
@ -132,14 +144,19 @@ export default class VersionNumbers extends PureComponent<
|
|||||||
const { commons, squid, brizo, aquarius } = this.state
|
const { commons, squid, brizo, aquarius } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<p className={styles.versionsMinimal}>
|
<p className={styles.versionsMinimal}>
|
||||||
<a
|
<a
|
||||||
title={`${squid.name} v${squid.version}\n${brizo.name} v${brizo.version}\n${aquarius.name} v${aquarius.version}`}
|
title={`${squid.name} v${squid.version}\n${brizo.name} v${brizo.version}\n${aquarius.name} v${aquarius.version}`}
|
||||||
href={'/about'}
|
href={'/about'}
|
||||||
>
|
>
|
||||||
v{commons.version} {squid.network && `(${squid.network})`}
|
v{commons.version}{' '}
|
||||||
|
{market.network && `(${market.network})`}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,13 +166,13 @@ export default class VersionNumbers extends PureComponent<
|
|||||||
return minimal ? (
|
return minimal ? (
|
||||||
<this.MinimalOutput />
|
<this.MinimalOutput />
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.versions} id="#oceanversions">
|
<>
|
||||||
<h2 className={styles.versionsTitle}>
|
<h2 className={styles.versionsTitle} id="#oceanversions">
|
||||||
Ocean Components Status
|
Ocean Components Status
|
||||||
</h2>
|
</h2>
|
||||||
<VersionStatus status={this.state.status} />
|
<VersionStatus status={this.state.status} />
|
||||||
<VersionTable data={this.state} />
|
<VersionTable data={this.state} />
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,10 +57,9 @@ export default class AssetsUser extends PureComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { account, isOceanNetwork } = this.context
|
const { account } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isOceanNetwork &&
|
|
||||||
account && (
|
account && (
|
||||||
<div className={styles.assetsUser}>
|
<div className={styles.assetsUser}>
|
||||||
{this.props.recent && (
|
{this.props.recent && (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { Market } from '../../context'
|
import { Market, User } from '../../context'
|
||||||
import Content from '../atoms/Content'
|
import Content from '../atoms/Content'
|
||||||
import { ReactComponent as AiCommons } from '../../img/aicommons.svg'
|
import { ReactComponent as AiCommons } from '../../img/aicommons.svg'
|
||||||
import styles from './Footer.module.scss'
|
import styles from './Footer.module.scss'
|
||||||
@ -7,18 +7,18 @@ import styles from './Footer.module.scss'
|
|||||||
import meta from '../../data/meta.json'
|
import meta from '../../data/meta.json'
|
||||||
import VersionNumbers from '../molecules/VersionNumbers'
|
import VersionNumbers from '../molecules/VersionNumbers'
|
||||||
|
|
||||||
const Footer = () => (
|
export default function Footer() {
|
||||||
|
const market = useContext(Market)
|
||||||
|
const user = useContext(User)
|
||||||
|
|
||||||
|
return (
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<aside className={styles.stats}>
|
<aside className={styles.stats}>
|
||||||
<Content wide>
|
<Content wide>
|
||||||
<p>
|
<p>
|
||||||
Online since March 2019.
|
Online since March 2019.
|
||||||
<Market.Consumer>
|
{market.totalAssets > 0 &&
|
||||||
{state =>
|
` With a total of ${market.totalAssets} registered assets.`}
|
||||||
state.totalAssets > 0 &&
|
|
||||||
` With a total of ${state.totalAssets} registered assets.`
|
|
||||||
}
|
|
||||||
</Market.Consumer>
|
|
||||||
</p>
|
</p>
|
||||||
<p className={styles.aicommons}>
|
<p className={styles.aicommons}>
|
||||||
Proud supporter of{' '}
|
Proud supporter of{' '}
|
||||||
@ -29,7 +29,7 @@ const Footer = () => (
|
|||||||
<AiCommons />
|
<AiCommons />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<VersionNumbers minimal />
|
<VersionNumbers account={user.account} minimal />
|
||||||
</Content>
|
</Content>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@ -50,5 +50,4 @@ const Footer = () => (
|
|||||||
</Content>
|
</Content>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
export default Footer
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } 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 { User } from '../../context'
|
|
||||||
import AccountStatus from '../molecules/AccountStatus'
|
import AccountStatus from '../molecules/AccountStatus'
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
|
|
||||||
@ -40,5 +39,3 @@ export default class Header extends PureComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.contextType = User
|
|
||||||
|
73
client/src/components/organisms/WalletSelector.module.scss
Normal file
73
client/src/components/organisms/WalletSelector.module.scss
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.openLink {
|
||||||
|
font-size: $font-size-small !important; // stylelint-disable-line
|
||||||
|
margin-left: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
background: $brand-white;
|
||||||
|
border: 1px solid $brand-grey-lighter;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: $spacer / 1.5;
|
||||||
|
font-family: $font-family-base;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border .2s ease-out;
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@media (min-width: $break-point--small) {
|
||||||
|
flex-basis: 48%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
border-color: $brand-pink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonActive {
|
||||||
|
composes: button;
|
||||||
|
pointer-events: none;
|
||||||
|
background: $body-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
position: absolute;
|
||||||
|
right: $spacer / 3;
|
||||||
|
top: $spacer / 4;
|
||||||
|
color: $brand-grey-light;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonIcon {
|
||||||
|
font-size: $font-size-h4;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: $spacer / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonTitle {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
margin-bottom: $spacer / 2;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonDescription {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $brand-grey-light;
|
||||||
|
}
|
23
client/src/components/organisms/WalletSelector.test.tsx
Normal file
23
client/src/components/organisms/WalletSelector.test.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import ReactModal from 'react-modal'
|
||||||
|
import WalletSelector from './WalletSelector'
|
||||||
|
import { User, Market } from '../../context'
|
||||||
|
import { userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
import { marketMock } from '../../../__mocks__/market-mock'
|
||||||
|
|
||||||
|
describe('WalletSelector', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
ReactModal.setAppElement(document.createElement('div'))
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Market.Provider value={marketMock}>
|
||||||
|
<WalletSelector />
|
||||||
|
</Market.Provider>
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
fireEvent.click(container.querySelector('button'))
|
||||||
|
})
|
||||||
|
})
|
108
client/src/components/organisms/WalletSelector.tsx
Normal file
108
client/src/components/organisms/WalletSelector.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import Modal from '../atoms/Modal'
|
||||||
|
import { User } from '../../context'
|
||||||
|
import styles from './WalletSelector.module.scss'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import content from '../../data/wallets.json'
|
||||||
|
|
||||||
|
export default class WalletSelector extends PureComponent<
|
||||||
|
{},
|
||||||
|
{ isModalOpen: boolean }
|
||||||
|
> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
|
public state = {
|
||||||
|
isModalOpen: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleModal = () => {
|
||||||
|
this.setState({ isModalOpen: !this.state.isModalOpen })
|
||||||
|
}
|
||||||
|
|
||||||
|
private loginBurnerWallet = () => {
|
||||||
|
this.context.loginBurnerWallet()
|
||||||
|
this.toggleModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
private loginMetamask = () => {
|
||||||
|
this.context.loginMetamask()
|
||||||
|
this.context.logoutBurnerWallet()
|
||||||
|
this.toggleModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
private WalletButton = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
}) => {
|
||||||
|
const active =
|
||||||
|
(title === 'Burner Wallet' && this.context.isBurner) ||
|
||||||
|
(title === 'MetaMask' && !this.context.isBurner)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={active ? styles.buttonActive : styles.button}
|
||||||
|
onClick={
|
||||||
|
title === 'MetaMask'
|
||||||
|
? this.loginMetamask
|
||||||
|
: this.loginBurnerWallet
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h3 className={styles.buttonTitle}>
|
||||||
|
<span
|
||||||
|
className={styles.buttonIcon}
|
||||||
|
role="img"
|
||||||
|
aria-label={title}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<span className={styles.buttonDescription}>
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
{active && (
|
||||||
|
<span className={styles.selected}>Selected</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
link
|
||||||
|
className={styles.openLink}
|
||||||
|
onClick={this.toggleModal}
|
||||||
|
data-action="wallet"
|
||||||
|
>
|
||||||
|
{content.title}
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
title={content.title}
|
||||||
|
description={content.description}
|
||||||
|
isOpen={this.state.isModalOpen}
|
||||||
|
toggleModal={this.toggleModal}
|
||||||
|
>
|
||||||
|
<div className={styles.info}>
|
||||||
|
{content.buttons.map(({ title, description, icon }) => (
|
||||||
|
<this.WalletButton
|
||||||
|
key={title}
|
||||||
|
title={title}
|
||||||
|
icon={icon}
|
||||||
|
description={description}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -4,19 +4,27 @@
|
|||||||
margin-bottom: $spacer;
|
margin-bottom: $spacer;
|
||||||
color: $brand-grey;
|
color: $brand-grey;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: .1rem solid $brand-grey-lighter;
|
|
||||||
border-top: .1rem solid $brand-grey-lighter;
|
|
||||||
padding-top: $spacer / 2;
|
padding-top: $spacer / 2;
|
||||||
padding-bottom: $spacer / 2;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warnings {
|
.account {
|
||||||
padding-left: $spacer;
|
margin-bottom: $spacer / 2;
|
||||||
|
background: $brand-white;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
border: 1px solid $brand-grey-lighter;
|
||||||
|
padding: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding-left: $spacer * 1.5;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
margin-left: -($spacer);
|
margin-left: -($spacer / 1.2);
|
||||||
margin-right: $spacer / 2;
|
margin-right: $spacer / 2.5;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,64 +1,60 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Web3message from './Web3message'
|
import Web3message from './Web3message'
|
||||||
import { User } from '../../context'
|
import { User, Market } from '../../context'
|
||||||
import { userMock, userMockConnected } from '../../../__mocks__/user-mock'
|
import { userMock, userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
import { marketMock } from '../../../__mocks__/market-mock'
|
||||||
|
|
||||||
describe('Web3message', () => {
|
describe('Web3message', () => {
|
||||||
it('renders with noWeb3 message', () => {
|
it('renders with burner wallet message', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={{ ...userMock }}>
|
<User.Provider value={{ ...userMockConnected, isBurner: true }}>
|
||||||
<Web3message />
|
<Market.Provider value={{ ...marketMock }}>
|
||||||
|
<Web3message extended />
|
||||||
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toHaveTextContent('Not a Web3 Browser')
|
expect(container.firstChild).toHaveTextContent('Burner Wallet')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders with wrongNetwork message', () => {
|
it('renders with wrongNetwork message', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={{ ...userMock, isWeb3: true }}>
|
<User.Provider value={{ ...userMockConnected, network: 'Pacific' }}>
|
||||||
<Web3message />
|
<Market.Provider
|
||||||
|
value={{
|
||||||
|
...marketMock,
|
||||||
|
networkMatch: false,
|
||||||
|
network: 'Nile'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Web3message extended />
|
||||||
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toHaveTextContent(
|
expect(container.firstChild).toHaveTextContent(
|
||||||
'Not connected to Pacific network'
|
'Not connected to Nile network'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders with noAccount message', () => {
|
it('renders with noAccount message', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider
|
<User.Provider value={userMock}>
|
||||||
value={{ ...userMock, isWeb3: true, isOceanNetwork: true }}
|
<Market.Provider value={marketMock}>
|
||||||
>
|
<Web3message extended />
|
||||||
<Web3message />
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toHaveTextContent('No accounts detected')
|
expect(container.firstChild).toHaveTextContent('No account selected')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders with hasAccount message', () => {
|
it('renders with hasAccount message', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Market.Provider value={marketMock}>
|
||||||
<Web3message />
|
<Web3message />
|
||||||
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toHaveTextContent('0xxxxxx')
|
expect(container.firstChild).toHaveTextContent('0xxxxxx')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('button click fires unlockAccounts', () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<User.Provider
|
|
||||||
value={{
|
|
||||||
...userMock,
|
|
||||||
isWeb3: true,
|
|
||||||
isOceanNetwork: true
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Web3message />
|
|
||||||
</User.Provider>
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(getByText('Unlock Account'))
|
|
||||||
expect(userMock.unlockAccounts).toBeCalled()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -1,53 +1,66 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Account from '../atoms/Account'
|
import Account from '../atoms/Account'
|
||||||
import Button from '../atoms/Button'
|
|
||||||
import AccountStatus from '../molecules/AccountStatus'
|
import AccountStatus from '../molecules/AccountStatus'
|
||||||
import styles from './Web3message.module.scss'
|
import styles from './Web3message.module.scss'
|
||||||
import { User } from '../../context'
|
import { User, Market } from '../../context'
|
||||||
import content from '../../data/web3message.json'
|
import content from '../../data/web3message.json'
|
||||||
|
|
||||||
export default class Web3message extends PureComponent {
|
export default class Web3message extends PureComponent<{ extended?: boolean }> {
|
||||||
private message = (
|
public static contextType = Market
|
||||||
message: string,
|
|
||||||
account?: string,
|
private messageOceanNetwork = () =>
|
||||||
unlockAccounts?: () => any
|
this.context.network === 'Pacific'
|
||||||
) => (
|
? content.wrongNetworkPacific
|
||||||
<div className={styles.message}>
|
: this.context.network === 'Nile'
|
||||||
{account ? (
|
? content.wrongNetworkNile
|
||||||
<Account account={account} />
|
: this.context.network === 'Duero'
|
||||||
) : (
|
? content.wrongNetworkDuero
|
||||||
<div className={styles.warnings}>
|
: content.wrongNetworkSpree
|
||||||
<AccountStatus className={styles.status} />
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: message }} />{' '}
|
private Message = () => {
|
||||||
{unlockAccounts && (
|
const { networkMatch, network } = this.context
|
||||||
<Button onClick={() => unlockAccounts()} link>
|
|
||||||
Unlock Account
|
return (
|
||||||
</Button>
|
<User.Consumer>
|
||||||
|
{user => (
|
||||||
|
<em
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html:
|
||||||
|
!networkMatch && !user.isBurner
|
||||||
|
? this.messageOceanNetwork()
|
||||||
|
: !user.isLogged
|
||||||
|
? content.noAccount
|
||||||
|
: user.isBurner
|
||||||
|
? content.hasBurnerWallet
|
||||||
|
: user.isLogged
|
||||||
|
? content.hasMetaMaskWallet.replace(
|
||||||
|
'NETWORK',
|
||||||
|
network
|
||||||
|
)
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
</User.Consumer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { networkMatch } = this.context
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.message}>
|
||||||
|
<div className={styles.account}>
|
||||||
|
<Account />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(!networkMatch || this.props.extended) && (
|
||||||
|
<div className={styles.text}>
|
||||||
|
<AccountStatus className={styles.status} />
|
||||||
|
<this.Message />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
public render() {
|
|
||||||
const {
|
|
||||||
isWeb3,
|
|
||||||
isOceanNetwork,
|
|
||||||
isLogged,
|
|
||||||
account,
|
|
||||||
unlockAccounts
|
|
||||||
} = this.context
|
|
||||||
|
|
||||||
return !isWeb3
|
|
||||||
? this.message(content.noweb3)
|
|
||||||
: !isOceanNetwork
|
|
||||||
? this.message(content.wrongNetwork)
|
|
||||||
: !isLogged
|
|
||||||
? this.message(content.noAccount, '', unlockAccounts)
|
|
||||||
: isLogged
|
|
||||||
? this.message(content.hasAccount, account)
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Web3message.contextType = User
|
|
||||||
|
@ -5,9 +5,10 @@ import { render, fireEvent } from '@testing-library/react'
|
|||||||
import { DDO } from '@oceanprotocol/squid'
|
import { DDO } from '@oceanprotocol/squid'
|
||||||
import { StateMock } from '@react-mock/state'
|
import { StateMock } from '@react-mock/state'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
import { User } from '../../../context'
|
import { User, Market } from '../../../context'
|
||||||
import AssetFile, { messages } from './AssetFile'
|
import AssetFile, { messages } from './AssetFile'
|
||||||
import { userMockConnected } from '../../../../__mocks__/user-mock'
|
import { userMockConnected } from '../../../../__mocks__/user-mock'
|
||||||
|
import { marketMock } from '../../../../__mocks__/market-mock'
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
index: 0,
|
index: 0,
|
||||||
@ -39,7 +40,9 @@ describe('AssetFile', () => {
|
|||||||
it('button to be enabled when connected', async () => {
|
it('button to be enabled when connected', async () => {
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
|
<Market.Provider value={marketMock}>
|
||||||
<AssetFile file={file} ddo={ddo} />
|
<AssetFile file={file} ddo={ddo} />
|
||||||
|
</Market.Provider>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
const button = getByText('Get file')
|
const button = getByText('Get file')
|
||||||
@ -50,12 +53,12 @@ describe('AssetFile', () => {
|
|||||||
|
|
||||||
it('renders feedback message: initial', async () => {
|
it('renders feedback message: initial', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<StateMock state={{ isLoading: true, step: null }}>
|
<StateMock state={{ isLoading: true, step: 99 }}>
|
||||||
<AssetFile file={file} ddo={ddo} />
|
<AssetFile file={file} ddo={ddo} />
|
||||||
</StateMock>
|
</StateMock>
|
||||||
)
|
)
|
||||||
expect(container.querySelector('.spinner')).toHaveTextContent(
|
expect(container.querySelector('.spinner')).toHaveTextContent(
|
||||||
messages.start
|
messages[99]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,13 +3,13 @@ import { Logger, DDO, File } from '@oceanprotocol/squid'
|
|||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
import Button from '../../atoms/Button'
|
import Button from '../../atoms/Button'
|
||||||
import Spinner from '../../atoms/Spinner'
|
import Spinner from '../../atoms/Spinner'
|
||||||
import { User } from '../../../context'
|
import { User, Market } from '../../../context'
|
||||||
import styles from './AssetFile.module.scss'
|
import styles from './AssetFile.module.scss'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
import cleanupContentType from '../../../utils/cleanupContentType'
|
import cleanupContentType from '../../../utils/cleanupContentType'
|
||||||
|
|
||||||
export const messages = {
|
export const messages: any = {
|
||||||
start: 'Decrypting file URL...',
|
99: 'Decrypting file URL...',
|
||||||
0: '1/3<br />Asking for agreement signature...',
|
0: '1/3<br />Asking for agreement signature...',
|
||||||
1: '1/3<br />Agreement initialized.',
|
1: '1/3<br />Agreement initialized.',
|
||||||
2: '2/3<br />Asking for two payment confirmations...',
|
2: '2/3<br />Asking for two payment confirmations...',
|
||||||
@ -25,24 +25,26 @@ interface AssetFileProps {
|
|||||||
interface AssetFileState {
|
interface AssetFileState {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
error: string
|
error: string
|
||||||
step: number | string | null
|
step: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AssetFile extends PureComponent<
|
export default class AssetFile extends PureComponent<
|
||||||
AssetFileProps,
|
AssetFileProps,
|
||||||
AssetFileState
|
AssetFileState
|
||||||
> {
|
> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: '',
|
error: '',
|
||||||
step: null
|
step: 99
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetState = () =>
|
private resetState = () =>
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
error: '',
|
error: '',
|
||||||
step: null
|
step: 99
|
||||||
})
|
})
|
||||||
|
|
||||||
private purchaseAsset = async (ddo: DDO, index: number) => {
|
private purchaseAsset = async (ddo: DDO, index: number) => {
|
||||||
@ -109,7 +111,7 @@ export default class AssetFile extends PureComponent<
|
|||||||
public render() {
|
public render() {
|
||||||
const { ddo, file } = this.props
|
const { ddo, file } = this.props
|
||||||
const { isLoading, error, step } = this.state
|
const { isLoading, error, step } = this.state
|
||||||
const { isLogged, isOceanNetwork } = this.context
|
const { isLogged } = this.context
|
||||||
const { index, contentType, contentLength } = file
|
const { index, contentType, contentLength } = file
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -134,28 +136,28 @@ export default class AssetFile extends PureComponent<
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Spinner
|
<Spinner message={messages[step]} />
|
||||||
message={
|
|
||||||
step === null ? messages.start : messages[step]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
className={styles.buttonMain}
|
className={styles.buttonMain}
|
||||||
// weird 0 hack so TypeScript is happy
|
// weird 0 hack so TypeScript is happy
|
||||||
onClick={() => this.purchaseAsset(ddo, index || 0)}
|
onClick={() =>
|
||||||
disabled={!isLogged || !isOceanNetwork}
|
this.purchaseAsset(ddo, index || 0)
|
||||||
|
}
|
||||||
|
disabled={!isLogged || !market.networkMatch}
|
||||||
name="Download"
|
name="Download"
|
||||||
>
|
>
|
||||||
Get file
|
Get file
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
|
)}
|
||||||
|
|
||||||
{error !== '' && <div className={styles.error}>{error}</div>}
|
{error !== '' && <div className={styles.error}>{error}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetFile.contextType = User
|
|
||||||
|
@ -4,8 +4,6 @@ import React from 'react'
|
|||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { DDO } from '@oceanprotocol/squid'
|
import { DDO } from '@oceanprotocol/squid'
|
||||||
import AssetFilesDetails from './AssetFilesDetails'
|
import AssetFilesDetails from './AssetFilesDetails'
|
||||||
import { User } from '../../../context'
|
|
||||||
import { userMockConnected } from '../../../../__mocks__/user-mock'
|
|
||||||
|
|
||||||
describe('AssetFilesDetails', () => {
|
describe('AssetFilesDetails', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
@ -28,16 +26,4 @@ describe('AssetFilesDetails', () => {
|
|||||||
)
|
)
|
||||||
expect(container.firstChild).toHaveTextContent('No files attached.')
|
expect(container.firstChild).toHaveTextContent('No files attached.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('hides Web3message when all connected', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<User.Provider value={userMockConnected}>
|
|
||||||
<AssetFilesDetails
|
|
||||||
files={[{ index: 0, url: '' }]}
|
|
||||||
ddo={({} as any) as DDO}
|
|
||||||
/>
|
|
||||||
</User.Provider>
|
|
||||||
)
|
|
||||||
expect(container.querySelector('.status')).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -19,9 +19,7 @@ export default class AssetFilesDetails extends PureComponent<{
|
|||||||
<AssetFile key={file.index} ddo={ddo} file={file} />
|
<AssetFile key={file.index} ddo={ddo} file={file} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{(!this.context.isOceanNetwork || !this.context.isLogged) && (
|
|
||||||
<Web3message />
|
<Web3message />
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div>No files attached.</div>
|
<div>No files attached.</div>
|
||||||
|
42
client/src/components/templates/Asset/Report.test.tsx
Normal file
42
client/src/components/templates/Asset/Report.test.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent, wait } from '@testing-library/react'
|
||||||
|
import ReactModal from 'react-modal'
|
||||||
|
import mockAxios from 'jest-mock-axios'
|
||||||
|
import Report from './Report'
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockAxios.reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
data: { status: 'success' }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Report', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
ReactModal.setAppElement(document.createElement('div'))
|
||||||
|
|
||||||
|
const { getByText, getByLabelText, getByTestId } = render(
|
||||||
|
<Report did="did:xxx" title="Hello" />
|
||||||
|
)
|
||||||
|
// Renders button by default
|
||||||
|
expect(getByText('Report Data Set')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
fireEvent.click(getByText('Report Data Set'))
|
||||||
|
await wait(() => expect(getByText('did:xxx')).toBeInTheDocument())
|
||||||
|
|
||||||
|
// add comment
|
||||||
|
const comment = getByLabelText('Comment')
|
||||||
|
fireEvent.change(comment, {
|
||||||
|
target: { value: 'Plants' }
|
||||||
|
})
|
||||||
|
expect(comment).toHaveTextContent('Plants')
|
||||||
|
fireEvent.click(getByTestId('report'))
|
||||||
|
mockAxios.mockResponse(mockResponse)
|
||||||
|
// expect(mockAxios.post).toHaveBeenCalled()
|
||||||
|
|
||||||
|
// close modal
|
||||||
|
fireEvent.click(getByTestId('closeModal'))
|
||||||
|
})
|
||||||
|
})
|
@ -142,6 +142,7 @@ export default class Report extends PureComponent<
|
|||||||
primary
|
primary
|
||||||
onClick={(e: Event) => this.sendEmail(e)}
|
onClick={(e: Event) => this.sendEmail(e)}
|
||||||
disabled={this.state.comment === ''}
|
disabled={this.state.comment === ''}
|
||||||
|
data-testid="report"
|
||||||
>
|
>
|
||||||
Report Data Set
|
Report Data Set
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import Details from './index'
|
import Details from './index'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/asset/did:xxx')
|
||||||
|
|
||||||
describe('Details', () => {
|
describe('Details', () => {
|
||||||
it('renders loading state by default', () => {
|
it('renders loading state by default', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Details
|
<Details
|
||||||
location={{
|
history={history}
|
||||||
search: '',
|
location={location}
|
||||||
pathname: '/',
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
state: '',
|
|
||||||
hash: ''
|
|
||||||
}}
|
|
||||||
match={{
|
|
||||||
params: {
|
|
||||||
did: ''
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -8,6 +8,7 @@ import stylesApp from '../../../App.module.scss'
|
|||||||
import Content from '../../atoms/Content'
|
import Content from '../../atoms/Content'
|
||||||
import CategoryImage from '../../atoms/CategoryImage'
|
import CategoryImage from '../../atoms/CategoryImage'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
import withTracker from '../../../hoc/withTracker'
|
||||||
|
|
||||||
interface AssetProps {
|
interface AssetProps {
|
||||||
match: {
|
match: {
|
||||||
@ -23,7 +24,9 @@ interface AssetState {
|
|||||||
error: string
|
error: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Asset extends Component<AssetProps, AssetState> {
|
class Asset extends Component<AssetProps, AssetState> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
ddo: ({} as any) as DDO,
|
ddo: ({} as any) as DDO,
|
||||||
metadata: ({ base: { name: '' } } as any) as MetaData,
|
metadata: ({ base: { name: '' } } as any) as MetaData,
|
||||||
@ -79,4 +82,4 @@ export default class Asset extends Component<AssetProps, AssetState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset.contextType = User
|
export default withTracker(Asset)
|
||||||
|
11
client/src/context/BurnerWalletProvider.test.ts
Normal file
11
client/src/context/BurnerWalletProvider.test.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { BurnerWalletProvider } from './BurnerWalletProvider'
|
||||||
|
|
||||||
|
describe('BurnerWalletProvider', () => {
|
||||||
|
it('Burner wallet can be created', async () => {
|
||||||
|
const burnerwalletProvider = new BurnerWalletProvider()
|
||||||
|
await burnerwalletProvider.startLogin()
|
||||||
|
const web3 = burnerwalletProvider.getProvider()
|
||||||
|
|
||||||
|
expect(web3)
|
||||||
|
})
|
||||||
|
})
|
53
client/src/context/BurnerWalletProvider.ts
Normal file
53
client/src/context/BurnerWalletProvider.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Web3 from 'web3'
|
||||||
|
import { nodeUri } from '../config'
|
||||||
|
import HDWalletProvider from 'truffle-hdwallet-provider'
|
||||||
|
import { requestFromFaucet } from '../ocean'
|
||||||
|
const bip39 = require('bip39') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
|
|
||||||
|
export class BurnerWalletProvider {
|
||||||
|
private web3: Web3
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
// Default
|
||||||
|
this.web3 = null as any
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isAvailable() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isLogged() {
|
||||||
|
if (localStorage.getItem('seedphrase') !== null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startLogin() {
|
||||||
|
let mnemonic
|
||||||
|
const isLogged = await this.isLogged()
|
||||||
|
|
||||||
|
if (isLogged) {
|
||||||
|
mnemonic = (await localStorage.getItem('seedphrase')) as string
|
||||||
|
} else {
|
||||||
|
mnemonic = bip39.generateMnemonic()
|
||||||
|
localStorage.setItem('seedphrase', mnemonic)
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('logType', 'BurnerWallet')
|
||||||
|
const provider = new HDWalletProvider(mnemonic, `${nodeUri}`, 0, 1)
|
||||||
|
this.web3 = new Web3(provider)
|
||||||
|
|
||||||
|
// fill with Ether
|
||||||
|
await requestFromFaucet(provider.addresses[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout() {
|
||||||
|
// localStorage.removeItem('seedphrase')
|
||||||
|
localStorage.removeItem('logType')
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProvider() {
|
||||||
|
return this.web3
|
||||||
|
}
|
||||||
|
}
|
22
client/src/context/MarketProvider.test.tsx
Normal file
22
client/src/context/MarketProvider.test.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import MarketProvider from './MarketProvider'
|
||||||
|
import { User, Market } from '../context'
|
||||||
|
import { userMockConnected } from '../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
describe('MarketProvider', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<User.Provider value={userMockConnected}>
|
||||||
|
<MarketProvider ocean={userMockConnected.ocean as any}>
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
|
<div data-testid="hello">{market.network}</div>
|
||||||
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
|
</MarketProvider>
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(getByTestId('hello')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import { Logger, Ocean } from '@oceanprotocol/squid'
|
import { Logger, Ocean } from '@oceanprotocol/squid'
|
||||||
import { Market } from '.'
|
import { Market, User } from '.'
|
||||||
import formPublish from '../data/form-publish.json'
|
import formPublish from '../data/form-publish.json'
|
||||||
|
|
||||||
const categories =
|
const categories =
|
||||||
@ -16,24 +16,34 @@ interface MarketProviderProps {
|
|||||||
interface MarketProviderState {
|
interface MarketProviderState {
|
||||||
totalAssets: number
|
totalAssets: number
|
||||||
categories: string[]
|
categories: string[]
|
||||||
|
network: string
|
||||||
|
networkMatch: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MarketProvider extends PureComponent<
|
export default class MarketProvider extends PureComponent<
|
||||||
MarketProviderProps,
|
MarketProviderProps,
|
||||||
MarketProviderState
|
MarketProviderState
|
||||||
> {
|
> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
totalAssets: 0,
|
totalAssets: 0,
|
||||||
categories
|
categories,
|
||||||
|
network: 'Pacific',
|
||||||
|
networkMatch: false
|
||||||
}
|
}
|
||||||
|
|
||||||
public async componentDidMount() {}
|
public async componentDidMount() {
|
||||||
|
await this.checkCorrectUserNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
public async componentDidUpdate(prevProps: any) {
|
public async componentDidUpdate(prevProps: any) {
|
||||||
// Using ocean prop instead of getting it from context to be able to compare.
|
// Using ocean prop instead of getting it from context to be able to compare.
|
||||||
// Cause there is no `prevContext`.
|
// Cause there is no `prevContext`.
|
||||||
if (prevProps.ocean !== this.props.ocean) {
|
if (prevProps.ocean !== this.props.ocean) {
|
||||||
await this.getTotalAssets()
|
await this.getTotalAssets()
|
||||||
|
await this.getMarketNetwork()
|
||||||
|
await this.checkCorrectUserNetwork()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +66,27 @@ export default class MarketProvider extends PureComponent<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getMarketNetwork = async () => {
|
||||||
|
try {
|
||||||
|
const { ocean } = this.props
|
||||||
|
// Set desired network to whatever Brizo is running in
|
||||||
|
const brizo = await ocean.brizo.getVersionInfo()
|
||||||
|
const network =
|
||||||
|
brizo.network.charAt(0).toUpperCase() + brizo.network.slice(1)
|
||||||
|
this.setState({ network })
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('Error', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkCorrectUserNetwork() {
|
||||||
|
if (this.context.network === this.state.network) {
|
||||||
|
this.setState({ networkMatch: true })
|
||||||
|
} else {
|
||||||
|
this.setState({ networkMatch: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<Market.Provider value={this.state}>
|
<Market.Provider value={this.state}>
|
||||||
|
11
client/src/context/MetamaskProvider.test.tsx
Normal file
11
client/src/context/MetamaskProvider.test.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { MetamaskProvider } from './MetamaskProvider'
|
||||||
|
|
||||||
|
describe('MetamaskProvider', () => {
|
||||||
|
it('MetamaskProvider can be created', async () => {
|
||||||
|
const metamaskProvider = new MetamaskProvider()
|
||||||
|
await metamaskProvider.startLogin()
|
||||||
|
const web3 = metamaskProvider.getProvider()
|
||||||
|
|
||||||
|
expect(web3)
|
||||||
|
})
|
||||||
|
})
|
48
client/src/context/MetamaskProvider.ts
Normal file
48
client/src/context/MetamaskProvider.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Web3 from 'web3'
|
||||||
|
|
||||||
|
export class MetamaskProvider {
|
||||||
|
private web3: Web3
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
// Default
|
||||||
|
this.web3 = null as any
|
||||||
|
// Modern dapp browsers
|
||||||
|
if (window.ethereum) {
|
||||||
|
this.web3 = new Web3(window.ethereum)
|
||||||
|
}
|
||||||
|
// Legacy dapp browsers
|
||||||
|
else if (window.web3) {
|
||||||
|
this.web3 = new Web3(window.web3.currentProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isAvailable() {
|
||||||
|
return this.web3 !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isLogged() {
|
||||||
|
if (this.web3 === null) return false
|
||||||
|
if ((await this.web3.eth.getAccounts()).length > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startLogin() {
|
||||||
|
try {
|
||||||
|
await window.ethereum.enable()
|
||||||
|
localStorage.setItem('logType', 'Metamask')
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout() {
|
||||||
|
localStorage.removeItem('logType')
|
||||||
|
// reload page?
|
||||||
|
}
|
||||||
|
|
||||||
|
public getProvider() {
|
||||||
|
return this.web3
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,16 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { Logger, Ocean, Account } from '@oceanprotocol/squid'
|
import { Ocean, Account } from '@oceanprotocol/squid'
|
||||||
import { User } from '.'
|
import { User } from '.'
|
||||||
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
||||||
import { nodeUri } from '../config'
|
import { nodeUri } from '../config'
|
||||||
import MarketProvider from './MarketProvider'
|
import MarketProvider from './MarketProvider'
|
||||||
|
import { MetamaskProvider } from './MetamaskProvider'
|
||||||
|
import { BurnerWalletProvider } from './BurnerWalletProvider'
|
||||||
|
|
||||||
const POLL_ACCOUNTS = 1000 // every 1s
|
const POLL_ACCOUNTS = 1000 // every 1s
|
||||||
const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min
|
const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min
|
||||||
|
const DEFAULT_WEB3 = new Web3(new Web3.providers.HttpProvider(nodeUri)) // default web3
|
||||||
|
|
||||||
// taken from
|
// taken from
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/web3/providers.d.ts
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/web3/providers.d.ts
|
||||||
@ -45,9 +48,9 @@ declare global {
|
|||||||
|
|
||||||
interface UserProviderState {
|
interface UserProviderState {
|
||||||
isLogged: boolean
|
isLogged: boolean
|
||||||
|
isBurner: boolean
|
||||||
|
isWeb3Capable: boolean
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isWeb3: boolean
|
|
||||||
isOceanNetwork: boolean
|
|
||||||
account: string
|
account: string
|
||||||
balance: {
|
balance: {
|
||||||
eth: number
|
eth: number
|
||||||
@ -57,35 +60,67 @@ interface UserProviderState {
|
|||||||
web3: Web3
|
web3: Web3
|
||||||
ocean: Ocean
|
ocean: Ocean
|
||||||
requestFromFaucet(account: string): Promise<FaucetResponse>
|
requestFromFaucet(account: string): Promise<FaucetResponse>
|
||||||
unlockAccounts(): Promise<any>
|
loginMetamask(): Promise<any>
|
||||||
|
loginBurnerWallet(): Promise<any>
|
||||||
|
logoutBurnerWallet(): Promise<any>
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
||||||
private unlockAccounts = async () => {
|
private loginMetamask = async () => {
|
||||||
try {
|
const metamaskProvider = new MetamaskProvider()
|
||||||
await window.ethereum.enable()
|
await metamaskProvider.startLogin()
|
||||||
} catch (error) {
|
const web3 = metamaskProvider.getProvider()
|
||||||
// User denied account access...
|
this.setState(
|
||||||
return null
|
{
|
||||||
|
isLogged: true,
|
||||||
|
isBurner: false,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private loginBurnerWallet = async () => {
|
||||||
|
const burnerwalletProvider = new BurnerWalletProvider()
|
||||||
|
await burnerwalletProvider.startLogin()
|
||||||
|
const web3 = burnerwalletProvider.getProvider()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLogged: true,
|
||||||
|
isBurner: true,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private logoutBurnerWallet = async () => {
|
||||||
|
const burnerwalletProvider = new BurnerWalletProvider()
|
||||||
|
await burnerwalletProvider.logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
isLogged: false,
|
isLogged: false,
|
||||||
|
isBurner: false,
|
||||||
|
isWeb3Capable: Boolean(window.web3 || window.ethereum),
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isWeb3: false,
|
|
||||||
isOceanNetwork: false,
|
|
||||||
balance: {
|
balance: {
|
||||||
eth: 0,
|
eth: 0,
|
||||||
ocn: 0
|
ocn: 0
|
||||||
},
|
},
|
||||||
network: '',
|
network: '',
|
||||||
web3: new Web3(new Web3.providers.HttpProvider(nodeUri)),
|
web3: DEFAULT_WEB3,
|
||||||
account: '',
|
account: '',
|
||||||
ocean: {} as any,
|
ocean: {} as any,
|
||||||
requestFromFaucet: () => requestFromFaucet(''),
|
requestFromFaucet: () => requestFromFaucet(''),
|
||||||
unlockAccounts: () => this.unlockAccounts(),
|
loginMetamask: () => this.loginMetamask(),
|
||||||
|
loginBurnerWallet: () => this.loginBurnerWallet(),
|
||||||
|
logoutBurnerWallet: () => this.logoutBurnerWallet(),
|
||||||
message: 'Connecting to Ocean...'
|
message: 'Connecting to Ocean...'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,9 +129,6 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
await this.bootstrap()
|
await this.bootstrap()
|
||||||
|
|
||||||
this.initAccountsPoll()
|
|
||||||
this.initNetworkPoll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initAccountsPoll() {
|
private initAccountsPoll() {
|
||||||
@ -114,109 +146,70 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWeb3 = () => {
|
private loadDefaultWeb3 = async () => {
|
||||||
// Modern dapp browsers
|
this.setState(
|
||||||
if (window.ethereum) {
|
{
|
||||||
window.web3 = new Web3(window.ethereum)
|
isLogged: false,
|
||||||
return window.web3
|
isBurner: false,
|
||||||
|
web3: DEFAULT_WEB3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
}
|
}
|
||||||
// Legacy dapp browsers
|
)
|
||||||
else if (window.web3) {
|
|
||||||
window.web3 = new Web3(window.web3.currentProvider)
|
|
||||||
return window.web3
|
|
||||||
}
|
|
||||||
// Non-dapp browsers
|
|
||||||
else {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadOcean = async () => {
|
||||||
|
const { ocean } = await provideOcean(this.state.web3)
|
||||||
|
this.setState({ ocean, isLoading: false }, () => {
|
||||||
|
this.initNetworkPoll()
|
||||||
|
this.initAccountsPoll()
|
||||||
|
this.fetchNetwork()
|
||||||
|
this.fetchAccounts()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private bootstrap = async () => {
|
private bootstrap = async () => {
|
||||||
try {
|
const logType = localStorage.getItem('logType')
|
||||||
//
|
const metamaskProvider = new MetamaskProvider()
|
||||||
// Start with Web3 detection only
|
|
||||||
//
|
|
||||||
this.setState({ message: 'Setting up Web3...' })
|
|
||||||
let web3 = await this.getWeb3()
|
|
||||||
|
|
||||||
web3
|
|
||||||
? this.setState({ isWeb3: true })
|
|
||||||
: this.setState({ isWeb3: false })
|
|
||||||
|
|
||||||
// Modern & legacy dapp browsers
|
|
||||||
if (web3 && this.state.isWeb3) {
|
|
||||||
//
|
|
||||||
// Detecting network with window.web3
|
|
||||||
//
|
|
||||||
let isOceanNetwork
|
|
||||||
|
|
||||||
await window.web3.eth.net.getId((err, netId) => {
|
|
||||||
if (err) return
|
|
||||||
|
|
||||||
const isPacific = netId === 0xcea11
|
|
||||||
const isNile = netId === 8995
|
|
||||||
const isDuero = netId === 2199
|
|
||||||
const isSpree = netId === 8996
|
|
||||||
|
|
||||||
isOceanNetwork = isPacific || isNile || isDuero || isSpree
|
|
||||||
|
|
||||||
const network = isPacific
|
|
||||||
? 'Pacific'
|
|
||||||
: isNile
|
|
||||||
? 'Nile'
|
|
||||||
: isDuero
|
|
||||||
? 'Duero'
|
|
||||||
: netId.toString()
|
|
||||||
|
|
||||||
|
switch (logType) {
|
||||||
|
case 'Metamask':
|
||||||
if (
|
if (
|
||||||
isOceanNetwork !== this.state.isOceanNetwork ||
|
(await metamaskProvider.isAvailable()) &&
|
||||||
network !== this.state.network
|
(await metamaskProvider.isLogged())
|
||||||
) {
|
) {
|
||||||
this.setState({ isOceanNetwork, network })
|
const web3 = metamaskProvider.getProvider()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLogged: true,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
} else {
|
||||||
if (!isOceanNetwork) {
|
this.loadDefaultWeb3()
|
||||||
web3 = this.state.web3 // eslint-disable-line
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
//
|
case 'BurnerWallet':
|
||||||
// Provide the Ocean
|
this.loginBurnerWallet()
|
||||||
//
|
break
|
||||||
this.setState({ message: 'Connecting to Ocean...' })
|
default:
|
||||||
|
this.loginBurnerWallet()
|
||||||
const { ocean } = await provideOcean(web3)
|
break
|
||||||
this.setState({ ocean, message: 'Getting accounts...' })
|
|
||||||
|
|
||||||
// Get accounts
|
|
||||||
await this.fetchAccounts()
|
|
||||||
|
|
||||||
this.setState({ isLoading: false, message: '' })
|
|
||||||
}
|
|
||||||
// Non-dapp browsers
|
|
||||||
else {
|
|
||||||
this.setState({ message: 'Connecting to Ocean...' })
|
|
||||||
const { ocean } = await provideOcean(this.state.web3)
|
|
||||||
this.setState({ ocean, isLoading: false })
|
|
||||||
|
|
||||||
this.fetchNetwork()
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// error in bootstrap process
|
|
||||||
// show error connecting to ocean
|
|
||||||
Logger.error('web3 error', e.message)
|
|
||||||
this.setState({ isLoading: false })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchAccounts = async () => {
|
private fetchAccounts = async () => {
|
||||||
const { ocean, isWeb3, isLogged, isOceanNetwork } = this.state
|
const { ocean, isLogged } = this.state
|
||||||
|
|
||||||
if (isWeb3) {
|
if (isLogged) {
|
||||||
let accounts
|
let accounts
|
||||||
|
|
||||||
// Modern dapp browsers
|
// Modern dapp browsers
|
||||||
if (window.ethereum && !isLogged && isOceanNetwork) {
|
if (window.ethereum && !isLogged) {
|
||||||
// simply set to empty, and have user click a button somewhere
|
// simply set to empty, and have user click a button somewhere
|
||||||
// to initiate account unlocking
|
// to initiate account unlocking
|
||||||
accounts = []
|
accounts = []
|
||||||
@ -254,19 +247,12 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fetchNetwork = async () => {
|
private fetchNetwork = async () => {
|
||||||
const { ocean, isWeb3 } = this.state
|
const { ocean } = this.state
|
||||||
|
let network = 'Unknown'
|
||||||
if (isWeb3) {
|
if (ocean.keeper) {
|
||||||
const network = await ocean.keeper.getNetworkName()
|
network = await ocean.keeper.getNetworkName()
|
||||||
const isPacific = network === 'Pacific'
|
|
||||||
const isNile = network === 'Nile'
|
|
||||||
const isDuero = network === 'Duero'
|
|
||||||
const isSpree = network === 'Spree'
|
|
||||||
const isOceanNetwork = isPacific || isNile || isDuero || isSpree
|
|
||||||
|
|
||||||
network !== this.state.network &&
|
|
||||||
this.setState({ isOceanNetwork, network })
|
|
||||||
}
|
}
|
||||||
|
network !== this.state.network && this.setState({ network })
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
@ -2,9 +2,9 @@ import React from 'react'
|
|||||||
|
|
||||||
export const User = React.createContext({
|
export const User = React.createContext({
|
||||||
isLogged: false,
|
isLogged: false,
|
||||||
|
isBurner: false,
|
||||||
|
isWeb3Capable: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isWeb3: false,
|
|
||||||
isOceanNetwork: false,
|
|
||||||
account: '',
|
account: '',
|
||||||
web3: {},
|
web3: {},
|
||||||
ocean: {},
|
ocean: {},
|
||||||
@ -16,10 +16,18 @@ export const User = React.createContext({
|
|||||||
requestFromFaucet: () => {
|
requestFromFaucet: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
},
|
},
|
||||||
unlockAccounts: () => {
|
loginMetamask: () => {
|
||||||
|
/* empty */
|
||||||
|
},
|
||||||
|
loginBurnerWallet: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
},
|
},
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Market = React.createContext({ totalAssets: 0, categories: [''] })
|
export const Market = React.createContext({
|
||||||
|
totalAssets: 0,
|
||||||
|
categories: [''],
|
||||||
|
network: '',
|
||||||
|
networkMatch: false
|
||||||
|
})
|
||||||
|
16
client/src/data/wallets.json
Normal file
16
client/src/data/wallets.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"title": "Select wallet",
|
||||||
|
"description": "Select the wallet you want to use in the Commons Marketplace. By default, we create a burner wallet in your browser allowing you to use all functionality without further setup.",
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"title": "Burner Wallet",
|
||||||
|
"icon": "🔥",
|
||||||
|
"description": "Easiest way to use Commons without further setup. But the wallet will be gone when you change browsers or clear your cache."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "MetaMask",
|
||||||
|
"icon": "🦊",
|
||||||
|
"description": "Most secure experience attaching everything you do in Commons to an account you control. But you need to setup MetaMask first."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"noweb3": "Not a Web3 Browser. For publishing and downloading an asset you need to <a href='https://docs.oceanprotocol.com/tutorials/metamask-setup/' target='_blank' rel='noopener noreferrer'>setup MetaMask</a> or use any other Web3-capable plugin or browser.",
|
"noAccount": "No wallet selected. For publishing and downloading an asset you need to use one.",
|
||||||
"noAccount": "No accounts detected. For publishing and downloading an asset you need to unlock your Web3 account.",
|
"hasBurnerWallet": "We created a temporary burner wallet for you, allowing you to use all Commons functionality without any setup on your side, and without a Web3-capable browser. This wallet will persist in your browser across sessions, but not across different browsers or devices. <strong>Never use this burner wallet to send or receive any tokens.</strong> To personalize your experience and improve your security, <a href='https://docs.oceanprotocol.com/tutorials/metamask-setup/'>migrate to MetaMask</a>.",
|
||||||
"hasAccount": "",
|
"hasMetaMaskWallet": "Connected with MetaMask to NETWORK. You're a Pro.",
|
||||||
"wrongNetwork": "Not connected to Pacific network.<br />Please connect in MetaMask with Custom RPC <code>https://pacific.oceanprotocol.com</code>"
|
"wrongNetworkPacific": "Not connected to Pacific network. Please connect in MetaMask with Custom RPC <code>https://pacific.oceanprotocol.com</code>",
|
||||||
|
"wrongNetworkNile": "Not connected to Nile network. Please connect in MetaMask with Custom RPC <code>https://nile.dev-ocean.com</code>",
|
||||||
|
"wrongNetworkDuero": "Not connected to Duero network. Please connect in MetaMask with Custom RPC <code>https://duero.dev-ocean.com</code>",
|
||||||
|
"wrongNetworkSpree": "Not connected to Spree network. Please connect in MetaMask with Custom RPC <code>http://localhost:8545</code>",
|
||||||
|
"seedphrase": "You can use this seed phrase to import this burner wallet account into other wallets, e.g. MetaMask."
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,14 @@ const withTracker = <P extends RouteComponentProps>(
|
|||||||
options: FieldsObject = {}
|
options: FieldsObject = {}
|
||||||
) => {
|
) => {
|
||||||
const trackPage = (page: string) => {
|
const trackPage = (page: string) => {
|
||||||
options.isWeb3 = window.web3 !== undefined
|
|
||||||
|
|
||||||
ReactGA.set({ page, ...options })
|
ReactGA.set({ page, ...options })
|
||||||
ReactGA.pageview(page)
|
ReactGA.pageview(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOC = (props: P) => {
|
const HOC = (props: P) => {
|
||||||
useEffect(() => trackPage(props.location.pathname), [
|
useEffect(() => {
|
||||||
props.location.pathname
|
trackPage(props.location.pathname)
|
||||||
])
|
}, [props.location.pathname])
|
||||||
|
|
||||||
return <WrappedComponent {...props} />
|
return <WrappedComponent {...props} />
|
||||||
}
|
}
|
||||||
|
3
client/src/img/caret.svg
Normal file
3
client/src/img/caret.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="10" viewBox="0 0 9 10">
|
||||||
|
<polygon points="9 5 0 10 0 0"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 124 B |
@ -11,9 +11,9 @@ import {
|
|||||||
verbose
|
verbose
|
||||||
} from './config'
|
} from './config'
|
||||||
|
|
||||||
export async function provideOcean(web3provider: Web3) {
|
export async function provideOcean(web3Provider: Web3) {
|
||||||
const config = {
|
const config = {
|
||||||
web3provider,
|
web3Provider,
|
||||||
nodeUri,
|
nodeUri,
|
||||||
aquariusUri,
|
aquariusUri,
|
||||||
brizoUri,
|
brizoUri,
|
||||||
@ -21,9 +21,7 @@ export async function provideOcean(web3provider: Web3) {
|
|||||||
secretStoreUri,
|
secretStoreUri,
|
||||||
verbose
|
verbose
|
||||||
}
|
}
|
||||||
|
const ocean: any = await Ocean.getInstance(config)
|
||||||
const ocean: Ocean = await Ocean.getInstance(config)
|
|
||||||
|
|
||||||
return { ocean }
|
return { ocean }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import About from './About'
|
import About from './About'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/about')
|
||||||
|
|
||||||
describe('About', () => {
|
describe('About', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<About />
|
<About
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -1,25 +1,30 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { useContext } from 'react'
|
||||||
|
import { Market, User } from '../context'
|
||||||
import Route from '../components/templates/Route'
|
import Route from '../components/templates/Route'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
import VersionNumbers from '../components/molecules/VersionNumbers'
|
import VersionNumbers from '../components/molecules/VersionNumbers'
|
||||||
|
import Web3message from '../components/organisms/Web3message'
|
||||||
|
import stylesVersionNumbers from '../components/molecules/VersionNumbers/index.module.scss'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
|
const About = () => {
|
||||||
|
const market = useContext(Market)
|
||||||
|
const user = useContext(User)
|
||||||
|
|
||||||
class About extends Component {
|
|
||||||
public render() {
|
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
title="About"
|
title="About"
|
||||||
description="A marketplace to find and publish open data sets in the Ocean Pacific Network."
|
description={`A marketplace to find and publish open data sets in the Ocean ${market.network} Network.`}
|
||||||
>
|
>
|
||||||
<Content>
|
<Content>
|
||||||
<p>
|
<p>
|
||||||
Commons is built on top of the Ocean{' '}
|
Commons is built on top of the Ocean{' '}
|
||||||
<a href="https://docs.oceanprotocol.com/concepts/pacific-network/">
|
<a href="https://docs.oceanprotocol.com/concepts/pacific-network/">
|
||||||
Pacific network
|
{market.network} network
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
and is targeted at enthusiastic data scientists with
|
and is targeted at enthusiastic data scientists with some
|
||||||
some crypto experience. It can be used with any
|
crypto experience. It can be used with any Web3-capable
|
||||||
Web3-capable browser, like Firefox with MetaMask
|
browser, like Firefox with MetaMask installed.
|
||||||
installed.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
@ -37,11 +42,14 @@ class About extends Component {
|
|||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<Content>
|
<Content>
|
||||||
<VersionNumbers />
|
<h2 className={stylesVersionNumbers.versionsTitle}>
|
||||||
|
Your Web3 Account Status
|
||||||
|
</h2>
|
||||||
|
<Web3message extended />
|
||||||
|
<VersionNumbers account={user.account} />
|
||||||
</Content>
|
</Content>
|
||||||
</Route>
|
</Route>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default About
|
export default withTracker(About)
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Channels from './Channels'
|
import Channels from './Channels'
|
||||||
import { User } from '../context'
|
import { User } from '../context'
|
||||||
import { userMockConnected } from '../../__mocks__/user-mock'
|
import { userMockConnected } from '../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/channels')
|
||||||
|
|
||||||
describe('Channels', () => {
|
describe('Channels', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Channels />
|
<Channels
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ import Route from '../components/templates/Route'
|
|||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
import channels from '../data/channels.json'
|
import channels from '../data/channels.json'
|
||||||
import ChannelTeaser from '../components/organisms/ChannelTeaser'
|
import ChannelTeaser from '../components/organisms/ChannelTeaser'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
class Channels extends Component {
|
class Channels extends Component {
|
||||||
public render() {
|
public render() {
|
||||||
@ -21,4 +22,4 @@ class Channels extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Channels
|
export default withTracker(Channels)
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import Faucet from './Faucet'
|
import Faucet from './Faucet'
|
||||||
import { User } from '../context'
|
import { User } from '../context'
|
||||||
import { userMockConnected } from '../../__mocks__/user-mock'
|
import { userMockConnected } from '../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/faucet')
|
||||||
|
|
||||||
const setup = () => {
|
const setup = () => {
|
||||||
const utils = render(
|
const utils = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Faucet />
|
<Faucet
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
|
@ -3,10 +3,11 @@ import { FaucetResponse } from '../ocean'
|
|||||||
import Route from '../components/templates/Route'
|
import Route from '../components/templates/Route'
|
||||||
import Button from '../components/atoms/Button'
|
import Button from '../components/atoms/Button'
|
||||||
import Spinner from '../components/atoms/Spinner'
|
import Spinner from '../components/atoms/Spinner'
|
||||||
import { User } from '../context'
|
import { User, Market } from '../context'
|
||||||
import Web3message from '../components/organisms/Web3message'
|
import Web3message from '../components/organisms/Web3message'
|
||||||
import styles from './Faucet.module.scss'
|
import styles from './Faucet.module.scss'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
interface FaucetState {
|
interface FaucetState {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
@ -15,7 +16,7 @@ interface FaucetState {
|
|||||||
trxHash?: string
|
trxHash?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Faucet extends PureComponent<{}, FaucetState> {
|
class Faucet extends PureComponent<{}, FaucetState> {
|
||||||
public static contextType = User
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
@ -99,9 +100,8 @@ export default class Faucet extends PureComponent<{}, FaucetState> {
|
|||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
onClick={() => this.getTokens(this.context.requestFromFaucet)}
|
onClick={() => this.getTokens(this.context.requestFromFaucet)}
|
||||||
disabled={
|
disabled={!this.context.isLogged}
|
||||||
!this.context.isLogged || !this.context.isOceanNetwork
|
name="Faucet"
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Request Ether
|
Request Ether
|
||||||
</Button>
|
</Button>
|
||||||
@ -112,13 +112,15 @@ export default class Faucet extends PureComponent<{}, FaucetState> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { isWeb3 } = this.context
|
const { isLogged } = this.context
|
||||||
const { isLoading, error, success } = this.state
|
const { isLoading, error, success } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<Route
|
<Route
|
||||||
title="Faucet"
|
title="Faucet"
|
||||||
description="Shower yourself with some Ether for Ocean's Pacific network."
|
description={`Shower yourself with some Ether for Ocean's ${market.network} network.`}
|
||||||
>
|
>
|
||||||
<Content>
|
<Content>
|
||||||
<Web3message />
|
<Web3message />
|
||||||
@ -131,13 +133,15 @@ export default class Faucet extends PureComponent<{}, FaucetState> {
|
|||||||
) : success ? (
|
) : success ? (
|
||||||
<this.Success />
|
<this.Success />
|
||||||
) : (
|
) : (
|
||||||
isWeb3 && <this.Action />
|
isLogged && <this.Action />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
</Route>
|
</Route>
|
||||||
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Faucet.contextType = User
|
export default withTracker(Faucet)
|
||||||
|
@ -1,45 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
import { User } from '../context'
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import History from './History'
|
import History from './History'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/history')
|
||||||
|
|
||||||
describe('History', () => {
|
describe('History', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<History />
|
<History
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('outputs Web3 message when no Web3 detected', () => {
|
|
||||||
const context = {
|
|
||||||
isLogged: false,
|
|
||||||
isLoading: false,
|
|
||||||
isWeb3: false,
|
|
||||||
isOceanNetwork: false,
|
|
||||||
account: '',
|
|
||||||
web3: {},
|
|
||||||
ocean: {},
|
|
||||||
balance: { eth: 0, ocn: 0 },
|
|
||||||
network: '',
|
|
||||||
requestFromFaucet: () => {},
|
|
||||||
unlockAccounts: () => {},
|
|
||||||
message: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const { container } = render(
|
|
||||||
<User.Provider value={context}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<History />
|
|
||||||
</MemoryRouter>
|
|
||||||
</User.Provider>
|
|
||||||
)
|
|
||||||
expect(container.querySelector('.message')).toBeInTheDocument()
|
|
||||||
expect(container.querySelector('.message')).toHaveTextContent(
|
|
||||||
'Not a Web3 Browser.'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -4,15 +4,16 @@ import AssetsUser from '../components/organisms/AssetsUser'
|
|||||||
import Web3message from '../components/organisms/Web3message'
|
import Web3message from '../components/organisms/Web3message'
|
||||||
import { User } from '../context'
|
import { User } from '../context'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
|
class History extends Component {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
export default class History extends Component {
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<Route title="History">
|
<Route title="History">
|
||||||
<Content>
|
<Content>
|
||||||
{(!this.context.isLogged ||
|
{!this.context.isLogged && <Web3message />}
|
||||||
!this.context.isOceanNetwork) && <Web3message />}
|
|
||||||
|
|
||||||
<AssetsUser list />
|
<AssetsUser list />
|
||||||
</Content>
|
</Content>
|
||||||
</Route>
|
</Route>
|
||||||
@ -20,4 +21,4 @@ export default class History extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
History.contextType = User
|
export default withTracker(History)
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Router } from 'react-router'
|
|
||||||
import { createBrowserHistory } from 'history'
|
|
||||||
import { render } from '@testing-library/react'
|
|
||||||
import Home from './Home'
|
|
||||||
import { userMock } from '../../__mocks__/user-mock'
|
|
||||||
import { User } from '../context'
|
|
||||||
|
|
||||||
const history = createBrowserHistory()
|
|
||||||
|
|
||||||
describe('Home', () => {
|
|
||||||
it('renders without crashing', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<User.Provider value={{ ...userMock }}>
|
|
||||||
<Router history={history}>
|
|
||||||
<Home history={history} />
|
|
||||||
</Router>
|
|
||||||
</User.Provider>
|
|
||||||
)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,102 +0,0 @@
|
|||||||
import React, { ChangeEvent, Component, FormEvent } from 'react'
|
|
||||||
import { History } from 'history'
|
|
||||||
import { User, Market } from '../context'
|
|
||||||
import CategoryImage from '../components/atoms/CategoryImage'
|
|
||||||
import CategoryLink from '../components/atoms/CategoryLink'
|
|
||||||
import Button from '../components/atoms/Button'
|
|
||||||
import Form from '../components/atoms/Form/Form'
|
|
||||||
import Input from '../components/atoms/Form/Input'
|
|
||||||
import Route from '../components/templates/Route'
|
|
||||||
import styles from './Home.module.scss'
|
|
||||||
|
|
||||||
import meta from '../data/meta.json'
|
|
||||||
import Content from '../components/atoms/Content'
|
|
||||||
import AssetsLatest from '../components/organisms/AssetsLatest'
|
|
||||||
import ChannelTeaser from '../components/organisms/ChannelTeaser'
|
|
||||||
|
|
||||||
interface HomeProps {
|
|
||||||
history: History
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeState {
|
|
||||||
search?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Home extends Component<HomeProps, HomeState> {
|
|
||||||
public static contextType = User
|
|
||||||
|
|
||||||
public state = {
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
private inputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
this.setState({
|
|
||||||
[event.target.name]: event.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private searchAssets = (event: FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault()
|
|
||||||
this.props.history.push(`/search?text=${this.state.search}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { search } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Route
|
|
||||||
title={meta.title}
|
|
||||||
description={meta.description}
|
|
||||||
className={styles.home}
|
|
||||||
>
|
|
||||||
<Content>
|
|
||||||
<Form onSubmit={this.searchAssets} minimal>
|
|
||||||
<Input
|
|
||||||
type="search"
|
|
||||||
name="search"
|
|
||||||
label="Search for data sets"
|
|
||||||
placeholder="e.g. shapes of plants"
|
|
||||||
value={search}
|
|
||||||
onChange={this.inputChange}
|
|
||||||
group={
|
|
||||||
<Button primary disabled={!search}>
|
|
||||||
Search
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</Content>
|
|
||||||
|
|
||||||
<Content wide>
|
|
||||||
<h2 className={styles.title}>Featured Channel</h2>
|
|
||||||
<ChannelTeaser channel="ai-for-good" />
|
|
||||||
<AssetsLatest />
|
|
||||||
</Content>
|
|
||||||
|
|
||||||
<Content wide>
|
|
||||||
<h2 className={styles.title}>Explore Categories</h2>
|
|
||||||
<div className={styles.categories}>
|
|
||||||
<Market.Consumer>
|
|
||||||
{({ categories }) =>
|
|
||||||
categories
|
|
||||||
.sort((a, b) => a.localeCompare(b)) // sort alphabetically
|
|
||||||
.map((category: string) => (
|
|
||||||
<CategoryLink
|
|
||||||
category={category}
|
|
||||||
key={category}
|
|
||||||
className={styles.category}
|
|
||||||
>
|
|
||||||
<CategoryImage
|
|
||||||
category={category}
|
|
||||||
/>
|
|
||||||
<h3>{category}</h3>
|
|
||||||
</CategoryLink>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</Market.Consumer>
|
|
||||||
</div>
|
|
||||||
</Content>
|
|
||||||
</Route>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
16
client/src/routes/Home/Search.test.tsx
Normal file
16
client/src/routes/Home/Search.test.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import Search from './Search'
|
||||||
|
|
||||||
|
describe('Search', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Search searchAssets={() => null} />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
// type search query
|
||||||
|
fireEvent.change(container.querySelector('input'), {
|
||||||
|
target: { value: 'Plants' }
|
||||||
|
})
|
||||||
|
expect(container.querySelector('input').value).toBe('Plants')
|
||||||
|
// fireEvent.click(getByText('Search'))
|
||||||
|
})
|
||||||
|
})
|
51
client/src/routes/Home/Search.tsx
Normal file
51
client/src/routes/Home/Search.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { ChangeEvent, FormEvent, PureComponent } from 'react'
|
||||||
|
import Button from '../../components/atoms/Button'
|
||||||
|
import Form from '../../components/atoms/Form/Form'
|
||||||
|
import Input from '../../components/atoms/Form/Input'
|
||||||
|
|
||||||
|
interface SearchProps {
|
||||||
|
searchAssets: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchState {
|
||||||
|
search: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Search extends PureComponent<SearchProps, SearchState> {
|
||||||
|
public state = {
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private inputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({
|
||||||
|
search: event.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { search } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
onSubmit={(e: FormEvent<HTMLFormElement>) =>
|
||||||
|
this.props.searchAssets(search, e)
|
||||||
|
}
|
||||||
|
minimal
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
name="search"
|
||||||
|
label="Search for data sets"
|
||||||
|
placeholder="e.g. shapes of plants"
|
||||||
|
value={search}
|
||||||
|
onChange={this.inputChange}
|
||||||
|
group={
|
||||||
|
<Button primary disabled={!search}>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
@import '../styles/variables';
|
@import '../../styles/variables';
|
||||||
|
|
||||||
.home {
|
.home {
|
||||||
display: block;
|
display: block;
|
27
client/src/routes/Home/index.test.tsx
Normal file
27
client/src/routes/Home/index.test.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Router } from 'react-router'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import Home from '.'
|
||||||
|
import { userMock } from '../../../__mocks__/user-mock'
|
||||||
|
import { User } from '../../context'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/')
|
||||||
|
|
||||||
|
describe('Home', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<User.Provider value={{ ...userMock }}>
|
||||||
|
<Router history={history}>
|
||||||
|
<Home
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
</User.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
74
client/src/routes/Home/index.tsx
Normal file
74
client/src/routes/Home/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React, { PureComponent, FormEvent } from 'react'
|
||||||
|
import { History } from 'history'
|
||||||
|
import { Market } from '../../context'
|
||||||
|
import CategoryImage from '../../components/atoms/CategoryImage'
|
||||||
|
import CategoryLink from '../../components/atoms/CategoryLink'
|
||||||
|
import Route from '../../components/templates/Route'
|
||||||
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
|
import meta from '../../data/meta.json'
|
||||||
|
import Content from '../../components/atoms/Content'
|
||||||
|
import AssetsLatest from '../../components/organisms/AssetsLatest'
|
||||||
|
import ChannelTeaser from '../../components/organisms/ChannelTeaser'
|
||||||
|
import Search from './Search'
|
||||||
|
import withTracker from '../../hoc/withTracker'
|
||||||
|
|
||||||
|
interface HomeProps {
|
||||||
|
history: History
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HomeState {
|
||||||
|
search?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class Home extends PureComponent<HomeProps, HomeState> {
|
||||||
|
public static contextType = Market
|
||||||
|
|
||||||
|
public searchAssets = (
|
||||||
|
search: string,
|
||||||
|
event: FormEvent<HTMLFormElement>
|
||||||
|
) => {
|
||||||
|
event.preventDefault()
|
||||||
|
this.props.history.push(`/search?text=${search}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
title={meta.title}
|
||||||
|
description={meta.description}
|
||||||
|
className={styles.home}
|
||||||
|
>
|
||||||
|
<Content>
|
||||||
|
<Search searchAssets={this.searchAssets} />
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Content wide>
|
||||||
|
<h2 className={styles.title}>Featured Channel</h2>
|
||||||
|
<ChannelTeaser channel="ai-for-good" />
|
||||||
|
<AssetsLatest />
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Content wide>
|
||||||
|
<h2 className={styles.title}>Explore Categories</h2>
|
||||||
|
<div className={styles.categories}>
|
||||||
|
{this.context.categories
|
||||||
|
.sort((a: any, b: any) => a.localeCompare(b)) // sort alphabetically
|
||||||
|
.map((category: string) => (
|
||||||
|
<CategoryLink
|
||||||
|
category={category}
|
||||||
|
key={category}
|
||||||
|
className={styles.category}
|
||||||
|
>
|
||||||
|
<CategoryImage category={category} />
|
||||||
|
<h3>{category}</h3>
|
||||||
|
</CategoryLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTracker(Home)
|
@ -1,13 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import NotFound from './NotFound'
|
import NotFound from './NotFound'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/whatever')
|
||||||
|
|
||||||
describe('NotFound', () => {
|
describe('NotFound', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<NotFound />
|
<NotFound
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import Route from '../components/templates/Route'
|
import Route from '../components/templates/Route'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
class NotFound extends Component {
|
class NotFound extends Component {
|
||||||
public render() {
|
public render() {
|
||||||
@ -12,4 +13,4 @@ class NotFound extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotFound
|
export default withTracker(NotFound)
|
||||||
|
@ -24,3 +24,7 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
margin-top: $spacer * $line-height;
|
||||||
|
}
|
||||||
|
@ -3,10 +3,11 @@ import Input from '../../components/atoms/Form/Input'
|
|||||||
import Label from '../../components/atoms/Form/Label'
|
import Label from '../../components/atoms/Form/Label'
|
||||||
import Row from '../../components/atoms/Form/Row'
|
import Row from '../../components/atoms/Form/Row'
|
||||||
import Button from '../../components/atoms/Button'
|
import Button from '../../components/atoms/Button'
|
||||||
import { User } from '../../context'
|
import { User, Market } from '../../context'
|
||||||
import Files from './Files/'
|
import Files from './Files/'
|
||||||
import StepRegisterContent from './StepRegisterContent'
|
import StepRegisterContent from './StepRegisterContent'
|
||||||
import styles from './Step.module.scss'
|
import styles from './Step.module.scss'
|
||||||
|
import Web3message from '../../components/organisms/Web3message'
|
||||||
|
|
||||||
interface Fields {
|
interface Fields {
|
||||||
label: string
|
label: string
|
||||||
@ -42,6 +43,8 @@ interface StepProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Step extends PureComponent<StepProps, {}> {
|
export default class Step extends PureComponent<StepProps, {}> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public previousButton() {
|
public previousButton() {
|
||||||
const { currentStep, prev } = this.props
|
const { currentStep, prev } = this.props
|
||||||
|
|
||||||
@ -151,9 +154,12 @@ export default class Step extends PureComponent<StepProps, {}> {
|
|||||||
{this.nextButton()}
|
{this.nextButton()}
|
||||||
|
|
||||||
{lastStep && (
|
{lastStep && (
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<Button
|
<Button
|
||||||
disabled={
|
disabled={
|
||||||
!this.context.isLogged ||
|
!this.context.isLogged ||
|
||||||
|
!market.networkMatch ||
|
||||||
this.props.state.isPublishing
|
this.props.state.isPublishing
|
||||||
}
|
}
|
||||||
primary
|
primary
|
||||||
@ -161,10 +167,13 @@ export default class Step extends PureComponent<StepProps, {}> {
|
|||||||
Register asset
|
Register asset
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.account}>
|
||||||
|
{!lastStep && <Web3message />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Step.contextType = User
|
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
import Publish from '.'
|
import Publish from '.'
|
||||||
import { User } from '../../context'
|
import { User } from '../../context'
|
||||||
import { userMockConnected } from '../../../__mocks__/user-mock'
|
import { userMockConnected } from '../../../__mocks__/user-mock'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/publish')
|
||||||
|
|
||||||
describe('Publish', () => {
|
describe('Publish', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container, getByText } = render(
|
const { container, getByText } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Publish />
|
<Publish
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
@ -22,7 +30,16 @@ describe('Publish', () => {
|
|||||||
const { getByText, getByLabelText, getByTestId } = render(
|
const { getByText, getByLabelText, getByTestId } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Publish />
|
<Publish
|
||||||
|
history={history}
|
||||||
|
location={{
|
||||||
|
pathname: '/publish',
|
||||||
|
search: '',
|
||||||
|
hash: '',
|
||||||
|
state: ''
|
||||||
|
}}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
)
|
)
|
||||||
|
@ -3,8 +3,7 @@ import { Logger } from '@oceanprotocol/squid'
|
|||||||
import Route from '../../components/templates/Route'
|
import Route from '../../components/templates/Route'
|
||||||
import Form from '../../components/atoms/Form/Form'
|
import Form from '../../components/atoms/Form/Form'
|
||||||
import AssetModel from '../../models/AssetModel'
|
import AssetModel from '../../models/AssetModel'
|
||||||
import { User } from '../../context'
|
import { User, Market } from '../../context'
|
||||||
import Web3message from '../../components/organisms/Web3message'
|
|
||||||
import Step from './Step'
|
import Step from './Step'
|
||||||
import Progress from './Progress'
|
import Progress from './Progress'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
@ -12,6 +11,7 @@ import ReactGA from 'react-ga'
|
|||||||
import { steps } from '../../data/form-publish.json'
|
import { steps } from '../../data/form-publish.json'
|
||||||
import Content from '../../components/atoms/Content'
|
import Content from '../../components/atoms/Content'
|
||||||
import { File } from './Files'
|
import { File } from './Files'
|
||||||
|
import withTracker from '../../hoc/withTracker'
|
||||||
|
|
||||||
type AssetType = 'dataset' | 'algorithm' | 'container' | 'workflow' | 'other'
|
type AssetType = 'dataset' | 'algorithm' | 'container' | 'workflow' | 'other'
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ interface PublishState {
|
|||||||
validationStatus?: any
|
validationStatus?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Publish extends Component<{}, PublishState> {
|
class Publish extends Component<{}, PublishState> {
|
||||||
public static contextType = User
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
@ -319,14 +319,13 @@ export default class Publish extends Component<{}, PublishState> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
|
<Market.Consumer>
|
||||||
|
{market => (
|
||||||
<Route
|
<Route
|
||||||
title="Publish"
|
title="Publish"
|
||||||
description="Publish a new data set into the Ocean Protocol Network."
|
description={`Publish a new data set into the Ocean Protocol ${market.network} Network.`}
|
||||||
>
|
>
|
||||||
<Content>
|
<Content>
|
||||||
{(!this.context.isLogged ||
|
|
||||||
!this.context.isOceanNetwork) && <Web3message />}
|
|
||||||
|
|
||||||
<Progress
|
<Progress
|
||||||
steps={steps}
|
steps={steps}
|
||||||
currentStep={this.state.currentStep}
|
currentStep={this.state.currentStep}
|
||||||
@ -354,6 +353,10 @@ export default class Publish extends Component<{}, PublishState> {
|
|||||||
</Form>
|
</Form>
|
||||||
</Content>
|
</Content>
|
||||||
</Route>
|
</Route>
|
||||||
|
)}
|
||||||
|
</Market.Consumer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withTracker(Publish)
|
||||||
|
@ -6,10 +6,10 @@ import { createMemoryHistory } from 'history'
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
import { userMockConnected } from '../../__mocks__/user-mock'
|
import { userMockConnected } from '../../__mocks__/user-mock'
|
||||||
|
|
||||||
describe('Search', () => {
|
|
||||||
it('renders without crashing', () => {
|
|
||||||
const history = createMemoryHistory()
|
const history = createMemoryHistory()
|
||||||
|
|
||||||
|
describe('Search', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<User.Provider value={userMockConnected}>
|
<User.Provider value={userMockConnected}>
|
||||||
<Router>
|
<Router>
|
||||||
@ -21,6 +21,7 @@ describe('Search', () => {
|
|||||||
hash: ''
|
hash: ''
|
||||||
}}
|
}}
|
||||||
history={history}
|
history={history}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
/>
|
/>
|
||||||
</Router>
|
</Router>
|
||||||
</User.Provider>
|
</User.Provider>
|
||||||
|
@ -10,6 +10,7 @@ import AssetTeaser from '../components/molecules/AssetTeaser'
|
|||||||
import Pagination from '../components/molecules/Pagination'
|
import Pagination from '../components/molecules/Pagination'
|
||||||
import styles from './Search.module.scss'
|
import styles from './Search.module.scss'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
interface SearchProps {
|
interface SearchProps {
|
||||||
location: Location
|
location: Location
|
||||||
@ -27,7 +28,9 @@ interface SearchState {
|
|||||||
searchCategories: string
|
searchCategories: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Search extends PureComponent<SearchProps, SearchState> {
|
class Search extends PureComponent<SearchProps, SearchState> {
|
||||||
|
public static contextType = User
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
results: [],
|
results: [],
|
||||||
totalResults: 0,
|
totalResults: 0,
|
||||||
@ -159,4 +162,4 @@ export default class Search extends PureComponent<SearchProps, SearchState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Search.contextType = User
|
export default withTracker(Search)
|
||||||
|
@ -2,12 +2,20 @@ import React from 'react'
|
|||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import Styleguide from './Styleguide'
|
import Styleguide from './Styleguide'
|
||||||
import { MemoryRouter } from 'react-router'
|
import { MemoryRouter } from 'react-router'
|
||||||
|
import { createMemoryHistory, createLocation } from 'history'
|
||||||
|
|
||||||
|
const history = createMemoryHistory()
|
||||||
|
const location = createLocation('/styleguide')
|
||||||
|
|
||||||
describe('Styleguide', () => {
|
describe('Styleguide', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Styleguide />
|
<Styleguide
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
match={{ params: '', path: '', url: '', isExact: true }}
|
||||||
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
@ -6,6 +6,7 @@ import Route from '../components/templates/Route'
|
|||||||
import styles from './Styleguide.module.scss'
|
import styles from './Styleguide.module.scss'
|
||||||
import form from '../data/form-styleguide.json'
|
import form from '../data/form-styleguide.json'
|
||||||
import Content from '../components/atoms/Content'
|
import Content from '../components/atoms/Content'
|
||||||
|
import withTracker from '../hoc/withTracker'
|
||||||
|
|
||||||
class Styleguide extends Component {
|
class Styleguide extends Component {
|
||||||
public formFields = (entries: any[]) =>
|
public formFields = (entries: any[]) =>
|
||||||
@ -47,4 +48,4 @@ class Styleguide extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Styleguide
|
export default withTracker(Styleguide)
|
||||||
|
@ -140,6 +140,11 @@ em,
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: $font-size-small;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
abbr[title],
|
abbr[title],
|
||||||
dfn {
|
dfn {
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"baseUrl": "http://localhost:3000",
|
"baseUrl": "http://localhost:3000",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_URI": "https://pacific.oceanprotocol.com",
|
|
||||||
"SEEDPHRASE": "taxi music thumb unique chat sand crew more leg another off lamp",
|
|
||||||
"CONSUME_ASSET": "did:op:3fa4721ad643489bad77e0e52ecefe9b73166873faaf4661a02486b735eea0c8"
|
"CONSUME_ASSET": "did:op:3fa4721ad643489bad77e0e52ecefe9b73166873faaf4661a02486b735eea0c8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,27 @@ context('Faucet', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/faucet')
|
cy.visit('/faucet')
|
||||||
// Wait for end of loading
|
// Wait for end of loading
|
||||||
cy.get('button', { timeout: 60000 }).should('have.length', 1)
|
cy.get('button[name="Faucet"]', { timeout: 60000 }).should(
|
||||||
|
'have.length',
|
||||||
|
1
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('button[name="Faucet"]')
|
||||||
|
.first()
|
||||||
|
.as('button')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Faucet button is clickable when user is connected.', () => {
|
it('Faucet button is clickable when user is connected.', () => {
|
||||||
cy.get('button')
|
cy.get('@button')
|
||||||
.contains('Request Ether')
|
.contains('Request Ether')
|
||||||
.should('not.be.disabled')
|
.should('not.be.disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Execute faucet call', () => {
|
it('Execute faucet call', () => {
|
||||||
// Execute call
|
// Execute call
|
||||||
cy.get('button')
|
cy.get('@button')
|
||||||
.contains('Request Ether')
|
.contains('Request Ether')
|
||||||
.click()
|
.click()
|
||||||
// Verify that we got response from server
|
// Verify that we got response from server
|
||||||
|
@ -1,14 +1,3 @@
|
|||||||
/// <reference types="Cypress" />
|
/// <reference types="Cypress" />
|
||||||
import Web3 from 'web3'
|
|
||||||
import HDWalletProvider from 'truffle-hdwallet-provider'
|
|
||||||
|
|
||||||
before(function() {
|
before(function() {})
|
||||||
cy.on('window:before:load', win => {
|
|
||||||
const provider = new HDWalletProvider(
|
|
||||||
Cypress.env('SEEDPHRASE'),
|
|
||||||
Cypress.env('NODE_URI')
|
|
||||||
)
|
|
||||||
win.web3 = new Web3(provider)
|
|
||||||
win.ethereum = win.web3
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
2169
package-lock.json
generated
2169
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,17 +24,14 @@
|
|||||||
"cypress:run": "cypress run --browser chrome",
|
"cypress:run": "cypress run --browser chrome",
|
||||||
"cypress:open": "cypress open"
|
"cypress:open": "cypress open"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"truffle-hdwallet-provider": "^1.0.10",
|
|
||||||
"web3": "1.0.0-beta.37"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@release-it/bumper": "^1.0.3",
|
"@release-it/bumper": "^1.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^1.11.0",
|
"@typescript-eslint/eslint-plugin": "^1.11.0",
|
||||||
"@typescript-eslint/parser": "^1.11.0",
|
"@typescript-eslint/parser": "^1.11.0",
|
||||||
"auto-changelog": "^1.13.0",
|
"auto-changelog": "^1.13.0",
|
||||||
"concurrently": "^4.1.1",
|
"concurrently": "^4.1.1",
|
||||||
"cypress": "^3.3.1",
|
"cypress": "^3.4.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-oceanprotocol": "^1.3.0",
|
"eslint-config-oceanprotocol": "^1.3.0",
|
||||||
"eslint-config-prettier": "^6.0.0",
|
"eslint-config-prettier": "^6.0.0",
|
||||||
|
43
server/package-lock.json
generated
43
server/package-lock.json
generated
@ -4096,9 +4096,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mixin-deep": {
|
"mixin-deep": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||||
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
|
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"for-in": "^1.0.2",
|
"for-in": "^1.0.2",
|
||||||
@ -5107,9 +5107,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"set-value": {
|
"set-value": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
|
||||||
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
|
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend-shallow": "^2.0.1",
|
"extend-shallow": "^2.0.1",
|
||||||
@ -5856,38 +5856,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||||
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
|
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-union": "^3.1.0",
|
"arr-union": "^3.1.0",
|
||||||
"get-value": "^2.0.6",
|
"get-value": "^2.0.6",
|
||||||
"is-extendable": "^0.1.1",
|
"is-extendable": "^0.1.1",
|
||||||
"set-value": "^0.4.3"
|
"set-value": "^2.0.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"set-value": {
|
|
||||||
"version": "0.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
|
|
||||||
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"is-extendable": "^0.1.1",
|
|
||||||
"is-plain-object": "^2.0.1",
|
|
||||||
"to-object-path": "^0.3.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unique-string": {
|
"unique-string": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user