mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into feature/issue-1244-edit-ui-highlights-clear
This commit is contained in:
commit
a9cf3bdc0a
@ -42,7 +42,7 @@ exclude_patterns:
|
|||||||
- '**/*.d.ts'
|
- '**/*.d.ts'
|
||||||
- '**/@types/'
|
- '**/@types/'
|
||||||
- '**/_types.*'
|
- '**/_types.*'
|
||||||
- '**/*.stories.tsx'
|
- '**/*.stories.*'
|
||||||
- '**/*.test.tsx'
|
- '**/*.test.*'
|
||||||
- '.storybook/'
|
- '.storybook/'
|
||||||
- '.jest/'
|
- '.jest/'
|
||||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1 +1 @@
|
|||||||
* @mihaisc @kremalicious @claudiaHash @bogdanfazakas @EnzoVezzaro
|
* @jamiehewitt15 @mihaisc @kremalicious @bogdanfazakas @EnzoVezzaro
|
||||||
|
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- v4
|
|
||||||
- v3
|
- v3
|
||||||
tags:
|
tags:
|
||||||
- '**'
|
- '**'
|
||||||
@ -20,7 +19,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
node: ['16']
|
node: ['18']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -47,7 +46,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
node: ['16']
|
node: ['18']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -82,7 +81,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
env:
|
env:
|
||||||
@ -110,7 +109,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
node: ['16']
|
node: ['18']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -12,6 +12,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|
||||||
- run: npm run build:static
|
- run: npm run build:static
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@ coverage
|
|||||||
.next
|
.next
|
||||||
.artifacts
|
.artifacts
|
||||||
.vercel
|
.vercel
|
||||||
|
.swc
|
||||||
repo-metadata.json
|
repo-metadata.json
|
||||||
networks-metadata.json
|
networks-metadata.json
|
||||||
src/@types/subgraph
|
src/@types/subgraph
|
||||||
|
91
.jest/__fixtures__/algorithmAquarius.ts
Normal file
91
.jest/__fixtures__/algorithmAquarius.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
|
export const algorithmAquarius: Asset = {
|
||||||
|
'@context': ['https://w3id.org/did/v1'],
|
||||||
|
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
|
||||||
|
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
version: '4.1.0',
|
||||||
|
chainId: 1,
|
||||||
|
metadata: {
|
||||||
|
created: '2022-09-29T11:30:26Z',
|
||||||
|
updated: '2022-09-29T11:30:26Z',
|
||||||
|
type: 'algorithm',
|
||||||
|
name: 'algorithmTestitest',
|
||||||
|
description: 'This is an algorithm test.',
|
||||||
|
links: ['https://www.oceanprotocol.com/sample'],
|
||||||
|
tags: [
|
||||||
|
'trading',
|
||||||
|
'defi',
|
||||||
|
'algorithm',
|
||||||
|
'algorithmic-crypto-trading',
|
||||||
|
'algo-trading',
|
||||||
|
'trading-strategy',
|
||||||
|
'cryptocurrency',
|
||||||
|
'crypto'
|
||||||
|
],
|
||||||
|
author: 'Test User',
|
||||||
|
license: 'https://market.oceanprotocol.com/terms',
|
||||||
|
additionalInformation: {
|
||||||
|
termsAndConditions: true
|
||||||
|
},
|
||||||
|
algorithm: {
|
||||||
|
language: 'json',
|
||||||
|
version: '0.1',
|
||||||
|
container: {
|
||||||
|
entrypoint: 'python $algo',
|
||||||
|
tag: 'latest',
|
||||||
|
image: 'https://docker.com/test.img',
|
||||||
|
checksum: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
id: 'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1',
|
||||||
|
type: 'access',
|
||||||
|
files:
|
||||||
|
'0x04022ef1afafe340f41b261ef721b8dd55dee094975cc70330803d760beef38871948ce572ff1c533d56cda2665749ed2eb8283e243ec5ee19011f510b6b263b2da0af537e3f1fdff7ddd90fa26c7a4761a6d26928bc1348a302634012aac7998e92c84456ab73e9a847120c44ebda15781787e8c382391b2eaefc8b8d36998f3998d1c4647f4f7bb28f4278093c1d231f66e78f81452049443b9e540aeb42ebbdc1b748c024eb10218532814736e241efa1c2a687685b4e2ea7a877685aa0ea325d1a8cf765d1b423b32d81ec3c3e22fc9c15c6b9b71f2862edaec4e4cf7c3a638ffc0ecb88ede3cabb511d4780543a53c001a95f42de1877796e13c997b57bc671507e92198934b4ea7c2e6554993388421253e8c2f10458dec872a7ebfa71b6e77ed359222c93261ba252028c5da06ccf8defcd529885b2125816325a47e23728b513',
|
||||||
|
datatokenAddress: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
|
||||||
|
serviceEndpoint: 'https://v4.provider.goerli.oceanprotocol.com',
|
||||||
|
timeout: 604800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
event: {
|
||||||
|
tx: '0x3e07a75c1cc5d4146222a93ab4319144e60ecca3ebfb8b15f1ff339d6f479dc9',
|
||||||
|
block: 7680195,
|
||||||
|
from: '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
|
||||||
|
contract: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
datetime: '2022-09-29T11:31:12'
|
||||||
|
},
|
||||||
|
nft: {
|
||||||
|
address: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
name: 'Ocean Data NFT',
|
||||||
|
symbol: 'OCEAN-NFT',
|
||||||
|
state: 0,
|
||||||
|
tokenURI:
|
||||||
|
'data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDo2NjU0YjA3OTM3NjViMjY5Njk2Y2VjOGQyZjBkMDc3ZDliYmNkZDNjNGYwMzNkOTQxYWI5Njg0ZThhZDA2NjMwIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjY2NTRiMDc5Mzc2NWIyNjk2OTZjZWM4ZDJmMGQwNzdkOWJiY2RkM2M0ZjAzM2Q5NDFhYjk2ODRlOGFkMDY2MzAiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDI5QzksMjUgMTksMjIgMjksMjFDMzgsMTkgNDksMTkgNjEsMjFDNzIsMjIgODUsMjUgOTksMjlMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDU1QzgsNDkgMTcsNDQgMjgsNDNDMzgsNDEgNTAsNDIgNjMsNDNDNzUsNDMgODcsNDIgOTksNDJMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw2OEMxMSw2NiAyMiw2NSAzMiw2N0M0MSw2OCA1MCw3MyA2MSw3NkM3MSw3OCA4NSw3OCA5OSw3OUw5OSw5OVonJTNFJTNDL3BhdGglM0UlM0Mvc3ZnJTNFIn0=',
|
||||||
|
owner: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
|
||||||
|
created: '2022-09-29T11:31:12'
|
||||||
|
},
|
||||||
|
datatokens: [
|
||||||
|
{
|
||||||
|
address: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
|
||||||
|
name: 'Stupendous Orca Token',
|
||||||
|
symbol: 'STUORC-59',
|
||||||
|
serviceId:
|
||||||
|
'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
stats: {
|
||||||
|
orders: 22,
|
||||||
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
purgatory: {
|
||||||
|
state: false,
|
||||||
|
reason: ''
|
||||||
|
}
|
||||||
|
}
|
80
.jest/__fixtures__/datasetAquarius.ts
Normal file
80
.jest/__fixtures__/datasetAquarius.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
|
export const datasetAquarius: Asset = {
|
||||||
|
'@context': ['https://w3id.org/did/v1'],
|
||||||
|
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
|
||||||
|
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
version: '4.1.0',
|
||||||
|
chainId: 1,
|
||||||
|
metadata: {
|
||||||
|
created: '2022-09-29T11:30:26Z',
|
||||||
|
updated: '2022-09-29T11:30:26Z',
|
||||||
|
type: 'dataset',
|
||||||
|
name: 'Testitest',
|
||||||
|
description: 'This is a test.',
|
||||||
|
tags: [
|
||||||
|
'trading',
|
||||||
|
'defi',
|
||||||
|
'algorithm',
|
||||||
|
'algorithmic-crypto-trading',
|
||||||
|
'algo-trading',
|
||||||
|
'trading-strategy',
|
||||||
|
'cryptocurrency',
|
||||||
|
'crypto'
|
||||||
|
],
|
||||||
|
author: 'Test User',
|
||||||
|
license: 'https://market.oceanprotocol.com/terms',
|
||||||
|
additionalInformation: {
|
||||||
|
termsAndConditions: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
id: 'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1',
|
||||||
|
type: 'access',
|
||||||
|
files:
|
||||||
|
'0x04022ef1afafe340f41b261ef721b8dd55dee094975cc70330803d760beef38871948ce572ff1c533d56cda2665749ed2eb8283e243ec5ee19011f510b6b263b2da0af537e3f1fdff7ddd90fa26c7a4761a6d26928bc1348a302634012aac7998e92c84456ab73e9a847120c44ebda15781787e8c382391b2eaefc8b8d36998f3998d1c4647f4f7bb28f4278093c1d231f66e78f81452049443b9e540aeb42ebbdc1b748c024eb10218532814736e241efa1c2a687685b4e2ea7a877685aa0ea325d1a8cf765d1b423b32d81ec3c3e22fc9c15c6b9b71f2862edaec4e4cf7c3a638ffc0ecb88ede3cabb511d4780543a53c001a95f42de1877796e13c997b57bc671507e92198934b4ea7c2e6554993388421253e8c2f10458dec872a7ebfa71b6e77ed359222c93261ba252028c5da06ccf8defcd529885b2125816325a47e23728b513',
|
||||||
|
datatokenAddress: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
|
||||||
|
serviceEndpoint: 'https://v4.provider.goerli.oceanprotocol.com',
|
||||||
|
timeout: 604800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
event: {
|
||||||
|
tx: '0x3e07a75c1cc5d4146222a93ab4319144e60ecca3ebfb8b15f1ff339d6f479dc9',
|
||||||
|
block: 7680195,
|
||||||
|
from: '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
|
||||||
|
contract: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
datetime: '2022-09-29T11:31:12'
|
||||||
|
},
|
||||||
|
nft: {
|
||||||
|
address: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
|
name: 'Ocean Data NFT',
|
||||||
|
symbol: 'OCEAN-NFT',
|
||||||
|
state: 0,
|
||||||
|
tokenURI:
|
||||||
|
'data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDo2NjU0YjA3OTM3NjViMjY5Njk2Y2VjOGQyZjBkMDc3ZDliYmNkZDNjNGYwMzNkOTQxYWI5Njg0ZThhZDA2NjMwIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjY2NTRiMDc5Mzc2NWIyNjk2OTZjZWM4ZDJmMGQwNzdkOWJiY2RkM2M0ZjAzM2Q5NDFhYjk2ODRlOGFkMDY2MzAiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDI5QzksMjUgMTksMjIgMjksMjFDMzgsMTkgNDksMTkgNjEsMjFDNzIsMjIgODUsMjUgOTksMjlMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDU1QzgsNDkgMTcsNDQgMjgsNDNDMzgsNDEgNTAsNDIgNjMsNDNDNzUsNDMgODcsNDIgOTksNDJMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw2OEMxMSw2NiAyMiw2NSAzMiw2N0M0MSw2OCA1MCw3MyA2MSw3NkM3MSw3OCA4NSw3OCA5OSw3OUw5OSw5OVonJTNFJTNDL3BhdGglM0UlM0Mvc3ZnJTNFIn0=',
|
||||||
|
owner: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
|
||||||
|
created: '2022-09-29T11:31:12'
|
||||||
|
},
|
||||||
|
datatokens: [
|
||||||
|
{
|
||||||
|
address: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
|
||||||
|
name: 'Stupendous Orca Token',
|
||||||
|
symbol: 'STUORC-59',
|
||||||
|
serviceId:
|
||||||
|
'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
stats: {
|
||||||
|
orders: 22,
|
||||||
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
purgatory: {
|
||||||
|
state: false,
|
||||||
|
reason: ''
|
||||||
|
}
|
||||||
|
}
|
27
.jest/__fixtures__/datasetWithAccessDetails.ts
Normal file
27
.jest/__fixtures__/datasetWithAccessDetails.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { datasetAquarius } from './datasetAquarius'
|
||||||
|
|
||||||
|
export const asset: AssetExtended = {
|
||||||
|
...datasetAquarius,
|
||||||
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
|
publisherMarketOrderFee: '0',
|
||||||
|
type: 'fixed',
|
||||||
|
addressOrId:
|
||||||
|
'0x00e3b740e4d8bf6e97010ecb5b14d1b7efc0913bfa291fcf5adb8eb9e6c29e93',
|
||||||
|
price: '3231343254',
|
||||||
|
isPurchasable: true,
|
||||||
|
isOwned: false,
|
||||||
|
validOrderTx: null,
|
||||||
|
baseToken: {
|
||||||
|
address: '0xcfdda22c9837ae76e0faa845354f33c62e03653a',
|
||||||
|
name: 'Ocean Token',
|
||||||
|
symbol: 'OCEAN',
|
||||||
|
decimals: 18
|
||||||
|
},
|
||||||
|
datatoken: {
|
||||||
|
address: '0x067e1e6ec580f3f0f6781679a4a5ab07a6464b08',
|
||||||
|
name: 'Stupendous Orca Token',
|
||||||
|
symbol: 'STUORC-59'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1577
.jest/__fixtures__/datasetsWithAccessDetails.ts
Normal file
1577
.jest/__fixtures__/datasetsWithAccessDetails.ts
Normal file
File diff suppressed because it is too large
Load Diff
64
.jest/__fixtures__/prices.ts
Normal file
64
.jest/__fixtures__/prices.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
export default {
|
||||||
|
prices: {
|
||||||
|
h2o: {
|
||||||
|
btc: 0.00009013,
|
||||||
|
cad: 2.38,
|
||||||
|
cny: 12.36,
|
||||||
|
eth: 0.00132713,
|
||||||
|
eur: 1.78,
|
||||||
|
gbp: 1.55,
|
||||||
|
hkd: 13.53,
|
||||||
|
inr: 141.78,
|
||||||
|
jpy: 253.21,
|
||||||
|
link: 0.24050954,
|
||||||
|
rub: 109.81,
|
||||||
|
sgd: 2.47,
|
||||||
|
usd: 1.72
|
||||||
|
},
|
||||||
|
'matic-network': {
|
||||||
|
btc: 0.0000414,
|
||||||
|
cad: 1.093,
|
||||||
|
cny: 5.67,
|
||||||
|
eth: 0.00061102,
|
||||||
|
eur: 0.816959,
|
||||||
|
gbp: 0.715112,
|
||||||
|
hkd: 6.21,
|
||||||
|
inr: 65.06,
|
||||||
|
jpy: 116.23,
|
||||||
|
link: 0.11072143,
|
||||||
|
rub: 50.4,
|
||||||
|
sgd: 1.14,
|
||||||
|
usd: 0.790922
|
||||||
|
},
|
||||||
|
ethereum: {
|
||||||
|
btc: 0.06775775,
|
||||||
|
cad: 1789.03,
|
||||||
|
cny: 9288.09,
|
||||||
|
eth: 1,
|
||||||
|
eur: 1337.1,
|
||||||
|
gbp: 1170.41,
|
||||||
|
hkd: 10161.71,
|
||||||
|
inr: 106490,
|
||||||
|
jpy: 190239,
|
||||||
|
link: 181.216,
|
||||||
|
rub: 82491,
|
||||||
|
sgd: 1859.58,
|
||||||
|
usd: 1294.49
|
||||||
|
},
|
||||||
|
'ocean-protocol': {
|
||||||
|
btc: 0.00000809,
|
||||||
|
cad: 0.213554,
|
||||||
|
cny: 1.11,
|
||||||
|
eth: 0.00011937,
|
||||||
|
eur: 0.159608,
|
||||||
|
gbp: 0.13971,
|
||||||
|
hkd: 1.21,
|
||||||
|
inr: 12.71,
|
||||||
|
jpy: 22.71,
|
||||||
|
link: 0.02163146,
|
||||||
|
rub: 9.85,
|
||||||
|
sgd: 0.221976,
|
||||||
|
usd: 0.154521
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
.jest/__fixtures__/table.ts
Normal file
41
.jest/__fixtures__/table.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export const columns = [
|
||||||
|
{
|
||||||
|
name: 'Name',
|
||||||
|
selector: (row: any) => row.name,
|
||||||
|
maxWidth: '45rem',
|
||||||
|
grow: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Symbol',
|
||||||
|
selector: (row: any) => row.symbol,
|
||||||
|
maxWidth: '10rem'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Price',
|
||||||
|
selector: (row: any) => row.price,
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const data = [
|
||||||
|
{
|
||||||
|
name: 'Title asset',
|
||||||
|
symbol: 'DATA-70',
|
||||||
|
price: '1.011'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Title asset Title asset Title asset Title asset Title asset',
|
||||||
|
symbol: 'DATA-71',
|
||||||
|
price: '1.011'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Title asset',
|
||||||
|
symbol: 'DATA-72',
|
||||||
|
price: '1.011'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset Title asset',
|
||||||
|
symbol: 'DATA-71',
|
||||||
|
price: '1.011'
|
||||||
|
}
|
||||||
|
]
|
9
.jest/__fixtures__/userPreferences.ts
Normal file
9
.jest/__fixtures__/userPreferences.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
debug: true,
|
||||||
|
currency: 'EUR',
|
||||||
|
locale: 'en-US',
|
||||||
|
chainIds: [5, 1, 137, 56, 1285, 246],
|
||||||
|
bookmarks: [],
|
||||||
|
privacyPolicySlug: '/privacy/en',
|
||||||
|
showPPC: true
|
||||||
|
}
|
33
.jest/__fixtures__/web3.ts
Normal file
33
.jest/__fixtures__/web3.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export default {
|
||||||
|
accountEns: 'jellymcjellyfish.eth',
|
||||||
|
accountEnsAvatar:
|
||||||
|
'https://metadata.ens.domains/mainnet/avatar/jellymcjellyfish.eth',
|
||||||
|
accountId: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
|
||||||
|
approvedBaseTokens: [
|
||||||
|
{
|
||||||
|
address: '0xcfdda22c9837ae76e0faa845354f33c62e03653a',
|
||||||
|
symbol: 'OCEAN',
|
||||||
|
name: 'Ocean Token',
|
||||||
|
decimals: 18
|
||||||
|
}
|
||||||
|
],
|
||||||
|
balance: { eth: '0', ocean: '1000' },
|
||||||
|
block: 7751969,
|
||||||
|
chainId: 5,
|
||||||
|
connect: jest.fn(),
|
||||||
|
isSupportedOceanNetwork: true,
|
||||||
|
isTestnet: true,
|
||||||
|
logout: jest.fn(),
|
||||||
|
networkData: { name: 'Görli', title: 'Ethereum Testnet Görli', chain: 'ETH' },
|
||||||
|
networkDisplayName: 'ETH Görli',
|
||||||
|
networkId: 5,
|
||||||
|
web3: { currentProvider: {} },
|
||||||
|
web3Loading: false,
|
||||||
|
web3Modal: { show: false, eventController: {}, connect: jest.fn() },
|
||||||
|
web3Provider: {},
|
||||||
|
web3ProviderInfo: {
|
||||||
|
id: 'injected',
|
||||||
|
name: 'MetaMask',
|
||||||
|
logo: ''
|
||||||
|
}
|
||||||
|
}
|
3
.jest/__mocks__/@utils/accessDetailsAndPricing.ts
Normal file
3
.jest/__mocks__/@utils/accessDetailsAndPricing.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { assets } from '../../__fixtures__/datasetsWithAccessDetails'
|
||||||
|
|
||||||
|
export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets)
|
20
.jest/__mocks__/hooksMocks.ts
Normal file
20
.jest/__mocks__/hooksMocks.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import marketMetadata from '../__fixtures__/marketMetadata'
|
||||||
|
import userPreferences from '../__fixtures__/userPreferences'
|
||||||
|
import web3 from '../__fixtures__/web3'
|
||||||
|
import { asset } from '../__fixtures__/datasetWithAccessDetails'
|
||||||
|
|
||||||
|
jest.mock('../../src/@context/MarketMetadata', () => ({
|
||||||
|
useMarketMetadata: () => marketMetadata
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../src/@context/UserPreferences', () => ({
|
||||||
|
useUserPreferences: () => userPreferences
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../src/@context/Web3', () => ({
|
||||||
|
useWeb3: () => web3
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('../../../@context/Asset', () => ({
|
||||||
|
useAsset: () => ({ asset })
|
||||||
|
}))
|
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default true
|
3
.jest/__mocks__/is-url-superb/index.ts
Normal file
3
.jest/__mocks__/is-url-superb/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function isUrl() {
|
||||||
|
return true
|
||||||
|
}
|
3
.jest/__mocks__/tar.ts
Normal file
3
.jest/__mocks__/tar.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// mocked, as this module makes Jest go all
|
||||||
|
// "Uncaught SyntaxError: Octal escape sequences are not allowed in strict mode"
|
||||||
|
export default jest.fn().mockImplementation(() => 'hello')
|
@ -9,13 +9,13 @@ const createJestConfig = nextJest({
|
|||||||
const customJestConfig = {
|
const customJestConfig = {
|
||||||
rootDir: '../',
|
rootDir: '../',
|
||||||
// Add more setup options before each test is run
|
// Add more setup options before each test is run
|
||||||
setupFilesAfterEnv: ['<rootDir>/.jest/jest.setup.js'],
|
setupFilesAfterEnv: ['<rootDir>/.jest/jest.setup.tsx'],
|
||||||
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
|
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
|
||||||
moduleDirectories: ['node_modules', '<rootDir>/src'],
|
moduleDirectories: ['node_modules', '<rootDir>/src'],
|
||||||
testEnvironment: 'jest-environment-jsdom',
|
testEnvironment: 'jsdom',
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^.+\\.(svg)$': '<rootDir>/.jest/__mocks__/svgrMock.tsx',
|
'^.+\\.(svg)$': '<rootDir>/.jest/__mocks__/svgrMock.tsx',
|
||||||
// '^@/components/(.*)$': '<rootDir>/components/$1',
|
'@components/(.*)$': '<rootDir>/src/components/$1',
|
||||||
'@shared(.*)$': '<rootDir>/src/components/@shared/$1',
|
'@shared(.*)$': '<rootDir>/src/components/@shared/$1',
|
||||||
'@hooks/(.*)$': '<rootDir>/src/@hooks/$1',
|
'@hooks/(.*)$': '<rootDir>/src/@hooks/$1',
|
||||||
'@context/(.*)$': '<rootDir>/src/@context/$1',
|
'@context/(.*)$': '<rootDir>/src/@context/$1',
|
||||||
@ -29,8 +29,25 @@ const customJestConfig = {
|
|||||||
'!src/**/*.{stories,test}.{ts,tsx}',
|
'!src/**/*.{stories,test}.{ts,tsx}',
|
||||||
'!src/@types/**/*.{ts,tsx}'
|
'!src/@types/**/*.{ts,tsx}'
|
||||||
],
|
],
|
||||||
testPathIgnorePatterns: ['node_modules', '\\.cache', '.next', 'coverage']
|
// Add ignores so ESM packages are not transformed by Jest
|
||||||
|
// note: this does not work with Next.js, hence workaround further down
|
||||||
|
// see: https://github.com/vercel/next.js/issues/35634#issuecomment-1115250297
|
||||||
|
// transformIgnorePatterns: ['node_modules/(?!(uuid|remark)/)'],
|
||||||
|
testPathIgnorePatterns: [
|
||||||
|
'<rootDir>/node_modules/',
|
||||||
|
'<rootDir>/.next/',
|
||||||
|
'<rootDir>/coverage'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
// https://github.com/vercel/next.js/issues/35634#issuecomment-1115250297
|
||||||
module.exports = createJestConfig(customJestConfig)
|
async function jestConfig() {
|
||||||
|
const nextJestConfig = await createJestConfig(customJestConfig)()
|
||||||
|
|
||||||
|
// Add ignores for specific ESM packages so they are transformed by Jest
|
||||||
|
// /node_modules/ is the first pattern
|
||||||
|
nextJestConfig.transformIgnorePatterns[0] = '/node_modules/(?!uuid|remark)/'
|
||||||
|
return nextJestConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = jestConfig
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import '@testing-library/jest-dom/extend-expect'
|
|
||||||
import './__mocks__/matchMedia'
|
|
||||||
import marketMetadataMock from './__mocks__/MarketMetadata'
|
|
||||||
|
|
||||||
jest.mock('../../src/@context/MarketMetadata', () => ({
|
|
||||||
useMarketMetadata: () => marketMetadataMock
|
|
||||||
}))
|
|
20
.jest/jest.setup.tsx
Normal file
20
.jest/jest.setup.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
|
import { jest } from '@jest/globals'
|
||||||
|
import './__mocks__/matchMedia'
|
||||||
|
import './__mocks__/hooksMocks'
|
||||||
|
|
||||||
|
jest.mock('next/router', () => ({
|
||||||
|
useRouter: jest.fn().mockImplementation(() => ({
|
||||||
|
route: '/',
|
||||||
|
pathname: '/'
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
// jest.mock('next/head', () => {
|
||||||
|
// return {
|
||||||
|
// __esModule: true,
|
||||||
|
// default: ({ children }: { children: Array<React.ReactElement> }) => {
|
||||||
|
// return <>{children}</>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
@ -39,6 +39,12 @@
|
|||||||
|
|
||||||
The app is a React app built with [Next.js](https://nextjs.org) + TypeScript + CSS modules and will connect to Ocean remote components by default.
|
The app is a React app built with [Next.js](https://nextjs.org) + TypeScript + CSS modules and will connect to Ocean remote components by default.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/) (required). Check the [.nvmrc](.nvmrc) file to make sure you are using the correct version of Node.js.
|
||||||
|
- [nvm](https://github.com/nvm-sh/nvm) (recommended). This is the recommend way to manage Node.js versions.
|
||||||
|
- [Git](https://git-scm.com/) is required to follow the instructions below.
|
||||||
|
|
||||||
To start local development:
|
To start local development:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -253,7 +259,7 @@ export default function NetworkName(): ReactElement {
|
|||||||
const { networkId, isTestnet } = useWeb3()
|
const { networkId, isTestnet } = useWeb3()
|
||||||
const { networksList } = useNetworkMetadata()
|
const { networksList } = useNetworkMetadata()
|
||||||
const networkData = getNetworkDataById(networksList, networkId)
|
const networkData = getNetworkDataById(networksList, networkId)
|
||||||
const networkName = getNetworkDisplayName(networkData, networkId)
|
const networkName = getNetworkDisplayName(networkData)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -14,10 +14,11 @@ module.exports = {
|
|||||||
chainIds: [1, 137, 56, 246, 1285],
|
chainIds: [1, 137, 56, 246, 1285],
|
||||||
|
|
||||||
// List of all supported chainIds. Used to populate the Chains user preferences list.
|
// List of all supported chainIds. Used to populate the Chains user preferences list.
|
||||||
chainIdsSupported: [1, 137, 56, 246, 1285, 5, 80001, 1287],
|
chainIdsSupported: [1, 137, 56, 246, 1285, 5, 80001],
|
||||||
|
|
||||||
infuraProjectId: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID || 'xxx',
|
infuraProjectId: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID || 'xxx',
|
||||||
|
|
||||||
|
defaultDatatokenTemplateIndex: 2,
|
||||||
// The ETH address the marketplace fee will be sent to.
|
// The ETH address the marketplace fee will be sent to.
|
||||||
marketFeeAddress:
|
marketFeeAddress:
|
||||||
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
||||||
|
@ -29,19 +29,66 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "New file",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. Leaving this field empty will not remove the current value.",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files"
|
{
|
||||||
|
"value": "ipfs",
|
||||||
|
"title": "IPFS",
|
||||||
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "arweave",
|
||||||
|
"title": "Arweave",
|
||||||
|
"label": "Transaction ID",
|
||||||
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sortOptions": false,
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "links",
|
"name": "links",
|
||||||
"label": "New sample file",
|
"label": "Sample file",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"prominentHelp": false,
|
||||||
"help": "Please provide a URL to a sample of your dataset file. This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** Leaving this field empty will not remove the current value.",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files"
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -66,6 +113,13 @@
|
|||||||
"type": "tags",
|
"type": "tags",
|
||||||
"placeholder": "e.g. logistics",
|
"placeholder": "e.g. logistics",
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paymentCollector",
|
||||||
|
"label": "Payment Collector Address",
|
||||||
|
"placeholder": "e.g. 0X123ABC...",
|
||||||
|
"help": "This address will receive the revenue from all sales. More info available in our [docs](https://docs.oceanprotocol.com/core-concepts/datanft-and-datatoken#revenue).",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,7 @@
|
|||||||
},
|
},
|
||||||
"approval": {
|
"approval": {
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
|
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences."
|
||||||
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,19 +104,65 @@
|
|||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "File",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files",
|
{
|
||||||
|
"value": "ipfs",
|
||||||
|
"title": "IPFS",
|
||||||
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "arweave",
|
||||||
|
"title": "Arweave",
|
||||||
|
"label": "Transaction ID",
|
||||||
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sortOptions": false,
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "links",
|
"name": "links",
|
||||||
"label": "Sample file",
|
"label": "Sample file",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"prominentHelp": false,
|
||||||
"help": "This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files"
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "algorithmPrivacy",
|
"name": "algorithmPrivacy",
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
],
|
],
|
||||||
"announcement": "[Lock your OCEAN](https://df.oceandao.org/) to get veOCEAN, earn rewards and curate data.",
|
"announcement": "[Lock your OCEAN](https://df.oceandao.org/) to get veOCEAN, earn rewards and curate data.",
|
||||||
"warning": {
|
"warning": {
|
||||||
"ctd": "Compute-to-Data is still in a testing phase, please use it only on test networks."
|
"ctd": "Please note that Compute-to-Data is still in alpha phase."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21507
package-lock.json
generated
21507
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
89
package.json
89
package.json
@ -17,7 +17,7 @@
|
|||||||
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
|
"format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
|
||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"deploy:s3": "bash scripts/deploy-s3.sh",
|
"deploy:s3": "bash scripts/deploy-s3.sh",
|
||||||
"postinstall": "husky install && rm -r node_modules/apollo-language-server/node_modules/graphql",
|
"postinstall": "husky install",
|
||||||
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.goerli.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/",
|
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.goerli.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/",
|
||||||
"storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
|
"storybook": "cross-env NODE_ENV=test start-storybook -p 6006 --quiet",
|
||||||
"storybook:build": "cross-env NODE_ENV=test build-storybook"
|
"storybook:build": "cross-env NODE_ENV=test build-storybook"
|
||||||
@ -26,99 +26,100 @@
|
|||||||
"@coingecko/cryptoformat": "^0.5.4",
|
"@coingecko/cryptoformat": "^0.5.4",
|
||||||
"@loadable/component": "^5.15.2",
|
"@loadable/component": "^5.15.2",
|
||||||
"@oceanprotocol/art": "^3.2.0",
|
"@oceanprotocol/art": "^3.2.0",
|
||||||
"@oceanprotocol/lib": "^2.2.3",
|
"@oceanprotocol/lib": "^2.6.0",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"@urql/exchange-refocus": "^1.0.0",
|
"@urql/exchange-refocus": "^1.0.0",
|
||||||
"@walletconnect/web3-provider": "^1.8.0",
|
"@walletconnect/web3-provider": "^1.8.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^1.2.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"decimal.js": "^10.3.1",
|
"decimal.js": "^10.4.2",
|
||||||
"dom-confetti": "^0.2.2",
|
"dom-confetti": "^0.2.2",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.3",
|
||||||
"filesize": "^10.0.5",
|
"filesize": "^10.0.5",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"is-ipfs": "^7.0.3",
|
||||||
"is-url-superb": "^6.1.0",
|
"is-url-superb": "^6.1.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
|
||||||
"lodash.omit": "^4.5.0",
|
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
"myetherwallet-blockies": "^0.1.1",
|
"myetherwallet-blockies": "^0.1.1",
|
||||||
"next": "12.3.1",
|
"next": "13.0.5",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-clipboard.js": "^2.0.16",
|
"react-clipboard.js": "^2.0.16",
|
||||||
"react-data-table-component": "^7.5.2",
|
"react-data-table-component": "^7.5.3",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dotdotdot": "^1.3.1",
|
"react-dotdotdot": "^1.3.1",
|
||||||
"react-modal": "^3.15.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-paginate": "^8.1.3",
|
"react-paginate": "^8.1.4",
|
||||||
"react-select": "^5.4.0",
|
"react-select": "^5.7.0",
|
||||||
"react-spring": "^9.5.2",
|
"react-spring": "^9.5.5",
|
||||||
"react-tabs": "^5.1.0",
|
"react-tabs": "^6.0.0",
|
||||||
"react-toastify": "^9.0.4",
|
"react-toastify": "^9.1.1",
|
||||||
"remark": "^13.0.0",
|
"remark": "^14.0.2",
|
||||||
"remark-gfm": "^1.0.0",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-html": "^13.0.1",
|
"remark-html": "^15.0.1",
|
||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
"slugify": "^1.6.5",
|
"slugify": "^1.6.5",
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"urql": "^3.0.3",
|
"urql": "^3.0.3",
|
||||||
"web3": "^1.8.0",
|
"web3": "^1.8.1",
|
||||||
"web3modal": "^1.9.9",
|
"web3modal": "^1.9.10",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-essentials": "^6.5.12",
|
"@storybook/addon-essentials": "^6.5.15",
|
||||||
"@storybook/builder-webpack5": "^6.5.12",
|
"@storybook/builder-webpack5": "^6.5.13",
|
||||||
"@storybook/manager-webpack5": "^6.5.12",
|
"@storybook/manager-webpack5": "^6.5.13",
|
||||||
"@storybook/react": "^6.5.12",
|
"@storybook/react": "^6.5.13",
|
||||||
"@svgr/webpack": "^6.3.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@types/jest": "^29.2.5",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
"@types/loadable__component": "^5.13.4",
|
"@types/loadable__component": "^5.13.4",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/node": "^18.8.5",
|
||||||
"@types/lodash.omit": "^4.5.7",
|
"@types/react": "^18.0.25",
|
||||||
"@types/node": "^18.7.18",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@types/react": "^18.0.21",
|
|
||||||
"@types/react-dom": "^18.0.5",
|
|
||||||
"@types/react-modal": "^3.13.1",
|
"@types/react-modal": "^3.13.1",
|
||||||
"@types/react-paginate": "^7.1.1",
|
"@types/react-paginate": "^7.1.1",
|
||||||
"@types/remove-markdown": "^0.3.1",
|
"@types/remove-markdown": "^0.3.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||||
"@typescript-eslint/parser": "^5.38.1",
|
"@typescript-eslint/parser": "^5.43.0",
|
||||||
"apollo": "^2.34.0",
|
"apollo": "^2.34.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.23.1",
|
"eslint": "^8.28.0",
|
||||||
"eslint-config-oceanprotocol": "^2.0.4",
|
"eslint-config-oceanprotocol": "^2.0.4",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-jest-dom": "^4.0.2",
|
"eslint-plugin-jest-dom": "^4.0.3",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.31.8",
|
"eslint-plugin-react": "^7.31.11",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-testing-library": "^5.7.0",
|
"eslint-plugin-testing-library": "^5.9.1",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.2",
|
||||||
"jest": "^29.1.2",
|
"jest": "^29.3.1",
|
||||||
"jest-environment-jsdom": "^29.0.3",
|
"jest-environment-jsdom": "^29.3.1",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.1",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"serve": "^14.0.1",
|
"serve": "^14.1.2",
|
||||||
"stream-http": "^3.2.0",
|
"stream-http": "^3.2.0",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.9.3"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"graphql": "15.8.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/oceanprotocol/market"
|
"url": "https://github.com/oceanprotocol/market"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": "18"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
|
@ -9,13 +9,14 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { Config, LoggerInstance, Purgatory } from '@oceanprotocol/lib'
|
import { Config, LoggerInstance, Purgatory } from '@oceanprotocol/lib'
|
||||||
import { CancelToken } from 'axios'
|
import { CancelToken } from 'axios'
|
||||||
import { retrieveAsset } from '@utils/aquarius'
|
import { getAsset } from '@utils/aquarius'
|
||||||
import { useWeb3 } from './Web3'
|
import { useWeb3 } from './Web3'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
|
import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
|
||||||
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
import { useMarketMetadata } from './MarketMetadata'
|
import { useMarketMetadata } from './MarketMetadata'
|
||||||
|
import { isValidDid } from '@utils/ddo'
|
||||||
|
|
||||||
export interface AssetProviderValue {
|
export interface AssetProviderValue {
|
||||||
isInPurgatory: boolean
|
isInPurgatory: boolean
|
||||||
@ -63,10 +64,17 @@ function AssetProvider({
|
|||||||
const fetchAsset = useCallback(
|
const fetchAsset = useCallback(
|
||||||
async (token?: CancelToken) => {
|
async (token?: CancelToken) => {
|
||||||
if (!did) return
|
if (!did) return
|
||||||
|
const isDid = isValidDid(did)
|
||||||
|
|
||||||
|
if (!isDid) {
|
||||||
|
setError(`The url is not for a valid DID`)
|
||||||
|
LoggerInstance.error(`[asset] Not a valid DID`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
LoggerInstance.log('[asset] Fetching asset...')
|
LoggerInstance.log('[asset] Fetching asset...')
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const asset = await retrieveAsset(did, token)
|
const asset = await getAsset(did, token)
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
setError(
|
setError(
|
||||||
@ -77,8 +85,8 @@ function AssetProvider({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset.nft.state) {
|
if ([1, 2, 3].includes(asset.nft.state)) {
|
||||||
// handle nft states as documented in https://docs.oceanprotocol.com/concepts/did-ddo/#state
|
// handle nft states as documented in https://docs.oceanprotocol.com/core-concepts/did-ddo/#state
|
||||||
let state
|
let state
|
||||||
switch (asset.nft.state) {
|
switch (asset.nft.state) {
|
||||||
case 1:
|
case 1:
|
||||||
@ -120,7 +128,7 @@ function AssetProvider({
|
|||||||
// Helper: Get and set asset access details
|
// Helper: Get and set asset access details
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
const fetchAccessDetails = useCallback(async (): Promise<void> => {
|
const fetchAccessDetails = useCallback(async (): Promise<void> => {
|
||||||
if (!asset?.chainId || !asset?.services) return
|
if (!asset?.chainId || !asset?.services?.length) return
|
||||||
|
|
||||||
const accessDetails = await getAccessDetails(
|
const accessDetails = await getAccessDetails(
|
||||||
asset.chainId,
|
asset.chainId,
|
||||||
|
@ -10,6 +10,7 @@ export interface AppConfig {
|
|||||||
infuraProjectId: string
|
infuraProjectId: string
|
||||||
chainIds: number[]
|
chainIds: number[]
|
||||||
chainIdsSupported: number[]
|
chainIdsSupported: number[]
|
||||||
|
defaultDatatokenTemplateIndex: number
|
||||||
marketFeeAddress: string
|
marketFeeAddress: string
|
||||||
publisherMarketOrderFee: string
|
publisherMarketOrderFee: string
|
||||||
publisherMarketFixedSwapFee: string
|
publisherMarketFixedSwapFee: string
|
||||||
|
@ -7,7 +7,7 @@ import React, {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { OpcQuery } from 'src/@types/subgraph/OpcQuery'
|
import { OpcQuery } from '../../../src/@types/subgraph/OpcQuery'
|
||||||
import { OperationResult } from 'urql'
|
import { OperationResult } from 'urql'
|
||||||
import { opcQuery } from './_queries'
|
import { opcQuery } from './_queries'
|
||||||
import { MarketMetadataProviderValue, OpcFee } from './_types'
|
import { MarketMetadataProviderValue, OpcFee } from './_types'
|
||||||
|
@ -29,6 +29,7 @@ interface ProfileProviderValue {
|
|||||||
downloadsTotal: number
|
downloadsTotal: number
|
||||||
isDownloadsLoading: boolean
|
isDownloadsLoading: boolean
|
||||||
sales: number
|
sales: number
|
||||||
|
ownAccount: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileContext = createContext({} as ProfileProviderValue)
|
const ProfileContext = createContext({} as ProfileProviderValue)
|
||||||
@ -46,17 +47,18 @@ const clearedProfile: Profile = {
|
|||||||
function ProfileProvider({
|
function ProfileProvider({
|
||||||
accountId,
|
accountId,
|
||||||
accountEns,
|
accountEns,
|
||||||
|
ownAccount,
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
accountId: string
|
accountId: string
|
||||||
accountEns: string
|
accountEns: string
|
||||||
|
ownAccount: boolean
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const { appConfig } = useMarketMetadata()
|
const { appConfig } = useMarketMetadata()
|
||||||
|
|
||||||
const [isEthAddress, setIsEthAddress] = useState<boolean>()
|
const [isEthAddress, setIsEthAddress] = useState<boolean>()
|
||||||
|
|
||||||
//
|
//
|
||||||
// Do nothing in all following effects
|
// Do nothing in all following effects
|
||||||
// when accountId is no ETH address
|
// when accountId is no ETH address
|
||||||
@ -111,7 +113,8 @@ function ProfileProvider({
|
|||||||
const result = await getPublishedAssets(
|
const result = await getPublishedAssets(
|
||||||
accountId,
|
accountId,
|
||||||
chainIds,
|
chainIds,
|
||||||
cancelTokenSource.token
|
cancelTokenSource.token,
|
||||||
|
ownAccount
|
||||||
)
|
)
|
||||||
setAssets(result.results)
|
setAssets(result.results)
|
||||||
setAssetsTotal(result.totalResults)
|
setAssetsTotal(result.totalResults)
|
||||||
@ -134,7 +137,13 @@ function ProfileProvider({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelTokenSource.cancel()
|
cancelTokenSource.cancel()
|
||||||
}
|
}
|
||||||
}, [accountId, appConfig.metadataCacheUri, chainIds, isEthAddress])
|
}, [
|
||||||
|
accountId,
|
||||||
|
appConfig.metadataCacheUri,
|
||||||
|
chainIds,
|
||||||
|
isEthAddress,
|
||||||
|
ownAccount
|
||||||
|
])
|
||||||
|
|
||||||
//
|
//
|
||||||
// DOWNLOADS
|
// DOWNLOADS
|
||||||
@ -154,11 +163,13 @@ function ProfileProvider({
|
|||||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||||
dtList.push(tokenOrders[i].datatoken.address)
|
dtList.push(tokenOrders[i].datatoken.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloads = await getDownloadAssets(
|
const downloads = await getDownloadAssets(
|
||||||
dtList,
|
dtList,
|
||||||
tokenOrders,
|
tokenOrders,
|
||||||
chainIds,
|
chainIds,
|
||||||
cancelToken
|
cancelToken,
|
||||||
|
ownAccount
|
||||||
)
|
)
|
||||||
setDownloads(downloads)
|
setDownloads(downloads)
|
||||||
setDownloadsTotal(downloads.length)
|
setDownloadsTotal(downloads.length)
|
||||||
@ -167,7 +178,7 @@ function ProfileProvider({
|
|||||||
downloads
|
downloads
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[accountId, chainIds]
|
[accountId, chainIds, ownAccount]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -230,6 +241,7 @@ function ProfileProvider({
|
|||||||
downloads,
|
downloads,
|
||||||
downloadsTotal,
|
downloadsTotal,
|
||||||
isDownloadsLoading,
|
isDownloadsLoading,
|
||||||
|
ownAccount,
|
||||||
sales
|
sales
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -24,8 +24,6 @@ interface UserPreferencesValue {
|
|||||||
removeBookmark: (did: string) => void
|
removeBookmark: (did: string) => void
|
||||||
setPrivacyPolicySlug: (slug: string) => void
|
setPrivacyPolicySlug: (slug: string) => void
|
||||||
setShowPPC: (value: boolean) => void
|
setShowPPC: (value: boolean) => void
|
||||||
infiniteApproval: boolean
|
|
||||||
setInfiniteApproval: (value: boolean) => void
|
|
||||||
locale: string
|
locale: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,10 +71,6 @@ function UserPreferencesProvider({
|
|||||||
localStorage?.showPPC !== false
|
localStorage?.showPPC !== false
|
||||||
)
|
)
|
||||||
|
|
||||||
const [infiniteApproval, setInfiniteApproval] = useState(
|
|
||||||
localStorage?.infiniteApproval || false
|
|
||||||
)
|
|
||||||
|
|
||||||
// Write values to localStorage on change
|
// Write values to localStorage on change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalStorage({
|
setLocalStorage({
|
||||||
@ -85,18 +79,9 @@ function UserPreferencesProvider({
|
|||||||
currency,
|
currency,
|
||||||
bookmarks,
|
bookmarks,
|
||||||
privacyPolicySlug,
|
privacyPolicySlug,
|
||||||
showPPC,
|
showPPC
|
||||||
infiniteApproval
|
|
||||||
})
|
})
|
||||||
}, [
|
}, [chainIds, debug, currency, bookmarks, privacyPolicySlug, showPPC])
|
||||||
chainIds,
|
|
||||||
debug,
|
|
||||||
currency,
|
|
||||||
bookmarks,
|
|
||||||
privacyPolicySlug,
|
|
||||||
showPPC,
|
|
||||||
infiniteApproval
|
|
||||||
])
|
|
||||||
|
|
||||||
// Set ocean.js log levels, default: Error
|
// Set ocean.js log levels, default: Error
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -152,8 +137,6 @@ function UserPreferencesProvider({
|
|||||||
bookmarks,
|
bookmarks,
|
||||||
privacyPolicySlug,
|
privacyPolicySlug,
|
||||||
showPPC,
|
showPPC,
|
||||||
infiniteApproval,
|
|
||||||
setInfiniteApproval,
|
|
||||||
setChainIds,
|
setChainIds,
|
||||||
setDebug,
|
setDebug,
|
||||||
setCurrency,
|
setCurrency,
|
||||||
|
@ -307,7 +307,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Construct network display name
|
// Construct network display name
|
||||||
const networkDisplayName = getNetworkDisplayName(networkData, networkId)
|
const networkDisplayName = getNetworkDisplayName(networkData)
|
||||||
setNetworkDisplayName(networkDisplayName)
|
setNetworkDisplayName(networkDisplayName)
|
||||||
|
|
||||||
setIsTestnet(getNetworkType(networkData) !== NetworkType.Mainnet)
|
setIsTestnet(getNetworkType(networkData) !== NetworkType.Mainnet)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useRef, useEffect, useCallback } from 'react'
|
import { useRef, useEffect, useCallback } from 'react'
|
||||||
import axios, { CancelToken } from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
|
|
||||||
export const useCancelToken = (): (() => CancelToken) => {
|
export const useCancelToken = (): (() => CancelToken) => {
|
||||||
const axiosSource = useRef(null)
|
const axiosSource = useRef(null)
|
||||||
|
|
||||||
const newCancelToken = useCallback(() => {
|
const newCancelToken = useCallback(() => {
|
||||||
axiosSource.current = axios.CancelToken.source()
|
axiosSource.current = axios.CancelToken.source()
|
||||||
return axiosSource.current.token
|
return axiosSource?.current?.token
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
43
src/@hooks/useNetworkMetadata/utils.test.ts
Normal file
43
src/@hooks/useNetworkMetadata/utils.test.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { getNetworkType, getNetworkDisplayName } from './utils'
|
||||||
|
|
||||||
|
describe('useNetworkMetadata/utils', () => {
|
||||||
|
test('getNetworkType returns mainnet', () => {
|
||||||
|
const type = getNetworkType({
|
||||||
|
name: 'Eth',
|
||||||
|
title: 'Eth'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
expect(type).toBe('mainnet')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getNetworkType returns testnet if "Test" is in name', () => {
|
||||||
|
const type = getNetworkType({
|
||||||
|
name: 'Testnet',
|
||||||
|
title: 'Testnet'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
expect(type).toBe('testnet')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getNetworkDisplayName returns correct values', () => {
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
const type1 = getNetworkDisplayName({
|
||||||
|
chainId: 1,
|
||||||
|
chain: 'ETH',
|
||||||
|
name: 'Ethereum Mainnet'
|
||||||
|
} as any)
|
||||||
|
expect(type1).toBe('ETH')
|
||||||
|
|
||||||
|
const type2 = getNetworkDisplayName({ chainId: 80001 } as any)
|
||||||
|
expect(type2).toBe('Mumbai')
|
||||||
|
|
||||||
|
const type3 = getNetworkDisplayName({ chainId: 8996 } as any)
|
||||||
|
expect(type3).toBe('Development')
|
||||||
|
|
||||||
|
const type4 = getNetworkDisplayName({ chainId: 2021000 } as any)
|
||||||
|
expect(type4).toBe('GAIA-X')
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
})
|
||||||
|
})
|
@ -11,9 +11,8 @@ export function getNetworkType(network: EthereumListsChain): string {
|
|||||||
// We hack in mainnet detection for moonriver.
|
// We hack in mainnet detection for moonriver.
|
||||||
if (
|
if (
|
||||||
network &&
|
network &&
|
||||||
!network.name.includes('Testnet') &&
|
!network.name?.includes('Testnet') &&
|
||||||
!network.title?.includes('Testnet') &&
|
!network.title?.includes('Testnet')
|
||||||
network.name !== 'Moonbase Alpha'
|
|
||||||
) {
|
) {
|
||||||
return NetworkType.Mainnet
|
return NetworkType.Mainnet
|
||||||
} else {
|
} else {
|
||||||
@ -21,19 +20,14 @@ export function getNetworkType(network: EthereumListsChain): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNetworkDisplayName(
|
export function getNetworkDisplayName(data: EthereumListsChain): string {
|
||||||
data: EthereumListsChain,
|
|
||||||
networkId: number
|
|
||||||
): string {
|
|
||||||
let displayName
|
let displayName
|
||||||
|
if (!data) return 'Unknown'
|
||||||
|
|
||||||
switch (networkId) {
|
switch (data.chainId) {
|
||||||
case 137:
|
case 137:
|
||||||
displayName = 'Polygon'
|
displayName = 'Polygon'
|
||||||
break
|
break
|
||||||
case 1287:
|
|
||||||
displayName = 'Moonbase'
|
|
||||||
break
|
|
||||||
case 1285:
|
case 1285:
|
||||||
displayName = 'Moonriver'
|
displayName = 'Moonriver'
|
||||||
break
|
break
|
||||||
@ -43,9 +37,6 @@ export function getNetworkDisplayName(
|
|||||||
case 8996:
|
case 8996:
|
||||||
displayName = 'Development'
|
displayName = 'Development'
|
||||||
break
|
break
|
||||||
case 3:
|
|
||||||
displayName = 'Ropsten'
|
|
||||||
break
|
|
||||||
case 5:
|
case 5:
|
||||||
displayName = 'Görli'
|
displayName = 'Görli'
|
||||||
break
|
break
|
||||||
@ -54,7 +45,9 @@ export function getNetworkDisplayName(
|
|||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
displayName = data
|
displayName = data
|
||||||
? `${data.chain} ${getNetworkType(data) === 'mainnet' ? '' : data.name}`
|
? `${data.chain}${
|
||||||
|
getNetworkType(data) === 'mainnet' ? '' : ` ${data.name}`
|
||||||
|
}`
|
||||||
: 'Unknown'
|
: 'Unknown'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
4
src/@types/Analytics.d.ts
vendored
Normal file
4
src/@types/Analytics.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
interface PageViews {
|
||||||
|
count: number
|
||||||
|
did: string
|
||||||
|
}
|
1
src/@types/AssetExtended.d.ts
vendored
1
src/@types/AssetExtended.d.ts
vendored
@ -5,5 +5,6 @@ import { Asset } from '@oceanprotocol/lib'
|
|||||||
declare global {
|
declare global {
|
||||||
interface AssetExtended extends Asset {
|
interface AssetExtended extends Asset {
|
||||||
accessDetails?: AccessDetails
|
accessDetails?: AccessDetails
|
||||||
|
views?: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
src/@types/Compute.d.ts
vendored
5
src/@types/Compute.d.ts
vendored
@ -27,4 +27,9 @@ declare global {
|
|||||||
computeJobs: ComputeJobMetaData[]
|
computeJobs: ComputeJobMetaData[]
|
||||||
isLoaded: boolean
|
isLoaded: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface totalPriceMap {
|
||||||
|
value: string
|
||||||
|
symbol: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
src/@types/Price.d.ts
vendored
1
src/@types/Price.d.ts
vendored
@ -39,6 +39,7 @@ declare global {
|
|||||||
interface AccessDetails {
|
interface AccessDetails {
|
||||||
type: 'fixed' | 'free' | 'NOT_SUPPORTED'
|
type: 'fixed' | 'free' | 'NOT_SUPPORTED'
|
||||||
price: string
|
price: string
|
||||||
|
templateId: number
|
||||||
addressOrId: string
|
addressOrId: string
|
||||||
baseToken: TokenInfo
|
baseToken: TokenInfo
|
||||||
datatoken: TokenInfo
|
datatoken: TokenInfo
|
||||||
|
1
src/@types/aquarius/BaseQueryParams.d.ts
vendored
1
src/@types/aquarius/BaseQueryParams.d.ts
vendored
@ -12,4 +12,5 @@ interface BaseQueryParams {
|
|||||||
aggs?: any
|
aggs?: any
|
||||||
filters?: FilterTerm[]
|
filters?: FilterTerm[]
|
||||||
ignorePurgatory?: boolean
|
ignorePurgatory?: boolean
|
||||||
|
ignoreState?: boolean
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ declare global {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
query: any
|
query: any
|
||||||
sort?: { [jsonPath: string]: SortDirectionOptions }
|
sort?: { [jsonPath: string]: SortDirectionOptions }
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
aggs?: any
|
aggs?: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ const tokensPriceQuery = gql`
|
|||||||
publishMarketFeeAddress
|
publishMarketFeeAddress
|
||||||
publishMarketFeeToken
|
publishMarketFeeToken
|
||||||
publishMarketFeeAmount
|
publishMarketFeeAmount
|
||||||
|
templateId
|
||||||
orders(
|
orders(
|
||||||
where: { payer: $account }
|
where: { payer: $account }
|
||||||
orderBy: createdTimestamp
|
orderBy: createdTimestamp
|
||||||
@ -84,6 +85,7 @@ const tokenPriceQuery = gql`
|
|||||||
id
|
id
|
||||||
symbol
|
symbol
|
||||||
name
|
name
|
||||||
|
templateId
|
||||||
publishMarketFeeAddress
|
publishMarketFeeAddress
|
||||||
publishMarketFeeToken
|
publishMarketFeeToken
|
||||||
publishMarketFeeAmount
|
publishMarketFeeAmount
|
||||||
@ -160,7 +162,7 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
// the last valid order should be the last reuse order tx id if there is one
|
// the last valid order should be the last reuse order tx id if there is one
|
||||||
accessDetails.validOrderTx = reusedOrder?.tx || order?.tx
|
accessDetails.validOrderTx = reusedOrder?.tx || order?.tx
|
||||||
}
|
}
|
||||||
|
accessDetails.templateId = tokenPrice.templateId
|
||||||
// TODO: fetch order fee from sub query
|
// TODO: fetch order fee from sub query
|
||||||
accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount
|
accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount
|
||||||
|
|
||||||
@ -169,6 +171,7 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
const dispenser = tokenPrice.dispensers[0]
|
const dispenser = tokenPrice.dispensers[0]
|
||||||
accessDetails.type = 'free'
|
accessDetails.type = 'free'
|
||||||
accessDetails.addressOrId = dispenser.token.id
|
accessDetails.addressOrId = dispenser.token.id
|
||||||
|
|
||||||
accessDetails.price = '0'
|
accessDetails.price = '0'
|
||||||
accessDetails.isPurchasable = dispenser.active
|
accessDetails.isPurchasable = dispenser.active
|
||||||
accessDetails.datatoken = {
|
accessDetails.datatoken = {
|
||||||
@ -323,7 +326,7 @@ export async function getAccessDetailsForAssets(
|
|||||||
},
|
},
|
||||||
queryContext
|
queryContext
|
||||||
)
|
)
|
||||||
tokenQueryResult.data?.tokens.forEach((token) => {
|
tokenQueryResult?.data?.tokens?.forEach((token) => {
|
||||||
const currentAsset = assetsExtended.find(
|
const currentAsset = assetsExtended.find(
|
||||||
(asset) =>
|
(asset) =>
|
||||||
asset.services[0].datatokenAddress.toLowerCase() === token.id
|
asset.services[0].datatokenAddress.toLowerCase() === token.id
|
||||||
|
70
src/@utils/aquarius/index.test.ts
Normal file
70
src/@utils/aquarius/index.test.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
SortDirectionOptions,
|
||||||
|
SortTermOptions
|
||||||
|
} from '../../@types/aquarius/SearchQuery'
|
||||||
|
import { escapeEsReservedCharacters, getFilterTerm, generateBaseQuery } from '.'
|
||||||
|
|
||||||
|
const defaultBaseQueryReturn = {
|
||||||
|
from: 0,
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{ term: { _index: 'aquarius' } },
|
||||||
|
{ terms: { chainId: [1, 3] } },
|
||||||
|
{ term: { 'purgatory.state': false } },
|
||||||
|
{ bool: { must_not: [{ term: { 'nft.state': 5 } }] } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('@utils/aquarius', () => {
|
||||||
|
test('escapeEsReservedCharacters', () => {
|
||||||
|
expect(escapeEsReservedCharacters('<')).toBe('\\<')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getFilterTerm with string value', () => {
|
||||||
|
expect(getFilterTerm('hello', 'world')).toStrictEqual({
|
||||||
|
term: { hello: 'world' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('getFilterTerm with array value', () => {
|
||||||
|
expect(getFilterTerm('hello', ['world', 'domination'])).toStrictEqual({
|
||||||
|
terms: { hello: ['world', 'domination'] }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generateBaseQuery', () => {
|
||||||
|
expect(generateBaseQuery({ chainIds: [1, 3] })).toStrictEqual(
|
||||||
|
defaultBaseQueryReturn
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generateBaseQuery aggs are passed through', () => {
|
||||||
|
expect(
|
||||||
|
generateBaseQuery({ chainIds: [1, 3], aggs: 'hello world' })
|
||||||
|
).toStrictEqual({
|
||||||
|
...defaultBaseQueryReturn,
|
||||||
|
aggs: 'hello world'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generateBaseQuery sortOptions are passed through', () => {
|
||||||
|
expect(
|
||||||
|
generateBaseQuery({
|
||||||
|
chainIds: [1, 3],
|
||||||
|
sortOptions: {
|
||||||
|
sortBy: SortTermOptions.Created,
|
||||||
|
sortDirection: SortDirectionOptions.Ascending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toStrictEqual({
|
||||||
|
...defaultBaseQueryReturn,
|
||||||
|
sort: {
|
||||||
|
'nft.created': 'asc'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,13 +1,13 @@
|
|||||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import { OrdersData_orders as OrdersData } from '../../@types/subgraph/OrdersData'
|
||||||
import { metadataCacheUri } from '../../app.config'
|
import { metadataCacheUri } from '../../../app.config'
|
||||||
import {
|
import {
|
||||||
SortDirectionOptions,
|
SortDirectionOptions,
|
||||||
SortTermOptions
|
SortTermOptions
|
||||||
} from '../@types/aquarius/SearchQuery'
|
} from '../../@types/aquarius/SearchQuery'
|
||||||
import { transformAssetToAssetSelection } from './assetConvertor'
|
import { transformAssetToAssetSelection } from '../assetConvertor'
|
||||||
|
|
||||||
export interface UserSales {
|
export interface UserSales {
|
||||||
id: string
|
id: string
|
||||||
@ -19,7 +19,7 @@ export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476
|
|||||||
export function escapeEsReservedCharacters(value: string): string {
|
export function escapeEsReservedCharacters(value: string): string {
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
const pattern = /([\!\*\+\-\=\<\>\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g
|
const pattern = /([\!\*\+\-\=\<\>\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g
|
||||||
return value.replace(pattern, '\\$1')
|
return value?.replace(pattern, '\\$1')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,6 +42,24 @@ export function getFilterTerm(
|
|||||||
export function generateBaseQuery(
|
export function generateBaseQuery(
|
||||||
baseQueryParams: BaseQueryParams
|
baseQueryParams: BaseQueryParams
|
||||||
): SearchQuery {
|
): SearchQuery {
|
||||||
|
const filters: unknown[] = [getFilterTerm('_index', 'aquarius')]
|
||||||
|
baseQueryParams.filters && filters.push(...baseQueryParams.filters)
|
||||||
|
baseQueryParams.chainIds &&
|
||||||
|
filters.push(getFilterTerm('chainId', baseQueryParams.chainIds))
|
||||||
|
!baseQueryParams.ignorePurgatory &&
|
||||||
|
filters.push(getFilterTerm('purgatory.state', false))
|
||||||
|
!baseQueryParams.ignoreState &&
|
||||||
|
filters.push({
|
||||||
|
bool: {
|
||||||
|
must_not: [
|
||||||
|
{
|
||||||
|
term: {
|
||||||
|
'nft.state': 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
const generatedQuery = {
|
const generatedQuery = {
|
||||||
from: baseQueryParams.esPaginationOptions?.from || 0,
|
from: baseQueryParams.esPaginationOptions?.from || 0,
|
||||||
size:
|
size:
|
||||||
@ -51,16 +69,7 @@ export function generateBaseQuery(
|
|||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
...baseQueryParams.nestedQuery,
|
...baseQueryParams.nestedQuery,
|
||||||
filter: [
|
filter: filters
|
||||||
...(baseQueryParams.filters || []),
|
|
||||||
baseQueryParams.chainIds
|
|
||||||
? getFilterTerm('chainId', baseQueryParams.chainIds)
|
|
||||||
: [],
|
|
||||||
getFilterTerm('_index', 'aquarius'),
|
|
||||||
...(baseQueryParams.ignorePurgatory
|
|
||||||
? []
|
|
||||||
: [getFilterTerm('purgatory.state', false)])
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as SearchQuery
|
} as SearchQuery
|
||||||
@ -75,7 +84,6 @@ export function generateBaseQuery(
|
|||||||
baseQueryParams.sortOptions.sortDirection ||
|
baseQueryParams.sortOptions.sortDirection ||
|
||||||
SortDirectionOptions.Descending
|
SortDirectionOptions.Descending
|
||||||
}
|
}
|
||||||
|
|
||||||
return generatedQuery
|
return generatedQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +136,7 @@ export async function queryMetadata(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function retrieveAsset(
|
export async function getAsset(
|
||||||
did: string,
|
did: string,
|
||||||
cancelToken: CancelToken
|
cancelToken: CancelToken
|
||||||
): Promise<Asset> {
|
): Promise<Asset> {
|
||||||
@ -171,73 +179,7 @@ export async function getAssetsNames(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAssetsFromDidList(
|
export async function getAssetsFromDids(
|
||||||
didList: string[],
|
|
||||||
chainIds: number[],
|
|
||||||
cancelToken: CancelToken
|
|
||||||
): Promise<PagedAssets> {
|
|
||||||
try {
|
|
||||||
if (!(didList.length > 0)) return
|
|
||||||
|
|
||||||
const baseParams = {
|
|
||||||
chainIds,
|
|
||||||
filters: [getFilterTerm('_id', didList)],
|
|
||||||
ignorePurgatory: true
|
|
||||||
} as BaseQueryParams
|
|
||||||
const query = generateBaseQuery(baseParams)
|
|
||||||
|
|
||||||
const queryResult = await queryMetadata(query, cancelToken)
|
|
||||||
return queryResult
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAssetsFromDtList(
|
|
||||||
dtList: string[],
|
|
||||||
chainIds: number[],
|
|
||||||
cancelToken: CancelToken
|
|
||||||
): Promise<Asset[]> {
|
|
||||||
try {
|
|
||||||
if (!(dtList.length > 0)) return
|
|
||||||
|
|
||||||
const baseParams = {
|
|
||||||
chainIds,
|
|
||||||
filters: [getFilterTerm('services.datatokenAddress', dtList)],
|
|
||||||
ignorePurgatory: true
|
|
||||||
} as BaseQueryParams
|
|
||||||
const query = generateBaseQuery(baseParams)
|
|
||||||
|
|
||||||
const queryResult = await queryMetadata(query, cancelToken)
|
|
||||||
return queryResult?.results
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAssetsFromNftList(
|
|
||||||
nftList: string[],
|
|
||||||
chainIds: number[],
|
|
||||||
cancelToken: CancelToken
|
|
||||||
): Promise<Asset[]> {
|
|
||||||
try {
|
|
||||||
if (!(nftList.length > 0)) return
|
|
||||||
|
|
||||||
const baseParams = {
|
|
||||||
chainIds,
|
|
||||||
filters: [getFilterTerm('nftAddress', nftList)],
|
|
||||||
ignorePurgatory: true
|
|
||||||
} as BaseQueryParams
|
|
||||||
const query = generateBaseQuery(baseParams)
|
|
||||||
|
|
||||||
const queryResult = await queryMetadata(query, cancelToken)
|
|
||||||
return queryResult?.results
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function retrieveDDOListByDIDs(
|
|
||||||
didList: string[],
|
didList: string[],
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken
|
cancelToken: CancelToken
|
||||||
@ -276,7 +218,7 @@ export async function getAlgorithmDatasetsForCompute(
|
|||||||
must: {
|
must: {
|
||||||
match: {
|
match: {
|
||||||
'services.compute.publisherTrustedAlgorithms.did': {
|
'services.compute.publisherTrustedAlgorithms.did': {
|
||||||
query: escapeEsReservedCharacters(algorithmId)
|
query: algorithmId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +231,6 @@ export async function getAlgorithmDatasetsForCompute(
|
|||||||
|
|
||||||
const query = generateBaseQuery(baseQueryParams)
|
const query = generateBaseQuery(baseQueryParams)
|
||||||
const computeDatasets = await queryMetadata(query, cancelToken)
|
const computeDatasets = await queryMetadata(query, cancelToken)
|
||||||
|
|
||||||
if (computeDatasets?.totalResults === 0) return []
|
if (computeDatasets?.totalResults === 0) return []
|
||||||
|
|
||||||
const datasets = await transformAssetToAssetSelection(
|
const datasets = await transformAssetToAssetSelection(
|
||||||
@ -304,6 +245,7 @@ export async function getPublishedAssets(
|
|||||||
accountId: string,
|
accountId: string,
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken,
|
cancelToken: CancelToken,
|
||||||
|
ignoreState = false,
|
||||||
page?: number,
|
page?: number,
|
||||||
type?: string,
|
type?: string,
|
||||||
accesType?: string
|
accesType?: string
|
||||||
@ -332,6 +274,7 @@ export async function getPublishedAssets(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ignorePurgatory: true,
|
ignorePurgatory: true,
|
||||||
|
ignoreState,
|
||||||
esPaginationOptions: {
|
esPaginationOptions: {
|
||||||
from: (Number(page) - 1 || 0) * 9,
|
from: (Number(page) - 1 || 0) * 9,
|
||||||
size: 9
|
size: 9
|
||||||
@ -352,7 +295,7 @@ export async function getPublishedAssets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTopPublishers(
|
async function getTopPublishers(
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken,
|
cancelToken: CancelToken,
|
||||||
page?: number,
|
page?: number,
|
||||||
@ -445,14 +388,17 @@ export async function getDownloadAssets(
|
|||||||
dtList: string[],
|
dtList: string[],
|
||||||
tokenOrders: OrdersData[],
|
tokenOrders: OrdersData[],
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken
|
cancelToken: CancelToken,
|
||||||
|
ignoreState = false
|
||||||
): Promise<DownloadedAsset[]> {
|
): Promise<DownloadedAsset[]> {
|
||||||
const baseQueryparams = {
|
const baseQueryparams = {
|
||||||
chainIds,
|
chainIds,
|
||||||
filters: [
|
filters: [
|
||||||
getFilterTerm('services.datatokenAddress', dtList),
|
getFilterTerm('services.datatokenAddress', dtList),
|
||||||
getFilterTerm('services.type', 'access')
|
getFilterTerm('services.type', 'access')
|
||||||
]
|
],
|
||||||
|
ignorePurgatory: true,
|
||||||
|
ignoreState
|
||||||
} as BaseQueryParams
|
} as BaseQueryParams
|
||||||
const query = generateBaseQuery(baseQueryparams)
|
const query = generateBaseQuery(baseQueryparams)
|
||||||
try {
|
try {
|
@ -1,6 +1,6 @@
|
|||||||
import { getAccessDetailsForAssets } from './accessDetailsAndPricing'
|
import { getAccessDetailsForAssets } from './accessDetailsAndPricing'
|
||||||
import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib'
|
import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib'
|
||||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||||
import { getServiceByName } from './ddo'
|
import { getServiceByName } from './ddo'
|
||||||
|
|
||||||
export async function transformAssetToAssetSelection(
|
export async function transformAssetToAssetSelection(
|
||||||
@ -14,11 +14,12 @@ export async function transformAssetToAssetSelection(
|
|||||||
const algorithmList: AssetSelectionAsset[] = []
|
const algorithmList: AssetSelectionAsset[] = []
|
||||||
|
|
||||||
for (const asset of extendedAssets) {
|
for (const asset of extendedAssets) {
|
||||||
const algoComputeService = getServiceByName(asset, 'compute')
|
const algoService =
|
||||||
|
getServiceByName(asset, 'compute') || getServiceByName(asset, 'access')
|
||||||
|
|
||||||
if (
|
if (
|
||||||
asset?.accessDetails?.price &&
|
asset?.accessDetails?.price &&
|
||||||
algoComputeService?.serviceEndpoint === datasetProviderEndpoint
|
algoService?.serviceEndpoint === datasetProviderEndpoint
|
||||||
) {
|
) {
|
||||||
let selected = false
|
let selected = false
|
||||||
selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => {
|
selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => {
|
||||||
|
@ -17,14 +17,14 @@ import {
|
|||||||
queryMetadata,
|
queryMetadata,
|
||||||
getFilterTerm,
|
getFilterTerm,
|
||||||
generateBaseQuery,
|
generateBaseQuery,
|
||||||
retrieveDDOListByDIDs
|
getAssetsFromDids
|
||||||
} from './aquarius'
|
} from './aquarius'
|
||||||
import { fetchDataForMultipleChains } from './subgraph'
|
import { fetchDataForMultipleChains } from './subgraph'
|
||||||
import { getServiceById, getServiceByName } from './ddo'
|
import { getServiceById, getServiceByName } from './ddo'
|
||||||
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
|
import { SortTermOptions } from '../@types/aquarius/SearchQuery'
|
||||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
import { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||||
import { transformAssetToAssetSelection } from './assetConvertor'
|
import { transformAssetToAssetSelection } from './assetConvertor'
|
||||||
import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
|
import { ComputeEditForm } from '../components/Asset/Edit/_types'
|
||||||
import { getFileDidInfo } from './provider'
|
import { getFileDidInfo } from './provider'
|
||||||
|
|
||||||
const getComputeOrders = gql`
|
const getComputeOrders = gql`
|
||||||
@ -338,7 +338,7 @@ export async function createTrustedAlgorithmList(
|
|||||||
if (!selectedAlgorithms || selectedAlgorithms.length === 0)
|
if (!selectedAlgorithms || selectedAlgorithms.length === 0)
|
||||||
return trustedAlgorithms
|
return trustedAlgorithms
|
||||||
|
|
||||||
const selectedAssets = await retrieveDDOListByDIDs(
|
const selectedAssets = await getAssetsFromDids(
|
||||||
selectedAlgorithms,
|
selectedAlgorithms,
|
||||||
[assetChainId],
|
[assetChainId],
|
||||||
cancelToken
|
cancelToken
|
||||||
@ -393,31 +393,3 @@ export async function transformComputeFormToServiceComputeOptions(
|
|||||||
|
|
||||||
return privacy
|
return privacy
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkComputeResourcesValidity(
|
|
||||||
asset: Asset,
|
|
||||||
accountId: string,
|
|
||||||
computeEnvMaxJobDuration: number,
|
|
||||||
datasetTimeout?: number,
|
|
||||||
algorithmTimeout?: number,
|
|
||||||
cancelToken?: CancelToken
|
|
||||||
): Promise<boolean> {
|
|
||||||
const jobs = await getComputeJobs(
|
|
||||||
[asset?.chainId],
|
|
||||||
accountId,
|
|
||||||
asset,
|
|
||||||
cancelToken
|
|
||||||
)
|
|
||||||
if (jobs.computeJobs.length <= 0) return false
|
|
||||||
const inputValues = []
|
|
||||||
computeEnvMaxJobDuration && inputValues.push(computeEnvMaxJobDuration * 60)
|
|
||||||
datasetTimeout && inputValues.push(datasetTimeout)
|
|
||||||
algorithmTimeout && inputValues.push(algorithmTimeout)
|
|
||||||
const minValue = Math.min(...inputValues)
|
|
||||||
const jobStartDate = new Date(
|
|
||||||
parseInt(jobs.computeJobs[0].dateCreated) * 1000
|
|
||||||
)
|
|
||||||
jobStartDate.setMinutes(jobStartDate.getMinutes() + Math.floor(minValue / 60))
|
|
||||||
const currentTime = new Date().getTime() / 1000
|
|
||||||
return Math.floor(jobStartDate.getTime() / 1000) > currentTime
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { Asset, DDO, Service } from '@oceanprotocol/lib'
|
import { Asset, DDO, Service } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
|
export function isValidDid(did: string): boolean {
|
||||||
|
const regex = /did:op:[A-Za-z0-9]{64}/
|
||||||
|
return regex.test(did)
|
||||||
|
}
|
||||||
|
|
||||||
export function getServiceByName(
|
export function getServiceByName(
|
||||||
ddo: Asset | DDO,
|
ddo: Asset | DDO,
|
||||||
name: 'access' | 'compute'
|
name: 'access' | 'compute'
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
import { LoggerInstance, Dispenser, Datatoken } from '@oceanprotocol/lib'
|
import { LoggerInstance, Datatoken } from '@oceanprotocol/lib'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { TransactionReceipt } from 'web3-core'
|
import { TransactionReceipt } from 'web3-core'
|
||||||
|
|
||||||
export async function setMinterToPublisher(
|
export async function setMinterToPublisher(
|
||||||
web3: Web3,
|
web3: Web3,
|
||||||
dispenserAddress: string,
|
|
||||||
datatokenAddress: string,
|
datatokenAddress: string,
|
||||||
accountId: string,
|
accountId: string,
|
||||||
setError: (msg: string) => void
|
setError: (msg: string) => void
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
const dispenserInstance = new Dispenser(dispenserAddress, web3)
|
|
||||||
const status = await dispenserInstance.status(datatokenAddress)
|
|
||||||
if (!status?.active) return
|
|
||||||
|
|
||||||
const datatokenInstance = new Datatoken(web3)
|
const datatokenInstance = new Datatoken(web3)
|
||||||
|
|
||||||
const response = await datatokenInstance.removeMinter(
|
const response = await datatokenInstance.removeMinter(
|
||||||
@ -20,6 +15,7 @@ export async function setMinterToPublisher(
|
|||||||
accountId,
|
accountId,
|
||||||
accountId
|
accountId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
setError('Updating DDO failed.')
|
setError('Updating DDO failed.')
|
||||||
LoggerInstance.error('Failed at cancelMinter')
|
LoggerInstance.error('Failed at cancelMinter')
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { getEnsName, getEnsAddress, getEnsProfile } from './ens'
|
import { getEnsName, getEnsAddress, getEnsProfile } from '.'
|
||||||
|
|
||||||
|
// TODO: this directly hits the ENS registry, which is not ideal
|
||||||
|
// so we need to rewrite this to mock responses instead for more reliable test runs.
|
||||||
describe('@utils/ens', () => {
|
describe('@utils/ens', () => {
|
||||||
jest.setTimeout(10000)
|
jest.setTimeout(10000)
|
||||||
jest.retryTimes(2)
|
jest.retryTimes(2)
|
@ -1,4 +1,4 @@
|
|||||||
import { fetchData } from './fetch'
|
import { fetchData } from '../fetch'
|
||||||
|
|
||||||
const apiUrl = 'https://ens-proxy.oceanprotocol.com/api'
|
const apiUrl = 'https://ens-proxy.oceanprotocol.com/api'
|
||||||
|
|
@ -1,12 +1,8 @@
|
|||||||
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
// Boolean value that will be true if we are inside a browser, false otherwise
|
// Boolean value that will be true if we are inside a browser, false otherwise
|
||||||
export const isBrowser = typeof window !== 'undefined'
|
export const isBrowser = typeof window !== 'undefined'
|
||||||
|
|
||||||
export function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, ms)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeItemFromArray<T>(arr: Array<T>, value: T): Array<T> {
|
export function removeItemFromArray<T>(arr: Array<T>, value: T): Array<T> {
|
||||||
const index = arr.indexOf(value)
|
const index = arr.indexOf(value)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@ -14,3 +10,10 @@ export function removeItemFromArray<T>(arr: Array<T>, value: T): Array<T> {
|
|||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sortAssets(items: Asset[], sorted: string[]) {
|
||||||
|
items.sort(function (a, b) {
|
||||||
|
return sorted?.indexOf(a.id) - sorted?.indexOf(b.id)
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
5
src/@utils/ipfs.ts
Normal file
5
src/@utils/ipfs.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as isIPFS from 'is-ipfs'
|
||||||
|
|
||||||
|
export function isCID(value: string) {
|
||||||
|
return isIPFS.cid(value)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import remark from 'remark'
|
import { remark } from 'remark'
|
||||||
import remarkHtml from 'remark-html'
|
import remarkHtml from 'remark-html'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ export function markdownToHtml(markdown: string): string {
|
|||||||
const result = remark()
|
const result = remark()
|
||||||
.use(remarkGfm)
|
.use(remarkGfm)
|
||||||
.use(remarkHtml) // serializes through remark-rehype and rehype-stringify
|
.use(remarkHtml) // serializes through remark-rehype and rehype-stringify
|
||||||
.processSync(markdown).contents
|
.processSync(markdown)
|
||||||
|
|
||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
@ -82,10 +82,13 @@ export function generateNftCreateData(
|
|||||||
|
|
||||||
export function decodeTokenURI(tokenURI: string): NftMetadata {
|
export function decodeTokenURI(tokenURI: string): NftMetadata {
|
||||||
if (!tokenURI) return undefined
|
if (!tokenURI) return undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nftMeta = JSON.parse(
|
const nftMeta = tokenURI.includes('data:application/json')
|
||||||
Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
|
? (JSON.parse(
|
||||||
) as NftMetadata
|
Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
|
||||||
|
) as NftMetadata)
|
||||||
|
: ({ image: tokenURI } as NftMetadata)
|
||||||
|
|
||||||
return nftMeta
|
return nftMeta
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
|
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||||
import { Decimal } from 'decimal.js'
|
import { Decimal } from 'decimal.js'
|
||||||
|
|
||||||
|
export function formatNumber(
|
||||||
|
price: number,
|
||||||
|
locale: string,
|
||||||
|
decimals?: string
|
||||||
|
): string {
|
||||||
|
return formatCurrency(price, '', locale, false, {
|
||||||
|
// Not exactly clear what `significant figures` are for this library,
|
||||||
|
// but setting this seems to give us the formatting we want.
|
||||||
|
// See https://github.com/oceanprotocol/market/issues/70
|
||||||
|
significantFigures: 4,
|
||||||
|
...(decimals && { decimalPlaces: Number(decimals) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Run decimal.js comparison
|
// Run decimal.js comparison
|
||||||
// http://mikemcl.github.io/decimal.js/#cmp
|
// http://mikemcl.github.io/decimal.js/#cmp
|
||||||
export function compareAsBN(balance: string, price: string): boolean {
|
export function compareAsBN(balance: string, price: string): boolean {
|
||||||
|
@ -3,12 +3,15 @@ import {
|
|||||||
approve,
|
approve,
|
||||||
approveWei,
|
approveWei,
|
||||||
Datatoken,
|
Datatoken,
|
||||||
|
Dispenser,
|
||||||
|
FixedRateExchange,
|
||||||
FreOrderParams,
|
FreOrderParams,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
OrderParams,
|
OrderParams,
|
||||||
ProviderComputeInitialize,
|
ProviderComputeInitialize,
|
||||||
ProviderFees,
|
ProviderFees,
|
||||||
ProviderInstance
|
ProviderInstance,
|
||||||
|
ProviderInitialize
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
@ -20,6 +23,26 @@ import {
|
|||||||
} from '../../app.config'
|
} from '../../app.config'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
async function initializeProvider(
|
||||||
|
asset: AssetExtended,
|
||||||
|
accountId: string,
|
||||||
|
providerFees?: ProviderFees
|
||||||
|
): Promise<ProviderInitialize> {
|
||||||
|
if (providerFees) return
|
||||||
|
try {
|
||||||
|
const provider = await ProviderInstance.initialize(
|
||||||
|
asset.id,
|
||||||
|
asset.services[0].id,
|
||||||
|
0,
|
||||||
|
accountId,
|
||||||
|
asset.services[0].serviceEndpoint
|
||||||
|
)
|
||||||
|
return provider
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.log('[Initialize Provider] Error:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param web3
|
* @param web3
|
||||||
* @param asset
|
* @param asset
|
||||||
@ -40,15 +63,11 @@ export async function order(
|
|||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const config = getOceanConfig(asset.chainId)
|
const config = getOceanConfig(asset.chainId)
|
||||||
|
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const orderParams = {
|
const orderParams = {
|
||||||
consumer: computeConsumerAddress || accountId,
|
consumer: computeConsumerAddress || accountId,
|
||||||
@ -66,22 +85,6 @@ export async function order(
|
|||||||
switch (asset.accessDetails?.type) {
|
switch (asset.accessDetails?.type) {
|
||||||
case 'fixed': {
|
case 'fixed': {
|
||||||
// this assumes all fees are in ocean
|
// this assumes all fees are in ocean
|
||||||
const txApprove = await approve(
|
|
||||||
web3,
|
|
||||||
config,
|
|
||||||
accountId,
|
|
||||||
asset.accessDetails.baseToken.address,
|
|
||||||
asset.accessDetails.datatoken.address,
|
|
||||||
await amountToUnits(
|
|
||||||
web3,
|
|
||||||
asset?.accessDetails?.baseToken?.address,
|
|
||||||
orderPriceAndFees.price
|
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
if (!txApprove) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const freParams = {
|
const freParams = {
|
||||||
exchangeContract: config.fixedRateExchangeAddress,
|
exchangeContract: config.fixedRateExchangeAddress,
|
||||||
@ -92,23 +95,96 @@ export async function order(
|
|||||||
swapMarketFee: consumeMarketFixedSwapFee,
|
swapMarketFee: consumeMarketFixedSwapFee,
|
||||||
marketFeeAddress
|
marketFeeAddress
|
||||||
} as FreOrderParams
|
} as FreOrderParams
|
||||||
const tx = await datatoken.buyFromFreAndOrder(
|
|
||||||
asset.accessDetails.datatoken.address,
|
|
||||||
accountId,
|
|
||||||
orderParams,
|
|
||||||
freParams
|
|
||||||
)
|
|
||||||
|
|
||||||
return tx
|
if (asset.accessDetails.templateId === 1) {
|
||||||
|
// buy datatoken
|
||||||
|
const txApprove = await approve(
|
||||||
|
web3,
|
||||||
|
config,
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails.baseToken.address,
|
||||||
|
config.fixedRateExchangeAddress,
|
||||||
|
await amountToUnits(
|
||||||
|
web3,
|
||||||
|
asset?.accessDetails?.baseToken?.address,
|
||||||
|
orderPriceAndFees.price
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
if (!txApprove) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fre = new FixedRateExchange(config.fixedRateExchangeAddress, web3)
|
||||||
|
const freTx = await fre.buyDatatokens(
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails?.addressOrId,
|
||||||
|
'1',
|
||||||
|
orderPriceAndFees.price,
|
||||||
|
marketFeeAddress,
|
||||||
|
consumeMarketFixedSwapFee
|
||||||
|
)
|
||||||
|
|
||||||
|
return await datatoken.startOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams.consumer,
|
||||||
|
orderParams.serviceIndex,
|
||||||
|
orderParams._providerFee,
|
||||||
|
orderParams._consumeMarketFee
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (asset.accessDetails.templateId === 2) {
|
||||||
|
const txApprove = await approve(
|
||||||
|
web3,
|
||||||
|
config,
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails.baseToken.address,
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
await amountToUnits(
|
||||||
|
web3,
|
||||||
|
asset?.accessDetails?.baseToken?.address,
|
||||||
|
orderPriceAndFees.price
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
if (!txApprove) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return await datatoken.buyFromFreAndOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
freParams
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
case 'free': {
|
case 'free': {
|
||||||
const tx = await datatoken.buyFromDispenserAndOrder(
|
if (asset.accessDetails.templateId === 1) {
|
||||||
asset.services[0].datatokenAddress,
|
const dispenser = new Dispenser(config.dispenserAddress, web3)
|
||||||
accountId,
|
const dispenserTx = await dispenser.dispense(
|
||||||
orderParams,
|
asset.accessDetails?.datatoken.address,
|
||||||
config.dispenserAddress
|
accountId,
|
||||||
)
|
'1',
|
||||||
return tx
|
accountId
|
||||||
|
)
|
||||||
|
return await datatoken.startOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams.consumer,
|
||||||
|
orderParams.serviceIndex,
|
||||||
|
orderParams._providerFee,
|
||||||
|
orderParams._consumeMarketFee
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (asset.accessDetails.templateId === 2) {
|
||||||
|
return await datatoken.buyFromDispenserAndOrder(
|
||||||
|
asset.services[0].datatokenAddress,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
config.dispenserAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,15 +206,11 @@ export async function reuseOrder(
|
|||||||
providerFees?: ProviderFees
|
providerFees?: ProviderFees
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const tx = await datatoken.reuseOrder(
|
const tx = await datatoken.reuseOrder(
|
||||||
asset.accessDetails.datatoken.address,
|
asset.accessDetails.datatoken.address,
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
|
Arweave,
|
||||||
ComputeAlgorithm,
|
ComputeAlgorithm,
|
||||||
ComputeAsset,
|
ComputeAsset,
|
||||||
ComputeEnvironment,
|
ComputeEnvironment,
|
||||||
downloadFileBrowser,
|
downloadFileBrowser,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
|
Ipfs,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
ProviderComputeInitializeResults,
|
ProviderComputeInitializeResults,
|
||||||
ProviderInstance
|
ProviderInstance,
|
||||||
|
UrlFile
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { getValidUntilTime } from './compute'
|
import { getValidUntilTime } from './compute'
|
||||||
@ -82,12 +85,45 @@ export async function getFileDidInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFileUrlInfo(
|
export async function getFileInfo(
|
||||||
url: string,
|
file: string,
|
||||||
providerUrl: string
|
providerUrl: string,
|
||||||
|
storageType: string
|
||||||
): Promise<FileInfo[]> {
|
): Promise<FileInfo[]> {
|
||||||
try {
|
try {
|
||||||
const response = await ProviderInstance.checkFileUrl(url, providerUrl)
|
let response
|
||||||
|
switch (storageType) {
|
||||||
|
case 'ipfs': {
|
||||||
|
const fileIPFS: Ipfs = {
|
||||||
|
type: 'ipfs',
|
||||||
|
hash: file
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileIPFS, providerUrl)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'arweave': {
|
||||||
|
const fileArweave: Arweave = {
|
||||||
|
type: 'arweave',
|
||||||
|
transactionId: file
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileArweave, providerUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const fileUrl: UrlFile = {
|
||||||
|
type: 'url',
|
||||||
|
index: 0,
|
||||||
|
url: file,
|
||||||
|
method: 'get'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileUrl, providerUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
|
@ -2,23 +2,9 @@ import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
|
|||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import { getUrqlClientInstance } from '@context/UrqlProvider'
|
import { getUrqlClientInstance } from '@context/UrqlProvider'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
import { AssetPreviousOrder } from '../@types/subgraph/AssetPreviousOrder'
|
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||||
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
|
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
|
||||||
|
import appConfig from '../../app.config'
|
||||||
const PreviousOrderQuery = gql`
|
|
||||||
query AssetPreviousOrder($id: String!, $account: String!) {
|
|
||||||
orders(
|
|
||||||
first: 1
|
|
||||||
where: { datatoken: $id, payer: $account }
|
|
||||||
orderBy: createdTimestamp
|
|
||||||
orderDirection: desc
|
|
||||||
) {
|
|
||||||
createdTimestamp
|
|
||||||
tx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const UserTokenOrders = gql`
|
const UserTokenOrders = gql`
|
||||||
query OrdersData($user: String!) {
|
query OrdersData($user: String!) {
|
||||||
@ -76,6 +62,11 @@ export function getSubgraphUri(chainId: number): string {
|
|||||||
|
|
||||||
export function getQueryContext(chainId: number): OperationContext {
|
export function getQueryContext(chainId: number): OperationContext {
|
||||||
try {
|
try {
|
||||||
|
if (!appConfig.chainIdsSupported.includes(chainId))
|
||||||
|
throw Object.assign(
|
||||||
|
new Error('network not supported, query context cancelled')
|
||||||
|
)
|
||||||
|
|
||||||
const queryContext: OperationContext = {
|
const queryContext: OperationContext = {
|
||||||
url: `${getSubgraphUri(
|
url: `${getSubgraphUri(
|
||||||
Number(chainId)
|
Number(chainId)
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { sanitizeUrl } from './url'
|
|
||||||
|
|
||||||
describe('@utils/url', () => {
|
|
||||||
test('sanitizeUrl', () => {
|
|
||||||
expect(sanitizeUrl('http://example.com')).toBe('http://example.com')
|
|
||||||
expect(sanitizeUrl('https://example.com')).toBe('https://example.com')
|
|
||||||
expect(sanitizeUrl('ftp://example.com')).toBe('about:blank')
|
|
||||||
})
|
|
||||||
})
|
|
26
src/@utils/url/index.test.ts
Normal file
26
src/@utils/url/index.test.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { sanitizeUrl, isGoogleUrl } from '.'
|
||||||
|
|
||||||
|
describe('@utils/url', () => {
|
||||||
|
test('sanitizeUrl', () => {
|
||||||
|
expect(sanitizeUrl('http://example.com')).toBe('http://example.com')
|
||||||
|
expect(sanitizeUrl('https://example.com')).toBe('https://example.com')
|
||||||
|
expect(sanitizeUrl('ftp://example.com')).toBe('about:blank')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isGoogleUrl', () => {
|
||||||
|
it('should return true if the url is a google domain', () => {
|
||||||
|
expect(isGoogleUrl('https://google.com')).toBe(true)
|
||||||
|
expect(isGoogleUrl('https://drive.google.com')).toBe(true)
|
||||||
|
expect(isGoogleUrl('https://docs.google.com')).toBe(true)
|
||||||
|
expect(isGoogleUrl('https://sheets.google.com')).toBe(true)
|
||||||
|
expect(isGoogleUrl('https://meet.google.com')).toBe(true)
|
||||||
|
expect(isGoogleUrl('https://calendar.google.com')).toBe(true)
|
||||||
|
})
|
||||||
|
it('should return false if the url is not a google domain', () => {
|
||||||
|
expect(isGoogleUrl('https://google.test.com')).toBe(false)
|
||||||
|
expect(isGoogleUrl('https://drive.gloogle.com')).toBe(false)
|
||||||
|
expect(isGoogleUrl('https://drive.google.test.com')).toBe(false)
|
||||||
|
expect(isGoogleUrl('https://google.com.test.com')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
@ -3,3 +3,10 @@ export function sanitizeUrl(url: string) {
|
|||||||
const isAllowedUrlScheme = u.startsWith('http://') || u.startsWith('https://')
|
const isAllowedUrlScheme = u.startsWith('http://') || u.startsWith('https://')
|
||||||
return isAllowedUrlScheme ? url : 'about:blank'
|
return isAllowedUrlScheme ? url : 'about:blank'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the url is a google domain
|
||||||
|
export const isGoogleUrl = (url: string): boolean => {
|
||||||
|
if (!url) return
|
||||||
|
const googleUrl = new URL(url)
|
||||||
|
return googleUrl.hostname.endsWith('google.com')
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { AllLocked } from 'src/@types/subgraph/AllLocked'
|
import { AllLockedQuery } from '../../src/@types/subgraph/AllLockedQuery'
|
||||||
import { OwnAllocations } from 'src/@types/subgraph/OwnAllocations'
|
import { OwnAllocationsQuery } from '../../src/@types/subgraph/OwnAllocationsQuery'
|
||||||
import { NftOwnAllocation } from 'src/@types/subgraph/NftOwnAllocation'
|
import { NftOwnAllocationQuery } from '../../src/@types/subgraph/NftOwnAllocationQuery'
|
||||||
import { OceanLocked } from 'src/@types/subgraph/OceanLocked'
|
import { OceanLockedQuery } from '../../src/@types/subgraph/OceanLockedQuery'
|
||||||
import { gql, OperationResult } from 'urql'
|
import { gql, OperationResult } from 'urql'
|
||||||
import { fetchData, getQueryContext } from './subgraph'
|
import { fetchData, getQueryContext } from './subgraph'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@ -11,12 +11,9 @@ import {
|
|||||||
getNetworkType,
|
getNetworkType,
|
||||||
NetworkType
|
NetworkType
|
||||||
} from '@hooks/useNetworkMetadata'
|
} from '@hooks/useNetworkMetadata'
|
||||||
import { getAssetsFromNftList } from './aquarius'
|
|
||||||
import { chainIdsSupported } from 'app.config'
|
|
||||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
|
||||||
|
|
||||||
const AllLocked = gql`
|
const AllLocked = gql`
|
||||||
query AllLocked {
|
query AllLockedQuery {
|
||||||
veOCEANs(first: 1000) {
|
veOCEANs(first: 1000) {
|
||||||
lockedAmount
|
lockedAmount
|
||||||
}
|
}
|
||||||
@ -24,7 +21,7 @@ const AllLocked = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const OwnAllocations = gql`
|
const OwnAllocations = gql`
|
||||||
query OwnAllocations($address: String) {
|
query OwnAllocationsQuery($address: String) {
|
||||||
veAllocations(where: { allocationUser: $address }) {
|
veAllocations(where: { allocationUser: $address }) {
|
||||||
id
|
id
|
||||||
nftAddress
|
nftAddress
|
||||||
@ -33,7 +30,7 @@ const OwnAllocations = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const NftOwnAllocation = gql`
|
const NftOwnAllocation = gql`
|
||||||
query NftOwnAllocation($address: String, $nftAddress: String) {
|
query NftOwnAllocationQuery($address: String, $nftAddress: String) {
|
||||||
veAllocations(
|
veAllocations(
|
||||||
where: { allocationUser: $address, nftAddress: $nftAddress }
|
where: { allocationUser: $address, nftAddress: $nftAddress }
|
||||||
) {
|
) {
|
||||||
@ -42,7 +39,7 @@ const NftOwnAllocation = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const OceanLocked = gql`
|
const OceanLocked = gql`
|
||||||
query OceanLocked($address: String) {
|
query OceanLockedQuery($address: ID!) {
|
||||||
veOCEAN(id: $address) {
|
veOCEAN(id: $address) {
|
||||||
id
|
id
|
||||||
lockedAmount
|
lockedAmount
|
||||||
@ -80,6 +77,7 @@ export function getVeChainNetworkIds(assetNetworkIds: number[]): number[] {
|
|||||||
})
|
})
|
||||||
return veNetworkIds
|
return veNetworkIds
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNftOwnAllocation(
|
export async function getNftOwnAllocation(
|
||||||
userAddress: string,
|
userAddress: string,
|
||||||
nftAddress: string,
|
nftAddress: string,
|
||||||
@ -87,7 +85,7 @@ export async function getNftOwnAllocation(
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const veNetworkId = getVeChainNetworkId(networkId)
|
const veNetworkId = getVeChainNetworkId(networkId)
|
||||||
const queryContext = getQueryContext(veNetworkId)
|
const queryContext = getQueryContext(veNetworkId)
|
||||||
const fetchedAllocation: OperationResult<NftOwnAllocation, any> =
|
const fetchedAllocation: OperationResult<NftOwnAllocationQuery, any> =
|
||||||
await fetchData(
|
await fetchData(
|
||||||
NftOwnAllocation,
|
NftOwnAllocation,
|
||||||
{
|
{
|
||||||
@ -115,7 +113,7 @@ export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
const fetchedLocked: OperationResult<AllLocked, any> = await fetchData(
|
const fetchedLocked: OperationResult<AllLockedQuery, any> = await fetchData(
|
||||||
AllLocked,
|
AllLocked,
|
||||||
null,
|
null,
|
||||||
queryContext
|
queryContext
|
||||||
@ -136,11 +134,12 @@ export async function getLocked(
|
|||||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||||
const queryContext = getQueryContext(veNetworkIds[i])
|
const queryContext = getQueryContext(veNetworkIds[i])
|
||||||
const fetchedLocked: OperationResult<OceanLocked, any> = await fetchData(
|
const fetchedLocked: OperationResult<OceanLockedQuery, any> =
|
||||||
OceanLocked,
|
await fetchData(
|
||||||
{ address: userAddress.toLowerCase() },
|
OceanLocked,
|
||||||
queryContext
|
{ address: userAddress.toLowerCase() },
|
||||||
)
|
queryContext
|
||||||
|
)
|
||||||
|
|
||||||
fetchedLocked.data?.veOCEAN?.lockedAmount &&
|
fetchedLocked.data?.veOCEAN?.lockedAmount &&
|
||||||
(total += Number(fetchedLocked.data?.veOCEAN?.lockedAmount))
|
(total += Number(fetchedLocked.data?.veOCEAN?.lockedAmount))
|
||||||
@ -157,7 +156,7 @@ export async function getOwnAllocations(
|
|||||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||||
const queryContext = getQueryContext(veNetworkIds[i])
|
const queryContext = getQueryContext(veNetworkIds[i])
|
||||||
const fetchedAllocations: OperationResult<OwnAllocations, any> =
|
const fetchedAllocations: OperationResult<OwnAllocationsQuery, any> =
|
||||||
await fetchData(
|
await fetchData(
|
||||||
OwnAllocations,
|
OwnAllocations,
|
||||||
{ address: userAddress.toLowerCase() },
|
{ address: userAddress.toLowerCase() },
|
||||||
@ -176,17 +175,3 @@ export async function getOwnAllocations(
|
|||||||
|
|
||||||
return allocations
|
return allocations
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOwnAssetsWithAllocation(
|
|
||||||
networkIds: number[],
|
|
||||||
userAddress: string
|
|
||||||
): Promise<Asset[]> {
|
|
||||||
const allocations = await getOwnAllocations(networkIds, userAddress)
|
|
||||||
const assets = await getAssetsFromNftList(
|
|
||||||
allocations.map((x) => x.nftAddress),
|
|
||||||
chainIdsSupported,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
return assets
|
|
||||||
}
|
|
||||||
|
@ -33,7 +33,7 @@ export async function addCustomNetwork(
|
|||||||
|
|
||||||
const newNetworkData = {
|
const newNetworkData = {
|
||||||
chainId: `0x${network.chainId.toString(16)}`,
|
chainId: `0x${network.chainId.toString(16)}`,
|
||||||
chainName: getNetworkDisplayName(network, network.chainId),
|
chainName: getNetworkDisplayName(network),
|
||||||
nativeCurrency: network.nativeCurrency,
|
nativeCurrency: network.nativeCurrency,
|
||||||
rpcUrls: network.rpc,
|
rpcUrls: network.rpc,
|
||||||
blockExplorerUrls
|
blockExplorerUrls
|
||||||
|
58
src/@utils/yup.ts
Normal file
58
src/@utils/yup.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { isCID } from '@utils/ipfs'
|
||||||
|
import isUrl from 'is-url-superb'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { isGoogleUrl } from './url/index'
|
||||||
|
|
||||||
|
export function testLinks(isEdit?: boolean) {
|
||||||
|
return Yup.string().test((value, context) => {
|
||||||
|
const { type } = context.parent
|
||||||
|
let validField
|
||||||
|
let errorMessage
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
// we allow submit if the type input is hidden as will be ignore
|
||||||
|
case 'hidden':
|
||||||
|
validField = true
|
||||||
|
break
|
||||||
|
case 'url':
|
||||||
|
validField = isUrl(value?.toString() || '')
|
||||||
|
// if we're in publish, the field must be valid
|
||||||
|
if (!validField) {
|
||||||
|
validField = false
|
||||||
|
errorMessage = 'Must be a valid url.'
|
||||||
|
}
|
||||||
|
// we allow submit if we're in the edit page and the field is empty
|
||||||
|
if (
|
||||||
|
(!value?.toString() && isEdit) ||
|
||||||
|
(!value?.toString() && context.path === 'services[0].links[0].url')
|
||||||
|
) {
|
||||||
|
validField = true
|
||||||
|
}
|
||||||
|
// if the url has google drive, we need to block the user from submit
|
||||||
|
if (isGoogleUrl(value?.toString())) {
|
||||||
|
validField = false
|
||||||
|
errorMessage =
|
||||||
|
'Google Drive is not a supported hosting service. Please use an alternative.'
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ipfs':
|
||||||
|
validField = isCID(value?.toString())
|
||||||
|
errorMessage = !value?.toString() ? 'CID required.' : 'CID not valid.'
|
||||||
|
break
|
||||||
|
case 'arweave':
|
||||||
|
validField = !value?.toString().includes('http')
|
||||||
|
errorMessage = !value?.toString()
|
||||||
|
? 'Transaction ID required.'
|
||||||
|
: 'Transaction ID not valid.'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validField) {
|
||||||
|
return context.createError({
|
||||||
|
message: errorMessage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
.accountList {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: calc(var(--spacer) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 25rem) {
|
|
||||||
.accountList {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
gap: var(--spacer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
color: var(--color-secondary);
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loaderWrap {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
29
src/components/@shared/AddToken/index.stories.tsx
Normal file
29
src/components/@shared/AddToken/index.stories.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||||
|
import AddToken, { AddTokenProps } from '@shared/AddToken'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Component/@shared/AddToken',
|
||||||
|
component: AddToken
|
||||||
|
} as ComponentMeta<typeof AddToken>
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof AddToken> = (args: AddTokenProps) => {
|
||||||
|
return <AddToken {...args} />
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
args: AddTokenProps
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default: Props = Template.bind({})
|
||||||
|
Default.args = {
|
||||||
|
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
|
||||||
|
symbol: 'OCEAN'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Minimal: Props = Template.bind({})
|
||||||
|
Minimal.args = {
|
||||||
|
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
|
||||||
|
symbol: 'OCEAN',
|
||||||
|
minimal: true
|
||||||
|
}
|
24
src/components/@shared/AddToken/index.test.tsx
Normal file
24
src/components/@shared/AddToken/index.test.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import AddToken from './index'
|
||||||
|
|
||||||
|
jest.mock('../../../@utils/web3', () => ({ addTokenToWallet: jest.fn() }))
|
||||||
|
|
||||||
|
describe('@shared/AddToken', () => {
|
||||||
|
const propsBase = {
|
||||||
|
address: '0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8',
|
||||||
|
symbol: 'OCEAN'
|
||||||
|
}
|
||||||
|
testRender(<AddToken {...propsBase} />)
|
||||||
|
|
||||||
|
it('renders with custom text', () => {
|
||||||
|
render(<AddToken {...propsBase} text="Hello Text" />)
|
||||||
|
expect(screen.getByText('Hello Text')).toBeInTheDocument()
|
||||||
|
fireEvent.click(screen.getByRole('button'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders minimal', () => {
|
||||||
|
render(<AddToken {...propsBase} minimal />)
|
||||||
|
})
|
||||||
|
})
|
@ -8,19 +8,21 @@ import styles from './index.module.css'
|
|||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
|
export interface AddTokenProps {
|
||||||
|
address: string
|
||||||
|
symbol: string
|
||||||
|
text?: string
|
||||||
|
className?: string
|
||||||
|
minimal?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export default function AddToken({
|
export default function AddToken({
|
||||||
address,
|
address,
|
||||||
symbol,
|
symbol,
|
||||||
text,
|
text,
|
||||||
className,
|
className,
|
||||||
minimal
|
minimal
|
||||||
}: {
|
}: AddTokenProps): ReactElement {
|
||||||
address: string
|
|
||||||
symbol: string
|
|
||||||
text?: string
|
|
||||||
className?: string
|
|
||||||
minimal?: boolean
|
|
||||||
}): ReactElement {
|
|
||||||
const { web3Provider } = useWeb3()
|
const { web3Provider } = useWeb3()
|
||||||
|
|
||||||
const styleClasses = cx({
|
const styleClasses = cx({
|
||||||
|
65
src/components/@shared/AnnouncementBanner/index.stories.tsx
Normal file
65
src/components/@shared/AnnouncementBanner/index.stories.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||||
|
import AddAnnouncementBanner, {
|
||||||
|
AnnouncementBannerProps
|
||||||
|
} from '@shared/AnnouncementBanner'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Component/@shared/AnnouncementBanner',
|
||||||
|
component: AddAnnouncementBanner
|
||||||
|
} as ComponentMeta<typeof AddAnnouncementBanner>
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof AddAnnouncementBanner> = (
|
||||||
|
args: AnnouncementBannerProps
|
||||||
|
) => <AddAnnouncementBanner {...args} />
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
args: AnnouncementBannerProps
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default: Props = Template.bind({})
|
||||||
|
Default.args = {
|
||||||
|
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
|
||||||
|
action: {
|
||||||
|
name: 'see more',
|
||||||
|
handleAction: () => {
|
||||||
|
alert('Link clicked!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Success: Props = Template.bind({})
|
||||||
|
Success.args = {
|
||||||
|
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
|
||||||
|
state: 'success',
|
||||||
|
action: {
|
||||||
|
name: 'see more',
|
||||||
|
handleAction: () => {
|
||||||
|
alert('Link clicked!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Warning: Props = Template.bind({})
|
||||||
|
Warning.args = {
|
||||||
|
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
|
||||||
|
state: 'warning',
|
||||||
|
action: {
|
||||||
|
name: 'see more',
|
||||||
|
handleAction: () => {
|
||||||
|
alert('Link clicked!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Error: Props = Template.bind({})
|
||||||
|
Error.args = {
|
||||||
|
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum ac enim auctor placerat.',
|
||||||
|
state: 'error',
|
||||||
|
action: {
|
||||||
|
name: 'see more',
|
||||||
|
handleAction: () => {
|
||||||
|
alert('Link clicked!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/components/@shared/AnnouncementBanner/index.test.tsx
Normal file
13
src/components/@shared/AnnouncementBanner/index.test.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import AnnouncementBanner from './index'
|
||||||
|
|
||||||
|
describe('@shared/AnnouncementBanner', () => {
|
||||||
|
testRender(
|
||||||
|
<AnnouncementBanner
|
||||||
|
text="# Hello World!"
|
||||||
|
action={{ name: 'hello', handleAction: jest.fn() }}
|
||||||
|
state="success"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
@ -12,17 +12,19 @@ export interface AnnouncementAction {
|
|||||||
handleAction: () => void
|
handleAction: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AnnouncementBannerProps {
|
||||||
|
text: string
|
||||||
|
action?: AnnouncementAction
|
||||||
|
state?: 'success' | 'warning' | 'error'
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function AnnouncementBanner({
|
export default function AnnouncementBanner({
|
||||||
text,
|
text,
|
||||||
action,
|
action,
|
||||||
state,
|
state,
|
||||||
className
|
className
|
||||||
}: {
|
}: AnnouncementBannerProps): ReactElement {
|
||||||
text: string
|
|
||||||
action?: AnnouncementAction
|
|
||||||
state?: 'success' | 'warning' | 'error'
|
|
||||||
className?: string
|
|
||||||
}): ReactElement {
|
|
||||||
const styleClasses = cx({
|
const styleClasses = cx({
|
||||||
banner: true,
|
banner: true,
|
||||||
error: state === 'error',
|
error: state === 'error',
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
.display {
|
|
||||||
composes: selection from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.display [class*='loaderWrap'] {
|
|
||||||
margin: calc(var(--spacer) / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll {
|
|
||||||
composes: scroll from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
margin-top: 0;
|
|
||||||
border-top: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
composes: row from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row:first-child {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row:hover {
|
|
||||||
background-color: var(--background-content);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
composes: title from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
composes: price from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.price [class*='symbol'] {
|
|
||||||
font-size: calc(var(--font-size-small) / 1.2) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.did {
|
|
||||||
composes: did from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
composes: empty from '@shared/FormFields/AssetSelection/index.module.css';
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import PriceUnit from '@shared/Price/PriceUnit'
|
|
||||||
import Loader from '@shared/atoms/Loader'
|
|
||||||
import styles from './AssetComputeList.module.css'
|
|
||||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
|
||||||
|
|
||||||
function Empty() {
|
|
||||||
return <div className={styles.empty}>No assets found.</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AssetComputeSelection({
|
|
||||||
assets
|
|
||||||
}: {
|
|
||||||
assets: AssetSelectionAsset[]
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className={styles.display}>
|
|
||||||
<div className={styles.scroll}>
|
|
||||||
{!assets ? (
|
|
||||||
<Loader />
|
|
||||||
) : assets && !assets.length ? (
|
|
||||||
<Empty />
|
|
||||||
) : (
|
|
||||||
assets.map((asset: AssetSelectionAsset) => (
|
|
||||||
<Link href={`/asset/${asset.did}`} key={asset.did}>
|
|
||||||
<a className={styles.row}>
|
|
||||||
<div className={styles.info}>
|
|
||||||
<h3 className={styles.title}>
|
|
||||||
<Dotdotdot clamp={1} tagName="span">
|
|
||||||
{asset.name}
|
|
||||||
</Dotdotdot>
|
|
||||||
</h3>
|
|
||||||
<Dotdotdot clamp={1} tagName="code" className={styles.did}>
|
|
||||||
{asset.symbol} | {asset.did}
|
|
||||||
</Dotdotdot>
|
|
||||||
</div>
|
|
||||||
<PriceUnit
|
|
||||||
price={Number(asset.price)}
|
|
||||||
size="small"
|
|
||||||
className={styles.price}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
35
src/components/@shared/AssetList/index.stories.tsx
Normal file
35
src/components/@shared/AssetList/index.stories.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||||
|
import MarketMetadataProvider from '@context/MarketMetadata'
|
||||||
|
import { UserPreferencesProvider } from '@context/UserPreferences'
|
||||||
|
import AssetList, { AssetListProps } from '.'
|
||||||
|
import { assets } from '../../../../.jest/__fixtures__/datasetsWithAccessDetails'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Component/@shared/AssetList',
|
||||||
|
component: AssetList
|
||||||
|
} as ComponentMeta<typeof AssetList>
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof AssetList> = (args: AssetListProps) => {
|
||||||
|
return (
|
||||||
|
<MarketMetadataProvider>
|
||||||
|
<UserPreferencesProvider>
|
||||||
|
<AssetList {...args} />
|
||||||
|
</UserPreferencesProvider>
|
||||||
|
</MarketMetadataProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default: { args: AssetListProps } = Template.bind({})
|
||||||
|
Default.args = {
|
||||||
|
assets,
|
||||||
|
showPagination: true,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Empty: { args: AssetListProps } = Template.bind({})
|
||||||
|
Empty.args = {
|
||||||
|
assets: [],
|
||||||
|
showPagination: false
|
||||||
|
}
|
32
src/components/@shared/AssetList/index.test.tsx
Normal file
32
src/components/@shared/AssetList/index.test.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import AssetList from './index'
|
||||||
|
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
|
||||||
|
|
||||||
|
describe('@shared/AssetList', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
const onPageChange = jest.fn()
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AssetList
|
||||||
|
assets={[datasetAquarius]}
|
||||||
|
showPagination
|
||||||
|
page={1}
|
||||||
|
totalPages={10}
|
||||||
|
onPageChange={onPageChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
await screen.findAllByText('OCEAN')
|
||||||
|
fireEvent.click(screen.getByLabelText('Page 2'))
|
||||||
|
expect(onPageChange).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders empty', async () => {
|
||||||
|
render(<AssetList assets={[]} showPagination={false} isLoading={false} />)
|
||||||
|
await screen.findByText('No results found')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders loading', async () => {
|
||||||
|
render(<AssetList assets={[]} showPagination={false} isLoading />)
|
||||||
|
})
|
||||||
|
})
|
@ -2,15 +2,11 @@ import AssetTeaser from '@shared/AssetTeaser'
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import Pagination from '@shared/Pagination'
|
import Pagination from '@shared/Pagination'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import Loader from '@shared/atoms/Loader'
|
import Loader from '@shared/atoms/Loader'
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
function LoaderArea() {
|
function LoaderArea() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.loaderWrap}>
|
<div className={styles.loaderWrap}>
|
||||||
@ -19,7 +15,7 @@ function LoaderArea() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
declare type AssetListProps = {
|
export declare type AssetListProps = {
|
||||||
assets: AssetExtended[]
|
assets: AssetExtended[]
|
||||||
showPagination: boolean
|
showPagination: boolean
|
||||||
page?: number
|
page?: number
|
||||||
@ -28,6 +24,8 @@ declare type AssetListProps = {
|
|||||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||||
className?: string
|
className?: string
|
||||||
noPublisher?: boolean
|
noPublisher?: boolean
|
||||||
|
noDescription?: boolean
|
||||||
|
noPrice?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetList({
|
export default function AssetList({
|
||||||
@ -38,16 +36,18 @@ export default function AssetList({
|
|||||||
isLoading,
|
isLoading,
|
||||||
onPageChange,
|
onPageChange,
|
||||||
className,
|
className,
|
||||||
noPublisher
|
noPublisher,
|
||||||
|
noDescription,
|
||||||
|
noPrice
|
||||||
}: AssetListProps): ReactElement {
|
}: AssetListProps): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
|
||||||
const { accountId } = useWeb3()
|
const { accountId } = useWeb3()
|
||||||
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>()
|
const [assetsWithPrices, setAssetsWithPrices] =
|
||||||
|
useState<AssetExtended[]>(assets)
|
||||||
const [loading, setLoading] = useState<boolean>(isLoading)
|
const [loading, setLoading] = useState<boolean>(isLoading)
|
||||||
const isMounted = useIsMounted()
|
const isMounted = useIsMounted()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!assets) return
|
if (!assets || !assets.length) return
|
||||||
|
|
||||||
setAssetsWithPrices(assets as AssetExtended[])
|
setAssetsWithPrices(assets as AssetExtended[])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@ -67,24 +67,21 @@ export default function AssetList({
|
|||||||
onPageChange(selected + 1)
|
onPageChange(selected + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleClasses = cx({
|
const styleClasses = `${styles.assetList} ${className || ''}`
|
||||||
assetList: true,
|
|
||||||
[className]: className
|
|
||||||
})
|
|
||||||
|
|
||||||
return chainIds.length === 0 ? (
|
return loading ? (
|
||||||
<div className={styleClasses}>
|
<LoaderArea />
|
||||||
<div className={styles.empty}>No network selected</div>
|
) : (
|
||||||
</div>
|
|
||||||
) : assetsWithPrices && !loading ? (
|
|
||||||
<>
|
<>
|
||||||
<div className={styleClasses}>
|
<div className={styleClasses}>
|
||||||
{assetsWithPrices.length > 0 ? (
|
{assetsWithPrices?.length > 0 ? (
|
||||||
assetsWithPrices.map((assetWithPrice) => (
|
assetsWithPrices?.map((assetWithPrice) => (
|
||||||
<AssetTeaser
|
<AssetTeaser
|
||||||
asset={assetWithPrice}
|
asset={assetWithPrice}
|
||||||
key={assetWithPrice.id}
|
key={assetWithPrice.id}
|
||||||
noPublisher={noPublisher}
|
noPublisher={noPublisher}
|
||||||
|
noDescription={noDescription}
|
||||||
|
noPrice={noPrice}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@ -100,7 +97,5 @@ export default function AssetList({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<LoaderArea />
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
24
src/components/@shared/AssetListTitle/index.test.tsx
Normal file
24
src/components/@shared/AssetListTitle/index.test.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import AssetListTitle from '.'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
jest.mock('../../../@utils/aquarius', () => ({
|
||||||
|
getAssetsNames: () => Promise.resolve('Test')
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('AssetListTitle', () => {
|
||||||
|
testRender(
|
||||||
|
<AssetListTitle asset={{ metadata: { name: 'Hello world' } } as any} />
|
||||||
|
)
|
||||||
|
|
||||||
|
it('renders with passed title', () => {
|
||||||
|
render(<AssetListTitle title="Hello Title" />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with passed DID', () => {
|
||||||
|
render(
|
||||||
|
<AssetListTitle did="did:op:764b81877039fa2651b919fc91c399799acb837f270e6d17bfb7973fbe6e9408" />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,7 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { getAssetsNames } from '@utils/aquarius'
|
import { getAssetsNames } from '@utils/aquarius'
|
||||||
import styles from './AssetListTitle.module.css'
|
import styles from './index.module.css'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
import { useMarketMetadata } from '@context/MarketMetadata'
|
import { useMarketMetadata } from '@context/MarketMetadata'
|
||||||
@ -41,9 +41,7 @@ export default function AssetListTitle({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Link href={`/asset/${did || asset?.id}`}>
|
<Link href={`/asset/${did || asset?.id}`}>{assetTitle}</Link>
|
||||||
<a>{assetTitle}</a>
|
|
||||||
</Link>
|
|
||||||
</h3>
|
</h3>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detailLine {
|
.detailLine {
|
||||||
margin-bottom: calc(var(--spacer) / 2);
|
margin-bottom: calc(var(--spacer) / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@ -43,8 +43,12 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
margin-top: calc(var(--spacer) / 12);
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: calc(var(--spacer) / 4);
|
margin-top: calc(var(--spacer) / 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeLabel {
|
.typeLabel {
|
||||||
|
8
src/components/@shared/AssetTeaser/index.test.tsx
Normal file
8
src/components/@shared/AssetTeaser/index.test.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import AssetTeaser from './index'
|
||||||
|
import { asset } from '../../../../.jest/__fixtures__/datasetWithAccessDetails'
|
||||||
|
|
||||||
|
describe('@shared/AssetTeaser', () => {
|
||||||
|
testRender(<AssetTeaser asset={asset} />)
|
||||||
|
})
|
@ -8,17 +8,21 @@ import AssetType from '@shared/AssetType'
|
|||||||
import NetworkName from '@shared/NetworkName'
|
import NetworkName from '@shared/NetworkName'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { getServiceByName } from '@utils/ddo'
|
import { getServiceByName } from '@utils/ddo'
|
||||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { formatNumber } from '@utils/numbers'
|
||||||
|
|
||||||
declare type AssetTeaserProps = {
|
export declare type AssetTeaserProps = {
|
||||||
asset: AssetExtended
|
asset: AssetExtended
|
||||||
noPublisher?: boolean
|
noPublisher?: boolean
|
||||||
|
noDescription?: boolean
|
||||||
|
noPrice?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetTeaser({
|
export default function AssetTeaser({
|
||||||
asset,
|
asset,
|
||||||
noPublisher
|
noPublisher,
|
||||||
|
noDescription,
|
||||||
|
noPrice
|
||||||
}: AssetTeaserProps): ReactElement {
|
}: AssetTeaserProps): ReactElement {
|
||||||
const { name, type, description } = asset.metadata
|
const { name, type, description } = asset.metadata
|
||||||
const { datatokens } = asset
|
const { datatokens } = asset
|
||||||
@ -31,55 +35,77 @@ export default function AssetTeaser({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={`${styles.teaser} ${styles[type]}`}>
|
<article className={`${styles.teaser} ${styles[type]}`}>
|
||||||
<Link href={`/asset/${asset.id}`}>
|
<Link href={`/asset/${asset.id}`} className={styles.link}>
|
||||||
<a className={styles.link}>
|
<aside className={styles.detailLine}>
|
||||||
<aside className={styles.detailLine}>
|
<AssetType
|
||||||
<AssetType
|
className={styles.typeLabel}
|
||||||
className={styles.typeLabel}
|
type={type}
|
||||||
type={type}
|
accessType={accessType}
|
||||||
accessType={accessType}
|
/>
|
||||||
/>
|
<span className={styles.typeLabel}>
|
||||||
<span className={styles.typeLabel}>
|
{datatokens[0]?.symbol.substring(0, 9)}
|
||||||
{datatokens[0]?.symbol.substring(0, 9)}
|
</span>
|
||||||
</span>
|
<NetworkName networkId={asset.chainId} className={styles.typeLabel} />
|
||||||
<NetworkName
|
</aside>
|
||||||
networkId={asset.chainId}
|
<header className={styles.header}>
|
||||||
className={styles.typeLabel}
|
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
||||||
/>
|
{name.slice(0, 200)}
|
||||||
</aside>
|
</Dotdotdot>
|
||||||
<header className={styles.header}>
|
{!noPublisher && <Publisher account={owner} minimal />}
|
||||||
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
</header>
|
||||||
{name.slice(0, 200)}
|
{!noDescription && (
|
||||||
</Dotdotdot>
|
|
||||||
{!noPublisher && <Publisher account={owner} minimal />}
|
|
||||||
</header>
|
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<Dotdotdot tagName="p" clamp={3}>
|
<Dotdotdot tagName="p" clamp={3}>
|
||||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||||
</Dotdotdot>
|
</Dotdotdot>
|
||||||
</div>
|
</div>
|
||||||
{isUnsupportedPricing ? (
|
)}
|
||||||
<strong>No pricing schema available</strong>
|
{!noPrice && (
|
||||||
) : (
|
<div className={styles.price}>
|
||||||
<Price accessDetails={asset.accessDetails} size="small" />
|
{isUnsupportedPricing || !asset.services.length ? (
|
||||||
)}
|
<strong>No pricing schema available</strong>
|
||||||
<footer className={styles.footer}>
|
) : (
|
||||||
{allocated && allocated > 0 ? (
|
<Price accessDetails={asset.accessDetails} size="small" />
|
||||||
<span className={styles.typeLabel}>
|
)}
|
||||||
{allocated < 0
|
</div>
|
||||||
? ''
|
)}
|
||||||
: `${formatPrice(allocated, locale)} veOCEAN`}
|
<footer className={styles.footer}>
|
||||||
</span>
|
{allocated && allocated > 0 ? (
|
||||||
) : null}
|
<span className={styles.typeLabel}>
|
||||||
{orders && orders > 0 ? (
|
{allocated < 0 ? (
|
||||||
<span className={styles.typeLabel}>
|
''
|
||||||
{orders < 0
|
) : (
|
||||||
? 'N/A'
|
<>
|
||||||
: `${orders} ${orders === 1 ? 'sale' : 'sales'}`}
|
<strong>{formatNumber(allocated, locale, '0')}</strong>{' '}
|
||||||
</span>
|
veOCEAN
|
||||||
) : null}
|
</>
|
||||||
</footer>
|
)}
|
||||||
</a>
|
</span>
|
||||||
|
) : null}
|
||||||
|
{orders && orders > 0 ? (
|
||||||
|
<span className={styles.typeLabel}>
|
||||||
|
{orders < 0 ? (
|
||||||
|
'N/A'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<strong>{orders}</strong> {orders === 1 ? 'sale' : 'sales'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{asset.views && asset.views > 0 ? (
|
||||||
|
<span className={styles.typeLabel}>
|
||||||
|
{asset.views < 0 ? (
|
||||||
|
'N/A'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<strong>{asset.views}</strong>{' '}
|
||||||
|
{asset.views === 1 ? 'view' : 'views'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</footer>
|
||||||
</Link>
|
</Link>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
|
@ -1,202 +0,0 @@
|
|||||||
import React, { FormEvent, ReactElement } from 'react'
|
|
||||||
import Button from '../atoms/Button'
|
|
||||||
import styles from './index.module.css'
|
|
||||||
import Loader from '../atoms/Loader'
|
|
||||||
|
|
||||||
interface ButtonBuyProps {
|
|
||||||
action: 'download' | 'compute'
|
|
||||||
disabled: boolean
|
|
||||||
hasPreviousOrder: boolean
|
|
||||||
hasDatatoken: boolean
|
|
||||||
btSymbol: string
|
|
||||||
dtSymbol: string
|
|
||||||
dtBalance: string
|
|
||||||
assetType: string
|
|
||||||
assetTimeout: string
|
|
||||||
isConsumable: boolean
|
|
||||||
consumableFeedback: string
|
|
||||||
hasPreviousOrderSelectedComputeAsset?: boolean
|
|
||||||
hasDatatokenSelectedComputeAsset?: boolean
|
|
||||||
dtSymbolSelectedComputeAsset?: string
|
|
||||||
dtBalanceSelectedComputeAsset?: string
|
|
||||||
selectedComputeAssetType?: string
|
|
||||||
isBalanceSufficient: boolean
|
|
||||||
isLoading?: boolean
|
|
||||||
onClick?: (e: FormEvent<HTMLButtonElement>) => void
|
|
||||||
stepText?: string
|
|
||||||
type?: 'submit'
|
|
||||||
priceType?: string
|
|
||||||
algorithmPriceType?: string
|
|
||||||
isAlgorithmConsumable?: boolean
|
|
||||||
hasProviderFee?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: we need to take a look at these messages
|
|
||||||
|
|
||||||
function getConsumeHelpText(
|
|
||||||
btSymbol: string,
|
|
||||||
dtBalance: string,
|
|
||||||
dtSymbol: string,
|
|
||||||
hasDatatoken: boolean,
|
|
||||||
hasPreviousOrder: boolean,
|
|
||||||
assetType: string,
|
|
||||||
isConsumable: boolean,
|
|
||||||
isBalanceSufficient: boolean,
|
|
||||||
consumableFeedback: string
|
|
||||||
) {
|
|
||||||
const text =
|
|
||||||
isConsumable === false
|
|
||||||
? consumableFeedback
|
|
||||||
: hasPreviousOrder
|
|
||||||
? `You bought this ${assetType} already allowing you to use it without paying again.`
|
|
||||||
: hasDatatoken
|
|
||||||
? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.`
|
|
||||||
: isBalanceSufficient === false
|
|
||||||
? `You do not have enough ${btSymbol} in your wallet to purchase this asset.`
|
|
||||||
: `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher.`
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComputeAssetHelpText(
|
|
||||||
hasPreviousOrder: boolean,
|
|
||||||
hasDatatoken: boolean,
|
|
||||||
btSymbol: string,
|
|
||||||
dtSymbol: string,
|
|
||||||
dtBalance: string,
|
|
||||||
isConsumable: boolean,
|
|
||||||
consumableFeedback: string,
|
|
||||||
isBalanceSufficient: boolean,
|
|
||||||
hasPreviousOrderSelectedComputeAsset?: boolean,
|
|
||||||
hasDatatokenSelectedComputeAsset?: boolean,
|
|
||||||
assetType?: string,
|
|
||||||
dtSymbolSelectedComputeAsset?: string,
|
|
||||||
dtBalanceSelectedComputeAsset?: string,
|
|
||||||
selectedComputeAssetType?: string,
|
|
||||||
isAlgorithmConsumable?: boolean,
|
|
||||||
hasProviderFee?: boolean
|
|
||||||
) {
|
|
||||||
const computeAssetHelpText = getConsumeHelpText(
|
|
||||||
btSymbol,
|
|
||||||
dtBalance,
|
|
||||||
dtSymbol,
|
|
||||||
hasDatatoken,
|
|
||||||
hasPreviousOrder,
|
|
||||||
assetType,
|
|
||||||
isConsumable,
|
|
||||||
isBalanceSufficient,
|
|
||||||
consumableFeedback
|
|
||||||
)
|
|
||||||
|
|
||||||
const computeAlgoHelpText =
|
|
||||||
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
|
|
||||||
isConsumable === false ||
|
|
||||||
isAlgorithmConsumable === false
|
|
||||||
? ''
|
|
||||||
: hasPreviousOrderSelectedComputeAsset
|
|
||||||
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
|
|
||||||
: hasDatatokenSelectedComputeAsset
|
|
||||||
? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying ${btSymbol} again.`
|
|
||||||
: isBalanceSufficient === false
|
|
||||||
? ''
|
|
||||||
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher.`
|
|
||||||
const providerFeeHelpText = hasProviderFee
|
|
||||||
? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
|
|
||||||
: 'C2D resources required to start the job are available, no payment required for those fees.'
|
|
||||||
const computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
|
|
||||||
return computeHelpText
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ButtonBuy({
|
|
||||||
action,
|
|
||||||
disabled,
|
|
||||||
hasPreviousOrder,
|
|
||||||
hasDatatoken,
|
|
||||||
btSymbol,
|
|
||||||
dtSymbol,
|
|
||||||
dtBalance,
|
|
||||||
assetType,
|
|
||||||
assetTimeout,
|
|
||||||
isConsumable,
|
|
||||||
consumableFeedback,
|
|
||||||
isBalanceSufficient,
|
|
||||||
hasPreviousOrderSelectedComputeAsset,
|
|
||||||
hasDatatokenSelectedComputeAsset,
|
|
||||||
dtSymbolSelectedComputeAsset,
|
|
||||||
dtBalanceSelectedComputeAsset,
|
|
||||||
selectedComputeAssetType,
|
|
||||||
onClick,
|
|
||||||
stepText,
|
|
||||||
isLoading,
|
|
||||||
type,
|
|
||||||
priceType,
|
|
||||||
algorithmPriceType,
|
|
||||||
isAlgorithmConsumable,
|
|
||||||
hasProviderFee
|
|
||||||
}: ButtonBuyProps): ReactElement {
|
|
||||||
const buttonText =
|
|
||||||
action === 'download'
|
|
||||||
? hasPreviousOrder
|
|
||||||
? 'Download'
|
|
||||||
: priceType === 'free'
|
|
||||||
? 'Get'
|
|
||||||
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
|
||||||
: hasPreviousOrder &&
|
|
||||||
hasPreviousOrderSelectedComputeAsset &&
|
|
||||||
!hasProviderFee
|
|
||||||
? 'Start Compute Job'
|
|
||||||
: priceType === 'free' && algorithmPriceType === 'free'
|
|
||||||
? 'Order Compute Job'
|
|
||||||
: `Buy Compute Job`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.actions}>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loader message={stepText} />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
style="primary"
|
|
||||||
type={type}
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={disabled}
|
|
||||||
className={action === 'compute' ? styles.actionsCenter : ''}
|
|
||||||
>
|
|
||||||
{buttonText}
|
|
||||||
</Button>
|
|
||||||
<div className={styles.help}>
|
|
||||||
{action === 'download'
|
|
||||||
? getConsumeHelpText(
|
|
||||||
btSymbol,
|
|
||||||
dtBalance,
|
|
||||||
dtSymbol,
|
|
||||||
hasDatatoken,
|
|
||||||
hasPreviousOrder,
|
|
||||||
assetType,
|
|
||||||
isConsumable,
|
|
||||||
isBalanceSufficient,
|
|
||||||
consumableFeedback
|
|
||||||
)
|
|
||||||
: getComputeAssetHelpText(
|
|
||||||
hasPreviousOrder,
|
|
||||||
hasDatatoken,
|
|
||||||
btSymbol,
|
|
||||||
dtSymbol,
|
|
||||||
dtBalance,
|
|
||||||
isConsumable,
|
|
||||||
consumableFeedback,
|
|
||||||
isBalanceSufficient,
|
|
||||||
hasPreviousOrderSelectedComputeAsset,
|
|
||||||
hasDatatokenSelectedComputeAsset,
|
|
||||||
assetType,
|
|
||||||
dtSymbolSelectedComputeAsset,
|
|
||||||
dtBalanceSelectedComputeAsset,
|
|
||||||
selectedComputeAssetType,
|
|
||||||
isAlgorithmConsumable,
|
|
||||||
hasProviderFee
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
7
src/components/@shared/DebugOutput/index.test.tsx
Normal file
7
src/components/@shared/DebugOutput/index.test.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import DebugOutput from './index'
|
||||||
|
|
||||||
|
describe('@shared/DebugOutput', () => {
|
||||||
|
testRender(<DebugOutput title="Debug" output="Hello Output" />)
|
||||||
|
})
|
21
src/components/@shared/ExplorerLink/index.test.tsx
Normal file
21
src/components/@shared/ExplorerLink/index.test.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import ExplorerLink from './index'
|
||||||
|
|
||||||
|
describe('@shared/ExplorerLink', () => {
|
||||||
|
testRender(
|
||||||
|
<ExplorerLink networkId={1} path="/tx">
|
||||||
|
Hello Link
|
||||||
|
</ExplorerLink>
|
||||||
|
)
|
||||||
|
|
||||||
|
it('renders without networkId', () => {
|
||||||
|
render(
|
||||||
|
<ExplorerLink networkId={null} path="/tx">
|
||||||
|
Hello Link
|
||||||
|
</ExplorerLink>
|
||||||
|
)
|
||||||
|
expect(screen.getByRole('link')).toHaveTextContent('Hello Link')
|
||||||
|
})
|
||||||
|
})
|
@ -1,12 +1,9 @@
|
|||||||
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||||
import External from '@images/external.svg'
|
import External from '@images/external.svg'
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import { Config } from '@oceanprotocol/lib'
|
import { Config } from '@oceanprotocol/lib'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
export default function ExplorerLink({
|
export default function ExplorerLink({
|
||||||
networkId,
|
networkId,
|
||||||
path,
|
path,
|
||||||
@ -20,10 +17,6 @@ export default function ExplorerLink({
|
|||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [url, setUrl] = useState<string>()
|
const [url, setUrl] = useState<string>()
|
||||||
const [oceanConfig, setOceanConfig] = useState<Config>()
|
const [oceanConfig, setOceanConfig] = useState<Config>()
|
||||||
const styleClasses = cx({
|
|
||||||
link: true,
|
|
||||||
[className]: className
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!networkId) return
|
if (!networkId) return
|
||||||
@ -39,7 +32,7 @@ export default function ExplorerLink({
|
|||||||
title={`View on ${oceanConfig?.explorerUri}`}
|
title={`View on ${oceanConfig?.explorerUri}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className={styleClasses}
|
className={`${styles.link} ${className || ''}`}
|
||||||
>
|
>
|
||||||
{children} <External />
|
{children} <External />
|
||||||
</a>
|
</a>
|
||||||
|
37
src/components/@shared/FileIcon/index.test.tsx
Normal file
37
src/components/@shared/FileIcon/index.test.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import testRender from '../../../../.jest/testRender'
|
||||||
|
import { FileInfo } from '@oceanprotocol/lib'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import FileIcon from './index'
|
||||||
|
|
||||||
|
describe('@shared/FileIcon', () => {
|
||||||
|
const file: FileInfo = {
|
||||||
|
type: 'url',
|
||||||
|
contentType: 'text/plain',
|
||||||
|
contentLength: '123'
|
||||||
|
}
|
||||||
|
|
||||||
|
testRender(<FileIcon file={file} />)
|
||||||
|
|
||||||
|
it('renders small', () => {
|
||||||
|
render(<FileIcon file={file} small />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders loading', () => {
|
||||||
|
render(<FileIcon file={file} isLoading />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders empty', () => {
|
||||||
|
const file: FileInfo = { type: 'url' }
|
||||||
|
render(<FileIcon file={file} />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with 0 contentLength', () => {
|
||||||
|
const file: FileInfo = {
|
||||||
|
type: 'url',
|
||||||
|
contentType: 'text/plain',
|
||||||
|
contentLength: '0'
|
||||||
|
}
|
||||||
|
render(<FileIcon file={file} />)
|
||||||
|
})
|
||||||
|
})
|
@ -30,9 +30,9 @@ export default function FileIcon({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styleClasses}>
|
<ul className={styleClasses}>
|
||||||
{!isLoading && file ? (
|
{!isLoading ? (
|
||||||
<>
|
<>
|
||||||
{file.contentType || file.contentLength ? (
|
{file?.contentType || file?.contentLength ? (
|
||||||
<>
|
<>
|
||||||
<li>{cleanupContentType(file.contentType)}</li>
|
<li>{cleanupContentType(file.contentType)}</li>
|
||||||
<li>
|
<li>
|
||||||
@ -40,6 +40,9 @@ export default function FileIcon({
|
|||||||
? filesize(Number(file.contentLength)).toString()
|
? filesize(Number(file.contentLength)).toString()
|
||||||
: ''}
|
: ''}
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{file.type === 'smartcontract' ? 'smart\ncontract' : file.type}
|
||||||
|
</li>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<li className={styles.empty}>No file info available</li>
|
<li className={styles.empty}>No file info available</li>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
export function prettySize(
|
|
||||||
bytes: number,
|
|
||||||
separator = ' ',
|
|
||||||
postFix = ''
|
|
||||||
): string {
|
|
||||||
if (bytes) {
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
|
||||||
const i = Math.min(
|
|
||||||
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
||||||
sizes.length - 1
|
|
||||||
)
|
|
||||||
return `${(bytes / 1024 ** i).toFixed(i ? 1 : 0)}${separator}${
|
|
||||||
sizes[i]
|
|
||||||
}${postFix}`
|
|
||||||
}
|
|
||||||
return 'n/a'
|
|
||||||
}
|
|
21
src/components/@shared/FormInput/Error.test.tsx
Normal file
21
src/components/@shared/FormInput/Error.test.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import Error from './Error'
|
||||||
|
|
||||||
|
describe('@shared/FormInput/Error', () => {
|
||||||
|
const propsBase = {
|
||||||
|
value: '',
|
||||||
|
touched: false,
|
||||||
|
initialTouched: false
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
render(<Error meta={{ ...propsBase, error: 'Hello Error' }} />)
|
||||||
|
expect(screen.getByText('Hello Error')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders nothing without error passed', () => {
|
||||||
|
render(<Error meta={{ ...propsBase }} />)
|
||||||
|
expect(screen.queryByText('Hello Error')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,9 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import styles from './Help.module.css'
|
import styles from './Help.module.css'
|
||||||
import Markdown from '@shared/Markdown'
|
import Markdown from '@shared/Markdown'
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const FormHelp = ({
|
const FormHelp = ({
|
||||||
children,
|
children,
|
||||||
@ -12,12 +9,9 @@ const FormHelp = ({
|
|||||||
children: string
|
children: string
|
||||||
className?: string
|
className?: string
|
||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
const styleClasses = cx({
|
return (
|
||||||
help: true,
|
<Markdown className={`${styles.help} ${className || ''}`} text={children} />
|
||||||
[className]: className
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return <Markdown className={styleClasses} text={children} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormHelp
|
export default FormHelp
|
||||||
|
@ -58,11 +58,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.radio {
|
.radio {
|
||||||
composes: radio from '@shared/FormInput/InputRadio.module.css';
|
composes: radio from '@shared/FormInput/InputElement/Radio/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
composes: checkbox from '@shared/FormInput/InputRadio.module.css';
|
composes: checkbox from '@shared/FormInput/InputElement/Radio/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
@ -0,0 +1,52 @@
|
|||||||
|
import AssetSelection, { AssetSelectionAsset } from './'
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
describe('@shared/FormInput/InputElement/AssetSelection', () => {
|
||||||
|
const assets: AssetSelectionAsset[] = [
|
||||||
|
{
|
||||||
|
did: 'did:op:xxx',
|
||||||
|
name: 'Asset',
|
||||||
|
price: '10',
|
||||||
|
checked: false,
|
||||||
|
symbol: 'OCEAN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
did: 'did:op:yyy',
|
||||||
|
name: 'Asset',
|
||||||
|
price: '10',
|
||||||
|
checked: true,
|
||||||
|
symbol: 'OCEAN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
did: 'did:op:zzz',
|
||||||
|
name: 'Asset',
|
||||||
|
price: '0',
|
||||||
|
checked: false,
|
||||||
|
symbol: 'OCEAN'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
render(<AssetSelection assets={assets} />)
|
||||||
|
const searchInput = screen.getByPlaceholderText(
|
||||||
|
'Search by title, datatoken, or DID...'
|
||||||
|
)
|
||||||
|
fireEvent.change(searchInput, { target: { value: 'Assets' } })
|
||||||
|
fireEvent.change(searchInput, { target: { value: '' } })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders empty assetSelection', () => {
|
||||||
|
render(<AssetSelection assets={[]} />)
|
||||||
|
expect(screen.getByText('No assets found.')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders disabled assetSelection', () => {
|
||||||
|
render(<AssetSelection assets={[]} disabled />)
|
||||||
|
expect(screen.getByText('No assets found.')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders assetSelectionMultiple', () => {
|
||||||
|
render(<AssetSelection assets={assets} multiple />)
|
||||||
|
})
|
||||||
|
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user