mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #628 from oceanprotocol/feature/multinetwork
Multinetwork Interface
This commit is contained in:
commit
4c653b6ae8
@ -1,7 +1,3 @@
|
||||
# Default network, possible values:
|
||||
# "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha",
|
||||
# "gaiaxtestnet", "mumbai", "bsc"
|
||||
GATSBY_NETWORK="rinkeby"
|
||||
|
||||
#GATSBY_INFURA_PROJECT_ID="xxx"
|
||||
#GATSBY_MARKET_FEE_ADDRESS="0xxx"
|
||||
|
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@ -16,7 +16,6 @@ jobs:
|
||||
|
||||
- run: npm run build
|
||||
env:
|
||||
GATSBY_NETWORK: ${{ secrets.GATSBY_NETWORK }}
|
||||
GATSBY_INFURA_PROJECT_ID: ${{ secrets.GATSBY_INFURA_PROJECT_ID }}
|
||||
GATSBY_PORTIS_ID: ${{ secrets.GATSBY_PORTIS_ID }}
|
||||
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -13,4 +13,6 @@ public/storybook
|
||||
.vercel
|
||||
repo-metadata.json
|
||||
content/networks-metadata.json
|
||||
src/@types/apollo
|
||||
src/@types/apollo
|
||||
graphql.schema.json
|
||||
src/@types/graph.types.ts
|
||||
|
31
README.md
31
README.md
@ -31,7 +31,7 @@
|
||||
|
||||
## 🏄 Get Started
|
||||
|
||||
The app is a React app built with [Gatsby.js](https://www.gatsbyjs.org) + TypeScript + CSS modules and will connect to Ocean components in Rinkeby by default.
|
||||
The app is a React app built with [Gatsby.js](https://www.gatsbyjs.org) + TypeScript + CSS modules and will connect to Ocean remote components by default.
|
||||
|
||||
To start local development:
|
||||
|
||||
@ -73,7 +73,7 @@ Barge will deploy contracts to the local Ganache node which will take some time.
|
||||
Finally, set environment variables to use this local connection in `.env` in the app:
|
||||
|
||||
```bash
|
||||
# modify env variables, setting GATSBY_NETWORK="development"
|
||||
# modify env variables
|
||||
cp .env.example .env
|
||||
|
||||
npm start
|
||||
@ -111,7 +111,7 @@ All this data then comes from multiple sources:
|
||||
|
||||
### Aquarius
|
||||
|
||||
All initial data sets and their metadata (DDO) is retrieved client-side on run-time from the [Aquarius](https://github.com/oceanprotocol/aquarius) instance for each network. All app calls to Aquarius are done with 2 internal methods which mimic the same methods in ocean.js, but allow us:
|
||||
All initial data sets and their metadata (DDO) is retrieved client-side on run-time from the [Aquarius](https://github.com/oceanprotocol/aquarius) instance, defined in `app.config.js`. All app calls to Aquarius are done with 2 internal methods which mimic the same methods in ocean.js, but allow us:
|
||||
|
||||
- to cancel requests when components get unmounted in combination with [axios](https://github.com/axios/axios)
|
||||
- hit Aquarius as early as possible without relying on any ocean.js initialization
|
||||
@ -127,25 +127,22 @@ const queryLatest = {
|
||||
offset: 9,
|
||||
query: {
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
|
||||
|
||||
query_string: { query: `-isInPurgatory:true` }
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const { config } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [result, setResult] = useState<QueryResult>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.metadataCacheUri) return
|
||||
if (!appConfig.metadataCacheUri) return
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function init() {
|
||||
const result = await queryMetadata(
|
||||
query,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const result = await queryMetadata(query, source.token)
|
||||
setResult(result)
|
||||
}
|
||||
init()
|
||||
@ -153,7 +150,7 @@ function Component() {
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [config?.metadataCacheUri, query])
|
||||
}, [appConfig.metadataCacheUri, query])
|
||||
|
||||
return <div>{result}</div>
|
||||
}
|
||||
@ -174,10 +171,10 @@ function Component() {
|
||||
|
||||
Most financial data in the market is retrieved with GraphQL from [our own subgraph](https://github.com/oceanprotocol/ocean-subgraph), rendered on top of the initial data coming from Aquarius.
|
||||
|
||||
The app has [Apollo Client](https://www.apollographql.com/docs/react/) setup to query the respective subgraph based on network. In any component this client can be used like so:
|
||||
The app has [Urql Client](https://formidable.com/open-source/urql/docs/basics/react-preact/) setup to query the respective subgraph based on network. In any component this client can be used like so:
|
||||
|
||||
```tsx
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, useQuery } from 'urql'
|
||||
|
||||
const query = gql`
|
||||
query PoolLiquidity($id: ID!, $shareId: ID) {
|
||||
@ -266,10 +263,14 @@ Within components this metadata can be queried for under `allNetworksMetadataJso
|
||||
|
||||
```tsx
|
||||
export default function NetworkName(): ReactElement {
|
||||
const { networkDisplayName, isTestnet } = useWeb3()
|
||||
const { networkId, isTestnet } = useWeb3()
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const networkData = getNetworkDataById(networksList, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData, networkId)
|
||||
|
||||
return (
|
||||
<>
|
||||
{networkDisplayName} {isTestnet && `(Test)`}
|
||||
{networkName} {isTestnet && `(Test)`}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,20 @@
|
||||
module.exports = {
|
||||
// The default network and its associated config the app should connect to
|
||||
// on start. App will automatically switch network configs when user switches
|
||||
// networks in their wallet.
|
||||
// Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development'
|
||||
network: process.env.GATSBY_NETWORK || 'mainnet',
|
||||
// URI of single metadata cache instance for all networks.
|
||||
// While ocean.js includes this value for each network as part of its ConfigHelper,
|
||||
// it is assumed to be the same for all networks.
|
||||
// In components can be accessed with the useSiteMetadata hook:
|
||||
// const { appConfig } = useSiteMetadata()
|
||||
// return appConfig.metadataCacheUri
|
||||
metadataCacheUri:
|
||||
process.env.METADATACACHE_URI || 'https://aquarius.oceanprotocol.com',
|
||||
|
||||
// List of chainIds which metadata cache queries will return by default.
|
||||
// This preselects the Chains user preferences.
|
||||
chainIds: [1, 137, 56],
|
||||
|
||||
// List of all supported chainIds. Used to populate the Chains user preferences list.
|
||||
chainIdsSupported: [1, 3, 4, 137, 80001, 1287, 56],
|
||||
|
||||
rbacUrl: process.env.GATSBY_RBAC_URL,
|
||||
|
||||
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
|
||||
|
22
codegen.yml
Normal file
22
codegen.yml
Normal file
@ -0,0 +1,22 @@
|
||||
overwrite: true
|
||||
schema: 'https://subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph'
|
||||
documents:
|
||||
- './src/utils/subgraph.ts'
|
||||
- './src/components/pages/History/PoolShares.tsx'
|
||||
- './src/components/pages/History/Downloads.tsx'
|
||||
- './src/components/pages/History/ComputeJobs/index.tsx'
|
||||
- './src/ //Users/bogdanfazakas/Sites/ocean-market/src/components/organisms/AssetContent/EditHistory.tsx'
|
||||
# - './src/components/organisms/AssetActions/Pool/index.tsx'
|
||||
- './src/components/organisms/AssetActions/Pool/Graph.tsx'
|
||||
- './src/components/organisms/AssetActions/Consume.tsx'
|
||||
- './src/components/molecules/PoolTransactions.tsx'
|
||||
- './src/components/molecules/MarketStats.tsx'
|
||||
generates:
|
||||
./src/@types/graph.types.ts:
|
||||
plugins:
|
||||
- 'typescript'
|
||||
- 'typescript-operations'
|
||||
- 'typescript-react-apollo'
|
||||
./graphql.schema.json:
|
||||
plugins:
|
||||
- 'introspection'
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"title": "Publish",
|
||||
"description": "Highlight the important features of your data set or algorithm to make it more discoverable and catch the interest of data consumers.",
|
||||
"warning": "Given the beta status, publishing on Ropsten or Rinkeby first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms)."
|
||||
"warning": "Given the beta status, publishing on Ropsten or Rinkeby first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms).",
|
||||
"tooltipNetwork": "Assets are published into the network your wallet is connected to. Switch your wallet's network to publish into another one."
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ execSync(`node ./scripts/write-repo-metadata > repo-metadata.json`, {
|
||||
stdio: 'inherit'
|
||||
})
|
||||
|
||||
// Generate GraphQl typings for urql
|
||||
// execSync(`npm run graphql:graphTypes`, { stdio: 'inherit' })
|
||||
|
||||
// Generate Apollo typings
|
||||
execSync(`npm run apollo:codegen`, { stdio: 'inherit' })
|
||||
|
||||
|
2398
package-lock.json
generated
2398
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -9,6 +9,7 @@
|
||||
"build": "gatsby build && cp _redirects public/_redirects",
|
||||
"serve": "serve -s public/",
|
||||
"jest": "NODE_ENV=test jest -c tests/unit/jest.config.js",
|
||||
"test-graphql": "npm run graphql:graphTypes && npm run lint && npm run jest",
|
||||
"test": "npm run apollo:codegen && npm run lint && npm run jest",
|
||||
"test:watch": "npm run lint && npm run jest -- --watch",
|
||||
"lint": "npm run write:repoMetadata && eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx . && npm run type-check",
|
||||
@ -19,11 +20,11 @@
|
||||
"storybook:build": "build-storybook -c .storybook -o public/storybook",
|
||||
"write:repoMetadata": "node ./scripts/write-repo-metadata > repo-metadata.json",
|
||||
"deploy:s3": "./scripts/deploy-s3.sh",
|
||||
"postinstall": "husky install",
|
||||
"apollo:codegen": "apollo client:codegen --target typescript --tsFileExtension=d.ts --outputFlat src/@types/apollo/",
|
||||
"postinstall": "husky install"
|
||||
"graphql:graphTypes": "graphql-codegen --config codegen.yml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@coingecko/cryptoformat": "^0.4.2",
|
||||
"@loadable/component": "^5.15.0",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
@ -32,6 +33,7 @@
|
||||
"@portis/web3": "^4.0.4",
|
||||
"@sindresorhus/slugify": "^2.1.0",
|
||||
"@tippyjs/react": "^4.2.5",
|
||||
"@urql/introspection": "^0.3.0",
|
||||
"@walletconnect/web3-provider": "^1.5.0-rc.7",
|
||||
"axios": "^0.21.1",
|
||||
"chart.js": "^2.9.4",
|
||||
@ -60,6 +62,7 @@
|
||||
"gatsby-transformer-remark": "^2.16.1",
|
||||
"gatsby-transformer-sharp": "^2.12.1",
|
||||
"graphql": "14.7.0",
|
||||
"graphql-schema-typescript": "^1.5.2",
|
||||
"is-url-superb": "^6.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -81,12 +84,18 @@
|
||||
"shortid": "^2.2.16",
|
||||
"slugify": "^1.5.3",
|
||||
"swr": "^0.5.6",
|
||||
"urql": "^2.0.3",
|
||||
"use-dark-mode": "^2.3.1",
|
||||
"web3": "^1.4.0",
|
||||
"web3modal": "^1.9.3",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "1.21.6",
|
||||
"@graphql-codegen/introspection": "1.18.2",
|
||||
"@graphql-codegen/typescript": "1.22.4",
|
||||
"@graphql-codegen/typescript-operations": "^1.18.3",
|
||||
"@graphql-codegen/typescript-react-apollo": "2.3.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
|
1
src/@types/ComputeJobMetaData.d.ts
vendored
1
src/@types/ComputeJobMetaData.d.ts
vendored
@ -3,4 +3,5 @@ import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Comput
|
||||
export interface ComputeJobMetaData extends ComputeJob {
|
||||
assetName: string
|
||||
assetDtSymbol: string
|
||||
networkId: number
|
||||
}
|
||||
|
@ -7,10 +7,9 @@ import Styles from '../global/Styles'
|
||||
import { useWeb3 } from '../providers/Web3'
|
||||
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||
import { useAccountPurgatory } from '../hooks/useAccountPurgatory'
|
||||
import NetworkBanner from './molecules/NetworkBanner'
|
||||
import styles from './App.module.css'
|
||||
import AnnouncementBanner from './atoms/AnnouncementBanner'
|
||||
import { useGraphSyncStatus } from '../hooks/useGraphSyncStatus'
|
||||
import styles from './App.module.css'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query AppQuery {
|
||||
@ -41,24 +40,15 @@ export default function App({
|
||||
const { warning } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||
const { isGraphSynced, blockHead, blockGraph } = useGraphSyncStatus()
|
||||
// const { isGraphSynced, blockHead, blockGraph } = useGraphSyncStatus()
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
<div className={styles.app}>
|
||||
{!isGraphSynced && (
|
||||
<AnnouncementBanner
|
||||
text={`The data for this network has only synced to Ethereum block ${blockGraph} (out of ${blockHead}). Please check back soon.`}
|
||||
state="error"
|
||||
/>
|
||||
)}
|
||||
{!location.pathname.includes('/asset/did') && <NetworkBanner />}
|
||||
|
||||
<Header />
|
||||
|
||||
{(props as PageProps).uri === '/' && (
|
||||
<Alert text={warning.main} state="info" />
|
||||
<AnnouncementBanner text={warning.main} />
|
||||
)}
|
||||
<Header />
|
||||
|
||||
{isInPurgatory && (
|
||||
<Alert
|
||||
|
@ -10,7 +10,7 @@
|
||||
.typeLabel {
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding-right: calc(var(--spacer) / 3.5);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
border-left: 1px solid var(--border-color);
|
||||
padding-left: calc(var(--spacer) / 3.5);
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
@ -21,9 +21,6 @@ export default function AssetType({
|
||||
})
|
||||
return (
|
||||
<div className={styleClasses}>
|
||||
<div className={styles.typeLabel}>
|
||||
{type === 'dataset' ? 'data set' : 'algorithm'}
|
||||
</div>
|
||||
{accessType === 'access' ? (
|
||||
<Download role="img" aria-label="Download" className={styles.icon} />
|
||||
) : accessType === 'compute' && type === 'algorithm' ? (
|
||||
@ -31,6 +28,10 @@ export default function AssetType({
|
||||
) : (
|
||||
<Compute role="img" aria-label="Compute" className={styles.icon} />
|
||||
)}
|
||||
|
||||
<div className={styles.typeLabel}>
|
||||
{type === 'dataset' ? 'data set' : 'algorithm'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 6px 17px 0 var(--box-shadow-color);
|
||||
overflow: hidden;
|
||||
padding: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,7 @@
|
||||
color: var(--brand-pink);
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
/* Size Modifiers */
|
||||
|
@ -4,10 +4,12 @@ import classNames from 'classnames/bind'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import styles from './ExplorerLink.module.css'
|
||||
import { getOceanConfig } from '../../utils/ocean'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function ExplorerLink({
|
||||
networkId,
|
||||
path,
|
||||
children,
|
||||
className
|
||||
@ -17,22 +19,29 @@ export default function ExplorerLink({
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { config, ocean } = useOcean()
|
||||
const [url, setUrl] = useState<string>()
|
||||
|
||||
const [oceanConfig, setOceanConfig] = useState<ConfigHelperConfig>()
|
||||
const styleClasses = cx({
|
||||
link: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setUrl((config as ConfigHelperConfig).explorerUri)
|
||||
}, [config])
|
||||
async function initOcean() {
|
||||
const oceanInitialConfig = getOceanConfig(networkId)
|
||||
setOceanConfig(oceanInitialConfig)
|
||||
setUrl(oceanInitialConfig?.explorerUri)
|
||||
}
|
||||
if (oceanConfig === undefined) {
|
||||
initOcean()
|
||||
}
|
||||
}, [config, networkId, ocean])
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`${url}/${path}`}
|
||||
title={`View on ${(config as ConfigHelperConfig).explorerUri}`}
|
||||
title={`View on ${oceanConfig?.explorerUri}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styleClasses}
|
||||
|
@ -27,6 +27,13 @@ export interface InputProps {
|
||||
| ChangeEvent<HTMLSelectElement>
|
||||
| ChangeEvent<HTMLTextAreaElement>
|
||||
): void
|
||||
onKeyPress?(
|
||||
e:
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLSelectElement>
|
||||
| React.KeyboardEvent<HTMLTextAreaElement>
|
||||
): void
|
||||
rows?: number
|
||||
multiple?: boolean
|
||||
pattern?: string
|
||||
|
@ -1,6 +1,6 @@
|
||||
.logo {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
28
src/components/atoms/NetworkName.module.css
Normal file
28
src/components/atoms/NetworkName.module.css
Normal file
@ -0,0 +1,28 @@
|
||||
.network {
|
||||
text-transform: capitalize;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
fill: currentColor;
|
||||
margin-right: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.minimal {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.minimal .name {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.minimal:hover .name {
|
||||
width: auto;
|
||||
opacity: 1;
|
||||
}
|
48
src/components/atoms/NetworkName.tsx
Normal file
48
src/components/atoms/NetworkName.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ReactComponent as EthIcon } from '../../images/eth.svg'
|
||||
import { ReactComponent as PolygonIcon } from '../../images/polygon.svg'
|
||||
import { ReactComponent as MoonbeamIcon } from '../../images/moonbeam.svg'
|
||||
import { ReactComponent as BscIcon } from '../../images/bsc.svg'
|
||||
import { getNetworkDataById, getNetworkDisplayName } from '../../utils/web3'
|
||||
import styles from './NetworkName.module.css'
|
||||
import useNetworkMetadata from '../../hooks/useNetworkMetadata'
|
||||
|
||||
export function NetworkIcon({ name }: { name: string }): ReactElement {
|
||||
const IconMapped = name.includes('ETH')
|
||||
? EthIcon
|
||||
: name.includes('Polygon')
|
||||
? PolygonIcon
|
||||
: name.includes('Moon')
|
||||
? MoonbeamIcon
|
||||
: name.includes('BSC')
|
||||
? BscIcon
|
||||
: EthIcon // ETH icon as fallback
|
||||
|
||||
return IconMapped ? <IconMapped className={styles.icon} /> : null
|
||||
}
|
||||
|
||||
export default function NetworkName({
|
||||
networkId,
|
||||
minimal,
|
||||
className
|
||||
}: {
|
||||
networkId: number
|
||||
minimal?: boolean
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const networkData = getNetworkDataById(networksList, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData, networkId)
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`${styles.network} ${minimal ? styles.minimal : null} ${
|
||||
className || ''
|
||||
}`}
|
||||
title={networkName}
|
||||
>
|
||||
<NetworkIcon name={networkName} />{' '}
|
||||
<span className={styles.name}>{networkName}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
@ -6,6 +6,9 @@
|
||||
font-weight: var(--font-weight-base);
|
||||
}
|
||||
|
||||
.removeTvlPadding {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
/* fiat currency symbol */
|
||||
.conversion strong span {
|
||||
font-weight: var(--font-weight-base);
|
||||
|
@ -10,11 +10,13 @@ const cx = classNames.bind(styles)
|
||||
export default function Conversion({
|
||||
price,
|
||||
className,
|
||||
hideApproximateSymbol
|
||||
hideApproximateSymbol,
|
||||
showTVLLabel
|
||||
}: {
|
||||
price: string // expects price in OCEAN, not wei
|
||||
className?: string
|
||||
hideApproximateSymbol?: boolean
|
||||
showTVLLabel?: boolean
|
||||
}): ReactElement {
|
||||
const { prices } = usePrices()
|
||||
const { currency, locale } = useUserPreferences()
|
||||
@ -27,6 +29,7 @@ export default function Conversion({
|
||||
|
||||
const styleClasses = cx({
|
||||
conversion: true,
|
||||
removeTvlPadding: showTVLLabel,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
@ -61,6 +64,7 @@ export default function Conversion({
|
||||
className={styleClasses}
|
||||
title="Approximation based on current OCEAN spot price on Coingecko"
|
||||
>
|
||||
{showTVLLabel && 'TVL'}
|
||||
{!hideApproximateSymbol && '≈ '}
|
||||
<strong dangerouslySetInnerHTML={{ __html: priceConverted }} />{' '}
|
||||
{!isFiat && currency}
|
||||
|
@ -10,11 +10,11 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: help;
|
||||
display: inline-block;
|
||||
margin-bottom: -0.1rem;
|
||||
margin-bottom: -0.1em;
|
||||
margin-left: calc(var(--spacer) / 6);
|
||||
fill: var(--color-secondary);
|
||||
}
|
||||
|
@ -28,9 +28,7 @@ export default function Tooltip({
|
||||
trigger,
|
||||
disabled,
|
||||
className,
|
||||
placement,
|
||||
link,
|
||||
reference
|
||||
placement
|
||||
}: {
|
||||
content: ReactNode
|
||||
children?: ReactNode
|
||||
@ -38,8 +36,6 @@ export default function Tooltip({
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
placement?: Placement
|
||||
link?: string
|
||||
reference?: string
|
||||
}): ReactElement {
|
||||
const [props, setSpring] = useSpring(() => animation.from)
|
||||
|
||||
@ -76,7 +72,6 @@ export default function Tooltip({
|
||||
<animated.div style={props}>
|
||||
<div className={styles.content} {...attrs}>
|
||||
{content}
|
||||
{link && <a href={link}>{reference}</a>}
|
||||
<div className={styles.arrow} data-popper-arrow />
|
||||
</div>
|
||||
</animated.div>
|
||||
|
@ -5,6 +5,7 @@ import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { getAssetsNames } from '../../utils/aquarius'
|
||||
import styles from './AssetListTitle.module.css'
|
||||
import axios from 'axios'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
|
||||
export default function AssetListTitle({
|
||||
ddo,
|
||||
@ -15,11 +16,11 @@ export default function AssetListTitle({
|
||||
did?: string
|
||||
title?: string
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [assetTitle, setAssetTitle] = useState<string>(title)
|
||||
|
||||
useEffect(() => {
|
||||
if (title || !config?.metadataCacheUri) return
|
||||
if (title || !appConfig.metadataCacheUri) return
|
||||
if (ddo) {
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
setAssetTitle(attributes.main.name)
|
||||
@ -29,11 +30,7 @@ export default function AssetListTitle({
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function getAssetName() {
|
||||
const title = await getAssetsNames(
|
||||
[did],
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const title = await getAssetsNames([did], source.token)
|
||||
setAssetTitle(title[did])
|
||||
}
|
||||
|
||||
@ -42,7 +39,7 @@ export default function AssetListTitle({
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [assetTitle, config?.metadataCacheUri, ddo, did, title])
|
||||
}, [assetTitle, appConfig.metadataCacheUri, ddo, did, title])
|
||||
|
||||
return (
|
||||
<h3 className={styles.title}>
|
||||
|
@ -57,11 +57,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: var(--font-size-mini);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.typeDetails {
|
||||
position: absolute;
|
||||
top: calc(var(--spacer) / 3);
|
||||
@ -69,3 +64,10 @@
|
||||
width: auto;
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.network {
|
||||
font-size: var(--font-size-mini);
|
||||
position: absolute;
|
||||
right: calc(var(--spacer) / 3);
|
||||
bottom: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ import React from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Price from '../atoms/Price'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
import { DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import Publisher from '../atoms/Publisher'
|
||||
import Time from '../atoms/Time'
|
||||
import AssetType from '../atoms/AssetType'
|
||||
import NetworkName from '../atoms/NetworkName'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: DDO
|
||||
@ -52,9 +53,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
||||
|
||||
<footer className={styles.foot}>
|
||||
<Price price={price} small />
|
||||
<p className={styles.date}>
|
||||
<Time date={ddo?.created} relative />
|
||||
</p>
|
||||
<NetworkName networkId={ddo.chainId} className={styles.network} />
|
||||
</footer>
|
||||
</Link>
|
||||
</article>
|
||||
|
@ -1,17 +1,21 @@
|
||||
import { useUserPreferences } from '../../providers/UserPreferences'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Table from '../atoms/Table'
|
||||
import { DDO, Logger, ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import { DDO, Logger, BestPrice } from '@oceanprotocol/lib'
|
||||
import Price from '../atoms/Price'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import AssetTitle from './AssetListTitle'
|
||||
import { queryMetadata } from '../../utils/aquarius'
|
||||
import {
|
||||
queryMetadata,
|
||||
transformChainIdsListToQuery
|
||||
} from '../../utils/aquarius'
|
||||
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
||||
import axios, { CancelToken } from 'axios'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
|
||||
async function getAssetsBookmarked(
|
||||
bookmarks: string[],
|
||||
metadataCacheUri: string,
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
) {
|
||||
const searchDids = JSON.stringify(bookmarks)
|
||||
@ -26,7 +30,9 @@ async function getAssetsBookmarked(
|
||||
offset: 100,
|
||||
query: {
|
||||
query_string: {
|
||||
query: searchDids,
|
||||
query: `(${searchDids}) AND (${transformChainIdsListToQuery(
|
||||
chainIds
|
||||
)})`,
|
||||
fields: ['dataToken'],
|
||||
default_operator: 'OR'
|
||||
}
|
||||
@ -35,11 +41,7 @@ async function getAssetsBookmarked(
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await queryMetadata(
|
||||
queryBookmarks,
|
||||
metadataCacheUri,
|
||||
cancelToken
|
||||
)
|
||||
const result = await queryMetadata(queryBookmarks, cancelToken)
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
@ -50,19 +52,19 @@ async function getAssetsBookmarked(
|
||||
const columns = [
|
||||
{
|
||||
name: 'Data Set',
|
||||
selector: function getAssetRow(row: DDO) {
|
||||
const { attributes } = row.findServiceByType('metadata')
|
||||
return <AssetTitle title={attributes.main.name} ddo={row} />
|
||||
selector: function getAssetRow(row: AssetListPrices) {
|
||||
const { attributes } = row.ddo.findServiceByType('metadata')
|
||||
return <AssetTitle title={attributes.main.name} ddo={row.ddo} />
|
||||
},
|
||||
maxWidth: '45rem',
|
||||
grow: 1
|
||||
},
|
||||
{
|
||||
name: 'Datatoken Symbol',
|
||||
selector: function getAssetRow(row: DDO) {
|
||||
selector: function getAssetRow(row: AssetListPrices) {
|
||||
return (
|
||||
<Tooltip content={row.dataTokenInfo.name}>
|
||||
{row.dataTokenInfo.symbol}
|
||||
<Tooltip content={row.ddo.dataTokenInfo.name}>
|
||||
{row.ddo.dataTokenInfo.symbol}
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
@ -70,7 +72,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'Price',
|
||||
selector: function getAssetRow(row: DDO) {
|
||||
selector: function getAssetRow(row: AssetListPrices) {
|
||||
return <Price price={row.price} small />
|
||||
},
|
||||
right: true
|
||||
@ -78,21 +80,20 @@ const columns = [
|
||||
]
|
||||
|
||||
export default function Bookmarks(): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { bookmarks } = useUserPreferences()
|
||||
|
||||
const [pinned, setPinned] = useState<DDO[]>()
|
||||
const [pinned, setPinned] = useState<AssetListPrices[]>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
|
||||
const networkName = (config as ConfigHelperConfig)?.network
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.metadataCacheUri || !networkName || bookmarks === {}) return
|
||||
if (!appConfig.metadataCacheUri || bookmarks === []) return
|
||||
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function init() {
|
||||
if (!bookmarks[networkName]?.length) {
|
||||
if (!bookmarks?.length) {
|
||||
setPinned([])
|
||||
return
|
||||
}
|
||||
@ -101,11 +102,14 @@ export default function Bookmarks(): ReactElement {
|
||||
|
||||
try {
|
||||
const resultPinned = await getAssetsBookmarked(
|
||||
bookmarks[networkName],
|
||||
config.metadataCacheUri,
|
||||
bookmarks,
|
||||
chainIds,
|
||||
source.token
|
||||
)
|
||||
setPinned(resultPinned?.results)
|
||||
const pinnedAssets: AssetListPrices[] = await getAssetsBestPrices(
|
||||
resultPinned?.results
|
||||
)
|
||||
setPinned(pinnedAssets)
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
}
|
||||
@ -117,7 +121,7 @@ export default function Bookmarks(): ReactElement {
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [bookmarks, config.metadataCacheUri, networkName])
|
||||
}, [bookmarks, chainIds])
|
||||
|
||||
return (
|
||||
<Table
|
||||
|
@ -4,25 +4,27 @@ import { useField } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import FileInfo from './Info'
|
||||
import FileInput from './Input'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { InputProps } from '../../../atoms/Input'
|
||||
import { fileinfo } from '../../../../utils/provider'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { getOceanConfig } from '../../../../utils/ocean'
|
||||
|
||||
export default function FilesInput(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [fileUrl, setFileUrl] = useState<string>()
|
||||
const { config } = useOcean()
|
||||
const { chainId } = useWeb3()
|
||||
|
||||
function loadFileInfo() {
|
||||
const source = axios.CancelToken.source()
|
||||
const config = getOceanConfig(chainId || 1)
|
||||
|
||||
async function validateUrl() {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const checkedFile = await fileinfo(
|
||||
fileUrl,
|
||||
config.providerUri,
|
||||
config?.providerUri,
|
||||
source.token
|
||||
)
|
||||
checkedFile && helpers.setValue([checkedFile])
|
||||
@ -43,7 +45,7 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
loadFileInfo()
|
||||
}, [fileUrl, config.providerUri])
|
||||
}, [fileUrl])
|
||||
|
||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||
// hack so the onBlur-triggered validation does not show,
|
||||
|
@ -1,18 +1,42 @@
|
||||
.stats {
|
||||
margin-bottom: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
||||
/* specificity sledgehammer override without !important */
|
||||
.stats,
|
||||
.stats *,
|
||||
.statsList * {
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 2;
|
||||
color: var(--color-secondary);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.stats > div > div {
|
||||
display: inline-block;
|
||||
.tooltipStats {
|
||||
margin-bottom: calc(var(--spacer) / 3);
|
||||
padding-bottom: calc(var(--spacer) / 3);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.total {
|
||||
color: var(--color-secondary) !important;
|
||||
font-size: var(--font-size-small) !important;
|
||||
.network {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 0.85rem;
|
||||
}
|
||||
|
||||
.statsList,
|
||||
.note {
|
||||
padding: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.statsList {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import styles from './MarketStats.module.css'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, OperationContext } from 'urql'
|
||||
import Conversion from '../atoms/Price/Conversion'
|
||||
import PriceUnit from '../atoms/Price/PriceUnit'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import NetworkName from '../atoms/NetworkName'
|
||||
import { fetchData, getSubgrahUri } from '../../utils/subgraph'
|
||||
import { filterNetworksByType } from './UserPreferences/Networks/index'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import useNetworkMetadata from '../../hooks/useNetworkMetadata'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import styles from './MarketStats.module.css'
|
||||
|
||||
const getTotalPoolsValues = gql`
|
||||
query PoolsData {
|
||||
@ -15,32 +21,160 @@ const getTotalPoolsValues = gql`
|
||||
}
|
||||
`
|
||||
|
||||
interface Value {
|
||||
[chainId: number]: string
|
||||
}
|
||||
|
||||
function MarketNetworkStats({
|
||||
totalValueLocked,
|
||||
poolCount,
|
||||
totalOceanLiquidity
|
||||
}: {
|
||||
totalValueLocked: string
|
||||
poolCount: string
|
||||
totalOceanLiquidity: string
|
||||
}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Conversion price={totalValueLocked} hideApproximateSymbol />{' '}
|
||||
<abbr title="Total Value Locked">TVL</abbr> across{' '}
|
||||
<strong>{poolCount}</strong> asset pools that contain{' '}
|
||||
<PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
|
||||
plus datatokens for each pool.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function MarketNetworkStatsTooltip({
|
||||
totalValueLocked,
|
||||
poolCount,
|
||||
totalOceanLiquidity,
|
||||
mainChainIds
|
||||
}: {
|
||||
totalValueLocked: Value
|
||||
poolCount: Value
|
||||
totalOceanLiquidity: Value
|
||||
mainChainIds: number[]
|
||||
}): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<ul className={styles.statsList}>
|
||||
{totalValueLocked &&
|
||||
totalOceanLiquidity &&
|
||||
poolCount &&
|
||||
mainChainIds?.map((chainId, key) => (
|
||||
<li className={styles.tooltipStats} key={key}>
|
||||
<NetworkName networkId={chainId} className={styles.network} />
|
||||
<br />
|
||||
<Conversion
|
||||
price={totalValueLocked[chainId] || '0'}
|
||||
hideApproximateSymbol
|
||||
/>{' '}
|
||||
<abbr title="Total Value Locked">TVL</abbr>
|
||||
{' | '}
|
||||
<strong>{poolCount[chainId] || '0'}</strong> pools
|
||||
{' | '}
|
||||
<PriceUnit price={totalOceanLiquidity[chainId] || '0'} small />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className={styles.note}>
|
||||
Counted on-chain from our pool factory. Does not filter out assets in{' '}
|
||||
<a href="https://github.com/oceanprotocol/list-purgatory">
|
||||
list-purgatory
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MarketStats(): ReactElement {
|
||||
const [totalValueLocked, setTotalValueLocked] = useState<string>()
|
||||
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<string>()
|
||||
const [poolCount, setPoolCount] = useState<number>()
|
||||
const { data } = useQuery(getTotalPoolsValues, { pollInterval: 20000 })
|
||||
const [totalValueLocked, setTotalValueLocked] = useState<Value>()
|
||||
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<Value>()
|
||||
const [poolCount, setPoolCount] = useState<Value>()
|
||||
const [totalValueLockedSum, setTotalValueLockedSum] = useState<string>()
|
||||
const [totalOceanLiquiditySum, setTotalOceanLiquiditySum] = useState<string>()
|
||||
const [poolCountSum, setPoolCountSum] = useState<string>()
|
||||
const [mainChainIds, setMainChainIds] = useState<number[]>()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { networksList } = useNetworkMetadata()
|
||||
|
||||
async function getMarketStats() {
|
||||
const mainChainIdsList = await filterNetworksByType(
|
||||
'mainnet',
|
||||
appConfig.chainIdsSupported,
|
||||
networksList
|
||||
)
|
||||
setMainChainIds(mainChainIdsList)
|
||||
|
||||
let newTotalValueLockedSum = 0
|
||||
let newTotalOceanLiquiditySum = 0
|
||||
let newPoolCountSum = 0
|
||||
|
||||
for (const chainId of mainChainIdsList) {
|
||||
const context: OperationContext = {
|
||||
url: `${getSubgrahUri(
|
||||
chainId
|
||||
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
||||
requestPolicy: 'network-only'
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchData(getTotalPoolsValues, null, context)
|
||||
if (!response) continue
|
||||
|
||||
const { totalValueLocked, totalOceanLiquidity, finalizedPoolCount } =
|
||||
response?.data?.poolFactories[0]
|
||||
|
||||
await setTotalValueLocked((prevState) => ({
|
||||
...prevState,
|
||||
[chainId]: totalValueLocked
|
||||
}))
|
||||
await setTotalOceanLiquidity((prevState) => ({
|
||||
...prevState,
|
||||
[chainId]: totalOceanLiquidity
|
||||
}))
|
||||
await setPoolCount((prevState) => ({
|
||||
...prevState,
|
||||
[chainId]: finalizedPoolCount
|
||||
}))
|
||||
|
||||
newTotalValueLockedSum += parseInt(totalValueLocked)
|
||||
newTotalOceanLiquiditySum += parseInt(totalOceanLiquidity)
|
||||
newPoolCountSum += parseInt(finalizedPoolCount)
|
||||
} catch (error) {
|
||||
Logger.error('Error fetchData: ', error.message)
|
||||
}
|
||||
}
|
||||
setTotalValueLockedSum(`${newTotalValueLockedSum}`)
|
||||
setTotalOceanLiquiditySum(`${newTotalOceanLiquiditySum}`)
|
||||
setPoolCountSum(`${newPoolCountSum}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !data.poolFactories || data.poolFactories.length === 0) return
|
||||
setTotalValueLocked(data.poolFactories[0].totalValueLocked)
|
||||
setTotalOceanLiquidity(data.poolFactories[0].totalOceanLiquidity)
|
||||
setPoolCount(data.poolFactories[0].finalizedPoolCount)
|
||||
}, [data])
|
||||
getMarketStats()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.stats}>
|
||||
<Conversion price={`${totalValueLocked}`} hideApproximateSymbol />{' '}
|
||||
<abbr title="Total Value Locked">TVL</abbr> across{' '}
|
||||
<strong>{poolCount}</strong> data set pools that contain{' '}
|
||||
<PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
|
||||
plus datatokens for each pool.
|
||||
<Tooltip
|
||||
className={styles.info}
|
||||
content="Counted on-chain from our pool factory. Does not filter out data sets in "
|
||||
reference="list-purgatory"
|
||||
link="https://github.com/oceanprotocol/list-purgatory"
|
||||
/>
|
||||
<>
|
||||
<MarketNetworkStats
|
||||
totalValueLocked={totalValueLockedSum || '0'}
|
||||
totalOceanLiquidity={totalOceanLiquiditySum || '0'}
|
||||
poolCount={poolCountSum || '0'}
|
||||
/>{' '}
|
||||
<Tooltip
|
||||
className={styles.info}
|
||||
content={
|
||||
<MarketNetworkStatsTooltip
|
||||
totalValueLocked={totalValueLocked}
|
||||
poolCount={poolCount}
|
||||
totalOceanLiquidity={totalOceanLiquidity}
|
||||
mainChainIds={mainChainIds}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,32 +1,66 @@
|
||||
.menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu > div {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: calc(var(--spacer) / 2);
|
||||
padding-bottom: calc(var(--spacer) / 2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.logoUnit {
|
||||
.logo {
|
||||
order: 1;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logoUnit svg {
|
||||
margin-left: -0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
.navigation {
|
||||
order: 3;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
width: calc(100% + 2rem);
|
||||
}
|
||||
|
||||
.actions {
|
||||
order: 2;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 40rem) {
|
||||
@media screen and (min-width: 42rem) {
|
||||
.menu {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
order: 2;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.actions {
|
||||
order: 3;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 55rem) {
|
||||
.menu {
|
||||
padding: var(--spacer);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
margin-right: var(--spacer);
|
||||
display: block;
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-h4);
|
||||
@ -38,6 +72,7 @@
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.navigation::-webkit-scrollbar,
|
||||
@ -48,19 +83,13 @@
|
||||
.navigation li {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: var(--spacer);
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60rem) {
|
||||
.navigation li {
|
||||
margin-left: calc(var(--spacer) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation button,
|
||||
.link {
|
||||
display: block;
|
||||
padding: calc(var(--spacer) / 2);
|
||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
@ -69,16 +98,11 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.navigation button {
|
||||
text-transform: none;
|
||||
padding-top: calc(var(--spacer) / 4);
|
||||
padding-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.actions,
|
||||
.link:hover,
|
||||
.link:focus,
|
||||
.link:active {
|
||||
color: var(--brand-grey);
|
||||
color: var(--font-color-text);
|
||||
}
|
||||
|
||||
.link[aria-current],
|
||||
@ -90,3 +114,11 @@
|
||||
.link:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.actions button {
|
||||
text-transform: none;
|
||||
}
|
||||
|
@ -4,10 +4,11 @@ import { useLocation } from '@reach/router'
|
||||
import loadable from '@loadable/component'
|
||||
import styles from './Menu.module.css'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import Container from '../atoms/Container'
|
||||
import UserPreferences from './UserPreferences'
|
||||
import Badge from '../atoms/Badge'
|
||||
import Logo from '../atoms/Logo'
|
||||
import Networks from './UserPreferences/Networks'
|
||||
import SearchBar from './SearchBar'
|
||||
|
||||
const Wallet = loadable(() => import('./Wallet'))
|
||||
|
||||
@ -36,28 +37,27 @@ export default function Menu(): ReactElement {
|
||||
|
||||
return (
|
||||
<nav className={styles.menu}>
|
||||
<Container>
|
||||
<Link to="/" className={styles.logoUnit}>
|
||||
<Logo />
|
||||
<h1 className={styles.title}>
|
||||
{siteTitle} <Badge label="beta" />
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to="/" className={styles.logo}>
|
||||
<Logo noWordmark />
|
||||
<h1 className={styles.title}>
|
||||
{siteTitle} <Badge label="v3" />
|
||||
</h1>
|
||||
</Link>
|
||||
|
||||
<ul className={styles.navigation}>
|
||||
{menu.map((item: MenuItem) => (
|
||||
<li key={item.name}>
|
||||
<MenuLink item={item} />
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<Wallet />
|
||||
<ul className={styles.navigation}>
|
||||
{menu.map((item: MenuItem) => (
|
||||
<li key={item.name}>
|
||||
<MenuLink item={item} />
|
||||
</li>
|
||||
<li>
|
||||
<UserPreferences />
|
||||
</li>
|
||||
</ul>
|
||||
</Container>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<SearchBar />
|
||||
<Networks />
|
||||
<Wallet />
|
||||
<UserPreferences />
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
@ -33,7 +33,6 @@
|
||||
}
|
||||
|
||||
.datatoken {
|
||||
margin-top: calc(var(--spacer) / 8);
|
||||
margin-bottom: 0;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interface
|
||||
import Markdown from '../atoms/Markdown'
|
||||
import Tags from '../atoms/Tags'
|
||||
import MetaItem from '../organisms/AssetContent/MetaItem'
|
||||
import styles from './MetadataPreview.module.css'
|
||||
import File from '../atoms/File'
|
||||
import {
|
||||
MetadataPublishFormDataset,
|
||||
@ -11,6 +10,11 @@ import {
|
||||
} from '../../@types/MetaData'
|
||||
import Button from '../atoms/Button'
|
||||
import { transformTags } from '../../utils/metadata'
|
||||
import NetworkName from '../atoms/NetworkName'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import styles from './MetadataPreview.module.css'
|
||||
import Web3Feedback from './Web3Feedback'
|
||||
import { useAsset } from '../../providers/Asset'
|
||||
|
||||
function Description({ description }: { description: string }) {
|
||||
const [fullDescription, setFullDescription] = useState<boolean>(false)
|
||||
@ -92,10 +96,14 @@ export function MetadataPreview({
|
||||
}: {
|
||||
values: Partial<MetadataPublishFormDataset>
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
|
||||
return (
|
||||
<div className={styles.preview}>
|
||||
<h2 className={styles.previewTitle}>Preview</h2>
|
||||
<header>
|
||||
{networkId && <NetworkName networkId={networkId} />}
|
||||
{values.name && <h3 className={styles.title}>{values.name}</h3>}
|
||||
{values.dataTokenOptions?.name && (
|
||||
<p
|
||||
@ -121,6 +129,9 @@ export function MetadataPreview({
|
||||
</header>
|
||||
|
||||
<MetaFull values={values} />
|
||||
{isAssetNetwork === false && (
|
||||
<Web3Feedback isAssetNetwork={isAssetNetwork} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -130,10 +141,13 @@ export function MetadataAlgorithmPreview({
|
||||
}: {
|
||||
values: Partial<MetadataPublishFormAlgorithm>
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
|
||||
return (
|
||||
<div className={styles.preview}>
|
||||
<h2 className={styles.previewTitle}>Preview</h2>
|
||||
<header>
|
||||
{networkId && <NetworkName networkId={networkId} />}
|
||||
{values.name && <h3 className={styles.title}>{values.name}</h3>}
|
||||
{values.dataTokenOptions?.name && (
|
||||
<p
|
||||
|
@ -1,84 +0,0 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import { addCustomNetwork, NetworkObject } from '../../utils/web3'
|
||||
import { getOceanConfig } from '../../utils/ocean'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import AnnouncementBanner, {
|
||||
AnnouncementAction
|
||||
} from '../atoms/AnnouncementBanner'
|
||||
|
||||
const networkMatic: NetworkObject = {
|
||||
chainId: 137,
|
||||
name: 'Matic Network',
|
||||
urlList: [
|
||||
'https://rpc-mainnet.matic.network',
|
||||
'https://rpc-mainnet.maticvigil.com/'
|
||||
]
|
||||
}
|
||||
|
||||
export default function NetworkBanner(): ReactElement {
|
||||
const { web3Provider, web3ProviderInfo } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
const { announcement } = useSiteMetadata()
|
||||
|
||||
const [text, setText] = useState<string>(announcement.main)
|
||||
const [action, setAction] = useState<AnnouncementAction>()
|
||||
|
||||
const addCustomNetworkAction = {
|
||||
name: 'Add custom network',
|
||||
handleAction: () => addCustomNetwork(web3Provider, networkMatic)
|
||||
}
|
||||
const switchToPolygonAction = {
|
||||
name: 'Switch to Polygon',
|
||||
handleAction: async () => {
|
||||
const config = getOceanConfig('polygon')
|
||||
await connect(config)
|
||||
}
|
||||
}
|
||||
const switchToEthAction = {
|
||||
name: 'Switch to ETH',
|
||||
handleAction: async () => {
|
||||
const config = getOceanConfig('mainnet')
|
||||
await connect(config)
|
||||
}
|
||||
}
|
||||
|
||||
function setBannerForMatic() {
|
||||
setText(announcement.polygon)
|
||||
setAction(undefined)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!web3ProviderInfo || (!web3Provider && !config)) return
|
||||
|
||||
switch (web3ProviderInfo.name) {
|
||||
case 'Web3':
|
||||
if (config.networkId !== 137) {
|
||||
setText(announcement.main)
|
||||
setAction(switchToPolygonAction)
|
||||
} else {
|
||||
setText(announcement.polygon)
|
||||
setAction(switchToEthAction)
|
||||
}
|
||||
break
|
||||
case 'MetaMask':
|
||||
if (config.networkId === 137) {
|
||||
setBannerForMatic()
|
||||
} else {
|
||||
setText(announcement.main)
|
||||
setAction(addCustomNetworkAction)
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (config.networkId === 137) {
|
||||
setBannerForMatic()
|
||||
} else {
|
||||
setText(announcement.main)
|
||||
setAction(undefined)
|
||||
}
|
||||
}
|
||||
}, [web3Provider, web3ProviderInfo, config, announcement])
|
||||
|
||||
return <AnnouncementBanner text={text} action={action} />
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import ExplorerLink from '../atoms/ExplorerLink'
|
||||
import Time from '../atoms/Time'
|
||||
import Table from '../atoms/Table'
|
||||
import AssetTitle from './AssetListTitle'
|
||||
import styles from './PoolTransactions.module.css'
|
||||
import { useUserPreferences } from '../../providers/UserPreferences'
|
||||
import { Ocean } from '@oceanprotocol/lib'
|
||||
import { formatPrice } from '../atoms/Price/PriceUnit'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import {
|
||||
TransactionHistory,
|
||||
TransactionHistory_poolTransactions as TransactionHistoryPoolTransactions
|
||||
} from '../../@types/apollo/TransactionHistory'
|
||||
|
||||
import web3 from 'web3'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
|
||||
const txHistoryQueryByPool = gql`
|
||||
query TransactionHistoryByPool($user: String, $pool: String) {
|
||||
poolTransactions(
|
||||
orderBy: timestamp
|
||||
orderDirection: desc
|
||||
where: { userAddress: $user, poolAddress: $pool }
|
||||
first: 1000
|
||||
) {
|
||||
tx
|
||||
event
|
||||
timestamp
|
||||
poolAddress {
|
||||
datatokenAddress
|
||||
}
|
||||
tokens {
|
||||
value
|
||||
type
|
||||
tokenAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const txHistoryQuery = gql`
|
||||
query TransactionHistory($user: String) {
|
||||
poolTransactions(
|
||||
orderBy: timestamp
|
||||
orderDirection: desc
|
||||
where: { userAddress: $user }
|
||||
first: 1000
|
||||
) {
|
||||
tx
|
||||
event
|
||||
timestamp
|
||||
poolAddress {
|
||||
datatokenAddress
|
||||
}
|
||||
tokens {
|
||||
value
|
||||
type
|
||||
tokenAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
async function getSymbol(ocean: Ocean, tokenAddress: string) {
|
||||
const symbol =
|
||||
ocean.pool.oceanAddress.toLowerCase() === tokenAddress.toLowerCase()
|
||||
? 'OCEAN'
|
||||
: await ocean.datatokens.getSymbol(tokenAddress)
|
||||
|
||||
return symbol
|
||||
}
|
||||
|
||||
async function getTitle(
|
||||
ocean: Ocean,
|
||||
row: TransactionHistoryPoolTransactions,
|
||||
locale: string
|
||||
) {
|
||||
let title = ''
|
||||
|
||||
switch (row.event) {
|
||||
case 'swap': {
|
||||
const inToken = row.tokens.filter((x) => x.type === 'in')[0]
|
||||
const inTokenSymbol = await getSymbol(ocean, inToken.tokenAddress)
|
||||
const outToken = row.tokens.filter((x) => x.type === 'out')[0]
|
||||
const outTokenSymbol = await getSymbol(ocean, outToken.tokenAddress)
|
||||
title += `Swap ${formatPrice(
|
||||
Math.abs(inToken.value).toString(),
|
||||
locale
|
||||
)}${inTokenSymbol} for ${formatPrice(
|
||||
Math.abs(outToken.value).toString(),
|
||||
locale
|
||||
)}${outTokenSymbol}`
|
||||
break
|
||||
}
|
||||
case 'setup': {
|
||||
const firstToken = row.tokens.filter(
|
||||
(x) =>
|
||||
x.tokenAddress.toLowerCase() === ocean.pool.oceanAddress.toLowerCase()
|
||||
)[0]
|
||||
const firstTokenSymbol = await getSymbol(ocean, firstToken.tokenAddress)
|
||||
const secondToken = row.tokens.filter(
|
||||
(x) =>
|
||||
x.tokenAddress.toLowerCase() !== ocean.pool.oceanAddress.toLowerCase()
|
||||
)[0]
|
||||
const secondTokenSymbol = await getSymbol(ocean, secondToken.tokenAddress)
|
||||
title += `Create pool with ${formatPrice(
|
||||
Math.abs(firstToken.value).toString(),
|
||||
locale
|
||||
)}${firstTokenSymbol} and ${formatPrice(
|
||||
Math.abs(secondToken.value).toString(),
|
||||
locale
|
||||
)}${secondTokenSymbol}`
|
||||
break
|
||||
}
|
||||
case 'join':
|
||||
case 'exit': {
|
||||
for (let i = 0; i < row.tokens.length; i++) {
|
||||
const tokenSymbol = await getSymbol(ocean, row.tokens[i].tokenAddress)
|
||||
if (i > 0) title += '\n'
|
||||
title += `${row.event === 'join' ? 'Add' : 'Remove'} ${formatPrice(
|
||||
Math.abs(row.tokens[i].value).toString(),
|
||||
locale
|
||||
)}${tokenSymbol}`
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
function Title({ row }: { row: TransactionHistoryPoolTransactions }) {
|
||||
const { networkId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const [title, setTitle] = useState<string>()
|
||||
const { locale } = useUserPreferences()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean || !locale || !row) return
|
||||
|
||||
async function init() {
|
||||
const title = await getTitle(ocean, row, locale)
|
||||
setTitle(title)
|
||||
}
|
||||
init()
|
||||
}, [ocean, row, locale])
|
||||
|
||||
return title ? (
|
||||
<ExplorerLink networkId={networkId} path={`/tx/${row.tx}`}>
|
||||
<span className={styles.titleText}>{title}</span>
|
||||
</ExplorerLink>
|
||||
) : null
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Title',
|
||||
selector: function getTitleRow(row: TransactionHistoryPoolTransactions) {
|
||||
return <Title row={row} />
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Data Set',
|
||||
selector: function getAssetRow(row: TransactionHistoryPoolTransactions) {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(row.poolAddress.datatokenAddress)
|
||||
.replace('0x', 'did:op:')
|
||||
|
||||
return <AssetTitle did={did} />
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Time',
|
||||
selector: function getTimeRow(row: TransactionHistoryPoolTransactions) {
|
||||
return (
|
||||
<Time
|
||||
className={styles.time}
|
||||
date={row.timestamp.toString()}
|
||||
relative
|
||||
isUnix
|
||||
/>
|
||||
)
|
||||
},
|
||||
maxWidth: '10rem'
|
||||
}
|
||||
]
|
||||
|
||||
// hack! if we use a function to omit one field this will display a strange refresh to the enduser for each row
|
||||
const columnsMinimal = [columns[0], columns[2]]
|
||||
|
||||
export default function PoolTransactions({
|
||||
poolAddress,
|
||||
minimal
|
||||
}: {
|
||||
poolAddress?: string
|
||||
minimal?: boolean
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const [logs, setLogs] = useState<TransactionHistoryPoolTransactions[]>()
|
||||
|
||||
const { data, loading } = useQuery<TransactionHistory>(
|
||||
poolAddress ? txHistoryQueryByPool : txHistoryQuery,
|
||||
{
|
||||
variables: {
|
||||
user: accountId?.toLowerCase(),
|
||||
pool: poolAddress?.toLowerCase()
|
||||
},
|
||||
pollInterval: 20000
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return
|
||||
setLogs(data.poolTransactions)
|
||||
}, [data, loading])
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={minimal ? columnsMinimal : columns}
|
||||
data={logs}
|
||||
isLoading={loading}
|
||||
noTableHead={minimal}
|
||||
dense={minimal}
|
||||
pagination={minimal ? logs?.length >= 4 : logs?.length >= 9}
|
||||
paginationPerPage={minimal ? 5 : 10}
|
||||
emptyMessage="Your pool transactions will show up here"
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.titleText {
|
||||
white-space: pre;
|
||||
}
|
88
src/components/molecules/PoolTransactions/Title.tsx
Normal file
88
src/components/molecules/PoolTransactions/Title.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import React, { useState, useEffect, ReactElement } from 'react'
|
||||
import { Datatoken, PoolTransaction } from '.'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import ExplorerLink from '../../atoms/ExplorerLink'
|
||||
import { formatPrice } from '../../atoms/Price/PriceUnit'
|
||||
import styles from './Title.module.css'
|
||||
|
||||
function getSymbol(tokenId: Datatoken) {
|
||||
const symbol = tokenId === null ? 'OCEAN' : tokenId.symbol
|
||||
return symbol
|
||||
}
|
||||
|
||||
async function getTitle(row: PoolTransaction, locale: string) {
|
||||
let title = ''
|
||||
switch (row.event) {
|
||||
case 'swap': {
|
||||
const inToken = row.tokens.filter((x) => x.type === 'in')[0]
|
||||
const inTokenSymbol = getSymbol(inToken.poolToken.tokenId)
|
||||
const outToken = row.tokens.filter((x) => x.type === 'out')[0]
|
||||
const outTokenSymbol = getSymbol(outToken.poolToken.tokenId)
|
||||
title += `Swap ${formatPrice(
|
||||
Math.abs(inToken.value).toString(),
|
||||
locale
|
||||
)}${inTokenSymbol} for ${formatPrice(
|
||||
Math.abs(outToken.value).toString(),
|
||||
locale
|
||||
)}${outTokenSymbol}`
|
||||
|
||||
break
|
||||
}
|
||||
case 'setup': {
|
||||
const firstToken = row.tokens.filter(
|
||||
(x) =>
|
||||
x.tokenAddress.toLowerCase() !==
|
||||
row.poolAddress.datatokenAddress.toLowerCase()
|
||||
)[0]
|
||||
const firstTokenSymbol = await getSymbol(firstToken.poolToken.tokenId)
|
||||
const secondToken = row.tokens.filter(
|
||||
(x) =>
|
||||
x.tokenAddress.toLowerCase() ===
|
||||
row.poolAddress.datatokenAddress.toLowerCase()
|
||||
)[0]
|
||||
const secondTokenSymbol = await getSymbol(secondToken.poolToken.tokenId)
|
||||
title += `Create pool with ${formatPrice(
|
||||
Math.abs(firstToken.value).toString(),
|
||||
locale
|
||||
)}${firstTokenSymbol} and ${formatPrice(
|
||||
Math.abs(secondToken.value).toString(),
|
||||
locale
|
||||
)}${secondTokenSymbol}`
|
||||
break
|
||||
}
|
||||
case 'join':
|
||||
case 'exit': {
|
||||
for (let i = 0; i < row.tokens.length; i++) {
|
||||
const tokenSymbol = await getSymbol(row.tokens[i].poolToken.tokenId)
|
||||
if (i > 0) title += '\n'
|
||||
title += `${row.event === 'join' ? 'Add' : 'Remove'} ${formatPrice(
|
||||
Math.abs(row.tokens[i].value).toString(),
|
||||
locale
|
||||
)}${tokenSymbol}`
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
export default function Title({ row }: { row: PoolTransaction }): ReactElement {
|
||||
const [title, setTitle] = useState<string>()
|
||||
const { locale } = useUserPreferences()
|
||||
|
||||
useEffect(() => {
|
||||
if (!locale || !row) return
|
||||
async function init() {
|
||||
const title = await getTitle(row, locale)
|
||||
setTitle(title)
|
||||
}
|
||||
init()
|
||||
}, [row, locale])
|
||||
|
||||
return title ? (
|
||||
<ExplorerLink networkId={row.networkId} path={`/tx/${row.tx}`}>
|
||||
<span className={styles.titleText}>{title}</span>
|
||||
</ExplorerLink>
|
||||
) : null
|
||||
}
|
@ -1,7 +1,3 @@
|
||||
.time {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.titleText {
|
||||
white-space: pre;
|
||||
}
|
232
src/components/molecules/PoolTransactions/index.tsx
Normal file
232
src/components/molecules/PoolTransactions/index.tsx
Normal file
@ -0,0 +1,232 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Time from '../../atoms/Time'
|
||||
import Table from '../../atoms/Table'
|
||||
import AssetTitle from '../AssetListTitle'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import { gql } from 'urql'
|
||||
import { TransactionHistory_poolTransactions as TransactionHistoryPoolTransactions } from '../../../@types/apollo/TransactionHistory'
|
||||
import web3 from 'web3'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { fetchDataForMultipleChains } from '../../../utils/subgraph'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import NetworkName from '../../atoms/NetworkName'
|
||||
import { retrieveDDO } from '../../../utils/aquarius'
|
||||
import axios from 'axios'
|
||||
import Title from './Title'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const REFETCH_INTERVAL = 20000
|
||||
|
||||
const txHistoryQueryByPool = gql`
|
||||
query TransactionHistoryByPool($user: String, $pool: String) {
|
||||
poolTransactions(
|
||||
orderBy: timestamp
|
||||
orderDirection: desc
|
||||
where: { userAddress: $user, poolAddress: $pool }
|
||||
first: 1000
|
||||
) {
|
||||
tokens {
|
||||
poolToken {
|
||||
tokenId {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
tx
|
||||
event
|
||||
timestamp
|
||||
poolAddress {
|
||||
datatokenAddress
|
||||
}
|
||||
tokens {
|
||||
value
|
||||
type
|
||||
tokenAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const txHistoryQuery = gql`
|
||||
query TransactionHistory($user: String) {
|
||||
poolTransactions(
|
||||
orderBy: timestamp
|
||||
orderDirection: desc
|
||||
where: { userAddress: $user }
|
||||
first: 1000
|
||||
) {
|
||||
tokens {
|
||||
poolToken {
|
||||
tokenId {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
tx
|
||||
event
|
||||
timestamp
|
||||
poolAddress {
|
||||
datatokenAddress
|
||||
}
|
||||
tokens {
|
||||
value
|
||||
type
|
||||
tokenAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export interface Datatoken {
|
||||
symbol: string
|
||||
}
|
||||
|
||||
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
|
||||
networkId: number
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'Title',
|
||||
selector: function getTitleRow(row: PoolTransaction) {
|
||||
return <Title row={row} />
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Data Set',
|
||||
selector: function getAssetRow(row: PoolTransaction) {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(row.poolAddress.datatokenAddress)
|
||||
.replace('0x', 'did:op:')
|
||||
|
||||
return <AssetTitle did={did} />
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
selector: function getNetwork(row: PoolTransaction) {
|
||||
return <NetworkName networkId={row.networkId} />
|
||||
},
|
||||
maxWidth: '12rem'
|
||||
},
|
||||
{
|
||||
name: 'Time',
|
||||
selector: function getTimeRow(row: PoolTransaction) {
|
||||
return (
|
||||
<Time
|
||||
className={styles.time}
|
||||
date={row.timestamp.toString()}
|
||||
relative
|
||||
isUnix
|
||||
/>
|
||||
)
|
||||
},
|
||||
maxWidth: '10rem'
|
||||
}
|
||||
]
|
||||
|
||||
// hack! if we use a function to omit one field this will display a strange refresh to the enduser for each row
|
||||
const columnsMinimal = [columns[0], columns[3]]
|
||||
|
||||
export default function PoolTransactions({
|
||||
poolAddress,
|
||||
minimal
|
||||
}: {
|
||||
poolAddress?: string
|
||||
minimal?: boolean
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const [logs, setLogs] = useState<PoolTransaction[]>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const { chainIds } = useUserPreferences()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
|
||||
const [data, setData] = useState<PoolTransaction[]>()
|
||||
|
||||
async function fetchPoolTransactionData() {
|
||||
const variables = { user: accountId?.toLowerCase() }
|
||||
const transactions: PoolTransaction[] = []
|
||||
const result = await fetchDataForMultipleChains(
|
||||
poolAddress ? txHistoryQueryByPool : txHistoryQuery,
|
||||
variables,
|
||||
chainIds
|
||||
)
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
result[i].poolTransactions.forEach((poolTransaction: PoolTransaction) => {
|
||||
transactions.push(poolTransaction)
|
||||
})
|
||||
}
|
||||
|
||||
if (JSON.stringify(data) !== JSON.stringify(transactions)) {
|
||||
setData(transactions)
|
||||
}
|
||||
}
|
||||
|
||||
function refetchPoolTransactions() {
|
||||
if (!dataFetchInterval) {
|
||||
setDataFetchInterval(
|
||||
setInterval(function () {
|
||||
fetchPoolTransactionData()
|
||||
}, REFETCH_INTERVAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearInterval(dataFetchInterval)
|
||||
}
|
||||
}, [dataFetchInterval])
|
||||
|
||||
useEffect(() => {
|
||||
if (!appConfig.metadataCacheUri) return
|
||||
|
||||
async function getTransactions() {
|
||||
const poolTransactions: PoolTransaction[] = []
|
||||
const source = axios.CancelToken.source()
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
if (!data) {
|
||||
await fetchPoolTransactionData()
|
||||
return
|
||||
}
|
||||
const poolTransactionsData = data.map((obj) => ({ ...obj }))
|
||||
|
||||
for (let i = 0; i < poolTransactionsData.length; i++) {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(
|
||||
poolTransactionsData[i].poolAddress.datatokenAddress
|
||||
)
|
||||
.replace('0x', 'did:op:')
|
||||
const ddo = await retrieveDDO(did, source.token)
|
||||
poolTransactionsData[i].networkId = ddo.chainId
|
||||
poolTransactions.push(poolTransactionsData[i])
|
||||
}
|
||||
const sortedTransactions = poolTransactions.sort(
|
||||
(a, b) => b.timestamp - a.timestamp
|
||||
)
|
||||
setLogs(sortedTransactions)
|
||||
refetchPoolTransactions()
|
||||
} catch (error) {
|
||||
console.error('Error fetching pool transactions: ', error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
getTransactions()
|
||||
}, [accountId, chainIds, appConfig.metadataCacheUri, poolAddress, data])
|
||||
|
||||
return accountId ? (
|
||||
<Table
|
||||
columns={minimal ? columnsMinimal : columns}
|
||||
data={logs}
|
||||
isLoading={isLoading}
|
||||
noTableHead={minimal}
|
||||
dense={minimal}
|
||||
pagination={minimal ? logs?.length >= 4 : logs?.length >= 9}
|
||||
paginationPerPage={minimal ? 5 : 10}
|
||||
/>
|
||||
) : (
|
||||
<div>Please connect your Web3 wallet.</div>
|
||||
)
|
||||
}
|
@ -1,17 +1,72 @@
|
||||
.form {
|
||||
margin-bottom: var(--spacer);
|
||||
.search {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: var(--color-secondary);
|
||||
cursor: pointer;
|
||||
background: var(--background-content);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
width: 100%;
|
||||
max-width: 30rem;
|
||||
right: 1px;
|
||||
left: 1px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.form > div > div {
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
color: var(--font-color-text);
|
||||
}
|
||||
|
||||
.input {
|
||||
background-color: transparent;
|
||||
height: 36px;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
padding-right: var(--spacer);
|
||||
width: 0;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.form label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form input {
|
||||
.input:focus {
|
||||
width: calc(100% - var(--spacer));
|
||||
background-color: var(--background-content);
|
||||
position: fixed;
|
||||
left: calc(var(--spacer) / 2);
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 78rem) {
|
||||
.input,
|
||||
.input:focus {
|
||||
width: auto;
|
||||
position: relative;
|
||||
left: initial;
|
||||
right: initial;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: auto;
|
||||
left: auto;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.input:focus + .button {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
fill: currentColor;
|
||||
transition: 0.2s ease-out;
|
||||
width: var(--font-size-h5);
|
||||
height: var(--font-size-h5);
|
||||
}
|
||||
|
@ -10,5 +10,3 @@ export default {
|
||||
export const Normal = () => <SearchBar />
|
||||
|
||||
export const WithInitialValue = () => <SearchBar initialValue="Water" />
|
||||
|
||||
export const WithFilters = () => <SearchBar filters />
|
||||
|
@ -1,27 +1,51 @@
|
||||
import React, { useState, ChangeEvent, FormEvent, ReactElement } from 'react'
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
ChangeEvent,
|
||||
FormEvent,
|
||||
KeyboardEvent,
|
||||
ReactElement
|
||||
} from 'react'
|
||||
import { navigate } from 'gatsby'
|
||||
import styles from './SearchBar.module.css'
|
||||
import Button from '../atoms/Button'
|
||||
import Input from '../atoms/Input'
|
||||
import InputGroup from '../atoms/Input/InputGroup'
|
||||
import queryString from 'query-string'
|
||||
import { addExistingParamsToUrl } from '../templates/Search/utils'
|
||||
import { ReactComponent as SearchIcon } from '../../images/search.svg'
|
||||
import InputElement from '../atoms/Input/InputElement'
|
||||
import styles from './SearchBar.module.css'
|
||||
|
||||
async function emptySearch() {
|
||||
const searchParams = new URLSearchParams(window.location.href)
|
||||
const text = searchParams.get('text')
|
||||
if (text !== ('' || undefined || null)) {
|
||||
const url = await addExistingParamsToUrl(location, [
|
||||
'text',
|
||||
'owner',
|
||||
'tags'
|
||||
])
|
||||
navigate(`${url}&text=%20`)
|
||||
}
|
||||
}
|
||||
|
||||
export default function SearchBar({
|
||||
placeholder,
|
||||
initialValue,
|
||||
filters,
|
||||
size
|
||||
initialValue
|
||||
}: {
|
||||
placeholder?: string
|
||||
initialValue?: string
|
||||
filters?: boolean
|
||||
size?: 'small' | 'large'
|
||||
}): ReactElement {
|
||||
let [value, setValue] = useState(initialValue || '')
|
||||
const [value, setValue] = useState(initialValue || '')
|
||||
const parsed = queryString.parse(location.search)
|
||||
const { text, owner } = parsed
|
||||
|
||||
useEffect(() => {
|
||||
;(text || owner) && setValue((text || owner) as string)
|
||||
}, [text, owner])
|
||||
|
||||
async function startSearch(e: FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
if (value === '') value = ' '
|
||||
|
||||
if (value === '') setValue(' ')
|
||||
|
||||
const urlEncodedValue = encodeURIComponent(value)
|
||||
const url = await addExistingParamsToUrl(location, [
|
||||
'text',
|
||||
@ -31,46 +55,38 @@ export default function SearchBar({
|
||||
navigate(`${url}&text=${urlEncodedValue}`)
|
||||
}
|
||||
|
||||
async function emptySearch() {
|
||||
const searchParams = new URLSearchParams(window.location.href)
|
||||
const text = searchParams.get('text')
|
||||
if (text !== ('' || undefined || null)) {
|
||||
const url = await addExistingParamsToUrl(location, [
|
||||
'text',
|
||||
'owner',
|
||||
'tags'
|
||||
])
|
||||
navigate(`${url}&text=%20`)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
setValue(e.target.value)
|
||||
e.target.value === '' && emptySearch()
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.form}>
|
||||
<InputGroup>
|
||||
<Input
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder={placeholder || 'What are you looking for?'}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
required
|
||||
size={size}
|
||||
/>
|
||||
<Button
|
||||
onClick={async (e: FormEvent<HTMLButtonElement>) =>
|
||||
await startSearch(e)
|
||||
}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</InputGroup>
|
||||
async function handleKeyPress(e: KeyboardEvent<HTMLInputElement>) {
|
||||
if (e.key === 'Enter') {
|
||||
await startSearch(e)
|
||||
}
|
||||
}
|
||||
|
||||
{filters && <fieldset className={styles.filters}>Type, Price</fieldset>}
|
||||
async function handleButtonClick(e: FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
await startSearch(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.search}>
|
||||
<InputElement
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder={placeholder || 'Search...'}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
required
|
||||
size="small"
|
||||
className={styles.input}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
<button onClick={handleButtonClick} className={styles.button}>
|
||||
<SearchIcon className={styles.searchIcon} />
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
.buttons {
|
||||
composes: buttons from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: button from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.button span {
|
||||
display: block;
|
||||
font-size: var(--font-size-small);
|
||||
font-family: var(--font-family-base);
|
||||
font-weight: var(--font-weight-base);
|
||||
margin-top: calc(var(--spacer) / 10);
|
||||
}
|
||||
|
||||
.selected {
|
||||
composes: selected from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.chains div[class*='boxSelectionsWrapper'] {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) / 4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.chains label[class*='boxSelection'] {
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Chain.module.css'
|
||||
|
||||
export default function Chain(): ReactElement {
|
||||
const { web3 } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
|
||||
async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
|
||||
const config = getOceanConfig(event.target.value)
|
||||
await connect(config)
|
||||
}
|
||||
|
||||
function isNetworkSelected(oceanConfig: string) {
|
||||
return (config as ConfigHelperConfig).network === oceanConfig
|
||||
}
|
||||
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'mainnet',
|
||||
checked: isNetworkSelected('mainnet'),
|
||||
title: 'ETH',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'polygon',
|
||||
checked: isNetworkSelected('polygon'),
|
||||
title: 'Polygon/Matic',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'bsc',
|
||||
checked: isNetworkSelected('bsc'),
|
||||
title: 'BSC',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'moonbeamalpha',
|
||||
checked: isNetworkSelected('moonbeamalpha'),
|
||||
title: 'Moonbase Alpha',
|
||||
text: 'Testnet'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
|
||||
// there are more considerations for users with a wallet connected (wallet network vs. setting network).
|
||||
// For now, only show the setting for non-wallet users.
|
||||
return !web3 ? (
|
||||
<li className={styles.chains}>
|
||||
<Label htmlFor="">Chain</Label>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="chain"
|
||||
handleChange={connectOcean}
|
||||
/>
|
||||
<FormHelp>Switch the data source for the interface.</FormHelp>
|
||||
</li>
|
||||
) : null
|
||||
}
|
@ -1,23 +1,21 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import InputElement from '../../atoms/Input/InputElement'
|
||||
import Input from '../../atoms/Input'
|
||||
|
||||
export default function Debug(): ReactElement {
|
||||
const { debug, setDebug } = useUserPreferences()
|
||||
|
||||
return (
|
||||
<li>
|
||||
<InputElement
|
||||
<Input
|
||||
label="Debug"
|
||||
help="Show geeky information in some places, and in your console."
|
||||
name="debug"
|
||||
type="checkbox"
|
||||
options={['Debug Mode']}
|
||||
options={['Activate Debug Mode']}
|
||||
defaultChecked={debug === true}
|
||||
onChange={() => setDebug(!debug)}
|
||||
/>
|
||||
<FormHelp>
|
||||
Show geeky information in some places, and in your console.
|
||||
</FormHelp>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
.radioWrap {
|
||||
composes: radioWrap from '../../../atoms/Input/InputElement.module.css';
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.radioWrap:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.input {
|
||||
composes: checkbox from '../../../atoms/Input/InputElement.module.css';
|
||||
vertical-align: baseline;
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
composes: radioLabel from '../../../atoms/Input/InputElement.module.css';
|
||||
font-weight: var(--font-weight-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input:checked + span {
|
||||
color: var(--font-color-text);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import { removeItemFromArray } from '../../../../utils'
|
||||
import NetworkName from '../../../atoms/NetworkName'
|
||||
import styles from './NetworkItem.module.css'
|
||||
|
||||
export default function NetworkItem({
|
||||
chainId
|
||||
}: {
|
||||
chainId: number
|
||||
}): ReactElement {
|
||||
const { chainIds, setChainIds } = useUserPreferences()
|
||||
|
||||
function handleNetworkChanged(e: ChangeEvent<HTMLInputElement>) {
|
||||
const { value } = e.target
|
||||
|
||||
// storing all chainId everywhere as a number so convert from here
|
||||
const valueAsNumber = Number(value)
|
||||
|
||||
const newChainIds = chainIds.includes(valueAsNumber)
|
||||
? [...removeItemFromArray(chainIds, valueAsNumber)]
|
||||
: [...chainIds, valueAsNumber]
|
||||
setChainIds(newChainIds)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.radioWrap} key={chainId}>
|
||||
<label className={styles.radioLabel} htmlFor={`opt-${chainId}`}>
|
||||
<input
|
||||
className={styles.input}
|
||||
id={`opt-${chainId}`}
|
||||
type="checkbox"
|
||||
name="chainIds"
|
||||
value={chainId}
|
||||
onChange={handleNetworkChanged}
|
||||
defaultChecked={chainIds.includes(chainId)}
|
||||
/>
|
||||
<NetworkName key={chainId} networkId={chainId} />
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
.titleGroup {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: calc(var(--spacer) / 6);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.networks {
|
||||
composes: box from '../../../atoms/Box.module.css';
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import NetworkItem from './NetworkItem'
|
||||
import styles from './NetworksList.module.css'
|
||||
|
||||
export default function NetworksList({
|
||||
title,
|
||||
networks
|
||||
}: {
|
||||
title: string
|
||||
networks: number[]
|
||||
}): ReactElement {
|
||||
const content = networks.map((chainId) => (
|
||||
<NetworkItem key={chainId} chainId={chainId} />
|
||||
))
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className={styles.titleGroup}>{title}</h4>
|
||||
<div className={styles.networks}>{content}</div>
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
.networks {
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chainsSelected {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chainsSelectedIndicator {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
margin: 0 1px;
|
||||
display: inline-block;
|
||||
background-color: var(--color-primary);
|
||||
}
|
76
src/components/molecules/UserPreferences/Networks/index.tsx
Normal file
76
src/components/molecules/UserPreferences/Networks/index.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Label from '../../../atoms/Input/Label'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import FormHelp from '../../../atoms/Input/Help'
|
||||
import { EthereumListsChain, getNetworkDataById } from '../../../../utils/web3'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import { ReactComponent as Caret } from '../../../../images/caret.svg'
|
||||
import { ReactComponent as Network } from '../../../../images/network.svg'
|
||||
import NetworksList from './NetworksList'
|
||||
import stylesIndex from '../index.module.css'
|
||||
import styles from './index.module.css'
|
||||
import useNetworkMetadata from '../../../../hooks/useNetworkMetadata'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
|
||||
export function filterNetworksByType(
|
||||
type: 'mainnet' | 'testnet',
|
||||
chainIds: number[],
|
||||
networksList: { node: EthereumListsChain }[]
|
||||
) {
|
||||
const finalNetworks = chainIds.filter((chainId: number) => {
|
||||
const networkData = getNetworkDataById(networksList, chainId)
|
||||
|
||||
// HEADS UP! Only networkData.network === 'mainnet' is consistent
|
||||
// while not every test network in the network data has 'testnet'
|
||||
// in its place. So for the 'testnet' case filter for all non-'mainnet'.
|
||||
return type === 'mainnet'
|
||||
? networkData.network === type
|
||||
: networkData.network !== 'mainnet'
|
||||
})
|
||||
return finalNetworks
|
||||
}
|
||||
|
||||
export default function Networks(): ReactElement {
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
const networksMain = filterNetworksByType(
|
||||
'mainnet',
|
||||
appConfig.chainIdsSupported,
|
||||
networksList
|
||||
)
|
||||
|
||||
const networksTest = filterNetworksByType(
|
||||
'testnet',
|
||||
appConfig.chainIdsSupported,
|
||||
networksList
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className={stylesIndex.preferencesDetails}>
|
||||
<li>
|
||||
<Label htmlFor="chains">Networks</Label>
|
||||
<FormHelp>Switch the data source for the interface.</FormHelp>
|
||||
|
||||
<NetworksList title="Main" networks={networksMain} />
|
||||
<NetworksList title="Test" networks={networksTest} />
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
trigger="click focus"
|
||||
className={`${stylesIndex.preferences} ${styles.networks}`}
|
||||
>
|
||||
<Network aria-label="Networks" className={stylesIndex.icon} />
|
||||
<Caret aria-hidden="true" className={stylesIndex.caret} />
|
||||
|
||||
<div className={styles.chainsSelected}>
|
||||
{chainIds.map((chainId) => (
|
||||
<span className={styles.chainsSelectedIndicator} key={chainId} />
|
||||
))}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
.preferences {
|
||||
padding: calc(var(--spacer) / 5) calc(var(--spacer) / 4)
|
||||
calc(var(--spacer) / 5) 0;
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3);
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
background-color: var(--background-content);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preferences svg {
|
||||
@ -13,19 +17,28 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.preferences svg:last-child {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
.caret,
|
||||
svg.caret {
|
||||
width: var(--font-size-small);
|
||||
height: var(--font-size-small);
|
||||
fill: var(--border-color);
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
transition: transform 0.2s ease-out;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 42rem) {
|
||||
.caret,
|
||||
svg.caret {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--brand-grey-light);
|
||||
transition: 0.2s ease-out;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.preferences:hover .icon,
|
||||
@ -40,16 +53,16 @@
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.preferencesDetails li > div,
|
||||
.preferencesDetails li p {
|
||||
margin: 0;
|
||||
.preferencesDetails > li > div,
|
||||
.preferencesDetails p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preferencesDetails li p {
|
||||
margin-top: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.preferencesDetails li {
|
||||
.preferencesDetails > li {
|
||||
padding-top: calc(var(--spacer) / 3);
|
||||
padding-bottom: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
@ -8,10 +8,9 @@ import { ReactComponent as Caret } from '../../../images/caret.svg'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import Appearance from './Appearance'
|
||||
import { darkModeConfig } from '../../../../app.config'
|
||||
import Chain from './Chain'
|
||||
|
||||
export default function UserPreferences(): ReactElement {
|
||||
// Calling this here because <Theme /> is not mounted on first load
|
||||
// Calling this here because <Style /> is not mounted on first load
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
|
||||
return (
|
||||
@ -20,7 +19,6 @@ export default function UserPreferences(): ReactElement {
|
||||
<ul className={styles.preferencesDetails}>
|
||||
<Currency />
|
||||
<Appearance darkMode={darkMode} />
|
||||
<Chain />
|
||||
<Debug />
|
||||
</ul>
|
||||
}
|
||||
@ -28,7 +26,7 @@ export default function UserPreferences(): ReactElement {
|
||||
className={styles.preferences}
|
||||
>
|
||||
<Cog aria-label="Preferences" className={styles.icon} />
|
||||
<Caret aria-hidden="true" />
|
||||
<Caret aria-hidden="true" className={styles.caret} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
text-transform: uppercase;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: calc(var(--spacer) / 4);
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3);
|
||||
white-space: nowrap;
|
||||
background: var(--background-content);
|
||||
margin: 0;
|
||||
transition: border 0.2s ease-out;
|
||||
cursor: pointer;
|
||||
min-width: 190px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button,
|
||||
@ -30,6 +31,16 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.button.initial span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 42rem) {
|
||||
.button.initial span {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.blockies {
|
||||
width: var(--font-size-large);
|
||||
height: var(--font-size-large);
|
||||
@ -37,15 +48,25 @@
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: calc(var(--spacer) / 6);
|
||||
}
|
||||
|
||||
.address {
|
||||
display: none;
|
||||
text-transform: none;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding-right: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60rem) {
|
||||
.address {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.blockies {
|
||||
margin-right: calc(var(--spacer) / 6);
|
||||
}
|
||||
}
|
||||
|
||||
.button svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
@ -65,3 +86,15 @@
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.caret,
|
||||
svg.caret {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 42rem) {
|
||||
.caret,
|
||||
svg.caret {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ const Account = React.forwardRef((props, ref: any) => {
|
||||
return !accountId && web3Modal?.cachedProvider ? (
|
||||
// Improve user experience for cached provider when connecting takes some time
|
||||
<button className={styles.button} onClick={(e) => e.preventDefault()}>
|
||||
<Loader message="Reconnecting wallet..." />
|
||||
<Loader message="Reconnecting..." />
|
||||
</button>
|
||||
) : accountId ? (
|
||||
<button
|
||||
@ -48,7 +48,7 @@ const Account = React.forwardRef((props, ref: any) => {
|
||||
<span className={styles.address} title={accountId}>
|
||||
{accountTruncate(accountId)}
|
||||
</span>
|
||||
<Caret aria-hidden="true" />
|
||||
<Caret aria-hidden="true" className={styles.caret} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@ -58,7 +58,7 @@ const Account = React.forwardRef((props, ref: any) => {
|
||||
// the Tippy to show in this state.
|
||||
ref={ref}
|
||||
>
|
||||
Connect Wallet
|
||||
Connect <span>Wallet</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import Button from '../../atoms/Button'
|
||||
import AddToken from '../../atoms/AddToken'
|
||||
import Conversion from '../../atoms/Price/Conversion'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
|
||||
import Web3Feedback from './Feedback'
|
||||
import Web3Feedback from '../Web3Feedback'
|
||||
import styles from './Details.module.css'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
|
||||
export default function Details(): ReactElement {
|
||||
const {
|
||||
@ -17,20 +17,34 @@ export default function Details(): ReactElement {
|
||||
web3Modal,
|
||||
connect,
|
||||
logout,
|
||||
networkData,
|
||||
networkId,
|
||||
networkData
|
||||
balance
|
||||
} = useWeb3()
|
||||
const { balance, config } = useOcean()
|
||||
const { locale } = useUserPreferences()
|
||||
|
||||
const [mainCurrency, setMainCurrency] = useState<string>()
|
||||
const [oceanTokenMetadata, setOceanTokenMetadata] =
|
||||
useState<{
|
||||
address: string
|
||||
symbol: string
|
||||
}>()
|
||||
// const [portisNetwork, setPortisNetwork] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!networkId) return
|
||||
|
||||
const symbol =
|
||||
networkId === 2021000 ? 'GX' : networkData?.nativeCurrency.symbol
|
||||
|
||||
setMainCurrency(symbol)
|
||||
|
||||
const oceanConfig = getOceanConfig(networkId)
|
||||
|
||||
oceanConfig &&
|
||||
setOceanTokenMetadata({
|
||||
address: oceanConfig.oceanTokenAddress,
|
||||
symbol: oceanConfig.oceanTokenSymbol
|
||||
})
|
||||
}, [networkData, networkId])
|
||||
|
||||
// Handle network change for Portis
|
||||
@ -49,7 +63,7 @@ export default function Details(): ReactElement {
|
||||
{Object.entries(balance).map(([key, value]) => (
|
||||
<li className={styles.balance} key={key}>
|
||||
<span className={styles.symbol}>
|
||||
{key === 'eth' ? mainCurrency : config.oceanTokenSymbol}
|
||||
{key === 'eth' ? mainCurrency : oceanTokenMetadata?.symbol}
|
||||
</span>{' '}
|
||||
{formatCurrency(Number(value), '', locale, false, {
|
||||
significantFigures: 4
|
||||
@ -76,8 +90,8 @@ export default function Details(): ReactElement {
|
||||
)} */}
|
||||
{web3ProviderInfo?.name === 'MetaMask' && (
|
||||
<AddToken
|
||||
address={config.oceanTokenAddress}
|
||||
symbol={config.oceanTokenSymbol}
|
||||
address={oceanTokenMetadata?.address}
|
||||
symbol={oceanTokenMetadata?.symbol}
|
||||
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png"
|
||||
className={styles.addToken}
|
||||
/>
|
||||
|
@ -1,51 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import Status from '../../atoms/Status'
|
||||
import styles from './Feedback.module.css'
|
||||
|
||||
export declare type Web3Error = {
|
||||
status: 'error' | 'warning' | 'success'
|
||||
title: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export default function Web3Feedback({
|
||||
isBalanceSufficient
|
||||
}: {
|
||||
isBalanceSufficient?: boolean
|
||||
}): ReactElement {
|
||||
const { account, ocean } = useOcean()
|
||||
const showFeedback = !account || !ocean || isBalanceSufficient === false
|
||||
|
||||
const state = !account
|
||||
? 'error'
|
||||
: account && isBalanceSufficient
|
||||
? 'success'
|
||||
: 'warning'
|
||||
|
||||
const title = !account
|
||||
? 'No account connected'
|
||||
: !ocean
|
||||
? 'Error connecting to Ocean'
|
||||
: account
|
||||
? isBalanceSufficient === false
|
||||
? 'Insufficient balance'
|
||||
: 'Connected to Ocean'
|
||||
: 'Something went wrong'
|
||||
|
||||
const message = !account
|
||||
? 'Please connect your Web3 wallet.'
|
||||
: !ocean
|
||||
? 'Please try again.'
|
||||
: isBalanceSufficient === false
|
||||
? 'You do not have enough OCEAN in your wallet to purchase this asset.'
|
||||
: 'Something went wrong.'
|
||||
|
||||
return showFeedback ? (
|
||||
<section className={styles.feedback}>
|
||||
<Status state={state} aria-hidden />
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
{message && <p className={styles.error}>{message}</p>}
|
||||
</section>
|
||||
) : null
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
border-right: none;
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 3);
|
||||
white-space: nowrap;
|
||||
margin-right: -3px;
|
||||
display: inline-flex;
|
||||
@ -12,8 +12,6 @@
|
||||
|
||||
.name {
|
||||
font-size: var(--font-size-small);
|
||||
display: inline-block;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
@ -1,41 +1,36 @@
|
||||
import React, { useState, useEffect, ReactElement } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import Status from '../../atoms/Status'
|
||||
import { ConfigHelper, ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import styles from './Network.module.css'
|
||||
import Badge from '../../atoms/Badge'
|
||||
import Tooltip from '../../atoms/Tooltip'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import NetworkName from '../../atoms/NetworkName'
|
||||
import styles from './Network.module.css'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
|
||||
export default function Network(): ReactElement {
|
||||
const { networkId, networkDisplayName, isTestnet } = useWeb3()
|
||||
const { config } = useOcean()
|
||||
const networkIdConfig = (config as ConfigHelperConfig).networkId
|
||||
const { networkId, isTestnet } = useWeb3()
|
||||
|
||||
const [isEthMainnet, setIsEthMainnet] = useState<boolean>()
|
||||
const [isSupportedNetwork, setIsSupportedNetwork] = useState<boolean>()
|
||||
const [isSupportedOceanNetwork, setIsSupportedOceanNetwork] =
|
||||
useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
// take network from user when present,
|
||||
// otherwise use the default configured one of app
|
||||
const network = networkId || networkIdConfig
|
||||
const isEthMainnet = network === 1
|
||||
setIsEthMainnet(isEthMainnet)
|
||||
// take network from user when present
|
||||
const network = networkId || 1
|
||||
|
||||
// Check networkId against ocean.js ConfigHelper configs
|
||||
// to figure out if network is supported.
|
||||
const isSupportedNetwork = Boolean(new ConfigHelper().getConfig(network))
|
||||
setIsSupportedNetwork(isSupportedNetwork)
|
||||
}, [networkId, networkIdConfig])
|
||||
const isSupportedOceanNetwork = Boolean(getOceanConfig(network))
|
||||
setIsSupportedOceanNetwork(isSupportedOceanNetwork)
|
||||
}, [networkId])
|
||||
|
||||
return !isEthMainnet && networkDisplayName ? (
|
||||
return networkId ? (
|
||||
<div className={styles.network}>
|
||||
{!isSupportedNetwork && (
|
||||
{!isSupportedOceanNetwork && (
|
||||
<Tooltip content="No Ocean Protocol contracts are deployed to this network.">
|
||||
<Status state="error" className={styles.warning} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<span className={styles.name}>{networkDisplayName}</span>
|
||||
<NetworkName className={styles.name} networkId={networkId} minimal />
|
||||
{isTestnet && <Badge label="Test" className={styles.badge} />}
|
||||
</div>
|
||||
) : null
|
||||
|
@ -1,3 +1,4 @@
|
||||
.wallet {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Web3Feedback from './Feedback'
|
||||
import Web3Feedback from '../Web3Feedback'
|
||||
import web3Mock from '../../../../tests/unit/__mocks__/web3'
|
||||
import { Center } from '../../../../.storybook/helpers'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Account from './Account'
|
||||
import Details from './Details'
|
||||
import Tooltip from '../../atoms/Tooltip'
|
||||
|
@ -0,0 +1,5 @@
|
||||
.text {
|
||||
color: var(--color-secondary);
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
51
src/components/molecules/WalletNetworkSwitcher.tsx
Normal file
51
src/components/molecules/WalletNetworkSwitcher.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import {
|
||||
addCustomNetwork,
|
||||
getNetworkConfigObject,
|
||||
getNetworkDisplayName,
|
||||
getNetworkDataById
|
||||
} from '../../utils/web3'
|
||||
import Button from '../atoms/Button'
|
||||
import styles from './WalletNetworkSwitcher.module.css'
|
||||
import useNetworkMetadata from '../../hooks/useNetworkMetadata'
|
||||
import { getOceanConfig } from '../../utils/ocean'
|
||||
import { useAsset } from '../../providers/Asset'
|
||||
|
||||
export default function WalletNetworkSwitcher(): ReactElement {
|
||||
const { networkId, web3Provider } = useWeb3()
|
||||
const { ddo } = useAsset()
|
||||
const oceanConfig = getOceanConfig(ddo.chainId)
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const ddoNetworkData = getNetworkDataById(networksList, ddo.chainId)
|
||||
const walletNetworkData = getNetworkDataById(networksList, networkId)
|
||||
|
||||
const ddoNetworkName = (
|
||||
<strong>{getNetworkDisplayName(ddoNetworkData, ddo.chainId)}</strong>
|
||||
)
|
||||
const walletNetworkName = (
|
||||
<strong>{getNetworkDisplayName(walletNetworkData, networkId)}</strong>
|
||||
)
|
||||
|
||||
async function switchWalletNetwork() {
|
||||
const networkNode = networksList.find(
|
||||
(data) => data.node.chainId === ddo.chainId
|
||||
).node
|
||||
const network = { ...networkNode, providerUri: oceanConfig.providerUri }
|
||||
const networkConfig = getNetworkConfigObject(network)
|
||||
addCustomNetwork(web3Provider, networkConfig)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className={styles.text}>
|
||||
This asset is published on {ddoNetworkName} but your wallet is connected
|
||||
to {walletNetworkName}. Connect to {ddoNetworkName} to interact with
|
||||
this asset.
|
||||
</p>
|
||||
<Button size="small" onClick={() => switchWalletNetwork()}>
|
||||
Switch to {ddoNetworkName}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
@ -2,8 +2,7 @@
|
||||
font-size: var(--font-size-small);
|
||||
padding-left: var(--spacer);
|
||||
padding-top: calc(var(--spacer) / 1.5);
|
||||
margin-top: var(--spacer);
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
@ -11,7 +10,7 @@
|
||||
.feedback i {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(var(--spacer) / 1.5);
|
||||
top: calc(var(--spacer) / 1.45);
|
||||
}
|
||||
|
||||
.title {
|
70
src/components/molecules/Web3Feedback.tsx
Normal file
70
src/components/molecules/Web3Feedback.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import Status from '../atoms/Status'
|
||||
import styles from './Web3Feedback.module.css'
|
||||
import WalletNetworkSwitcher from './WalletNetworkSwitcher'
|
||||
import { useGraphSyncStatus } from '../../hooks/useGraphSyncStatus'
|
||||
|
||||
export declare type Web3Error = {
|
||||
status: 'error' | 'warning' | 'success'
|
||||
title: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export default function Web3Feedback({
|
||||
isBalanceSufficient,
|
||||
isAssetNetwork
|
||||
}: {
|
||||
isBalanceSufficient?: boolean
|
||||
isAssetNetwork?: boolean
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { isGraphSynced, blockGraph, blockHead } = useGraphSyncStatus()
|
||||
const showFeedback =
|
||||
!accountId ||
|
||||
// !ocean ||
|
||||
isBalanceSufficient === false ||
|
||||
isAssetNetwork === false ||
|
||||
isGraphSynced === false
|
||||
|
||||
const state =
|
||||
!accountId || !isGraphSynced
|
||||
? 'error'
|
||||
: accountId && isBalanceSufficient && isAssetNetwork
|
||||
? 'success'
|
||||
: 'warning'
|
||||
|
||||
const title = !accountId
|
||||
? 'No account connected'
|
||||
: // : !ocean
|
||||
// ? 'Error connecting to Ocean'
|
||||
accountId && isAssetNetwork === false
|
||||
? 'Not connected to asset network'
|
||||
: isGraphSynced === false
|
||||
? `Data out of sync`
|
||||
: accountId
|
||||
? isBalanceSufficient === false
|
||||
? 'Insufficient balance'
|
||||
: 'Connected to Ocean'
|
||||
: 'Something went wrong'
|
||||
|
||||
const message = !accountId
|
||||
? 'Please connect your Web3 wallet.'
|
||||
: isBalanceSufficient === false
|
||||
? 'You do not have enough OCEAN in your wallet to purchase this asset.'
|
||||
: isGraphSynced === false
|
||||
? `The data for this network has only synced to Ethereum block ${blockGraph} (out of ${blockHead}). Transactions may fail! Please check back soon`
|
||||
: 'Something went wrong.'
|
||||
|
||||
return showFeedback ? (
|
||||
<section className={styles.feedback}>
|
||||
<Status state={state} aria-hidden />
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
{isAssetNetwork === false ? (
|
||||
<WalletNetworkSwitcher />
|
||||
) : (
|
||||
message && <p className={styles.error}>{message}</p>
|
||||
)}
|
||||
</section>
|
||||
) : null
|
||||
}
|
@ -94,7 +94,7 @@ export default function FormStartCompute({
|
||||
|
||||
const { isValid, values }: FormikContextType<{ algorithm: string }> =
|
||||
useFormikContext()
|
||||
const { price, ddo } = useAsset()
|
||||
const { price, ddo, isAssetNetwork } = useAsset()
|
||||
const [totalPrice, setTotalPrice] = useState(price?.value)
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
@ -175,7 +175,10 @@ export default function FormStartCompute({
|
||||
<ButtonBuy
|
||||
action="compute"
|
||||
disabled={
|
||||
isComputeButtonDisabled || !isValid || algorithmConsumableStatus > 0
|
||||
isComputeButtonDisabled ||
|
||||
!isValid ||
|
||||
!isAssetNetwork ||
|
||||
algorithmConsumableStatus > 0
|
||||
}
|
||||
hasPreviousOrder={hasPreviousOrder}
|
||||
hasDatatoken={hasDatatoken}
|
||||
|
@ -10,7 +10,6 @@ import { toast } from 'react-toastify'
|
||||
import Price from '../../../atoms/Price'
|
||||
import File from '../../../atoms/File'
|
||||
import Alert from '../../../atoms/Alert'
|
||||
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
@ -39,6 +38,7 @@ import { secondsToString } from '../../../../utils/metadata'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute'
|
||||
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
|
||||
import { chainIds } from '../../../../../app.config'
|
||||
|
||||
const SuccessAction = () => (
|
||||
<Button style="text" to="/history?defaultTab=ComputeJobs" size="small">
|
||||
@ -63,8 +63,8 @@ export default function Compute({
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, account, config } = useOcean()
|
||||
const { price, type, ddo } = useAsset()
|
||||
const { ocean, account } = useOcean()
|
||||
const { price, type, ddo, isAssetNetwork } = useAsset()
|
||||
const { buyDT, pricingError, pricingStepText } = usePricing()
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
@ -142,7 +142,8 @@ export default function Compute({
|
||||
}
|
||||
|
||||
function getQuerryString(
|
||||
trustedAlgorithmList: publisherTrustedAlgorithm[]
|
||||
trustedAlgorithmList: publisherTrustedAlgorithm[],
|
||||
chainId?: number
|
||||
): SearchQuery {
|
||||
let algoQuerry = ''
|
||||
trustedAlgorithmList.forEach((trusteAlgo) => {
|
||||
@ -157,7 +158,7 @@ export default function Compute({
|
||||
offset: 500,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `${algorithmQuery} service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
query: `${algorithmQuery} service.attributes.main.type:algorithm AND chainId:${chainId} -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
@ -180,9 +181,9 @@ export default function Compute({
|
||||
} else {
|
||||
const gueryResults = await queryMetadata(
|
||||
getQuerryString(
|
||||
computeService.attributes.main.privacy.publisherTrustedAlgorithms
|
||||
computeService.attributes.main.privacy.publisherTrustedAlgorithms,
|
||||
ddo.chainId
|
||||
),
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
setDdoAlgorithmList(gueryResults.results)
|
||||
@ -190,7 +191,6 @@ export default function Compute({
|
||||
algorithmSelectionList = await transformDDOToAssetSelection(
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
gueryResults.results,
|
||||
config.metadataCacheUri,
|
||||
[]
|
||||
)
|
||||
}
|
||||
@ -472,9 +472,6 @@ export default function Compute({
|
||||
action={<SuccessAction />}
|
||||
/>
|
||||
)}
|
||||
{type !== 'algorithm' && (
|
||||
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
||||
)}
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
|
@ -6,7 +6,6 @@
|
||||
.info {
|
||||
display: flex;
|
||||
width: auto;
|
||||
padding: 0 calc(var(--spacer)) 0 calc(var(--spacer));
|
||||
}
|
||||
|
||||
.filewrapper {
|
||||
@ -15,4 +14,5 @@
|
||||
|
||||
.feedback {
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer));
|
||||
}
|
||||
|
@ -3,12 +3,9 @@ import { toast } from 'react-toastify'
|
||||
import { File as FileMetadata, DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import File from '../../atoms/File'
|
||||
import Price from '../../atoms/Price'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import styles from './Consume.module.css'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
import { secondsToString } from '../../../utils/metadata'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, useQuery } from 'urql'
|
||||
import { OrdersData } from '../../../@types/apollo/OrdersData'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
@ -16,7 +13,9 @@ import { useWeb3 } from '../../../providers/Web3'
|
||||
import { usePricing } from '../../../hooks/usePricing'
|
||||
import { useConsume } from '../../../hooks/useConsume'
|
||||
import ButtonBuy from '../../atoms/ButtonBuy'
|
||||
import { secondsToString } from '../../../utils/metadata'
|
||||
import AlgorithmDatasetsListForCompute from '../AssetContent/AlgorithmDatasetsListForCompute'
|
||||
import styles from './Consume.module.css'
|
||||
|
||||
const previousOrderQuery = gql`
|
||||
query PreviousOrder($id: String!, $account: String!) {
|
||||
@ -54,7 +53,7 @@ export default function Consume({
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
||||
const { isInPurgatory, price, type } = useAsset()
|
||||
const { isInPurgatory, price, type, isAssetNetwork } = useAsset()
|
||||
const { buyDT, pricingStepText, pricingError, pricingIsLoading } =
|
||||
usePricing()
|
||||
const { consumeStepText, consume, consumeError, isLoading } = useConsume()
|
||||
@ -63,13 +62,15 @@ export default function Consume({
|
||||
const [maxDt, setMaxDT] = useState<number>(1)
|
||||
const [isConsumablePrice, setIsConsumablePrice] = useState(true)
|
||||
const [assetTimeout, setAssetTimeout] = useState('')
|
||||
const { data } = useQuery<OrdersData>(previousOrderQuery, {
|
||||
const [result] = useQuery<OrdersData>({
|
||||
query: previousOrderQuery,
|
||||
variables: {
|
||||
id: ddo.dataToken?.toLowerCase(),
|
||||
account: accountId?.toLowerCase()
|
||||
},
|
||||
pollInterval: 5000
|
||||
}
|
||||
// pollInterval: 5000
|
||||
})
|
||||
const { data } = result
|
||||
|
||||
async function checkMaxAvaialableTokens(price: BestPrice) {
|
||||
if (!ocean || !price) return
|
||||
@ -122,6 +123,7 @@ export default function Consume({
|
||||
!isConsumable ||
|
||||
((!ocean ||
|
||||
!isBalanceSufficient ||
|
||||
!isAssetNetwork ||
|
||||
typeof consumeStepText !== 'undefined' ||
|
||||
pricingIsLoading ||
|
||||
(!hasPreviousOrder && !hasDatatoken && !(maxDt >= 1)) ||
|
||||
@ -133,6 +135,7 @@ export default function Consume({
|
||||
ocean,
|
||||
hasPreviousOrder,
|
||||
isBalanceSufficient,
|
||||
isAssetNetwork,
|
||||
consumeStepText,
|
||||
pricingIsLoading,
|
||||
isConsumablePrice,
|
||||
@ -198,9 +201,6 @@ export default function Consume({
|
||||
{type === 'algorithm' && (
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} dataset={ddo} />
|
||||
)}
|
||||
<footer className={styles.feedback}>
|
||||
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
||||
</footer>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
setMinterToDispenser,
|
||||
setMinterToPublisher
|
||||
} from '../../../../utils/freePrice'
|
||||
import Web3Feedback from '../../../molecules/Web3Feedback'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query EditAvanceSettingsQuery {
|
||||
@ -72,7 +73,7 @@ export default function EditAdvancedSettings({
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { metadata, ddo, refreshDdo, price } = useAsset()
|
||||
const { isAssetNetwork, ddo, refreshDdo, price } = useAsset()
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
@ -168,7 +169,7 @@ export default function EditAdvancedSettings({
|
||||
setShowEdit={setShowEdit}
|
||||
/>
|
||||
</article>
|
||||
|
||||
<Web3Feedback isAssetNetwork={isAssetNetwork} />
|
||||
{debug === true && (
|
||||
<div className={styles.grid}>
|
||||
<DebugEditCredential
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
setMinterToDispenser,
|
||||
setMinterToPublisher
|
||||
} from '../../../../utils/freePrice'
|
||||
import Web3Feedback from '../../../molecules/Web3Feedback'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query EditComputeDataQuery {
|
||||
@ -66,7 +67,7 @@ export default function EditComputeDataset({
|
||||
const { debug } = useUserPreferences()
|
||||
const { ocean } = useOcean()
|
||||
const { accountId } = useWeb3()
|
||||
const { ddo, refreshDdo, price } = useAsset()
|
||||
const { ddo, price, isAssetNetwork, refreshDdo } = useAsset()
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
@ -169,7 +170,7 @@ export default function EditComputeDataset({
|
||||
setShowEdit={setShowEdit}
|
||||
/>
|
||||
</article>
|
||||
|
||||
<Web3Feedback isAssetNetwork={isAssetNetwork} />
|
||||
{debug === true && (
|
||||
<div className={styles.grid}>
|
||||
<DebugEditCompute values={values} ddo={ddo} />
|
||||
|
@ -0,0 +1,20 @@
|
||||
.actions {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 2) var(--spacer) 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.actions {
|
||||
padding-top: var(--spacer);
|
||||
}
|
||||
}
|
||||
|
||||
.actions a,
|
||||
.actions button {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
37
src/components/organisms/AssetActions/Edit/FormActions.tsx
Normal file
37
src/components/organisms/AssetActions/Edit/FormActions.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { AdvancedSettingsForm } from '../../../../models/FormEditCredential'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import Button from '../../../atoms/Button'
|
||||
import styles from './FormActions.module.css'
|
||||
|
||||
export default function FormActions({
|
||||
setShowEdit,
|
||||
handleClick
|
||||
}: {
|
||||
setShowEdit: (show: boolean) => void
|
||||
handleClick?: () => void
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { isValid }: FormikContextType<Partial<AdvancedSettingsForm>> =
|
||||
useFormikContext()
|
||||
|
||||
return (
|
||||
<footer className={styles.actions}>
|
||||
<Button
|
||||
style="primary"
|
||||
disabled={!ocean || !accountId || !isValid || !isAssetNetwork}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</footer>
|
||||
)
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Button from '../../../atoms/Button'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { AdvancedSettingsForm } from '../../../../models/FormEditCredential'
|
||||
import FormActions from './FormActions'
|
||||
|
||||
export default function FormAdvancedSettings({
|
||||
data,
|
||||
@ -15,10 +13,7 @@ export default function FormAdvancedSettings({
|
||||
data: FormFieldProps[]
|
||||
setShowEdit: (show: boolean) => void
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, config } = useOcean()
|
||||
const {
|
||||
isValid,
|
||||
validateField,
|
||||
setFieldValue
|
||||
}: FormikContextType<Partial<AdvancedSettingsForm>> = useFormikContext()
|
||||
@ -46,14 +41,7 @@ export default function FormAdvancedSettings({
|
||||
/>
|
||||
))}
|
||||
|
||||
<footer className={styles.actions}>
|
||||
<Button style="primary" disabled={!ocean || !accountId || !isValid}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</footer>
|
||||
<FormActions setShowEdit={setShowEdit} />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Button from '../../../atoms/Button'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import stylesIndex from './index.module.css'
|
||||
@ -16,6 +13,8 @@ import { useAsset } from '../../../../providers/Asset'
|
||||
import { ComputePrivacyForm } from '../../../../models/FormEditComputeDataset'
|
||||
import { publisherTrustedAlgorithm as PublisherTrustedAlgorithm } from '@oceanprotocol/lib'
|
||||
import axios from 'axios'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import FormActions from './FormActions'
|
||||
|
||||
export default function FormEditComputeDataset({
|
||||
data,
|
||||
@ -26,11 +25,9 @@ export default function FormEditComputeDataset({
|
||||
title: string
|
||||
setShowEdit: (show: boolean) => void
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, config } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { ddo } = useAsset()
|
||||
const { isValid, values }: FormikContextType<ComputePrivacyForm> =
|
||||
useFormikContext()
|
||||
const { values }: FormikContextType<ComputePrivacyForm> = useFormikContext()
|
||||
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
|
||||
|
||||
const { publisherTrustedAlgorithms } =
|
||||
@ -44,21 +41,16 @@ export default function FormEditComputeDataset({
|
||||
offset: 500,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
query: `service.attributes.main.type:algorithm AND chainId:${ddo.chainId} -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
const querryResult = await queryMetadata(
|
||||
query,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const querryResult = await queryMetadata(query, source.token)
|
||||
const datasetComputeService = ddo.findServiceByType('compute')
|
||||
const algorithmSelectionList = await transformDDOToAssetSelection(
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
querryResult.results,
|
||||
config.metadataCacheUri,
|
||||
publisherTrustedAlgorithms
|
||||
)
|
||||
return algorithmSelectionList
|
||||
@ -68,7 +60,7 @@ export default function FormEditComputeDataset({
|
||||
getAlgorithmList(publisherTrustedAlgorithms).then((algorithms) => {
|
||||
setAllAlgorithms(algorithms)
|
||||
})
|
||||
}, [config.metadataCacheUri, publisherTrustedAlgorithms])
|
||||
}, [appConfig.metadataCacheUri, publisherTrustedAlgorithms])
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
@ -90,14 +82,8 @@ export default function FormEditComputeDataset({
|
||||
component={Input}
|
||||
/>
|
||||
))}
|
||||
<footer className={styles.actions}>
|
||||
<Button style="primary" disabled={!ocean || !accountId || !isValid}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</footer>
|
||||
|
||||
<FormActions setShowEdit={setShowEdit} />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
@ -2,27 +2,6 @@
|
||||
composes: box from '../../../atoms/Box.module.css';
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 2) var(--spacer) 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.actions {
|
||||
padding-top: var(--spacer);
|
||||
}
|
||||
}
|
||||
|
||||
.actions a,
|
||||
.actions button {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
.form select[multiple] {
|
||||
height: 130px;
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { MetadataPublishFormDataset } from '../../../../@types/MetaData'
|
||||
import { checkIfTimeoutInPredefinedValues } from '../../../../utils/metadata'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import FormActions from './FormActions'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
|
||||
function handleTimeoutCustomOption(
|
||||
data: FormFieldProps[],
|
||||
@ -56,10 +55,8 @@ export default function FormEditMetadata({
|
||||
values: Partial<MetadataPublishFormDataset>
|
||||
showPrice: boolean
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, config } = useOcean()
|
||||
const { config } = useOcean()
|
||||
const {
|
||||
isValid,
|
||||
validateField,
|
||||
setFieldValue
|
||||
}: FormikContextType<Partial<MetadataPublishFormDataset>> = useFormikContext()
|
||||
@ -96,18 +93,10 @@ export default function FormEditMetadata({
|
||||
)
|
||||
)}
|
||||
|
||||
<footer className={styles.actions}>
|
||||
<Button
|
||||
style="primary"
|
||||
disabled={!ocean || !accountId || !isValid}
|
||||
onClick={() => setTimeoutStringValue(values.timeout)}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</footer>
|
||||
<FormActions
|
||||
setShowEdit={setShowEdit}
|
||||
handleClick={() => setTimeoutStringValue(values.timeout)}
|
||||
/>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import { useAsset } from '../../../../providers/Asset'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import { MetadataPreview } from '../../../molecules/MetadataPreview'
|
||||
import Debug from './DebugEditMetadata'
|
||||
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||
import Web3Feedback from '../../../molecules/Web3Feedback'
|
||||
import FormEditMetadata from './FormEditMetadata'
|
||||
import { mapTimeoutStringToSeconds } from '../../../../utils/metadata'
|
||||
import styles from './index.module.css'
|
||||
|
@ -13,6 +13,7 @@ import { FormAddLiquidity } from '.'
|
||||
import { PoolBalance } from '../../../../../@types/TokenBalance'
|
||||
import UserLiquidity from '../../../../atoms/UserLiquidity'
|
||||
import { useOcean } from '../../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../../providers/Web3'
|
||||
|
||||
export default function FormAdd({
|
||||
coin,
|
||||
@ -37,7 +38,8 @@ export default function FormAdd({
|
||||
setNewPoolTokens: (value: string) => void
|
||||
setNewPoolShare: (value: string) => void
|
||||
}): ReactElement {
|
||||
const { ocean, balance } = useOcean()
|
||||
const { balance } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
|
||||
// Connect with form
|
||||
const {
|
||||
|
@ -65,8 +65,8 @@ export default function Add({
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool.add
|
||||
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, balance } = useOcean()
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { debug } = useUserPreferences()
|
||||
const [txId, setTxId] = useState<string>()
|
||||
const [coin, setCoin] = useState('OCEAN')
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
ChartTooltipItem,
|
||||
ChartTooltipOptions
|
||||
} from 'chart.js'
|
||||
import styles from './Graph.module.css'
|
||||
import Loader from '../../../atoms/Loader'
|
||||
import { formatPrice } from '../../../atoms/Price/PriceUnit'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
@ -17,8 +16,10 @@ import { darkModeConfig } from '../../../../../app.config'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { PoolHistory } from '../../../../@types/apollo/PoolHistory'
|
||||
import { fetchData, getQueryContext } from '../../../../utils/subgraph'
|
||||
import styles from './Graph.module.css'
|
||||
|
||||
declare type GraphType = 'liquidity' | 'price'
|
||||
|
||||
@ -27,6 +28,8 @@ defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystem
|
||||
'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.global.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
const REFETCH_INTERVAL = 10000
|
||||
|
||||
const lineStyle: Partial<ChartDataSets> = {
|
||||
fill: false,
|
||||
lineTension: 0.1,
|
||||
@ -121,22 +124,53 @@ export default function Graph(): ReactElement {
|
||||
const [options, setOptions] = useState<ChartOptions>()
|
||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||
|
||||
const { price } = useAsset()
|
||||
const { price, ddo } = useAsset()
|
||||
|
||||
const [lastBlock, setLastBlock] = useState<number>(0)
|
||||
const [priceHistory, setPriceHistory] = useState([])
|
||||
const [error, setError] = useState<Error>()
|
||||
const [liquidityHistory, setLiquidityHistory] = useState([])
|
||||
const [timestamps, setTimestamps] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [dataHistory, setDataHistory] = useState<PoolHistory>()
|
||||
const [graphData, setGraphData] = useState<ChartData>()
|
||||
const [graphFetchInterval, setGraphFetchInterval] = useState<NodeJS.Timeout>()
|
||||
|
||||
const { data, refetch, error } = useQuery<PoolHistory>(poolHistory, {
|
||||
variables: {
|
||||
id: price.address.toLowerCase(),
|
||||
block: lastBlock
|
||||
},
|
||||
pollInterval: 20000
|
||||
})
|
||||
async function getPoolHistory() {
|
||||
try {
|
||||
const queryContext = getQueryContext(ddo.chainId)
|
||||
const queryVariables = {
|
||||
id: price.address.toLowerCase(),
|
||||
block: lastBlock
|
||||
}
|
||||
|
||||
const queryResult: OperationResult<PoolHistory> = await fetchData(
|
||||
poolHistory,
|
||||
queryVariables,
|
||||
queryContext
|
||||
)
|
||||
setDataHistory(queryResult?.data)
|
||||
} catch (error) {
|
||||
console.error('Error fetchData: ', error.message)
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
function refetchGraph() {
|
||||
if (!graphFetchInterval) {
|
||||
setGraphFetchInterval(
|
||||
setInterval(function () {
|
||||
getPoolHistory()
|
||||
}, REFETCH_INTERVAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearInterval(graphFetchInterval)
|
||||
}
|
||||
}, [graphFetchInterval])
|
||||
|
||||
useEffect(() => {
|
||||
Logger.log('Fired GraphOptions!')
|
||||
@ -145,59 +179,70 @@ export default function Graph(): ReactElement {
|
||||
}, [locale, darkMode.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return
|
||||
Logger.log('Fired GraphData!')
|
||||
getPoolHistory()
|
||||
}, [lastBlock])
|
||||
|
||||
const latestTimestamps = [
|
||||
...timestamps,
|
||||
...data.poolTransactions.map((item) => {
|
||||
const date = new Date(item.timestamp * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
]
|
||||
setTimestamps(latestTimestamps)
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
const data: PoolHistory = dataHistory
|
||||
if (!data) {
|
||||
await getPoolHistory()
|
||||
return
|
||||
}
|
||||
Logger.log('Fired GraphData!')
|
||||
|
||||
const latestLiquidtyHistory = [
|
||||
...liquidityHistory,
|
||||
...data.poolTransactions.map((item) => item.oceanReserve)
|
||||
]
|
||||
const latestTimestamps = [
|
||||
...timestamps,
|
||||
...data.poolTransactions.map((item) => {
|
||||
const date = new Date(item.timestamp * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
]
|
||||
setTimestamps(latestTimestamps)
|
||||
|
||||
setLiquidityHistory(latestLiquidtyHistory)
|
||||
const latestLiquidtyHistory = [
|
||||
...liquidityHistory,
|
||||
...data.poolTransactions.map((item) => item.oceanReserve)
|
||||
]
|
||||
|
||||
const latestPriceHistory = [
|
||||
...priceHistory,
|
||||
...data.poolTransactions.map((item) => item.spotPrice)
|
||||
]
|
||||
setLiquidityHistory(latestLiquidtyHistory)
|
||||
|
||||
setPriceHistory(latestPriceHistory)
|
||||
const latestPriceHistory = [
|
||||
...priceHistory,
|
||||
...data.poolTransactions.map((item) => item.spotPrice)
|
||||
]
|
||||
|
||||
if (data.poolTransactions.length > 0) {
|
||||
const newBlock =
|
||||
data.poolTransactions[data.poolTransactions.length - 1].block
|
||||
if (newBlock === lastBlock) return
|
||||
setLastBlock(
|
||||
data.poolTransactions[data.poolTransactions.length - 1].block
|
||||
)
|
||||
refetch()
|
||||
} else {
|
||||
setGraphData({
|
||||
labels: latestTimestamps.slice(0),
|
||||
datasets: [
|
||||
{
|
||||
...lineStyle,
|
||||
label: 'Liquidity (OCEAN)',
|
||||
data:
|
||||
graphType === 'liquidity'
|
||||
? latestLiquidtyHistory.slice(0)
|
||||
: latestPriceHistory.slice(0),
|
||||
borderColor: `#8b98a9`,
|
||||
pointBackgroundColor: `#8b98a9`
|
||||
}
|
||||
]
|
||||
})
|
||||
setIsLoading(false)
|
||||
setPriceHistory(latestPriceHistory)
|
||||
|
||||
if (data.poolTransactions.length > 0) {
|
||||
const newBlock =
|
||||
data.poolTransactions[data.poolTransactions.length - 1].block
|
||||
if (newBlock === lastBlock) return
|
||||
setLastBlock(
|
||||
data.poolTransactions[data.poolTransactions.length - 1].block
|
||||
)
|
||||
} else {
|
||||
setGraphData({
|
||||
labels: latestTimestamps.slice(0),
|
||||
datasets: [
|
||||
{
|
||||
...lineStyle,
|
||||
label: 'Liquidity (OCEAN)',
|
||||
data:
|
||||
graphType === 'liquidity'
|
||||
? latestLiquidtyHistory.slice(0)
|
||||
: latestPriceHistory.slice(0),
|
||||
borderColor: `#8b98a9`,
|
||||
pointBackgroundColor: `#8b98a9`
|
||||
}
|
||||
]
|
||||
})
|
||||
setIsLoading(false)
|
||||
refetchGraph()
|
||||
}
|
||||
}
|
||||
}, [data, graphType])
|
||||
init()
|
||||
}, [dataHistory, graphType])
|
||||
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
|
@ -11,7 +11,8 @@ export default function TokenList({
|
||||
dtSymbol,
|
||||
poolShares,
|
||||
conversion,
|
||||
highlight
|
||||
highlight,
|
||||
showTVLLabel
|
||||
}: {
|
||||
title: string | ReactNode
|
||||
children: ReactNode
|
||||
@ -21,6 +22,7 @@ export default function TokenList({
|
||||
poolShares: string
|
||||
conversion: number
|
||||
highlight?: boolean
|
||||
showTVLLabel?: boolean
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={`${styles.tokenlist} ${highlight ? styles.highlight : ''}`}>
|
||||
@ -33,6 +35,7 @@ export default function TokenList({
|
||||
<Conversion
|
||||
price={`${conversion}`}
|
||||
className={styles.totalLiquidity}
|
||||
showTVLLabel={showTVLLabel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -15,10 +15,13 @@ import { PoolBalance } from '../../../../@types/TokenBalance'
|
||||
import Transactions from './Transactions'
|
||||
import Graph from './Graph'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { PoolLiquidity } from '../../../../@types/apollo/PoolLiquidity'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { fetchData, getQueryContext } from '../../../../utils/subgraph'
|
||||
|
||||
const REFETCH_INTERVAL = 5000
|
||||
|
||||
const contentQuery = graphql`
|
||||
query PoolQuery {
|
||||
@ -62,9 +65,10 @@ export default function Pool(): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool
|
||||
|
||||
const { accountId, networkId } = useWeb3()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { isInPurgatory, ddo, owner, price, refreshInterval } = useAsset()
|
||||
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
||||
useAsset()
|
||||
const dtSymbol = ddo?.dataTokenInfo.symbol
|
||||
|
||||
const [poolTokens, setPoolTokens] = useState<string>()
|
||||
@ -88,20 +92,50 @@ export default function Pool(): ReactElement {
|
||||
const [creatorLiquidity, setCreatorLiquidity] = useState<PoolBalance>()
|
||||
const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>()
|
||||
const [creatorPoolShare, setCreatorPoolShare] = useState<string>()
|
||||
const [dataLiquidity, setdataLiquidity] = useState<PoolLiquidity>()
|
||||
const [liquidityFetchInterval, setLiquidityFetchInterval] =
|
||||
useState<NodeJS.Timeout>()
|
||||
|
||||
// the purpose of the value is just to trigger the effect
|
||||
const [refreshPool, setRefreshPool] = useState(false)
|
||||
const { data: dataLiquidity } = useQuery<PoolLiquidity>(poolLiquidityQuery, {
|
||||
variables: {
|
||||
|
||||
async function getPoolLiquidity() {
|
||||
const queryContext = getQueryContext(ddo.chainId)
|
||||
const queryVariables = {
|
||||
id: price.address.toLowerCase(),
|
||||
shareId: `${price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}`
|
||||
},
|
||||
pollInterval: 5000
|
||||
})
|
||||
}
|
||||
|
||||
const queryResult: OperationResult<PoolLiquidity> = await fetchData(
|
||||
poolLiquidityQuery,
|
||||
queryVariables,
|
||||
queryContext
|
||||
)
|
||||
setdataLiquidity(queryResult?.data)
|
||||
}
|
||||
|
||||
function refetchLiquidity() {
|
||||
if (!liquidityFetchInterval) {
|
||||
setLiquidityFetchInterval(
|
||||
setInterval(function () {
|
||||
getPoolLiquidity()
|
||||
}, REFETCH_INTERVAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearInterval(liquidityFetchInterval)
|
||||
}
|
||||
}, [liquidityFetchInterval])
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
if (!dataLiquidity || !dataLiquidity.pool) return
|
||||
if (!dataLiquidity || !dataLiquidity.pool) {
|
||||
await getPoolLiquidity()
|
||||
return
|
||||
}
|
||||
|
||||
// Total pool shares
|
||||
const totalPoolTokens = dataLiquidity.pool.totalShares
|
||||
@ -149,6 +183,7 @@ export default function Pool(): ReactElement {
|
||||
creatorLiquidity &&
|
||||
((Number(creatorPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
||||
setCreatorPoolShare(creatorPoolShare)
|
||||
refetchLiquidity()
|
||||
}
|
||||
init()
|
||||
}, [dataLiquidity, ddo.dataToken, price.datatoken, price.ocean, price?.value])
|
||||
@ -243,15 +278,15 @@ export default function Pool(): ReactElement {
|
||||
<Tooltip content={content.tooltips.price} />
|
||||
<div className={styles.dataTokenLinks}>
|
||||
<ExplorerLink
|
||||
networkId={networkId}
|
||||
networkId={ddo.chainId}
|
||||
path={`address/${price?.address}`}
|
||||
>
|
||||
Pool
|
||||
</ExplorerLink>
|
||||
<ExplorerLink
|
||||
networkId={networkId}
|
||||
networkId={ddo.chainId}
|
||||
path={
|
||||
networkId === 137 || networkId === 1287
|
||||
ddo.chainId === 2021000 || ddo.chainId === 1287
|
||||
? `tokens/${ddo.dataToken}`
|
||||
: `token/${ddo.dataToken}`
|
||||
}
|
||||
@ -284,7 +319,7 @@ export default function Pool(): ReactElement {
|
||||
</TokenList>
|
||||
|
||||
<TokenList
|
||||
title="Pool Creator Liquidity"
|
||||
title="Pool Creator Statistics"
|
||||
ocean={`${creatorLiquidity?.ocean}`}
|
||||
dt={`${creatorLiquidity?.datatoken}`}
|
||||
dtSymbol={dtSymbol}
|
||||
@ -314,6 +349,7 @@ export default function Pool(): ReactElement {
|
||||
dtSymbol={dtSymbol}
|
||||
poolShares={totalPoolTokens}
|
||||
conversion={totalLiquidityInOcean}
|
||||
showTVLLabel
|
||||
>
|
||||
<Token symbol="% swap fee" balance={swapFee} noIcon />
|
||||
</TokenList>
|
||||
@ -330,14 +366,18 @@ export default function Pool(): ReactElement {
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={() => setShowAdd(true)}
|
||||
disabled={isInPurgatory}
|
||||
disabled={isInPurgatory || !isAssetNetwork}
|
||||
>
|
||||
Add Liquidity
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{hasAddedLiquidity && !isRemoveDisabled && (
|
||||
<Button size="small" onClick={() => setShowRemove(true)}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setShowRemove(true)}
|
||||
disabled={!isAssetNetwork}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
|
@ -14,6 +14,7 @@ import { FormTradeData, initialValues } from '../../../../models/FormTrade'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query TradeQuery {
|
||||
@ -49,6 +50,7 @@ export default function FormTrade({
|
||||
const content = data.content.edges[0].node.childContentJson.trade
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { debug } = useUserPreferences()
|
||||
const [txId, setTxId] = useState<string>()
|
||||
|
||||
@ -139,7 +141,7 @@ export default function FormTrade({
|
||||
</div>
|
||||
)}
|
||||
<Actions
|
||||
isDisabled={!isWarningAccepted}
|
||||
isDisabled={!isWarningAccepted || !isAssetNetwork}
|
||||
isLoading={isSubmitting}
|
||||
loaderMessage="Swapping tokens..."
|
||||
successMessage="Successfully swapped tokens."
|
||||
|
@ -6,8 +6,8 @@ import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
|
||||
export default function Trade(): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, balance } = useOcean()
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const [tokenBalance, setTokenBalance] = useState<PoolBalance>()
|
||||
const { price, ddo } = useAsset()
|
||||
const [maxDt, setMaxDt] = useState(0)
|
||||
|
@ -11,13 +11,14 @@ import Trade from './Trade'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import Web3Feedback from '../../molecules/Web3Feedback'
|
||||
import { getFileInfo } from '../../../utils/provider'
|
||||
import axios from 'axios'
|
||||
|
||||
export default function AssetActions(): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { config, ocean, balance, account } = useOcean()
|
||||
const { price, ddo } = useAsset()
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean, config, account } = useOcean()
|
||||
const { price, ddo, isAssetNetwork } = useAsset()
|
||||
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
@ -31,7 +32,7 @@ export default function AssetActions(): ReactElement {
|
||||
useEffect(() => {
|
||||
if (!ddo || !accountId) return
|
||||
async function checkIsConsumable() {
|
||||
const consumable = await ocean.assets.isConsumable(
|
||||
const consumable: any = await ocean.assets.isConsumable(
|
||||
ddo,
|
||||
accountId.toLowerCase()
|
||||
)
|
||||
@ -72,6 +73,7 @@ export default function AssetActions(): ReactElement {
|
||||
// Get and set user DT balance
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const dtBalance = await ocean.datatokens.balance(
|
||||
@ -84,7 +86,7 @@ export default function AssetActions(): ReactElement {
|
||||
}
|
||||
}
|
||||
init()
|
||||
}, [ocean, accountId, ddo.dataToken])
|
||||
}, [ocean, accountId, ddo.dataToken, isAssetNetwork])
|
||||
|
||||
// Check user balance against price
|
||||
useEffect(() => {
|
||||
@ -141,8 +143,14 @@ export default function AssetActions(): ReactElement {
|
||||
)
|
||||
|
||||
return (
|
||||
<Permission eventType="consume">
|
||||
<Tabs items={tabs} className={styles.actions} />
|
||||
</Permission>
|
||||
<>
|
||||
<Permission eventType="consume">
|
||||
<Tabs items={tabs} className={styles.actions} />
|
||||
</Permission>
|
||||
<Web3Feedback
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
isAssetNetwork={isAssetNetwork}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import styles from './AlgorithmDatasetsListForCompute.module.css'
|
||||
import { getAlgorithmDatasetsForCompute } from '../../../utils/aquarius'
|
||||
import { AssetSelectionAsset } from '../../molecules/FormFields/AssetSelection'
|
||||
import AssetComputeList from '../../molecules/AssetComputeList'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
@ -14,7 +13,6 @@ export default function AlgorithmDatasetsListForCompute({
|
||||
algorithmDid: string
|
||||
dataset: DDO
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { type } = useAsset()
|
||||
const [datasetsForCompute, setDatasetsForCompute] =
|
||||
useState<AssetSelectionAsset[]>()
|
||||
@ -28,7 +26,7 @@ export default function AlgorithmDatasetsListForCompute({
|
||||
const datasets = await getAlgorithmDatasetsForCompute(
|
||||
algorithmDid,
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
config.metadataCacheUri
|
||||
dataset?.chainId
|
||||
)
|
||||
setDatasetsForCompute(datasets)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
.bookmark {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
top: -3px;
|
||||
right: calc(var(--spacer) / 8);
|
||||
appearance: none;
|
||||
background: none;
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
.bookmark:hover,
|
||||
.bookmark:focus {
|
||||
transform: translate3d(0, 6px, 0);
|
||||
transform: translate3d(0, -3px, 0);
|
||||
}
|
||||
|
||||
.bookmark.active svg {
|
||||
|
@ -2,15 +2,12 @@ import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Bookmark.module.css'
|
||||
import { ReactComponent as BookmarkIcon } from '../../../images/bookmark.svg'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
|
||||
export default function Bookmark({ did }: { did: string }): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { bookmarks, addBookmark, removeBookmark } = useUserPreferences()
|
||||
const isBookmarked =
|
||||
bookmarks &&
|
||||
bookmarks[(config as ConfigHelperConfig).network]?.includes(did)
|
||||
const isBookmarked = bookmarks && bookmarks?.includes(did)
|
||||
|
||||
function handleBookmark() {
|
||||
isBookmarked ? removeBookmark(did) : addBookmark(did)
|
||||
|
@ -3,7 +3,7 @@ import { useAsset } from '../../../providers/Asset'
|
||||
import ExplorerLink from '../../atoms/ExplorerLink'
|
||||
import Time from '../../atoms/Time'
|
||||
import styles from './EditHistory.module.css'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { gql, useQuery } from 'urql'
|
||||
import { ReceiptData_datatokens_updates as ReceiptData } from '../../../@types/apollo/ReceiptData'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
|
||||
@ -22,9 +22,11 @@ const getReceipts = gql`
|
||||
export default function EditHistory(): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
const { ddo } = useAsset()
|
||||
const { data } = useQuery(getReceipts, {
|
||||
const [result] = useQuery({
|
||||
query: getReceipts,
|
||||
variables: { address: ddo?.dataToken.toLowerCase() }
|
||||
})
|
||||
const { data } = result
|
||||
|
||||
const [receipts, setReceipts] = useState<ReceiptData[]>()
|
||||
const [creationTx, setCreationTx] = useState<string>()
|
||||
@ -48,14 +50,14 @@ export default function EditHistory(): ReactElement {
|
||||
<ul className={styles.history}>
|
||||
{receipts?.map((receipt) => (
|
||||
<li key={receipt.id} className={styles.item}>
|
||||
<ExplorerLink networkId={networkId} path={`/tx/${receipt.tx}`}>
|
||||
<ExplorerLink networkId={ddo.chainId} path={`/tx/${receipt.tx}`}>
|
||||
edited{' '}
|
||||
<Time date={receipt.timestamp.toString()} relative isUnix />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.item}>
|
||||
<ExplorerLink networkId={networkId} path={`/tx/${creationTx}`}>
|
||||
<ExplorerLink networkId={ddo.chainId} path={`/tx/${creationTx}`}>
|
||||
published <Time date={ddo.created} relative />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
|
@ -9,7 +9,7 @@ import AssetType from '../../atoms/AssetType'
|
||||
import styles from './MetaMain.module.css'
|
||||
|
||||
export default function MetaMain(): ReactElement {
|
||||
const { ddo, owner, type } = useAsset()
|
||||
const { ddo, owner, type, isAssetNetwork } = useAsset()
|
||||
const { networkId, web3ProviderInfo } = useWeb3()
|
||||
const isCompute = Boolean(ddo?.findServiceByType('compute'))
|
||||
const accessType = isCompute ? 'compute' : 'access'
|
||||
@ -34,7 +34,7 @@ export default function MetaMain(): ReactElement {
|
||||
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||
</ExplorerLink>
|
||||
|
||||
{web3ProviderInfo?.name === 'MetaMask' && (
|
||||
{web3ProviderInfo?.name === 'MetaMask' && isAssetNetwork && (
|
||||
<span className={styles.addWrap}>
|
||||
<AddToken
|
||||
address={ddo?.dataTokenInfo.address}
|
||||
|
@ -23,8 +23,8 @@ export default function Dynamic({
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
const { account, balance } = useOcean()
|
||||
const { networkId, balance } = useWeb3()
|
||||
const { account } = useOcean()
|
||||
const [firstPrice, setFirstPrice] = useState<string>()
|
||||
|
||||
// Connect with form
|
||||
|
@ -1,8 +1,13 @@
|
||||
.networkWrap {
|
||||
display: block;
|
||||
margin-top: -1rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) * 1.5);
|
||||
position: relative;
|
||||
margin-top: -1.5rem;
|
||||
margin-top: -1rem;
|
||||
}
|
||||
|
||||
.grid > div {
|
||||
|
@ -19,6 +19,7 @@ import { useWeb3 } from '../../../providers/Web3'
|
||||
import styles from './index.module.css'
|
||||
import EditAdvancedSettings from '../AssetActions/Edit/EditAdvancedSettings'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import NetworkName from '../../atoms/NetworkName'
|
||||
|
||||
export interface AssetContentProps {
|
||||
path?: string
|
||||
@ -46,7 +47,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
const content = data.purgatory.edges[0].node.childContentJson.asset
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const { owner, isInPurgatory, purgatoryData } = useAsset()
|
||||
const { owner, isInPurgatory, purgatoryData, isAssetNetwork } = useAsset()
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const [showEdit, setShowEdit] = useState<boolean>()
|
||||
const [showEditCompute, setShowEditCompute] = useState<boolean>()
|
||||
@ -87,72 +88,82 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
) : showEditAdvancedSettings ? (
|
||||
<EditAdvancedSettings setShowEdit={setShowEditAdvancedSettings} />
|
||||
) : (
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
{showPricing && <Pricing ddo={ddo} />}
|
||||
<div className={styles.content}>
|
||||
<MetaMain />
|
||||
<Bookmark did={ddo.id} />
|
||||
<>
|
||||
<div className={styles.networkWrap}>
|
||||
<NetworkName networkId={ddo.chainId} className={styles.network} />
|
||||
</div>
|
||||
|
||||
{isInPurgatory ? (
|
||||
<Alert
|
||||
title={content.title}
|
||||
badge={`Reason: ${purgatoryData?.reason}`}
|
||||
text={content.description}
|
||||
state="error"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Markdown
|
||||
className={styles.description}
|
||||
text={metadata?.additionalInformation?.description || ''}
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
{showPricing && <Pricing ddo={ddo} />}
|
||||
<div className={styles.content}>
|
||||
<MetaMain />
|
||||
<Bookmark did={ddo.id} />
|
||||
|
||||
{isInPurgatory ? (
|
||||
<Alert
|
||||
title={content.title}
|
||||
badge={`Reason: ${purgatoryData?.reason}`}
|
||||
text={content.description}
|
||||
state="error"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Markdown
|
||||
className={styles.description}
|
||||
text={metadata?.additionalInformation?.description || ''}
|
||||
/>
|
||||
|
||||
<MetaSecondary />
|
||||
<MetaSecondary />
|
||||
|
||||
{isOwner && (
|
||||
<div className={styles.ownerActions}>
|
||||
<Button style="text" size="small" onClick={handleEditButton}>
|
||||
Edit Metadata
|
||||
</Button>
|
||||
{appConfig.allowAdvancedSettings === 'true' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditAdvancedSettingsButton}
|
||||
>
|
||||
Edit Advanced Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{ddo.findServiceByType('compute') && type === 'dataset' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditComputeButton}
|
||||
>
|
||||
Edit Compute Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isOwner && isAssetNetwork && (
|
||||
<div className={styles.ownerActions}>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditButton}
|
||||
>
|
||||
Edit Metadata
|
||||
</Button>
|
||||
{appConfig.allowAdvancedSettings === 'true' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditAdvancedSettingsButton}
|
||||
>
|
||||
Edit Advanced Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{ddo.findServiceByType('compute') && type === 'dataset' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditComputeButton}
|
||||
>
|
||||
Edit Compute Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<MetaFull />
|
||||
<EditHistory />
|
||||
{debug === true && <DebugOutput title="DDO" output={ddo} />}
|
||||
<MetaFull />
|
||||
<EditHistory />
|
||||
{debug === true && <DebugOutput title="DDO" output={ddo} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<AssetActions />
|
||||
</div>
|
||||
</article>
|
||||
<div className={styles.actions}>
|
||||
<AssetActions />
|
||||
</div>
|
||||
</article>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ export default function Footer(): ReactElement {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.content}>
|
||||
<SyncStatus /> | <BuildId />
|
||||
{/* <SyncStatus /> | */}
|
||||
<BuildId />
|
||||
<MarketStats />
|
||||
<div className={styles.copyright}>
|
||||
© {year} <Markdown text={copyright} /> —{' '}
|
||||
|
@ -10,6 +10,7 @@ import { retrieveDDO } from '../../../../utils/aquarius'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import Results from './Results'
|
||||
import styles from './Details.module.css'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
|
||||
function Asset({
|
||||
title,
|
||||
@ -41,7 +42,7 @@ function Asset({
|
||||
}
|
||||
|
||||
function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
|
||||
const { config } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [algoName, setAlgoName] = useState<string>()
|
||||
const [algoDtSymbol, setAlgoDtSymbol] = useState<string>()
|
||||
|
||||
@ -49,18 +50,14 @@ function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
|
||||
async function getAlgoMetadata() {
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
const ddo = await retrieveDDO(
|
||||
job.algoDID,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const ddo = await retrieveDDO(job.algoDID, source.token)
|
||||
setAlgoDtSymbol(ddo.dataTokenInfo.symbol)
|
||||
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
setAlgoName(attributes?.main.name)
|
||||
}
|
||||
getAlgoMetadata()
|
||||
}, [config?.metadataCacheUri, job.algoDID])
|
||||
}, [appConfig.metadataCacheUri, job.algoDID])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user