mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into feature/calica-integration
This commit is contained in:
commit
85e183929f
@ -42,7 +42,7 @@ exclude_patterns:
|
||||
- '**/*.d.ts'
|
||||
- '**/@types/'
|
||||
- '**/_types.*'
|
||||
- '**/*.stories.tsx'
|
||||
- '**/*.test.tsx'
|
||||
- '**/*.stories.*'
|
||||
- '**/*.test.*'
|
||||
- '.storybook/'
|
||||
- '.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:
|
||||
branches:
|
||||
- main
|
||||
- v4
|
||||
- v3
|
||||
tags:
|
||||
- '**'
|
||||
@ -20,7 +19,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: ['16']
|
||||
node: ['18']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -47,7 +46,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: ['16']
|
||||
node: ['18']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -82,7 +81,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '18'
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
@ -110,7 +109,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: ['16']
|
||||
node: ['18']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -12,6 +12,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- run: npm ci
|
||||
|
||||
- run: npm run build:static
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@ coverage
|
||||
.next
|
||||
.artifacts
|
||||
.vercel
|
||||
.swc
|
||||
repo-metadata.json
|
||||
networks-metadata.json
|
||||
src/@types/subgraph
|
||||
|
@ -57,12 +57,12 @@ export const assetAquarius: Asset = {
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 22
|
||||
// price: {
|
||||
// value: 3231343254,
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN'
|
||||
// }
|
||||
orders: 22,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
purgatory: {
|
||||
state: false,
|
||||
|
@ -68,8 +68,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 0
|
||||
// price: {}
|
||||
orders: 0,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -151,12 +155,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 45554.69921875,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0x282d8efCe846A88B159800bd4130ad77443Fa1A1',
|
||||
// tokenSymbol: 'mOCEAN',
|
||||
// value: 100
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -245,12 +249,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 7
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -339,12 +343,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 50.2051887512,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 10
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -439,12 +443,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 6
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -569,12 +573,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 5
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0'
|
||||
},
|
||||
@ -657,12 +661,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 5
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -751,12 +755,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 5
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -844,10 +848,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 114.1658859253,
|
||||
orders: 1
|
||||
// price: {
|
||||
// value: 0
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -927,12 +933,12 @@ export const assets: AssetExtended[] = [
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 2
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1018,10 +1024,12 @@ export const assets: AssetExtended[] = [
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 1
|
||||
// price: {
|
||||
// value: 0
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1103,10 +1111,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 11159.279296875,
|
||||
orders: 1
|
||||
// price: {
|
||||
// value: 0
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1186,10 +1196,12 @@ export const assets: AssetExtended[] = [
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 0
|
||||
// price: {
|
||||
// value: 0
|
||||
// }
|
||||
orders: 0,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1270,8 +1282,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 0,
|
||||
orders: 0
|
||||
// price: {}
|
||||
orders: 0,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1339,12 +1355,12 @@ export const assets: AssetExtended[] = [
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 1
|
||||
// price: {
|
||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
// tokenSymbol: 'OCEAN',
|
||||
// value: 1
|
||||
// }
|
||||
orders: 1,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1430,10 +1446,12 @@ export const assets: AssetExtended[] = [
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
orders: 0
|
||||
// price: {
|
||||
// value: 0
|
||||
// }
|
||||
orders: 0,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1525,7 +1543,12 @@ export const assets: AssetExtended[] = [
|
||||
],
|
||||
stats: {
|
||||
allocated: 422.9883117676,
|
||||
orders: 0
|
||||
orders: 0,
|
||||
price: {
|
||||
value: 3231343254,
|
||||
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||
tokenSymbol: 'OCEAN'
|
||||
}
|
||||
},
|
||||
version: '4.1.0',
|
||||
accessDetails: {
|
||||
@ -1551,4 +1574,4 @@ export const assets: AssetExtended[] = [
|
||||
validOrderTx: null
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default true
|
@ -1 +1,3 @@
|
||||
export default true
|
||||
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 = {
|
||||
rootDir: '../',
|
||||
// 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
|
||||
moduleDirectories: ['node_modules', '<rootDir>/src'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
testEnvironment: 'jsdom',
|
||||
moduleNameMapper: {
|
||||
'^.+\\.(svg)$': '<rootDir>/.jest/__mocks__/svgrMock.tsx',
|
||||
// '^@/components/(.*)$': '<rootDir>/components/$1',
|
||||
'@components/(.*)$': '<rootDir>/src/components/$1',
|
||||
'@shared(.*)$': '<rootDir>/src/components/@shared/$1',
|
||||
'@hooks/(.*)$': '<rootDir>/src/@hooks/$1',
|
||||
'@context/(.*)$': '<rootDir>/src/@context/$1',
|
||||
@ -29,8 +29,25 @@ const customJestConfig = {
|
||||
'!src/**/*.{stories,test}.{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
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
// https://github.com/vercel/next.js/issues/35634#issuecomment-1115250297
|
||||
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,3 +0,0 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
import './__mocks__/matchMedia'
|
||||
import './__mocks__/hooksMocks'
|
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}</>
|
||||
// }
|
||||
// }
|
||||
// })
|
@ -253,7 +253,7 @@ export default function NetworkName(): ReactElement {
|
||||
const { networkId, isTestnet } = useWeb3()
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const networkData = getNetworkDataById(networksList, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -29,19 +29,62 @@
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "New 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.** 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.",
|
||||
"prominentHelp": true,
|
||||
"type": "files"
|
||||
},
|
||||
{
|
||||
"label": "File",
|
||||
"prominentHelp": false,
|
||||
"type": "tabs",
|
||||
"fields": [{
|
||||
"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",
|
||||
"label": "New sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"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.",
|
||||
"prominentHelp": true,
|
||||
"type": "files"
|
||||
"label": "Sample file",
|
||||
"prominentHelp": false,
|
||||
"type": "tabs",
|
||||
"fields": [
|
||||
{
|
||||
"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 +109,13 @@
|
||||
"type": "tags",
|
||||
"placeholder": "e.g. logistics",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -104,19 +104,61 @@
|
||||
{
|
||||
"name": "files",
|
||||
"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.** 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",
|
||||
"prominentHelp": false,
|
||||
"type": "tabs",
|
||||
"fields": [{
|
||||
"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",
|
||||
"label": "Sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"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.**",
|
||||
"prominentHelp": true,
|
||||
"type": "files"
|
||||
"prominentHelp": false,
|
||||
"type": "tabs",
|
||||
"fields": [
|
||||
{
|
||||
"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",
|
||||
|
9098
package-lock.json
generated
9098
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@ -26,47 +26,48 @@
|
||||
"@coingecko/cryptoformat": "^0.5.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^2.4.0",
|
||||
"@oceanprotocol/lib": "^2.5.2",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@urql/exchange-refocus": "^1.0.0",
|
||||
"@walletconnect/web3-provider": "^1.8.0",
|
||||
"axios": "^1.1.3",
|
||||
"axios": "^1.2.0",
|
||||
"classnames": "^2.3.2",
|
||||
"date-fns": "^2.29.3",
|
||||
"decimal.js": "^10.3.1",
|
||||
"decimal.js": "^10.4.2",
|
||||
"dom-confetti": "^0.2.2",
|
||||
"dotenv": "^16.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"filesize": "^10.0.5",
|
||||
"formik": "^2.2.9",
|
||||
"gray-matter": "^4.0.3",
|
||||
"is-ipfs": "^7.0.3",
|
||||
"is-url-superb": "^6.1.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"match-sorter": "^6.3.1",
|
||||
"myetherwallet-blockies": "^0.1.1",
|
||||
"next": "12.3.1",
|
||||
"next": "13.0.5",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-clipboard.js": "^2.0.16",
|
||||
"react-data-table-component": "^7.5.2",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-data-table-component": "^7.5.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-paginate": "^8.1.3",
|
||||
"react-select": "^5.4.0",
|
||||
"react-spring": "^9.5.2",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-paginate": "^8.1.4",
|
||||
"react-select": "^5.6.1",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-tabs": "^5.1.0",
|
||||
"react-toastify": "^9.0.4",
|
||||
"remark": "^13.0.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"remark-html": "^13.0.1",
|
||||
"react-toastify": "^9.1.1",
|
||||
"remark": "^14.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^15.0.1",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"slugify": "^1.6.5",
|
||||
"swr": "^1.3.0",
|
||||
"urql": "^3.0.3",
|
||||
"web3": "^1.8.0",
|
||||
"web3modal": "^1.9.9",
|
||||
"web3": "^1.8.1",
|
||||
"web3modal": "^1.9.10",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -77,37 +78,38 @@
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/jest": "^29.2.3",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/loadable__component": "^5.13.4",
|
||||
"@types/node": "^18.8.5",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-paginate": "^7.1.1",
|
||||
"@types/remove-markdown": "^0.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"apollo": "^2.34.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-oceanprotocol": "^2.0.4",
|
||||
"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-react": "^7.31.10",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-testing-library": "^5.7.2",
|
||||
"eslint-plugin-testing-library": "^5.9.1",
|
||||
"https-browserify": "^1.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"jest": "^29.1.2",
|
||||
"jest-environment-jsdom": "^29.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"husky": "^8.0.2",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"prettier": "^2.8.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"process": "^0.11.10",
|
||||
"serve": "^14.0.1",
|
||||
"serve": "^14.1.2",
|
||||
"stream-http": "^3.2.0",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"overrides": {
|
||||
"graphql": "15.8.0"
|
||||
@ -117,7 +119,7 @@
|
||||
"url": "https://github.com/oceanprotocol/market"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16"
|
||||
"node": "18"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
|
@ -9,7 +9,7 @@ import React, {
|
||||
} from 'react'
|
||||
import { Config, LoggerInstance, Purgatory } from '@oceanprotocol/lib'
|
||||
import { CancelToken } from 'axios'
|
||||
import { retrieveAsset } from '@utils/aquarius'
|
||||
import { getAsset } from '@utils/aquarius'
|
||||
import { useWeb3 } from './Web3'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
|
||||
@ -66,7 +66,7 @@ function AssetProvider({
|
||||
|
||||
LoggerInstance.log('[asset] Fetching asset...')
|
||||
setLoading(true)
|
||||
const asset = await retrieveAsset(did, token)
|
||||
const asset = await getAsset(did, token)
|
||||
|
||||
if (!asset) {
|
||||
setError(
|
||||
|
@ -7,7 +7,7 @@ import React, {
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { OpcQuery } from 'src/@types/subgraph/OpcQuery'
|
||||
import { OpcQuery } from '../../../src/@types/subgraph/OpcQuery'
|
||||
import { OperationResult } from 'urql'
|
||||
import { opcQuery } from './_queries'
|
||||
import { MarketMetadataProviderValue, OpcFee } from './_types'
|
||||
|
@ -307,7 +307,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
||||
)
|
||||
|
||||
// Construct network display name
|
||||
const networkDisplayName = getNetworkDisplayName(networkData, networkId)
|
||||
const networkDisplayName = getNetworkDisplayName(networkData)
|
||||
setNetworkDisplayName(networkDisplayName)
|
||||
|
||||
setIsTestnet(getNetworkType(networkData) !== NetworkType.Mainnet)
|
||||
|
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.
|
||||
if (
|
||||
network &&
|
||||
!network.name.includes('Testnet') &&
|
||||
!network.title?.includes('Testnet') &&
|
||||
network.name !== 'Moonbase Alpha'
|
||||
!network.name?.includes('Testnet') &&
|
||||
!network.title?.includes('Testnet')
|
||||
) {
|
||||
return NetworkType.Mainnet
|
||||
} else {
|
||||
@ -21,19 +20,13 @@ export function getNetworkType(network: EthereumListsChain): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function getNetworkDisplayName(
|
||||
data: EthereumListsChain,
|
||||
networkId: number
|
||||
): string {
|
||||
export function getNetworkDisplayName(data: EthereumListsChain): string {
|
||||
let displayName
|
||||
|
||||
switch (networkId) {
|
||||
switch (data.chainId) {
|
||||
case 137:
|
||||
displayName = 'Polygon'
|
||||
break
|
||||
case 1287:
|
||||
displayName = 'Moonbase'
|
||||
break
|
||||
case 1285:
|
||||
displayName = 'Moonriver'
|
||||
break
|
||||
@ -43,9 +36,6 @@ export function getNetworkDisplayName(
|
||||
case 8996:
|
||||
displayName = 'Development'
|
||||
break
|
||||
case 3:
|
||||
displayName = 'Ropsten'
|
||||
break
|
||||
case 5:
|
||||
displayName = 'Görli'
|
||||
break
|
||||
@ -54,7 +44,9 @@ export function getNetworkDisplayName(
|
||||
break
|
||||
default:
|
||||
displayName = data
|
||||
? `${data.chain} ${getNetworkType(data) === 'mainnet' ? '' : data.name}`
|
||||
? `${data.chain}${
|
||||
getNetworkType(data) === 'mainnet' ? '' : ` ${data.name}`
|
||||
}`
|
||||
: 'Unknown'
|
||||
break
|
||||
}
|
||||
|
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: [
|
||||
{ terms: { chainId: [1, 3] } },
|
||||
{ term: { _index: 'aquarius' } },
|
||||
{ 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 { AssetSelectionAsset } from '@shared/FormInput/InputElement/AssetSelection'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||
import { metadataCacheUri } from '../../app.config'
|
||||
import { OrdersData_orders as OrdersData } from '../../@types/subgraph/OrdersData'
|
||||
import { metadataCacheUri } from '../../../app.config'
|
||||
import {
|
||||
SortDirectionOptions,
|
||||
SortTermOptions
|
||||
} from '../@types/aquarius/SearchQuery'
|
||||
import { transformAssetToAssetSelection } from './assetConvertor'
|
||||
} from '../../@types/aquarius/SearchQuery'
|
||||
import { transformAssetToAssetSelection } from '../assetConvertor'
|
||||
|
||||
export interface UserSales {
|
||||
id: string
|
||||
@ -19,7 +19,7 @@ export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476
|
||||
export function escapeEsReservedCharacters(value: string): string {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const pattern = /([\!\*\+\-\=\<\>\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g
|
||||
return value.replace(pattern, '\\$1')
|
||||
return value?.replace(pattern, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,13 +55,13 @@ export function generateBaseQuery(
|
||||
...(baseQueryParams.filters || []),
|
||||
baseQueryParams.chainIds
|
||||
? getFilterTerm('chainId', baseQueryParams.chainIds)
|
||||
: '',
|
||||
: [],
|
||||
getFilterTerm('_index', 'aquarius'),
|
||||
...(baseQueryParams.ignorePurgatory
|
||||
? ''
|
||||
? []
|
||||
: [getFilterTerm('purgatory.state', false)]),
|
||||
...(baseQueryParams.ignoreState
|
||||
? ''
|
||||
? []
|
||||
: [
|
||||
{
|
||||
bool: {
|
||||
@ -143,7 +143,7 @@ export async function queryMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
export async function retrieveAsset(
|
||||
export async function getAsset(
|
||||
did: string,
|
||||
cancelToken: CancelToken
|
||||
): Promise<Asset> {
|
||||
@ -186,73 +186,7 @@ export async function getAssetsNames(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAssetsFromDidList(
|
||||
didList: string[],
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<PagedAssets> {
|
||||
try {
|
||||
if (!didList.length) 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) 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(
|
||||
export async function getAssetsFromDids(
|
||||
didList: string[],
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
@ -368,7 +302,7 @@ export async function getPublishedAssets(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTopPublishers(
|
||||
async function getTopPublishers(
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken,
|
||||
page?: number,
|
@ -17,7 +17,7 @@ import {
|
||||
queryMetadata,
|
||||
getFilterTerm,
|
||||
generateBaseQuery,
|
||||
retrieveDDOListByDIDs
|
||||
getAssetsFromDids
|
||||
} from './aquarius'
|
||||
import { fetchDataForMultipleChains } from './subgraph'
|
||||
import { getServiceById, getServiceByName } from './ddo'
|
||||
@ -338,7 +338,7 @@ export async function createTrustedAlgorithmList(
|
||||
if (!selectedAlgorithms || selectedAlgorithms.length === 0)
|
||||
return trustedAlgorithms
|
||||
|
||||
const selectedAssets = await retrieveDDOListByDIDs(
|
||||
const selectedAssets = await getAssetsFromDids(
|
||||
selectedAlgorithms,
|
||||
[assetChainId],
|
||||
cancelToken
|
||||
@ -393,31 +393,3 @@ export async function transformComputeFormToServiceComputeOptions(
|
||||
|
||||
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,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', () => {
|
||||
jest.setTimeout(10000)
|
||||
jest.retryTimes(2)
|
@ -1,4 +1,4 @@
|
||||
import { fetchData } from './fetch'
|
||||
import { fetchData } from '../fetch'
|
||||
|
||||
const apiUrl = 'https://ens-proxy.oceanprotocol.com/api'
|
||||
|
@ -3,12 +3,6 @@ import { Asset } from '@oceanprotocol/lib'
|
||||
// Boolean value that will be true if we are inside a browser, false otherwise
|
||||
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> {
|
||||
const index = arr.indexOf(value)
|
||||
if (index > -1) {
|
||||
|
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 remarkGfm from 'remark-gfm'
|
||||
|
||||
@ -6,7 +6,7 @@ export function markdownToHtml(markdown: string): string {
|
||||
const result = remark()
|
||||
.use(remarkGfm)
|
||||
.use(remarkHtml) // serializes through remark-rehype and rehype-stringify
|
||||
.processSync(markdown).contents
|
||||
.processSync(markdown)
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import {
|
||||
Arweave,
|
||||
ComputeAlgorithm,
|
||||
ComputeAsset,
|
||||
ComputeEnvironment,
|
||||
downloadFileBrowser,
|
||||
FileInfo,
|
||||
Ipfs,
|
||||
LoggerInstance,
|
||||
ProviderComputeInitializeResults,
|
||||
ProviderInstance,
|
||||
@ -83,18 +85,45 @@ export async function getFileDidInfo(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFileUrlInfo(
|
||||
url: string,
|
||||
providerUrl: string
|
||||
export async function getFileInfo(
|
||||
file: string,
|
||||
providerUrl: string,
|
||||
storageType: string
|
||||
): Promise<FileInfo[]> {
|
||||
try {
|
||||
const fileUrl: UrlFile = {
|
||||
type: 'url',
|
||||
index: 0,
|
||||
url,
|
||||
method: 'get'
|
||||
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
|
||||
}
|
||||
}
|
||||
const response = await ProviderInstance.getFileInfo(fileUrl, providerUrl)
|
||||
return response
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { sanitizeUrl } from './url'
|
||||
import { sanitizeUrl } from '.'
|
||||
|
||||
describe('@utils/url', () => {
|
||||
test('sanitizeUrl', () => {
|
@ -1,7 +1,7 @@
|
||||
import { AllLockedQuery } from 'src/@types/subgraph/AllLockedQuery'
|
||||
import { OwnAllocationsQuery } from 'src/@types/subgraph/OwnAllocationsQuery'
|
||||
import { NftOwnAllocationQuery } from 'src/@types/subgraph/NftOwnAllocationQuery'
|
||||
import { OceanLockedQuery } from 'src/@types/subgraph/OceanLockedQuery'
|
||||
import { AllLockedQuery } from '../../src/@types/subgraph/AllLockedQuery'
|
||||
import { OwnAllocationsQuery } from '../../src/@types/subgraph/OwnAllocationsQuery'
|
||||
import { NftOwnAllocationQuery } from '../../src/@types/subgraph/NftOwnAllocationQuery'
|
||||
import { OceanLockedQuery } from '../../src/@types/subgraph/OceanLockedQuery'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { fetchData, getQueryContext } from './subgraph'
|
||||
import axios from 'axios'
|
||||
@ -11,9 +11,6 @@ import {
|
||||
getNetworkType,
|
||||
NetworkType
|
||||
} from '@hooks/useNetworkMetadata'
|
||||
import { getAssetsFromNftList } from './aquarius'
|
||||
import { chainIdsSupported } from '../../app.config'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
|
||||
const AllLocked = gql`
|
||||
query AllLockedQuery {
|
||||
@ -80,6 +77,7 @@ export function getVeChainNetworkIds(assetNetworkIds: number[]): number[] {
|
||||
})
|
||||
return veNetworkIds
|
||||
}
|
||||
|
||||
export async function getNftOwnAllocation(
|
||||
userAddress: string,
|
||||
nftAddress: string,
|
||||
@ -177,17 +175,3 @@ export async function getOwnAllocations(
|
||||
|
||||
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 = {
|
||||
chainId: `0x${network.chainId.toString(16)}`,
|
||||
chainName: getNetworkDisplayName(network, network.chainId),
|
||||
chainName: getNetworkDisplayName(network),
|
||||
nativeCurrency: network.nativeCurrency,
|
||||
rpcUrls: network.rpc,
|
||||
blockExplorerUrls
|
||||
|
57
src/@utils/yup.ts
Normal file
57
src/@utils/yup.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { isCID } from '@utils/ipfs'
|
||||
import isUrl from 'is-url-superb'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
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 (value?.toString().includes('drive.google')) {
|
||||
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
|
||||
})
|
||||
}
|
@ -41,9 +41,7 @@ export default function AssetListTitle({
|
||||
|
||||
return (
|
||||
<h3 className={styles.title}>
|
||||
<Link href={`/asset/${did || asset?.id}`}>
|
||||
<a>{assetTitle}</a>
|
||||
</Link>
|
||||
<Link href={`/asset/${did || asset?.id}`}>{assetTitle}</Link>
|
||||
</h3>
|
||||
)
|
||||
}
|
||||
|
@ -35,83 +35,77 @@ export default function AssetTeaser({
|
||||
|
||||
return (
|
||||
<article className={`${styles.teaser} ${styles[type]}`}>
|
||||
<Link href={`/asset/${asset.id}`}>
|
||||
<a className={styles.link}>
|
||||
<aside className={styles.detailLine}>
|
||||
<AssetType
|
||||
className={styles.typeLabel}
|
||||
type={type}
|
||||
accessType={accessType}
|
||||
/>
|
||||
<span className={styles.typeLabel}>
|
||||
{datatokens[0]?.symbol.substring(0, 9)}
|
||||
</span>
|
||||
<NetworkName
|
||||
networkId={asset.chainId}
|
||||
className={styles.typeLabel}
|
||||
/>
|
||||
</aside>
|
||||
<header className={styles.header}>
|
||||
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
||||
{name.slice(0, 200)}
|
||||
<Link href={`/asset/${asset.id}`} className={styles.link}>
|
||||
<aside className={styles.detailLine}>
|
||||
<AssetType
|
||||
className={styles.typeLabel}
|
||||
type={type}
|
||||
accessType={accessType}
|
||||
/>
|
||||
<span className={styles.typeLabel}>
|
||||
{datatokens[0]?.symbol.substring(0, 9)}
|
||||
</span>
|
||||
<NetworkName networkId={asset.chainId} className={styles.typeLabel} />
|
||||
</aside>
|
||||
<header className={styles.header}>
|
||||
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
||||
{name.slice(0, 200)}
|
||||
</Dotdotdot>
|
||||
{!noPublisher && <Publisher account={owner} minimal />}
|
||||
</header>
|
||||
{!noDescription && (
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||
</Dotdotdot>
|
||||
{!noPublisher && <Publisher account={owner} minimal />}
|
||||
</header>
|
||||
{!noDescription && (
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||
</Dotdotdot>
|
||||
</div>
|
||||
)}
|
||||
{!noPrice && (
|
||||
<div className={styles.price}>
|
||||
{isUnsupportedPricing || !asset.services.length ? (
|
||||
<strong>No pricing schema available</strong>
|
||||
</div>
|
||||
)}
|
||||
{!noPrice && (
|
||||
<div className={styles.price}>
|
||||
{isUnsupportedPricing || !asset.services.length ? (
|
||||
<strong>No pricing schema available</strong>
|
||||
) : (
|
||||
<Price accessDetails={asset.accessDetails} size="small" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<footer className={styles.footer}>
|
||||
{allocated && allocated > 0 ? (
|
||||
<span className={styles.typeLabel}>
|
||||
{allocated < 0 ? (
|
||||
''
|
||||
) : (
|
||||
<Price accessDetails={asset.accessDetails} size="small" />
|
||||
<>
|
||||
<strong>{formatNumber(allocated, locale, '0')}</strong>{' '}
|
||||
veOCEAN
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<footer className={styles.footer}>
|
||||
{allocated && allocated > 0 ? (
|
||||
<span className={styles.typeLabel}>
|
||||
{allocated < 0 ? (
|
||||
''
|
||||
) : (
|
||||
<>
|
||||
<strong>{formatNumber(allocated, locale, '0')}</strong>{' '}
|
||||
veOCEAN
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
</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>
|
||||
</article>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement, useState } from 'react'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { FormPublishData } from '@components/Publish/_types'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import ImageInfo from './Info'
|
||||
import { getContainerChecksum } from '@utils/docker'
|
||||
@ -75,6 +75,7 @@ export default function ContainerInput(props: InputProps): ReactElement {
|
||||
name={`${field.name}[0].url`}
|
||||
checkUrl={false}
|
||||
isLoading={isLoading}
|
||||
storageType={'url'}
|
||||
handleButtonClick={handleValidation}
|
||||
/>
|
||||
)}
|
||||
|
@ -23,7 +23,7 @@ export default function FileInfo({
|
||||
{hideUrl ? 'https://oceanprotocol/placeholder' : file.url}
|
||||
</h3>
|
||||
<ul>
|
||||
<li className={styles.success}>✓ URL confirmed</li>
|
||||
<li className={styles.success}>✓ File confirmed</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
||||
</ul>
|
||||
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import FilesInput from './index'
|
||||
import { useField } from 'formik'
|
||||
import { getFileUrlInfo } from '@utils/provider'
|
||||
import { getFileInfo } from '@utils/provider'
|
||||
|
||||
jest.mock('formik')
|
||||
jest.mock('@utils/provider')
|
||||
@ -48,16 +48,17 @@ const mockForm = {
|
||||
describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
it('renders without crashing', async () => {
|
||||
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
|
||||
;(getFileUrlInfo as jest.Mock).mockReturnValue([
|
||||
;(getFileInfo as jest.Mock).mockReturnValue([
|
||||
{
|
||||
valid: true,
|
||||
url: 'https://hello.com',
|
||||
type: 'url',
|
||||
contentType: 'text/html',
|
||||
contentLength: 100
|
||||
}
|
||||
])
|
||||
|
||||
render(<FilesInput form={mockForm} {...props} />)
|
||||
render(<FilesInput form={mockForm} field={mockField} {...props} />)
|
||||
expect(screen.getByText('Validate')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByText('Validate'))
|
||||
|
||||
@ -67,13 +68,14 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
expect(mockHelpers.setValue).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders fileinfo when file is valid', () => {
|
||||
it('renders fileinfo when file url is valid', () => {
|
||||
;(useField as jest.Mock).mockReturnValue([
|
||||
{
|
||||
value: [
|
||||
{
|
||||
valid: true,
|
||||
url: 'https://hello.com',
|
||||
type: 'url',
|
||||
contentType: 'text/html',
|
||||
contentLength: 100
|
||||
}
|
||||
@ -82,10 +84,52 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
mockMeta,
|
||||
mockHelpers
|
||||
])
|
||||
render(<FilesInput {...props} />)
|
||||
render(<FilesInput {...props} field={mockField} />)
|
||||
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders fileinfo when ipfs is valid', () => {
|
||||
;(useField as jest.Mock).mockReturnValue([
|
||||
{
|
||||
value: [
|
||||
{
|
||||
valid: true,
|
||||
hash: 'bafkreicxccbk4blsx5qtovqfgsuutxjxom47dvyzyz3asi2ggjg5ipwlc4',
|
||||
type: 'ipfs',
|
||||
contentLength: '40492',
|
||||
contentType: 'text/csv',
|
||||
index: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
mockMeta,
|
||||
mockHelpers
|
||||
])
|
||||
render(<FilesInput {...props} field={mockField} />)
|
||||
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders fileinfo when arweave is valid', () => {
|
||||
;(useField as jest.Mock).mockReturnValue([
|
||||
{
|
||||
value: [
|
||||
{
|
||||
valid: true,
|
||||
transactionId: 'T6NL8Zc0LCbT3bF9HacAGQC4W0_hW7b3tXbm8OtWtlA',
|
||||
type: 'arweave',
|
||||
contentLength: '57043',
|
||||
contentType: 'image/jpeg',
|
||||
index: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
mockMeta,
|
||||
mockHelpers
|
||||
])
|
||||
render(<FilesInput {...props} field={mockField} />)
|
||||
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders fileinfo without contentType', () => {
|
||||
;(useField as jest.Mock).mockReturnValue([
|
||||
{
|
||||
@ -93,6 +137,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
{
|
||||
valid: true,
|
||||
url: 'https://hello.com',
|
||||
type: 'url',
|
||||
contentLength: 100
|
||||
}
|
||||
]
|
||||
@ -100,7 +145,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
mockMeta,
|
||||
mockHelpers
|
||||
])
|
||||
render(<FilesInput {...props} />)
|
||||
render(<FilesInput {...props} field={mockField} />)
|
||||
})
|
||||
|
||||
it('renders fileinfo placeholder when hideUrl is passed', () => {
|
||||
@ -117,7 +162,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||
mockMeta,
|
||||
mockHelpers
|
||||
])
|
||||
render(<FilesInput {...props} />)
|
||||
render(<FilesInput {...props} field={mockField} />)
|
||||
expect(
|
||||
screen.getByText('https://oceanprotocol/placeholder')
|
||||
).toBeInTheDocument()
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useField } from 'formik'
|
||||
import FileInfo from './Info'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { getFileUrlInfo } from '@utils/provider'
|
||||
import { getFileInfo } from '@utils/provider'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '@context/Asset'
|
||||
|
||||
@ -12,16 +12,27 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { asset } = useAsset()
|
||||
|
||||
const providerUrl = props.form?.values?.services
|
||||
? props.form?.values?.services[0].providerUrl.url
|
||||
: asset.services[0].serviceEndpoint
|
||||
|
||||
const storageType = field.value[0].type
|
||||
|
||||
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||
e?.preventDefault()
|
||||
|
||||
try {
|
||||
const providerUrl = props.form?.values?.services
|
||||
? props.form?.values?.services[0].providerUrl.url
|
||||
: asset.services[0].serviceEndpoint
|
||||
setIsLoading(true)
|
||||
const checkedFile = await getFileUrlInfo(url, providerUrl)
|
||||
|
||||
// TODO: handled on provider
|
||||
if (url.includes('drive.google')) {
|
||||
throw Error(
|
||||
'Google Drive is not a supported hosting service. Please use an alternative.'
|
||||
)
|
||||
}
|
||||
|
||||
const checkedFile = await getFileInfo(url, providerUrl, storageType)
|
||||
|
||||
// error if something's not right from response
|
||||
if (!checkedFile)
|
||||
@ -31,7 +42,7 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
throw Error('✗ No valid file detected. Check your URL and try again.')
|
||||
|
||||
// if all good, add file to formik state
|
||||
helpers.setValue([{ url, ...checkedFile[0] }])
|
||||
helpers.setValue([{ url, type: storageType, ...checkedFile[0] }])
|
||||
} catch (error) {
|
||||
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
||||
LoggerInstance.error(error.message)
|
||||
@ -42,7 +53,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
|
||||
function handleClose() {
|
||||
helpers.setTouched(false)
|
||||
helpers.setValue(meta.initialValue)
|
||||
helpers.setValue([
|
||||
{ url: '', type: storageType === 'hidden' ? 'ipfs' : storageType }
|
||||
])
|
||||
}
|
||||
|
||||
return (
|
||||
@ -56,7 +69,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
{...props}
|
||||
name={`${field.name}[0].url`}
|
||||
isLoading={isLoading}
|
||||
checkUrl={true}
|
||||
handleButtonClick={handleValidation}
|
||||
storageType={storageType}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -6,7 +6,7 @@ import FileInfo from '../FilesInput/Info'
|
||||
import styles from './index.module.css'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { LoggerInstance, ProviderInstance } from '@oceanprotocol/lib'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { FormPublishData } from '@components/Publish/_types'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import axios from 'axios'
|
||||
|
@ -6,6 +6,7 @@ import styles from './index.module.css'
|
||||
import InputGroup from '@shared/FormInput/InputGroup'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
import isUrl from 'is-url-superb'
|
||||
import { isCID } from '@utils/ipfs'
|
||||
|
||||
export interface URLInputProps {
|
||||
submitText: string
|
||||
@ -13,6 +14,7 @@ export interface URLInputProps {
|
||||
isLoading: boolean
|
||||
name: string
|
||||
checkUrl?: boolean
|
||||
storageType?: string
|
||||
}
|
||||
|
||||
export default function URLInput({
|
||||
@ -21,6 +23,7 @@ export default function URLInput({
|
||||
isLoading,
|
||||
name,
|
||||
checkUrl,
|
||||
storageType,
|
||||
...props
|
||||
}: URLInputProps): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
@ -32,7 +35,8 @@ export default function URLInput({
|
||||
setIsButtonDisabled(
|
||||
!field?.value ||
|
||||
field.value === '' ||
|
||||
(checkUrl && !isUrl(field.value)) ||
|
||||
(checkUrl && storageType === 'url' && !isUrl(field.value)) ||
|
||||
(checkUrl && storageType === 'ipfs' && !isCID(field.value)) ||
|
||||
field.value.includes('javascript:') ||
|
||||
meta?.error
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useCallback, useState } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import { InputProps } from '..'
|
||||
import FilesInput from './FilesInput'
|
||||
@ -11,6 +11,7 @@ import Nft from './Nft'
|
||||
import InputRadio from './Radio'
|
||||
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
||||
import TagsAutoComplete from './TagsAutoComplete'
|
||||
import TabsFile from '@shared/atoms/TabsFile'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
@ -55,6 +56,7 @@ export default function InputElement({
|
||||
...props
|
||||
}: InputProps): ReactElement {
|
||||
const styleClasses = cx({ select: true, [size]: size })
|
||||
|
||||
switch (props.type) {
|
||||
case 'select': {
|
||||
const sortedOptions =
|
||||
@ -80,6 +82,26 @@ export default function InputElement({
|
||||
</select>
|
||||
)
|
||||
}
|
||||
case 'tabs': {
|
||||
const tabs: any = []
|
||||
props.fields.map((field: any, i) => {
|
||||
return tabs.push({
|
||||
title: field.title,
|
||||
field,
|
||||
props,
|
||||
content: (
|
||||
<FilesInput
|
||||
key={`fileInput_${i}`}
|
||||
{...field}
|
||||
form={form}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return <TabsFile items={tabs} className={styles.pricing} />
|
||||
}
|
||||
case 'textarea':
|
||||
return <textarea id={props.name} className={styles.textarea} {...props} />
|
||||
|
||||
|
@ -32,6 +32,7 @@ export interface InputProps {
|
||||
type?: string
|
||||
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
||||
sortOptions?: boolean
|
||||
fields?: FieldInputProps<any>[]
|
||||
additionalComponent?: ReactElement
|
||||
value?: string | number
|
||||
onChange?(
|
||||
|
@ -17,7 +17,7 @@ export default function NetworkName({
|
||||
}): ReactElement {
|
||||
const { networksList } = useNetworkMetadata()
|
||||
const networkData = getNetworkDataById(networksList, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData, networkId)
|
||||
const networkName = getNetworkDisplayName(networkData)
|
||||
|
||||
return (
|
||||
<span
|
||||
|
@ -41,8 +41,8 @@ export default function Publisher({
|
||||
name
|
||||
) : (
|
||||
<>
|
||||
<Link href={`/profile/${account}`}>
|
||||
<a title="Show profile page.">{name}</a>
|
||||
<Link href={`/profile/${account}`} title="Show profile page.">
|
||||
{name}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
@ -18,10 +18,10 @@ export default function WalletNetworkSwitcher(): ReactElement {
|
||||
const walletNetworkData = getNetworkDataById(networksList, networkId)
|
||||
|
||||
const ddoNetworkName = (
|
||||
<strong>{getNetworkDisplayName(ddoNetworkData, asset.chainId)}</strong>
|
||||
<strong>{getNetworkDisplayName(ddoNetworkData)}</strong>
|
||||
)
|
||||
const walletNetworkName = (
|
||||
<strong>{getNetworkDisplayName(walletNetworkData, networkId)}</strong>
|
||||
<strong>{getNetworkDisplayName(walletNetworkData)}</strong>
|
||||
)
|
||||
|
||||
async function switchWalletNetwork() {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Status from '@shared/atoms/Status'
|
||||
import styles from './index.module.css'
|
||||
import WalletNetworkSwitcher from '../WalletNetworkSwitcher'
|
||||
|
@ -41,10 +41,8 @@ export default function Button({
|
||||
})
|
||||
|
||||
return to ? (
|
||||
<Link href={to}>
|
||||
<a className={styleClasses} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
<Link href={to} className={styleClasses} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
) : href ? (
|
||||
<a href={href} className={styleClasses} {...props}>
|
||||
|
91
src/components/@shared/atoms/TabsFile/index.module.css
Normal file
91
src/components/@shared/atoms/TabsFile/index.module.css
Normal file
@ -0,0 +1,91 @@
|
||||
.tabListContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tabList {
|
||||
text-align: center;
|
||||
padding: calc(var(--spacer) / 2) 0 0 0;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: inline-block;
|
||||
padding: calc(var(--spacer) / 8) calc(var(--spacer) / 2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-small);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-secondary);
|
||||
margin-right: -1px;
|
||||
background: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.tabHidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tab,
|
||||
.tab label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tab[aria-selected='true'] {
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
list-style: none;
|
||||
background-color: var(--background-body);
|
||||
border: 1px solid var(--border-color);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-top-right-radius: var(--border-radius);
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.tab[aria-disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tab > div {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.tabLabel {
|
||||
color: var(--font-color-text);
|
||||
font-size: var(--font-size-small);
|
||||
font-family: var(--font-family-heading);
|
||||
line-height: 1.2;
|
||||
display: block;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.tabContent {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.tab {
|
||||
padding: calc(var(--spacer) / 8) var(--spacer);
|
||||
}
|
||||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '../../FormInput/InputElement/Radio/index.module.css';
|
||||
}
|
52
src/components/@shared/atoms/TabsFile/index.stories.tsx
Normal file
52
src/components/@shared/atoms/TabsFile/index.stories.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||
|
||||
import Tabs, { TabsProps } from '@shared/atoms/Tabs'
|
||||
|
||||
export default {
|
||||
title: 'Component/@shared/atoms/Tabs',
|
||||
component: Tabs
|
||||
} as ComponentMeta<typeof Tabs>
|
||||
|
||||
const Template: ComponentStory<typeof Tabs> = (args) => <Tabs {...args} />
|
||||
|
||||
interface Props {
|
||||
args: TabsProps
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'First tab',
|
||||
content: 'this is the content for the first tab'
|
||||
},
|
||||
{
|
||||
title: 'Second tab',
|
||||
content: 'this is the content for the second tab'
|
||||
},
|
||||
{
|
||||
title: 'Third tab',
|
||||
content: 'this is the content for the third tab'
|
||||
}
|
||||
]
|
||||
|
||||
export const Default = Template.bind({})
|
||||
Default.args = {
|
||||
items
|
||||
}
|
||||
|
||||
export const WithRadio: Props = Template.bind({})
|
||||
WithRadio.args = {
|
||||
items,
|
||||
showRadio: true
|
||||
}
|
||||
|
||||
export const WithDefaultIndex: Props = Template.bind({})
|
||||
WithDefaultIndex.args = {
|
||||
items,
|
||||
defaultIndex: 1
|
||||
}
|
||||
|
||||
export const LotsOfTabs: Props = Template.bind({})
|
||||
LotsOfTabs.args = {
|
||||
items: items.flatMap((i) => [i, i, i])
|
||||
}
|
25
src/components/@shared/atoms/TabsFile/index.test.tsx
Normal file
25
src/components/@shared/atoms/TabsFile/index.test.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { Default, WithRadio } from './index.stories'
|
||||
|
||||
describe('Tabs', () => {
|
||||
test('should be able to change', async () => {
|
||||
render(<Default {...Default.args} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Second tab'))
|
||||
const secondTab = await screen.findByText(/content for the second tab/i)
|
||||
expect(secondTab).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('should fire custom change handler', async () => {
|
||||
const handler = jest.fn()
|
||||
render(<Default {...Default.args} handleTabChange={handler} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Second tab'))
|
||||
expect(handler).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('renders WithRadio', () => {
|
||||
render(<Default {...WithRadio.args} />)
|
||||
})
|
||||
})
|
109
src/components/@shared/atoms/TabsFile/index.tsx
Normal file
109
src/components/@shared/atoms/TabsFile/index.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import Label from '@shared/FormInput/Label'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import { useFormikContext } from 'formik'
|
||||
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import Tooltip from '../Tooltip'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export interface TabsItem {
|
||||
field: any
|
||||
title: string
|
||||
content: ReactNode
|
||||
disabled?: boolean
|
||||
props: any
|
||||
}
|
||||
|
||||
export interface TabsProps {
|
||||
items: TabsItem[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function TabsFile({
|
||||
items,
|
||||
className
|
||||
}: TabsProps): ReactElement {
|
||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||
const [tabIndex, setTabIndex] = useState(0)
|
||||
// hide tabs if are hidden
|
||||
const isHidden = items[tabIndex].props.value[0].type === 'hidden'
|
||||
|
||||
const setIndex = (tabName: string) => {
|
||||
const index = items.findIndex((tab: any) => {
|
||||
if (tab.title !== tabName) return false
|
||||
return tab
|
||||
})
|
||||
setTabIndex(index)
|
||||
setFieldValue(`${items[index].props.name}[0]`, {
|
||||
url: '',
|
||||
type: items[index].field.value
|
||||
})
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName: string) => {
|
||||
setIndex(tabName)
|
||||
}
|
||||
|
||||
let textToolTip = false
|
||||
if (values?.services) {
|
||||
textToolTip = values.services[0].access === 'compute'
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactTabs className={`${className || ''}`} defaultIndex={tabIndex}>
|
||||
<div className={styles.tabListContainer}>
|
||||
<TabList className={styles.tabList}>
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<Tab
|
||||
className={`${styles.tab} ${
|
||||
isHidden ? styles.tabHidden : null
|
||||
}`}
|
||||
key={`tab_${items[tabIndex].props.name}_${index}`}
|
||||
onClick={
|
||||
handleTabChange ? () => handleTabChange(item.title) : null
|
||||
}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.title}
|
||||
</Tab>
|
||||
)
|
||||
})}
|
||||
</TabList>
|
||||
</div>
|
||||
<div className={styles.tabContent}>
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
<TabPanel key={`tabpanel_${items[tabIndex].props.name}_${index}`}>
|
||||
{!isHidden && (
|
||||
<label className={styles.tabLabel}>
|
||||
{item.field.label}
|
||||
{item.field.required && (
|
||||
<span title="Required" className={styles.required}>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
{item.field.help && item.field.prominentHelp && (
|
||||
<Tooltip
|
||||
content={
|
||||
<Markdown
|
||||
text={`${item.field.help} ${
|
||||
textToolTip ? item.field.computeHelp : ''
|
||||
}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
{item.content}
|
||||
</TabPanel>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ReactTabs>
|
||||
)
|
||||
}
|
@ -15,10 +15,12 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
|
||||
return noLinks ? (
|
||||
<span className={styles.tag}>{tag}</span>
|
||||
) : (
|
||||
<Link href={`/search?tags=${urlEncodedTag}&sort=_score&sortOrder=desc`}>
|
||||
<a className={styles.tag} title={tag}>
|
||||
{tag}
|
||||
</a>
|
||||
<Link
|
||||
href={`/search?tags=${urlEncodedTag}&sort=_score&sortOrder=desc`}
|
||||
className={styles.tag}
|
||||
title={tag}
|
||||
>
|
||||
{tag}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -24,24 +24,26 @@ export default function AssetComputeSelection({
|
||||
<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}
|
||||
<Link
|
||||
href={`/asset/${asset.did}`}
|
||||
key={asset.did}
|
||||
className={styles.row}
|
||||
>
|
||||
<div className={styles.info}>
|
||||
<h3 className={styles.title}>
|
||||
<Dotdotdot clamp={1} tagName="span">
|
||||
{asset.name}
|
||||
</Dotdotdot>
|
||||
</div>
|
||||
<PriceUnit
|
||||
price={Number(asset.price)}
|
||||
size="small"
|
||||
className={styles.price}
|
||||
/>
|
||||
</a>
|
||||
</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}
|
||||
/>
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
|
@ -10,7 +10,7 @@ export default function ComputeHistory({
|
||||
}: {
|
||||
title: string
|
||||
children: ReactNode
|
||||
refetchJobs?: any
|
||||
refetchJobs?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
|
@ -7,13 +7,13 @@ import { compareAsBN } from '@utils/numbers'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Web3Feedback from '@shared/Web3Feedback'
|
||||
import { getFileDidInfo, getFileUrlInfo } from '@utils/provider'
|
||||
import { getFileDidInfo, getFileInfo } from '@utils/provider'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import styles from './index.module.css'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { FormPublishData } from '@components/Publish/_types'
|
||||
import { getTokenBalanceFromSymbol } from '@utils/web3'
|
||||
import AssetStats from './AssetStats'
|
||||
import CalicaIntegration from './CalicaIntegration'
|
||||
@ -53,14 +53,20 @@ export default function AssetActions({
|
||||
formikState?.values?.services[0].providerUrl.url ||
|
||||
asset?.services[0]?.serviceEndpoint
|
||||
|
||||
const storageType = formikState?.values?.services
|
||||
? formikState?.values?.services[0].files[0].type
|
||||
: null
|
||||
|
||||
try {
|
||||
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
|
||||
.url
|
||||
? await getFileUrlInfo(
|
||||
? await getFileInfo(
|
||||
formikState?.values?.services?.[0].files?.[0].url,
|
||||
providerUrl
|
||||
providerUrl,
|
||||
storageType
|
||||
)
|
||||
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)
|
||||
|
||||
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
||||
|
||||
// set the content type in the Dataset Schema
|
||||
|
@ -1,12 +1,29 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import MetaItem from './MetaItem'
|
||||
import styles from './MetaFull.module.css'
|
||||
import Publisher from '@shared/Publisher'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { Asset, Datatoken, LoggerInstance } from '@oceanprotocol/lib'
|
||||
|
||||
export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
|
||||
const [paymentCollector, setPaymentCollector] = useState<string>()
|
||||
const { isInPurgatory } = useAsset()
|
||||
const { web3 } = useWeb3()
|
||||
|
||||
useEffect(() => {
|
||||
async function getInitialPaymentCollector() {
|
||||
try {
|
||||
const datatoken = new Datatoken(web3)
|
||||
setPaymentCollector(
|
||||
await datatoken.getPaymentCollector(ddo.datatokens[0].address)
|
||||
)
|
||||
} catch (error) {
|
||||
LoggerInstance.error('[MetaFull: getInitialPaymentCollector]', error)
|
||||
}
|
||||
}
|
||||
getInitialPaymentCollector()
|
||||
}, [ddo, web3])
|
||||
|
||||
function DockerImage() {
|
||||
const containerInfo = ddo?.metadata?.algorithm?.container
|
||||
@ -23,6 +40,12 @@ export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
|
||||
title="Owner"
|
||||
content={<Publisher account={ddo?.nft?.owner} />}
|
||||
/>
|
||||
{paymentCollector && paymentCollector !== ddo?.nft?.owner && (
|
||||
<MetaItem
|
||||
title="Revenue Sent To"
|
||||
content={<Publisher account={paymentCollector} />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{ddo?.metadata?.type === 'algorithm' && ddo?.metadata?.algorithm && (
|
||||
<MetaItem title="Docker Image" content={<DockerImage />} />
|
||||
|
@ -3,7 +3,7 @@ import Tooltip from '@shared/atoms/Tooltip'
|
||||
import { decodeTokenURI } from '@utils/nft'
|
||||
import { useFormikContext } from 'formik'
|
||||
import React from 'react'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { FormPublishData } from '@components/Publish/_types'
|
||||
import Logo from '@shared/atoms/Logo'
|
||||
import NftTooltip from './NftTooltip'
|
||||
import styles from './index.module.css'
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { Formik } from 'formik'
|
||||
import {
|
||||
LoggerInstance,
|
||||
Metadata,
|
||||
FixedRateExchange,
|
||||
Asset,
|
||||
Service
|
||||
Service,
|
||||
Datatoken
|
||||
} from '@oceanprotocol/lib'
|
||||
import { validationSchema } from './_validation'
|
||||
import { getInitialValues } from './_constants'
|
||||
@ -36,10 +37,28 @@ export default function Edit({
|
||||
const { accountId, web3 } = useWeb3()
|
||||
const newAbortController = useAbortController()
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [paymentCollector, setPaymentCollector] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const isComputeType = asset?.services[0]?.type === 'compute'
|
||||
const hasFeedback = error || success
|
||||
|
||||
useEffect(() => {
|
||||
async function getInitialPaymentCollector() {
|
||||
try {
|
||||
const datatoken = new Datatoken(web3)
|
||||
setPaymentCollector(
|
||||
await datatoken.getPaymentCollector(asset?.datatokens[0].address)
|
||||
)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(
|
||||
'[EditMetadata: getInitialPaymentCollector]',
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
getInitialPaymentCollector()
|
||||
}, [asset, web3])
|
||||
|
||||
async function updateFixedPrice(newPrice: string) {
|
||||
const config = getOceanConfig(asset.chainId)
|
||||
|
||||
@ -81,13 +100,22 @@ export default function Edit({
|
||||
values.price !== asset.accessDetails.price &&
|
||||
(await updateFixedPrice(values.price))
|
||||
|
||||
if (values.paymentCollector !== paymentCollector) {
|
||||
const datatoken = new Datatoken(web3)
|
||||
await datatoken.setPaymentCollector(
|
||||
asset?.datatokens[0].address,
|
||||
accountId,
|
||||
values.paymentCollector
|
||||
)
|
||||
}
|
||||
|
||||
if (values.files[0]?.url) {
|
||||
const file = {
|
||||
nftAddress: asset.nftAddress,
|
||||
datatokenAddress: asset.services[0].datatokenAddress,
|
||||
files: [
|
||||
{
|
||||
type: 'url',
|
||||
type: values.files[0].type,
|
||||
index: 0,
|
||||
url: values.files[0].url,
|
||||
method: 'GET'
|
||||
@ -147,7 +175,8 @@ export default function Edit({
|
||||
initialValues={getInitialValues(
|
||||
asset?.metadata,
|
||||
asset?.services[0]?.timeout,
|
||||
asset?.accessDetails?.price
|
||||
asset?.accessDetails?.price,
|
||||
paymentCollector
|
||||
)}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
|
15
src/components/Asset/Edit/FormActions.test.tsx
Normal file
15
src/components/Asset/Edit/FormActions.test.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import { useFormikContext } from 'formik'
|
||||
import FormActions from './FormActions'
|
||||
|
||||
jest.mock('formik')
|
||||
|
||||
describe('@components/Asset/Edit/FormActions.tsx', () => {
|
||||
it('renders fixed price', () => {
|
||||
const isValid = true
|
||||
;(useFormikContext as jest.Mock).mockReturnValue([isValid])
|
||||
render(<FormActions />)
|
||||
expect(screen.getByText('Submit')).toBeInTheDocument()
|
||||
})
|
||||
})
|
@ -1,10 +1,11 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { Field, Form, useField, useFormikContext } from 'formik'
|
||||
import Input, { InputProps } from '@shared/FormInput'
|
||||
import { Field, Form, useFormikContext } from 'formik'
|
||||
import Input from '@shared/FormInput'
|
||||
import FormActions from './FormActions'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
import { getFileUrlInfo } from '@utils/provider'
|
||||
import { FormPublishData } from '@components/Publish/_types'
|
||||
import { getFileInfo } from '@utils/provider'
|
||||
import { getFieldContent } from '@utils/form'
|
||||
|
||||
export function checkIfTimeoutInPredefinedValues(
|
||||
timeout: string,
|
||||
@ -21,11 +22,11 @@ export default function FormEditMetadata({
|
||||
showPrice,
|
||||
isComputeDataset
|
||||
}: {
|
||||
data: InputProps[]
|
||||
data: FormFieldContent[]
|
||||
showPrice: boolean
|
||||
isComputeDataset: boolean
|
||||
}): ReactElement {
|
||||
const { oceanConfig, asset } = useAsset()
|
||||
const { asset } = useAsset()
|
||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||
|
||||
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
||||
@ -46,25 +47,38 @@ export default function FormEditMetadata({
|
||||
|
||||
useEffect(() => {
|
||||
// let's initiate files with empty url (we can't access the asset url) with type hidden (for UI frontend)
|
||||
setFieldValue('files', [
|
||||
{
|
||||
url: '',
|
||||
type: 'hidden'
|
||||
}
|
||||
])
|
||||
setTimeout(() => {
|
||||
setFieldValue('files', [
|
||||
{
|
||||
url: '',
|
||||
type: 'hidden'
|
||||
}
|
||||
])
|
||||
}, 500)
|
||||
|
||||
const providerUrl = values?.services
|
||||
? values?.services[0].providerUrl.url
|
||||
: asset.services[0].serviceEndpoint
|
||||
|
||||
// if we have a sample file, we need to get the files' info before setting defaults links value
|
||||
asset?.metadata?.links?.[0] &&
|
||||
getFileUrlInfo(asset.metadata.links[0], providerUrl).then(
|
||||
getFileInfo(asset.metadata.links[0], providerUrl, 'url').then(
|
||||
(checkedFile) => {
|
||||
console.log(checkedFile)
|
||||
// set valid false if url is using google drive
|
||||
if (asset.metadata.links[0].includes('drive.google')) {
|
||||
setFieldValue('links', [
|
||||
{
|
||||
url: asset.metadata.links[0],
|
||||
valid: false
|
||||
}
|
||||
])
|
||||
return
|
||||
}
|
||||
// initiate link with values from asset metadata
|
||||
setFieldValue('links', [
|
||||
{
|
||||
url: asset.metadata.links[0],
|
||||
type: 'url',
|
||||
...checkedFile[0]
|
||||
}
|
||||
])
|
||||
@ -74,23 +88,53 @@ export default function FormEditMetadata({
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{data.map(
|
||||
(field: InputProps) =>
|
||||
(!showPrice && field.name === 'price') || (
|
||||
<Field
|
||||
key={field.name}
|
||||
options={
|
||||
field.name === 'timeout' && isComputeDataset === true
|
||||
? timeoutOptionsArray
|
||||
: field.options
|
||||
}
|
||||
{...field}
|
||||
component={Input}
|
||||
prefix={field.name === 'price' && oceanConfig?.oceanTokenSymbol}
|
||||
/>
|
||||
)
|
||||
<Field {...getFieldContent('name', data)} component={Input} name="name" />
|
||||
|
||||
<Field
|
||||
{...getFieldContent('description', data)}
|
||||
component={Input}
|
||||
name="description"
|
||||
/>
|
||||
|
||||
{showPrice && (
|
||||
<Field
|
||||
{...getFieldContent('price', data)}
|
||||
component={Input}
|
||||
name="price"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Field
|
||||
{...getFieldContent('files', data)}
|
||||
component={Input}
|
||||
name="files"
|
||||
/>
|
||||
|
||||
<Field
|
||||
{...getFieldContent('links', data)}
|
||||
component={Input}
|
||||
name="links"
|
||||
/>
|
||||
|
||||
<Field
|
||||
{...getFieldContent('timeout', data)}
|
||||
component={Input}
|
||||
name="timeout"
|
||||
/>
|
||||
|
||||
<Field
|
||||
{...getFieldContent('author', data)}
|
||||
component={Input}
|
||||
name="author"
|
||||
/>
|
||||
|
||||
<Field {...getFieldContent('tags', data)} component={Input} name="tags" />
|
||||
<Field
|
||||
{...getFieldContent('paymentCollector', data)}
|
||||
component={Input}
|
||||
name="paymentCollector"
|
||||
/>
|
||||
|
||||
<FormActions />
|
||||
</Form>
|
||||
)
|
||||
|
@ -5,17 +5,19 @@ import { ComputeEditForm, MetadataEditForm } from './_types'
|
||||
export function getInitialValues(
|
||||
metadata: Metadata,
|
||||
timeout: number,
|
||||
price: string
|
||||
price: string,
|
||||
paymentCollector: string
|
||||
): Partial<MetadataEditForm> {
|
||||
return {
|
||||
name: metadata?.name,
|
||||
description: metadata?.description,
|
||||
price,
|
||||
links: [{ url: '', type: '' }],
|
||||
files: [{ url: '', type: '' }],
|
||||
links: [{ url: '', type: 'url' }],
|
||||
files: [{ url: '', type: 'ipfs' }],
|
||||
timeout: secondsToString(timeout),
|
||||
author: metadata?.author,
|
||||
tags: metadata?.tags
|
||||
tags: metadata?.tags,
|
||||
paymentCollector
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ export interface MetadataEditForm {
|
||||
name: string
|
||||
description: string
|
||||
timeout: string
|
||||
paymentCollector: string
|
||||
price?: string
|
||||
files: FileInfo[]
|
||||
links?: FileInfo[]
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { FileInfo } from '@oceanprotocol/lib'
|
||||
import * as Yup from 'yup'
|
||||
import web3 from 'web3'
|
||||
import { testLinks } from '../../../@utils/yup'
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
name: Yup.string()
|
||||
@ -10,38 +12,38 @@ export const validationSchema = Yup.object().shape({
|
||||
files: Yup.array<FileInfo[]>()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
url: Yup.string()
|
||||
.url('Must be a valid URL.')
|
||||
.test(
|
||||
'GoogleNotSupported',
|
||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
||||
(value) => {
|
||||
return !value?.toString().includes('drive.google')
|
||||
}
|
||||
),
|
||||
valid: Yup.boolean().isTrue()
|
||||
url: testLinks(true),
|
||||
valid: Yup.boolean().test((value, context) => {
|
||||
const { type } = context.parent
|
||||
// allow user to submit if the value type is hidden
|
||||
if (type === 'hidden') return true
|
||||
return value || false
|
||||
})
|
||||
})
|
||||
)
|
||||
.nullable(),
|
||||
links: Yup.array<FileInfo[]>()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
url: Yup.string()
|
||||
.url('Must be a valid URL.')
|
||||
.test(
|
||||
'GoogleNotSupported',
|
||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
||||
(value) => {
|
||||
return !value?.toString().includes('drive.google')
|
||||
}
|
||||
),
|
||||
valid: Yup.boolean().isTrue()
|
||||
links: Yup.array<FileInfo[]>().of(
|
||||
Yup.object().shape({
|
||||
url: testLinks(true),
|
||||
valid: Yup.boolean().test((value, context) => {
|
||||
// allow user to submit if the value is null
|
||||
const { valid, url } = context.parent
|
||||
// allow user to continue if the url is empty
|
||||
if (!url) return true
|
||||
return valid
|
||||
})
|
||||
)
|
||||
.nullable(),
|
||||
})
|
||||
),
|
||||
timeout: Yup.string().required('Required'),
|
||||
author: Yup.string().nullable(),
|
||||
tags: Yup.array<string[]>().nullable()
|
||||
tags: Yup.array<string[]>().nullable(),
|
||||
paymentCollector: Yup.string().test(
|
||||
'ValidAddress',
|
||||
'Must be a valid Ethereum Address.',
|
||||
(value) => {
|
||||
return web3.utils.isAddress(value)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
export const computeSettingsValidationSchema = Yup.object().shape({
|
||||
|
@ -24,17 +24,11 @@ export default function Links() {
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
<Link href="/imprint">
|
||||
<a>Imprint</a>
|
||||
</Link>
|
||||
<Link href="/imprint">Imprint</Link>
|
||||
{' — '}
|
||||
<Link href="/terms">
|
||||
<a>Terms</a>
|
||||
</Link>
|
||||
<Link href="/terms">Terms</Link>
|
||||
{' — '}
|
||||
<Link href={privacyPolicySlug}>
|
||||
<a>Privacy</a>
|
||||
</Link>
|
||||
<Link href={privacyPolicySlug}>Privacy</Link>
|
||||
{appConfig?.privacyPreferenceCenter === 'true' && (
|
||||
<>
|
||||
{' — '}
|
||||
|
@ -6,7 +6,7 @@ import useNetworkMetadata, {
|
||||
} from '@hooks/useNetworkMetadata'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import styles from './index.module.css'
|
||||
import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from 'src/@types/subgraph/FooterStatsValues'
|
||||
import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from '../../../../src/@types/subgraph/FooterStatsValues'
|
||||
import MarketStatsTotal from './Total'
|
||||
import { queryGlobalStatistics } from './_queries'
|
||||
import { StatsTotal } from './_types'
|
||||
|
@ -24,8 +24,8 @@ function MenuLink({ item }: { item: MenuItem }) {
|
||||
: styles.link
|
||||
|
||||
return (
|
||||
<Link key={item.name} href={item.link}>
|
||||
<a className={classes}>{item.name}</a>
|
||||
<Link key={item.name} href={item.link} className={classes}>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@ -35,11 +35,9 @@ export default function Menu(): ReactElement {
|
||||
|
||||
return (
|
||||
<nav className={styles.menu}>
|
||||
<Link href="/">
|
||||
<a className={styles.logo}>
|
||||
<Logo noWordmark />
|
||||
<h1 className={styles.title}>{siteContent?.siteTitle}</h1>
|
||||
</a>
|
||||
<Link href="/" className={styles.logo}>
|
||||
<Logo noWordmark />
|
||||
<h1 className={styles.title}>{siteContent?.siteTitle}</h1>
|
||||
</Link>
|
||||
|
||||
<ul className={styles.navigation}>
|
||||
|
@ -17,12 +17,7 @@ async function emptySearch() {
|
||||
const text = searchParams.get('text')
|
||||
|
||||
if (text !== ('' || undefined || null)) {
|
||||
const url = await addExistingParamsToUrl(location, [
|
||||
'text',
|
||||
'owner',
|
||||
'tags'
|
||||
])
|
||||
// router.push(`${url}&text=%20`)
|
||||
await addExistingParamsToUrl(location, ['text', 'owner', 'tags'])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import Price from '@shared/Price'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import AssetTitle from '@shared/AssetListTitle'
|
||||
import { retrieveDDOListByDIDs } from '@utils/aquarius'
|
||||
import { getAssetsFromDids } from '@utils/aquarius'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
@ -59,7 +59,7 @@ export default function Bookmarks(): ReactElement {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const result = await retrieveDDOListByDIDs(
|
||||
const result = await getAssetsFromDids(
|
||||
bookmarks,
|
||||
chainIds,
|
||||
newCancelToken()
|
||||
|
@ -30,24 +30,25 @@ export default function Account({
|
||||
}, [account?.id])
|
||||
|
||||
return (
|
||||
<Link href={`/profile/${profile?.name || account.id}`}>
|
||||
<a className={styles.teaser}>
|
||||
{place && <span className={styles.place}>{place}</span>}
|
||||
<Avatar
|
||||
accountId={account.id}
|
||||
className={styles.avatar}
|
||||
src={profile?.avatar}
|
||||
/>
|
||||
<div>
|
||||
<Dotdotdot tagName="h4" clamp={2} className={styles.name}>
|
||||
{profile?.name ? profile?.name : accountTruncate(account.id)}
|
||||
</Dotdotdot>
|
||||
<p className={styles.sales}>
|
||||
<span>{account.totalSales}</span>
|
||||
{`${account.totalSales === 1 ? ' sale' : ' sales'}`}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<Link
|
||||
href={`/profile/${profile?.name || account.id}`}
|
||||
className={styles.teaser}
|
||||
>
|
||||
{place && <span className={styles.place}>{place}</span>}
|
||||
<Avatar
|
||||
accountId={account.id}
|
||||
className={styles.avatar}
|
||||
src={profile?.avatar}
|
||||
/>
|
||||
<div>
|
||||
<Dotdotdot tagName="h4" clamp={2} className={styles.name}>
|
||||
{profile?.name ? profile?.name : accountTruncate(account.id)}
|
||||
</Dotdotdot>
|
||||
<p className={styles.sales}>
|
||||
<span>{account.totalSales}</span>
|
||||
{`${account.totalSales === 1 ? ' sale' : ' sales'}`}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import Loader from '../../../@shared/atoms/Loader'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import Account from 'src/components/Home/TopSales/Account'
|
||||
import Account from '@components/Home/TopSales/Account'
|
||||
import { UserSales } from '@utils/aquarius'
|
||||
|
||||
function LoaderArea() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import AccountList from 'src/components/Home/TopSales/AccountList'
|
||||
import AccountList from '@components/Home/TopSales/AccountList'
|
||||
import { getTopAssetsPublishers, UserSales } from '@utils/aquarius'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import styles from './index.module.css'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { generateBaseQuery, queryMetadata } from '@utils/aquarius'
|
||||
import axios, { CancelToken } from 'axios'
|
||||
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
|
||||
import { SortTermOptions } from '../../../../src/@types/aquarius/SearchQuery'
|
||||
|
||||
export async function getTopTags(
|
||||
chainIds: number[],
|
||||
@ -32,7 +32,7 @@ export async function getTopTags(
|
||||
try {
|
||||
const result = await queryMetadata(query, cancelToken)
|
||||
const tagsList = result?.aggregations?.topTags?.buckets.map(
|
||||
(x: { key: any }) => x.key
|
||||
(x: { key: string }) => x.key
|
||||
)
|
||||
return tagsList
|
||||
} catch (error) {
|
||||
|
@ -21,14 +21,13 @@ export default function PrivacyLanguages({
|
||||
return (
|
||||
<React.Fragment key={policy.policy}>
|
||||
{i > 0 && ' — '}
|
||||
<Link href={slug}>
|
||||
<a
|
||||
onClick={() => {
|
||||
setPrivacyPolicySlug(slug)
|
||||
}}
|
||||
>
|
||||
{policy.language}
|
||||
</a>
|
||||
<Link
|
||||
href={slug}
|
||||
onClick={() => {
|
||||
setPrivacyPolicySlug(slug)
|
||||
}}
|
||||
>
|
||||
{policy.language}
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ import Time from '@shared/atoms/Time'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import Modal from '@shared/atoms/Modal'
|
||||
import External from '@images/external.svg'
|
||||
import { retrieveAsset } from '@utils/aquarius'
|
||||
import { getAsset } from '@utils/aquarius'
|
||||
import Results from './Results'
|
||||
import styles from './Details.module.css'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
@ -49,7 +49,7 @@ function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
|
||||
|
||||
useEffect(() => {
|
||||
async function getAlgoMetadata() {
|
||||
const ddo = await retrieveAsset(job.algoDID, newCancelToken())
|
||||
const ddo = await getAsset(job.algoDID, newCancelToken())
|
||||
setAlgoDtSymbol(ddo.datatokens[0].symbol)
|
||||
setAlgoName(ddo?.metadata.name)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import FormHelp from '@shared/FormInput/Help'
|
||||
import content from '../../../../../content/pages/history.json'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { retrieveAsset } from '@utils/aquarius'
|
||||
import { getAsset } from '@utils/aquarius'
|
||||
|
||||
export default function Results({
|
||||
job
|
||||
@ -21,7 +21,6 @@ export default function Results({
|
||||
}): ReactElement {
|
||||
const providerInstance = new Provider()
|
||||
const { accountId, web3 } = useWeb3()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const isFinished = job.dateFinished !== null
|
||||
|
||||
const [datasetProvider, setDatasetProvider] = useState<string>()
|
||||
@ -29,7 +28,7 @@ export default function Results({
|
||||
|
||||
useEffect(() => {
|
||||
async function getAssetMetadata() {
|
||||
const ddo = await retrieveAsset(job.inputDID[0], newCancelToken())
|
||||
const ddo = await getAsset(job.inputDID[0], newCancelToken())
|
||||
setDatasetProvider(ddo.services[0].serviceEndpoint)
|
||||
}
|
||||
getAssetMetadata()
|
||||
@ -61,7 +60,6 @@ export default function Results({
|
||||
if (!accountId || !job) return
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const jobResult = await providerInstance.getComputeResultUrl(
|
||||
datasetProvider,
|
||||
web3,
|
||||
@ -72,8 +70,6 @@ export default function Results({
|
||||
await downloadFileBrowser(jobResult)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +93,7 @@ export default function Results({
|
||||
</Button>
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem>No results found.</ListItem>
|
||||
<ListItem key={i}>No results found.</ListItem>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
@ -5,7 +5,6 @@ import Downloads from './Downloads'
|
||||
import ComputeJobs from './ComputeJobs'
|
||||
import styles from './index.module.css'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { chainIds } from 'app.config'
|
||||
import { getComputeJobs } from '@utils/compute'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
@ -17,6 +16,7 @@ interface HistoryTab {
|
||||
}
|
||||
|
||||
const refreshInterval = 10000 // 10 sec.
|
||||
|
||||
function getTabs(
|
||||
accountId: string,
|
||||
userAccountId: string,
|
||||
|
@ -8,7 +8,7 @@ import SuccessConfetti from '@shared/SuccessConfetti'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { useRouter } from 'next/router'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import AvailableNetworks from 'src/components/Publish/AvailableNetworks'
|
||||
import AvailableNetworks from '@components/Publish/AvailableNetworks'
|
||||
import Info from '@images/info.svg'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { wizardSteps } from '../_constants'
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { useFormikContext } from 'formik'
|
||||
import AssetContent from 'src/components/Asset/AssetContent'
|
||||
import AssetContent from '@components/Asset/AssetContent'
|
||||
import { transformPublishFormToDdo } from '../_utils'
|
||||
import { ZERO_ADDRESS } from '@oceanprotocol/lib'
|
||||
|
||||
|
@ -14,6 +14,8 @@ export function Steps({
|
||||
const { values, setFieldValue, touched, setTouched } =
|
||||
useFormikContext<FormPublishData>()
|
||||
|
||||
const isCustomProviderUrl = values?.services?.[0]?.providerUrl.custom
|
||||
|
||||
// auto-sync user chainId & account into form data values
|
||||
useEffect(() => {
|
||||
if (!chainId || !accountId) return
|
||||
@ -41,11 +43,7 @@ export function Steps({
|
||||
|
||||
// Auto-change default providerUrl on user network change
|
||||
useEffect(() => {
|
||||
if (
|
||||
!values?.user?.chainId ||
|
||||
values?.services[0]?.providerUrl.custom === true
|
||||
)
|
||||
return
|
||||
if (!values?.user?.chainId || isCustomProviderUrl === true) return
|
||||
|
||||
const config = getOceanConfig(values.user.chainId)
|
||||
if (config) {
|
||||
@ -57,12 +55,7 @@ export function Steps({
|
||||
}
|
||||
|
||||
setTouched({ ...touched, services: [{ providerUrl: { url: true } }] })
|
||||
}, [
|
||||
values?.user?.chainId,
|
||||
values?.services[0]?.providerUrl.custom,
|
||||
setFieldValue,
|
||||
setTouched
|
||||
])
|
||||
}, [values?.user?.chainId, isCustomProviderUrl, setFieldValue, setTouched])
|
||||
|
||||
const { component } = wizardSteps.filter((stepContent) => {
|
||||
return stepContent.step === values.user.stepCurrent
|
||||
|
@ -5,7 +5,7 @@ import styles from './index.module.css'
|
||||
import content from '../../../../content/publish/index.json'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Info from '@images/info.svg'
|
||||
import AvailableNetworks from 'src/components/Publish/AvailableNetworks'
|
||||
import AvailableNetworks from '@components/Publish/AvailableNetworks'
|
||||
|
||||
export default function Title({
|
||||
networkId
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { allowFixedPricing } from '../../../app.config.js'
|
||||
import { allowFixedPricing } from '../../../app.config'
|
||||
import {
|
||||
FormPublishData,
|
||||
MetadataAlgorithmContainer,
|
||||
@ -72,8 +72,8 @@ export const initialValues: FormPublishData = {
|
||||
},
|
||||
services: [
|
||||
{
|
||||
files: [{ url: '', type: '' }],
|
||||
links: [{ url: '', type: '' }],
|
||||
files: [{ url: '', type: 'ipfs' }],
|
||||
links: [{ url: '', type: 'url' }],
|
||||
dataTokenOptions: { name: '', symbol: '' },
|
||||
timeout: '',
|
||||
access: 'access',
|
||||
|
@ -139,18 +139,24 @@ export async function transformPublishFormToDdo(
|
||||
}
|
||||
|
||||
// this is the default format hardcoded
|
||||
|
||||
const file = {
|
||||
nftAddress,
|
||||
datatokenAddress,
|
||||
files: [
|
||||
{
|
||||
type: 'url',
|
||||
type: files[0].type,
|
||||
index: 0,
|
||||
url: files[0].url,
|
||||
[files[0].type === 'ipfs'
|
||||
? 'hash'
|
||||
: files[0].type === 'arweave'
|
||||
? 'transactionId'
|
||||
: 'url']: files[0].url,
|
||||
method: 'GET'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const filesEncrypted =
|
||||
!isPreview &&
|
||||
files?.length &&
|
||||
|
@ -2,6 +2,7 @@ import { MAX_DECIMALS } from '@utils/constants'
|
||||
import * as Yup from 'yup'
|
||||
import { getMaxDecimalsValidation } from '@utils/numbers'
|
||||
import { FileInfo } from '@oceanprotocol/lib'
|
||||
import { testLinks } from '../../@utils/yup'
|
||||
|
||||
// TODO: conditional validation
|
||||
// e.g. when algo is selected, Docker image is required
|
||||
@ -32,16 +33,7 @@ const validationService = {
|
||||
files: Yup.array<FileInfo[]>()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
url: Yup.string()
|
||||
.test(
|
||||
'GoogleNotSupported',
|
||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
||||
(value) => {
|
||||
return !value?.toString().includes('drive.google')
|
||||
}
|
||||
)
|
||||
.url('Must be a valid URL.')
|
||||
.required('Required'),
|
||||
url: testLinks().required('Required'),
|
||||
|
||||
valid: Yup.boolean().isTrue().required('File must be valid.')
|
||||
})
|
||||
@ -51,16 +43,7 @@ const validationService = {
|
||||
links: Yup.array<FileInfo[]>()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
url: Yup.string()
|
||||
.url('Must be a valid URL.')
|
||||
.test(
|
||||
'GoogleNotSupported',
|
||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
||||
(value) => {
|
||||
return !value?.toString().includes('drive.google')
|
||||
}
|
||||
),
|
||||
// TODO: require valid file only when URL is given
|
||||
url: testLinks(),
|
||||
valid: Yup.boolean()
|
||||
// valid: Yup.boolean().isTrue('File must be valid.')
|
||||
})
|
||||
|
@ -79,8 +79,6 @@ export default function SearchPage({
|
||||
fetchAssets(parsed, chainIds)
|
||||
}, [parsed, chainIds, newCancelToken, fetchAssets])
|
||||
|
||||
console.log(queryResult?.results)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.search}>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import {
|
||||
escapeEsReservedCharacters,
|
||||
generateBaseQuery,
|
||||
getFilterTerm,
|
||||
queryMetadata
|
||||
@ -26,10 +27,6 @@ export function updateQueryStringParameter(
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeESReservedChars(text: string): string {
|
||||
return text?.replace(/([!*+\-=<>&|()\\[\]{}^~?:\\/"])/g, '\\$1')
|
||||
}
|
||||
|
||||
export function getSearchQuery(
|
||||
chainIds: number[],
|
||||
text?: string,
|
||||
@ -42,7 +39,7 @@ export function getSearchQuery(
|
||||
serviceType?: string,
|
||||
accessType?: string
|
||||
): SearchQuery {
|
||||
text = escapeESReservedChars(text)
|
||||
text = escapeEsReservedCharacters(text)
|
||||
const emptySearchTerm = text === undefined || text === ''
|
||||
const filters: FilterTerm[] = []
|
||||
let searchTerm = text || ''
|
||||
@ -150,7 +147,7 @@ export async function getResults(
|
||||
},
|
||||
chainIds: number[],
|
||||
cancelToken?: CancelToken
|
||||
): Promise<any> {
|
||||
): Promise<PagedAssets> {
|
||||
const {
|
||||
text,
|
||||
owner,
|
||||
|
@ -6,7 +6,7 @@ import { UserPreferencesProvider } from '@context/UserPreferences'
|
||||
import PricesProvider from '@context/Prices'
|
||||
import UrqlProvider from '@context/UrqlProvider'
|
||||
import ConsentProvider from '@context/CookieConsent'
|
||||
import App from 'src/components/App'
|
||||
import App from '../../src/components/App'
|
||||
|
||||
import '@oceanprotocol/typographies/css/ocean-typo.css'
|
||||
import '../stylesGlobal/styles.css'
|
||||
|
@ -1,33 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "ES2020",
|
||||
"lib": ["dom", "ES2020"],
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"jsx": "preserve",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"baseUrl": ".", // This must be specified if "paths" is.
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"paths": {
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@shared/*": ["./src/components/@shared/*"],
|
||||
"@hooks/*": ["./src/@hooks/*"],
|
||||
"@context/*": ["./src/@context/*"],
|
||||
"@images/*": ["./src/@images/*"],
|
||||
"@utils/*": ["./src/@utils/*"],
|
||||
"@content/*": ["./@content/*"]
|
||||
},
|
||||
"baseUrl": ".",
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": true
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", ".next", "*.js"],
|
||||
"include": ["./src/**/*", "./.jest/**/*", "./next-env.d.ts", "./content/**/*"]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", ".next", "*.js"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user