mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into feature/issue-1657-shacl-schema
This commit is contained in:
commit
7ed9298530
@ -5,7 +5,7 @@ export const assetAquarius: Asset = {
|
|||||||
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
|
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
|
||||||
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
chainId: 5,
|
chainId: 1,
|
||||||
metadata: {
|
metadata: {
|
||||||
created: '2022-09-29T11:30:26Z',
|
created: '2022-09-29T11:30:26Z',
|
||||||
updated: '2022-09-29T11:30:26Z',
|
updated: '2022-09-29T11:30:26Z',
|
||||||
@ -57,12 +57,12 @@ export const assetAquarius: Asset = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 22
|
orders: 22,
|
||||||
// price: {
|
price: {
|
||||||
// value: 3231343254,
|
value: 3231343254,
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// tokenSymbol: 'OCEAN'
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
purgatory: {
|
purgatory: {
|
||||||
state: false,
|
state: false,
|
||||||
|
@ -3,6 +3,7 @@ import { assetAquarius } from './assetAquarius'
|
|||||||
export const asset: AssetExtended = {
|
export const asset: AssetExtended = {
|
||||||
...assetAquarius,
|
...assetAquarius,
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
|
@ -68,11 +68,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {}
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
type: 'NOT_SUPPORTED'
|
type: 'NOT_SUPPORTED'
|
||||||
} as any
|
} as any
|
||||||
},
|
},
|
||||||
@ -150,15 +155,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 45554.69921875,
|
allocated: 45554.69921875,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0x282d8efCe846A88B159800bd4130ad77443Fa1A1',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'mOCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 100
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -243,15 +249,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 7
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -336,15 +343,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 50.2051887512,
|
allocated: 50.2051887512,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 10
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -435,15 +443,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 6
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -468,6 +477,7 @@ export const assets: AssetExtended[] = [
|
|||||||
{
|
{
|
||||||
'@context': ['https://w3id.org/did/v1'],
|
'@context': ['https://w3id.org/did/v1'],
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -563,12 +573,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0'
|
version: '4.1.0'
|
||||||
},
|
},
|
||||||
@ -651,15 +661,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -744,15 +755,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -836,13 +848,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 114.1658859253,
|
allocated: 114.1658859253,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'free',
|
type: 'free',
|
||||||
addressOrId: '0x0a81f1c69e5428067e6124817c7affe8bc0adf9f',
|
addressOrId: '0x0a81f1c69e5428067e6124817c7affe8bc0adf9f',
|
||||||
@ -918,15 +933,16 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 2
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 1,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -1008,13 +1024,16 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'free',
|
type: 'free',
|
||||||
addressOrId: '0x772224c2c2bddb88a55b3905aaaf8c7188b02ce3',
|
addressOrId: '0x772224c2c2bddb88a55b3905aaaf8c7188b02ce3',
|
||||||
@ -1092,13 +1111,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 11159.279296875,
|
allocated: 11159.279296875,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'free',
|
type: 'free',
|
||||||
addressOrId: '0x89a0170556bb80438081d69f43d8c07a90e9aa24',
|
addressOrId: '0x89a0170556bb80438081d69f43d8c07a90e9aa24',
|
||||||
@ -1174,13 +1196,16 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'free',
|
type: 'free',
|
||||||
addressOrId: '0xad42c7afee47140b5cd87f05d5846c418145f43a',
|
addressOrId: '0xad42c7afee47140b5cd87f05d5846c418145f43a',
|
||||||
@ -1257,8 +1282,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {}
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1326,15 +1355,16 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 1
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
@ -1416,13 +1446,16 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'free',
|
type: 'free',
|
||||||
addressOrId: '0x23c1fd10dadcaf558fb7173b79cfd0d867568a3d',
|
addressOrId: '0x23c1fd10dadcaf558fb7173b79cfd0d867568a3d',
|
||||||
@ -1510,10 +1543,16 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 422.9883117676,
|
allocated: 422.9883117676,
|
||||||
orders: 0
|
orders: 0,
|
||||||
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
templateId: 2,
|
||||||
publisherMarketOrderFee: '0',
|
publisherMarketOrderFee: '0',
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
addressOrId:
|
addressOrId:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ module.exports = {
|
|||||||
|
|
||||||
infuraProjectId: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID || 'xxx',
|
infuraProjectId: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID || 'xxx',
|
||||||
|
|
||||||
|
defaultDatatokenTemplateIndex: 2,
|
||||||
// The ETH address the marketplace fee will be sent to.
|
// The ETH address the marketplace fee will be sent to.
|
||||||
marketFeeAddress:
|
marketFeeAddress:
|
||||||
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
||||||
|
@ -29,19 +29,62 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "New file",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. Leaving this field empty will not remove the current value.",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [{
|
||||||
"type": "files"
|
"value": "ipfs",
|
||||||
},
|
"title": "IPFS",
|
||||||
{
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "arweave",
|
||||||
|
"title": "Arweave",
|
||||||
|
"label": "Transaction ID",
|
||||||
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
}],
|
||||||
|
"sortOptions": false,
|
||||||
|
"required": true
|
||||||
|
},{
|
||||||
"name": "links",
|
"name": "links",
|
||||||
"label": "New sample file",
|
"label": "Sample file",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"prominentHelp": false,
|
||||||
"help": "Please provide a URL to a sample of your dataset file. This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** Leaving this field empty will not remove the current value.",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files"
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": false
|
||||||
|
}],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -66,6 +109,13 @@
|
|||||||
"type": "tags",
|
"type": "tags",
|
||||||
"placeholder": "e.g. logistics",
|
"placeholder": "e.g. logistics",
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paymentCollector",
|
||||||
|
"label": "Payment Collector Address",
|
||||||
|
"placeholder": "e.g. 0X123ABC...",
|
||||||
|
"help": "This address will receive the revenue from all sales. More info available in our [docs](https://docs.oceanprotocol.com/core-concepts/datanft-and-datatoken#revenue).",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -104,19 +104,61 @@
|
|||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "File",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [{
|
||||||
"type": "files",
|
"value": "ipfs",
|
||||||
|
"title": "IPFS",
|
||||||
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "arweave",
|
||||||
|
"title": "Arweave",
|
||||||
|
"label": "Transaction ID",
|
||||||
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID will be stored encrypted after publishing.",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
}],
|
||||||
|
"sortOptions": false,
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},{
|
||||||
{
|
|
||||||
"name": "links",
|
"name": "links",
|
||||||
"label": "Sample file",
|
"label": "Sample file",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"prominentHelp": false,
|
||||||
"help": "This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
"type": "tabs",
|
||||||
"prominentHelp": true,
|
"fields": [
|
||||||
"type": "files"
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
||||||
|
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
||||||
|
"prominentHelp": true,
|
||||||
|
"type": "files",
|
||||||
|
"required": false
|
||||||
|
}],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "algorithmPrivacy",
|
"name": "algorithmPrivacy",
|
||||||
|
47079
package-lock.json
generated
47079
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@
|
|||||||
"@coingecko/cryptoformat": "^0.5.4",
|
"@coingecko/cryptoformat": "^0.5.4",
|
||||||
"@loadable/component": "^5.15.2",
|
"@loadable/component": "^5.15.2",
|
||||||
"@oceanprotocol/art": "^3.2.0",
|
"@oceanprotocol/art": "^3.2.0",
|
||||||
"@oceanprotocol/lib": "^2.4.0",
|
"@oceanprotocol/lib": "^2.5.2",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
@ -41,6 +41,7 @@
|
|||||||
"filesize": "^10.0.5",
|
"filesize": "^10.0.5",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"is-ipfs": "^7.0.3",
|
||||||
"is-url-superb": "^6.1.0",
|
"is-url-superb": "^6.1.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
|
@ -10,6 +10,7 @@ export interface AppConfig {
|
|||||||
infuraProjectId: string
|
infuraProjectId: string
|
||||||
chainIds: number[]
|
chainIds: number[]
|
||||||
chainIdsSupported: number[]
|
chainIdsSupported: number[]
|
||||||
|
defaultDatatokenTemplateIndex: number
|
||||||
marketFeeAddress: string
|
marketFeeAddress: string
|
||||||
publisherMarketOrderFee: string
|
publisherMarketOrderFee: string
|
||||||
publisherMarketFixedSwapFee: string
|
publisherMarketFixedSwapFee: string
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useRef, useEffect, useCallback } from 'react'
|
import { useRef, useEffect, useCallback } from 'react'
|
||||||
import axios, { CancelToken } from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
|
|
||||||
export const useCancelToken = (): (() => CancelToken) => {
|
export const useCancelToken = (): (() => CancelToken) => {
|
||||||
const axiosSource = useRef(null)
|
const axiosSource = useRef(null)
|
||||||
|
|
||||||
const newCancelToken = useCallback(() => {
|
const newCancelToken = useCallback(() => {
|
||||||
axiosSource.current = axios.CancelToken.source()
|
axiosSource.current = axios.CancelToken.source()
|
||||||
return axiosSource.current.token
|
return axiosSource?.current?.token
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
4
src/@types/Analytics.d.ts
vendored
Normal file
4
src/@types/Analytics.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
interface PageViews {
|
||||||
|
count: number
|
||||||
|
did: string
|
||||||
|
}
|
1
src/@types/AssetExtended.d.ts
vendored
1
src/@types/AssetExtended.d.ts
vendored
@ -5,5 +5,6 @@ import { Asset } from '@oceanprotocol/lib'
|
|||||||
declare global {
|
declare global {
|
||||||
interface AssetExtended extends Asset {
|
interface AssetExtended extends Asset {
|
||||||
accessDetails?: AccessDetails
|
accessDetails?: AccessDetails
|
||||||
|
views?: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
src/@types/Price.d.ts
vendored
1
src/@types/Price.d.ts
vendored
@ -39,6 +39,7 @@ declare global {
|
|||||||
interface AccessDetails {
|
interface AccessDetails {
|
||||||
type: 'fixed' | 'free' | 'NOT_SUPPORTED'
|
type: 'fixed' | 'free' | 'NOT_SUPPORTED'
|
||||||
price: string
|
price: string
|
||||||
|
templateId: number
|
||||||
addressOrId: string
|
addressOrId: string
|
||||||
baseToken: TokenInfo
|
baseToken: TokenInfo
|
||||||
datatoken: TokenInfo
|
datatoken: TokenInfo
|
||||||
|
@ -30,6 +30,7 @@ const tokensPriceQuery = gql`
|
|||||||
publishMarketFeeAddress
|
publishMarketFeeAddress
|
||||||
publishMarketFeeToken
|
publishMarketFeeToken
|
||||||
publishMarketFeeAmount
|
publishMarketFeeAmount
|
||||||
|
templateId
|
||||||
orders(
|
orders(
|
||||||
where: { payer: $account }
|
where: { payer: $account }
|
||||||
orderBy: createdTimestamp
|
orderBy: createdTimestamp
|
||||||
@ -84,6 +85,7 @@ const tokenPriceQuery = gql`
|
|||||||
id
|
id
|
||||||
symbol
|
symbol
|
||||||
name
|
name
|
||||||
|
templateId
|
||||||
publishMarketFeeAddress
|
publishMarketFeeAddress
|
||||||
publishMarketFeeToken
|
publishMarketFeeToken
|
||||||
publishMarketFeeAmount
|
publishMarketFeeAmount
|
||||||
@ -160,7 +162,7 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
// the last valid order should be the last reuse order tx id if there is one
|
// the last valid order should be the last reuse order tx id if there is one
|
||||||
accessDetails.validOrderTx = reusedOrder?.tx || order?.tx
|
accessDetails.validOrderTx = reusedOrder?.tx || order?.tx
|
||||||
}
|
}
|
||||||
|
accessDetails.templateId = tokenPrice.templateId
|
||||||
// TODO: fetch order fee from sub query
|
// TODO: fetch order fee from sub query
|
||||||
accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount
|
accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount
|
||||||
|
|
||||||
@ -169,6 +171,7 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
const dispenser = tokenPrice.dispensers[0]
|
const dispenser = tokenPrice.dispensers[0]
|
||||||
accessDetails.type = 'free'
|
accessDetails.type = 'free'
|
||||||
accessDetails.addressOrId = dispenser.token.id
|
accessDetails.addressOrId = dispenser.token.id
|
||||||
|
|
||||||
accessDetails.price = '0'
|
accessDetails.price = '0'
|
||||||
accessDetails.isPurchasable = dispenser.active
|
accessDetails.isPurchasable = dispenser.active
|
||||||
accessDetails.datatoken = {
|
accessDetails.datatoken = {
|
||||||
|
@ -61,13 +61,13 @@ export function generateBaseQuery(
|
|||||||
...(baseQueryParams.filters || []),
|
...(baseQueryParams.filters || []),
|
||||||
baseQueryParams.chainIds
|
baseQueryParams.chainIds
|
||||||
? getFilterTerm('chainId', baseQueryParams.chainIds)
|
? getFilterTerm('chainId', baseQueryParams.chainIds)
|
||||||
: [],
|
: '',
|
||||||
getFilterTerm('_index', 'aquarius'),
|
getFilterTerm('_index', 'aquarius'),
|
||||||
...(baseQueryParams.ignorePurgatory
|
...(baseQueryParams.ignorePurgatory
|
||||||
? []
|
? ''
|
||||||
: [getFilterTerm('purgatory.state', false)]),
|
: [getFilterTerm('purgatory.state', false)]),
|
||||||
...(baseQueryParams.ignoreState
|
...(baseQueryParams.ignoreState
|
||||||
? []
|
? ''
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
bool: {
|
bool: {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
// Boolean value that will be true if we are inside a browser, false otherwise
|
// Boolean value that will be true if we are inside a browser, false otherwise
|
||||||
export const isBrowser = typeof window !== 'undefined'
|
export const isBrowser = typeof window !== 'undefined'
|
||||||
|
|
||||||
@ -14,3 +16,10 @@ export function removeItemFromArray<T>(arr: Array<T>, value: T): Array<T> {
|
|||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sortAssets(items: Asset[], sorted: string[]) {
|
||||||
|
items.sort(function (a, b) {
|
||||||
|
return sorted?.indexOf(a.id) - sorted?.indexOf(b.id)
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
5
src/@utils/ipfs.ts
Normal file
5
src/@utils/ipfs.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as isIPFS from 'is-ipfs'
|
||||||
|
|
||||||
|
export function isCID(value: string) {
|
||||||
|
return isIPFS.cid(value)
|
||||||
|
}
|
@ -82,10 +82,13 @@ export function generateNftCreateData(
|
|||||||
|
|
||||||
export function decodeTokenURI(tokenURI: string): NftMetadata {
|
export function decodeTokenURI(tokenURI: string): NftMetadata {
|
||||||
if (!tokenURI) return undefined
|
if (!tokenURI) return undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nftMeta = JSON.parse(
|
const nftMeta = tokenURI.includes('data:application/json')
|
||||||
Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
|
? (JSON.parse(
|
||||||
) as NftMetadata
|
Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
|
||||||
|
) as NftMetadata)
|
||||||
|
: ({ image: tokenURI } as NftMetadata)
|
||||||
|
|
||||||
return nftMeta
|
return nftMeta
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
|
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||||
import { Decimal } from 'decimal.js'
|
import { Decimal } from 'decimal.js'
|
||||||
|
|
||||||
|
export function formatNumber(
|
||||||
|
price: number,
|
||||||
|
locale: string,
|
||||||
|
decimals?: string
|
||||||
|
): string {
|
||||||
|
return formatCurrency(price, '', locale, false, {
|
||||||
|
// Not exactly clear what `significant figures` are for this library,
|
||||||
|
// but setting this seems to give us the formatting we want.
|
||||||
|
// See https://github.com/oceanprotocol/market/issues/70
|
||||||
|
significantFigures: 4,
|
||||||
|
...(decimals && { decimalPlaces: Number(decimals) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Run decimal.js comparison
|
// Run decimal.js comparison
|
||||||
// http://mikemcl.github.io/decimal.js/#cmp
|
// http://mikemcl.github.io/decimal.js/#cmp
|
||||||
export function compareAsBN(balance: string, price: string): boolean {
|
export function compareAsBN(balance: string, price: string): boolean {
|
||||||
|
@ -3,12 +3,15 @@ import {
|
|||||||
approve,
|
approve,
|
||||||
approveWei,
|
approveWei,
|
||||||
Datatoken,
|
Datatoken,
|
||||||
|
Dispenser,
|
||||||
|
FixedRateExchange,
|
||||||
FreOrderParams,
|
FreOrderParams,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
OrderParams,
|
OrderParams,
|
||||||
ProviderComputeInitialize,
|
ProviderComputeInitialize,
|
||||||
ProviderFees,
|
ProviderFees,
|
||||||
ProviderInstance
|
ProviderInstance,
|
||||||
|
ProviderInitialize
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
@ -20,6 +23,26 @@ import {
|
|||||||
} from '../../app.config'
|
} from '../../app.config'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
async function initializeProvider(
|
||||||
|
asset: AssetExtended,
|
||||||
|
accountId: string,
|
||||||
|
providerFees?: ProviderFees
|
||||||
|
): Promise<ProviderInitialize> {
|
||||||
|
if (providerFees) return
|
||||||
|
try {
|
||||||
|
const provider = await ProviderInstance.initialize(
|
||||||
|
asset.id,
|
||||||
|
asset.services[0].id,
|
||||||
|
0,
|
||||||
|
accountId,
|
||||||
|
asset.services[0].serviceEndpoint
|
||||||
|
)
|
||||||
|
return provider
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.log('[Initialize Provider] Error:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param web3
|
* @param web3
|
||||||
* @param asset
|
* @param asset
|
||||||
@ -40,15 +63,11 @@ export async function order(
|
|||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const config = getOceanConfig(asset.chainId)
|
const config = getOceanConfig(asset.chainId)
|
||||||
|
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const orderParams = {
|
const orderParams = {
|
||||||
consumer: computeConsumerAddress || accountId,
|
consumer: computeConsumerAddress || accountId,
|
||||||
@ -66,22 +85,6 @@ export async function order(
|
|||||||
switch (asset.accessDetails?.type) {
|
switch (asset.accessDetails?.type) {
|
||||||
case 'fixed': {
|
case 'fixed': {
|
||||||
// this assumes all fees are in ocean
|
// this assumes all fees are in ocean
|
||||||
const txApprove = await approve(
|
|
||||||
web3,
|
|
||||||
config,
|
|
||||||
accountId,
|
|
||||||
asset.accessDetails.baseToken.address,
|
|
||||||
asset.accessDetails.datatoken.address,
|
|
||||||
await amountToUnits(
|
|
||||||
web3,
|
|
||||||
asset?.accessDetails?.baseToken?.address,
|
|
||||||
orderPriceAndFees.price
|
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
if (!txApprove) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const freParams = {
|
const freParams = {
|
||||||
exchangeContract: config.fixedRateExchangeAddress,
|
exchangeContract: config.fixedRateExchangeAddress,
|
||||||
@ -92,23 +95,96 @@ export async function order(
|
|||||||
swapMarketFee: consumeMarketFixedSwapFee,
|
swapMarketFee: consumeMarketFixedSwapFee,
|
||||||
marketFeeAddress
|
marketFeeAddress
|
||||||
} as FreOrderParams
|
} as FreOrderParams
|
||||||
const tx = await datatoken.buyFromFreAndOrder(
|
|
||||||
asset.accessDetails.datatoken.address,
|
|
||||||
accountId,
|
|
||||||
orderParams,
|
|
||||||
freParams
|
|
||||||
)
|
|
||||||
|
|
||||||
return tx
|
if (asset.accessDetails.templateId === 1) {
|
||||||
|
// buy datatoken
|
||||||
|
const txApprove = await approve(
|
||||||
|
web3,
|
||||||
|
config,
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails.baseToken.address,
|
||||||
|
config.fixedRateExchangeAddress,
|
||||||
|
await amountToUnits(
|
||||||
|
web3,
|
||||||
|
asset?.accessDetails?.baseToken?.address,
|
||||||
|
orderPriceAndFees.price
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
if (!txApprove) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fre = new FixedRateExchange(config.fixedRateExchangeAddress, web3)
|
||||||
|
const freTx = await fre.buyDatatokens(
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails?.addressOrId,
|
||||||
|
'1',
|
||||||
|
orderPriceAndFees.price,
|
||||||
|
marketFeeAddress,
|
||||||
|
consumeMarketFixedSwapFee
|
||||||
|
)
|
||||||
|
|
||||||
|
return await datatoken.startOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams.consumer,
|
||||||
|
orderParams.serviceIndex,
|
||||||
|
orderParams._providerFee,
|
||||||
|
orderParams._consumeMarketFee
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (asset.accessDetails.templateId === 2) {
|
||||||
|
const txApprove = await approve(
|
||||||
|
web3,
|
||||||
|
config,
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails.baseToken.address,
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
await amountToUnits(
|
||||||
|
web3,
|
||||||
|
asset?.accessDetails?.baseToken?.address,
|
||||||
|
orderPriceAndFees.price
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
if (!txApprove) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return await datatoken.buyFromFreAndOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
freParams
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
case 'free': {
|
case 'free': {
|
||||||
const tx = await datatoken.buyFromDispenserAndOrder(
|
if (asset.accessDetails.templateId === 1) {
|
||||||
asset.services[0].datatokenAddress,
|
const dispenser = new Dispenser(config.dispenserAddress, web3)
|
||||||
accountId,
|
const dispenserTx = await dispenser.dispense(
|
||||||
orderParams,
|
asset.accessDetails?.datatoken.address,
|
||||||
config.dispenserAddress
|
accountId,
|
||||||
)
|
'1',
|
||||||
return tx
|
accountId
|
||||||
|
)
|
||||||
|
return await datatoken.startOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams.consumer,
|
||||||
|
orderParams.serviceIndex,
|
||||||
|
orderParams._providerFee,
|
||||||
|
orderParams._consumeMarketFee
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (asset.accessDetails.templateId === 2) {
|
||||||
|
return await datatoken.buyFromDispenserAndOrder(
|
||||||
|
asset.services[0].datatokenAddress,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
config.dispenserAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,15 +206,11 @@ export async function reuseOrder(
|
|||||||
providerFees?: ProviderFees
|
providerFees?: ProviderFees
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const tx = await datatoken.reuseOrder(
|
const tx = await datatoken.reuseOrder(
|
||||||
asset.accessDetails.datatoken.address,
|
asset.accessDetails.datatoken.address,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
Arweave,
|
||||||
ComputeAlgorithm,
|
ComputeAlgorithm,
|
||||||
ComputeAsset,
|
ComputeAsset,
|
||||||
ComputeEnvironment,
|
ComputeEnvironment,
|
||||||
downloadFileBrowser,
|
downloadFileBrowser,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
|
Ipfs,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
ProviderComputeInitializeResults,
|
ProviderComputeInitializeResults,
|
||||||
ProviderInstance,
|
ProviderInstance,
|
||||||
@ -83,18 +85,45 @@ export async function getFileDidInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFileUrlInfo(
|
export async function getFileInfo(
|
||||||
url: string,
|
file: string,
|
||||||
providerUrl: string
|
providerUrl: string,
|
||||||
|
storageType: string
|
||||||
): Promise<FileInfo[]> {
|
): Promise<FileInfo[]> {
|
||||||
try {
|
try {
|
||||||
const fileUrl: UrlFile = {
|
let response
|
||||||
type: 'url',
|
switch (storageType) {
|
||||||
index: 0,
|
case 'ipfs': {
|
||||||
url,
|
const fileIPFS: Ipfs = {
|
||||||
method: 'get'
|
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
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
|
@ -2,23 +2,9 @@ import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
|
|||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import { getUrqlClientInstance } from '@context/UrqlProvider'
|
import { getUrqlClientInstance } from '@context/UrqlProvider'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
import { AssetPreviousOrder } from '../@types/subgraph/AssetPreviousOrder'
|
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||||
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
|
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
|
||||||
|
import appConfig from '../../app.config'
|
||||||
const PreviousOrderQuery = gql`
|
|
||||||
query AssetPreviousOrder($id: String!, $account: String!) {
|
|
||||||
orders(
|
|
||||||
first: 1
|
|
||||||
where: { datatoken: $id, payer: $account }
|
|
||||||
orderBy: createdTimestamp
|
|
||||||
orderDirection: desc
|
|
||||||
) {
|
|
||||||
createdTimestamp
|
|
||||||
tx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const UserTokenOrders = gql`
|
const UserTokenOrders = gql`
|
||||||
query OrdersData($user: String!) {
|
query OrdersData($user: String!) {
|
||||||
@ -76,6 +62,11 @@ export function getSubgraphUri(chainId: number): string {
|
|||||||
|
|
||||||
export function getQueryContext(chainId: number): OperationContext {
|
export function getQueryContext(chainId: number): OperationContext {
|
||||||
try {
|
try {
|
||||||
|
if (!appConfig.chainIdsSupported.includes(chainId))
|
||||||
|
throw Object.assign(
|
||||||
|
new Error('network not supported, query context cancelled')
|
||||||
|
)
|
||||||
|
|
||||||
const queryContext: OperationContext = {
|
const queryContext: OperationContext = {
|
||||||
url: `${getSubgraphUri(
|
url: `${getSubgraphUri(
|
||||||
Number(chainId)
|
Number(chainId)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AllLocked } from 'src/@types/subgraph/AllLocked'
|
import { AllLockedQuery } from 'src/@types/subgraph/AllLockedQuery'
|
||||||
import { OwnAllocations } from 'src/@types/subgraph/OwnAllocations'
|
import { OwnAllocationsQuery } from 'src/@types/subgraph/OwnAllocationsQuery'
|
||||||
import { NftOwnAllocation } from 'src/@types/subgraph/NftOwnAllocation'
|
import { NftOwnAllocationQuery } from 'src/@types/subgraph/NftOwnAllocationQuery'
|
||||||
import { OceanLocked } from 'src/@types/subgraph/OceanLocked'
|
import { OceanLockedQuery } from 'src/@types/subgraph/OceanLockedQuery'
|
||||||
import { gql, OperationResult } from 'urql'
|
import { gql, OperationResult } from 'urql'
|
||||||
import { fetchData, getQueryContext } from './subgraph'
|
import { fetchData, getQueryContext } from './subgraph'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@ -12,11 +12,11 @@ import {
|
|||||||
NetworkType
|
NetworkType
|
||||||
} from '@hooks/useNetworkMetadata'
|
} from '@hooks/useNetworkMetadata'
|
||||||
import { getAssetsFromNftList } from './aquarius'
|
import { getAssetsFromNftList } from './aquarius'
|
||||||
import { chainIdsSupported } from 'app.config'
|
import { chainIdsSupported } from '../../app.config'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
const AllLocked = gql`
|
const AllLocked = gql`
|
||||||
query AllLocked {
|
query AllLockedQuery {
|
||||||
veOCEANs(first: 1000) {
|
veOCEANs(first: 1000) {
|
||||||
lockedAmount
|
lockedAmount
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ const AllLocked = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const OwnAllocations = gql`
|
const OwnAllocations = gql`
|
||||||
query OwnAllocations($address: String) {
|
query OwnAllocationsQuery($address: String) {
|
||||||
veAllocations(where: { allocationUser: $address }) {
|
veAllocations(where: { allocationUser: $address }) {
|
||||||
id
|
id
|
||||||
nftAddress
|
nftAddress
|
||||||
@ -33,7 +33,7 @@ const OwnAllocations = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const NftOwnAllocation = gql`
|
const NftOwnAllocation = gql`
|
||||||
query NftOwnAllocation($address: String, $nftAddress: String) {
|
query NftOwnAllocationQuery($address: String, $nftAddress: String) {
|
||||||
veAllocations(
|
veAllocations(
|
||||||
where: { allocationUser: $address, nftAddress: $nftAddress }
|
where: { allocationUser: $address, nftAddress: $nftAddress }
|
||||||
) {
|
) {
|
||||||
@ -42,7 +42,7 @@ const NftOwnAllocation = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const OceanLocked = gql`
|
const OceanLocked = gql`
|
||||||
query OceanLocked($address: ID!) {
|
query OceanLockedQuery($address: ID!) {
|
||||||
veOCEAN(id: $address) {
|
veOCEAN(id: $address) {
|
||||||
id
|
id
|
||||||
lockedAmount
|
lockedAmount
|
||||||
@ -87,7 +87,7 @@ export async function getNftOwnAllocation(
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const veNetworkId = getVeChainNetworkId(networkId)
|
const veNetworkId = getVeChainNetworkId(networkId)
|
||||||
const queryContext = getQueryContext(veNetworkId)
|
const queryContext = getQueryContext(veNetworkId)
|
||||||
const fetchedAllocation: OperationResult<NftOwnAllocation, any> =
|
const fetchedAllocation: OperationResult<NftOwnAllocationQuery, any> =
|
||||||
await fetchData(
|
await fetchData(
|
||||||
NftOwnAllocation,
|
NftOwnAllocation,
|
||||||
{
|
{
|
||||||
@ -115,7 +115,7 @@ export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
const fetchedLocked: OperationResult<AllLocked, any> = await fetchData(
|
const fetchedLocked: OperationResult<AllLockedQuery, any> = await fetchData(
|
||||||
AllLocked,
|
AllLocked,
|
||||||
null,
|
null,
|
||||||
queryContext
|
queryContext
|
||||||
@ -136,11 +136,12 @@ export async function getLocked(
|
|||||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||||
const queryContext = getQueryContext(veNetworkIds[i])
|
const queryContext = getQueryContext(veNetworkIds[i])
|
||||||
const fetchedLocked: OperationResult<OceanLocked, any> = await fetchData(
|
const fetchedLocked: OperationResult<OceanLockedQuery, any> =
|
||||||
OceanLocked,
|
await fetchData(
|
||||||
{ address: userAddress.toLowerCase() },
|
OceanLocked,
|
||||||
queryContext
|
{ address: userAddress.toLowerCase() },
|
||||||
)
|
queryContext
|
||||||
|
)
|
||||||
|
|
||||||
fetchedLocked.data?.veOCEAN?.lockedAmount &&
|
fetchedLocked.data?.veOCEAN?.lockedAmount &&
|
||||||
(total += Number(fetchedLocked.data?.veOCEAN?.lockedAmount))
|
(total += Number(fetchedLocked.data?.veOCEAN?.lockedAmount))
|
||||||
@ -157,7 +158,7 @@ export async function getOwnAllocations(
|
|||||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||||
const queryContext = getQueryContext(veNetworkIds[i])
|
const queryContext = getQueryContext(veNetworkIds[i])
|
||||||
const fetchedAllocations: OperationResult<OwnAllocations, any> =
|
const fetchedAllocations: OperationResult<OwnAllocationsQuery, any> =
|
||||||
await fetchData(
|
await fetchData(
|
||||||
OwnAllocations,
|
OwnAllocations,
|
||||||
{ address: userAddress.toLowerCase() },
|
{ address: userAddress.toLowerCase() },
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
@ -69,11 +69,13 @@ export default function AssetList({
|
|||||||
|
|
||||||
const styleClasses = `${styles.assetList} ${className || ''}`
|
const styleClasses = `${styles.assetList} ${className || ''}`
|
||||||
|
|
||||||
return assetsWithPrices && !loading ? (
|
return loading ? (
|
||||||
|
<LoaderArea />
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className={styleClasses}>
|
<div className={styleClasses}>
|
||||||
{assetsWithPrices.length > 0 ? (
|
{assetsWithPrices?.length > 0 ? (
|
||||||
assetsWithPrices.map((assetWithPrice) => (
|
assetsWithPrices?.map((assetWithPrice) => (
|
||||||
<AssetTeaser
|
<AssetTeaser
|
||||||
asset={assetWithPrice}
|
asset={assetWithPrice}
|
||||||
key={assetWithPrice.id}
|
key={assetWithPrice.id}
|
||||||
@ -95,7 +97,5 @@ export default function AssetList({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<LoaderArea />
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: calc(var(--spacer) / 12);
|
margin-top: calc(var(--spacer) / 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeLabel {
|
.typeLabel {
|
||||||
|
@ -8,8 +8,8 @@ import AssetType from '@shared/AssetType'
|
|||||||
import NetworkName from '@shared/NetworkName'
|
import NetworkName from '@shared/NetworkName'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { getServiceByName } from '@utils/ddo'
|
import { getServiceByName } from '@utils/ddo'
|
||||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { formatNumber } from '@utils/numbers'
|
||||||
|
|
||||||
export declare type AssetTeaserProps = {
|
export declare type AssetTeaserProps = {
|
||||||
asset: AssetExtended
|
asset: AssetExtended
|
||||||
@ -77,16 +77,37 @@ export default function AssetTeaser({
|
|||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
{allocated && allocated > 0 ? (
|
{allocated && allocated > 0 ? (
|
||||||
<span className={styles.typeLabel}>
|
<span className={styles.typeLabel}>
|
||||||
{allocated < 0
|
{allocated < 0 ? (
|
||||||
? ''
|
''
|
||||||
: `${formatPrice(allocated, locale)} veOCEAN`}
|
) : (
|
||||||
|
<>
|
||||||
|
<strong>{formatNumber(allocated, locale, '0')}</strong>{' '}
|
||||||
|
veOCEAN
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{orders && orders > 0 ? (
|
{orders && orders > 0 ? (
|
||||||
<span className={styles.typeLabel}>
|
<span className={styles.typeLabel}>
|
||||||
{orders < 0
|
{orders < 0 ? (
|
||||||
? 'N/A'
|
'N/A'
|
||||||
: `${orders} ${orders === 1 ? 'sale' : 'sales'}`}
|
) : (
|
||||||
|
<>
|
||||||
|
<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>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -75,6 +75,7 @@ export default function ContainerInput(props: InputProps): ReactElement {
|
|||||||
name={`${field.name}[0].url`}
|
name={`${field.name}[0].url`}
|
||||||
checkUrl={false}
|
checkUrl={false}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
storageType={'url'}
|
||||||
handleButtonClick={handleValidation}
|
handleButtonClick={handleValidation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -23,7 +23,7 @@ export default function FileInfo({
|
|||||||
{hideUrl ? 'https://oceanprotocol/placeholder' : file.url}
|
{hideUrl ? 'https://oceanprotocol/placeholder' : file.url}
|
||||||
</h3>
|
</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.success}>✓ URL confirmed</li>
|
<li className={styles.success}>✓ File confirmed</li>
|
||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||||
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import FilesInput from './index'
|
import FilesInput from './index'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
|
|
||||||
jest.mock('formik')
|
jest.mock('formik')
|
||||||
jest.mock('@utils/provider')
|
jest.mock('@utils/provider')
|
||||||
@ -48,16 +48,17 @@ const mockForm = {
|
|||||||
describe('@shared/FormInput/InputElement/FilesInput', () => {
|
describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', async () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
|
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
|
||||||
;(getFileUrlInfo as jest.Mock).mockReturnValue([
|
;(getFileInfo as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
render(<FilesInput form={mockForm} {...props} />)
|
render(<FilesInput form={mockForm} field={mockField} {...props} />)
|
||||||
expect(screen.getByText('Validate')).toBeInTheDocument()
|
expect(screen.getByText('Validate')).toBeInTheDocument()
|
||||||
fireEvent.click(screen.getByText('Validate'))
|
fireEvent.click(screen.getByText('Validate'))
|
||||||
|
|
||||||
@ -67,13 +68,14 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
expect(mockHelpers.setValue).toHaveBeenCalled()
|
expect(mockHelpers.setValue).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders fileinfo when file is valid', () => {
|
it('renders fileinfo when file url is valid', () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
@ -82,10 +84,52 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
|
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', () => {
|
it('renders fileinfo without contentType', () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
@ -93,6 +137,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -100,7 +145,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders fileinfo placeholder when hideUrl is passed', () => {
|
it('renders fileinfo placeholder when hideUrl is passed', () => {
|
||||||
@ -117,7 +162,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('https://oceanprotocol/placeholder')
|
screen.getByText('https://oceanprotocol/placeholder')
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import FileInfo from './Info'
|
import FileInfo from './Info'
|
||||||
import UrlInput from '../URLInput'
|
import UrlInput from '../URLInput'
|
||||||
import { InputProps } from '@shared/FormInput'
|
import { InputProps } from '@shared/FormInput'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
|
|
||||||
@ -12,16 +12,27 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { asset } = useAsset()
|
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) {
|
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const providerUrl = props.form?.values?.services
|
|
||||||
? props.form?.values?.services[0].providerUrl.url
|
|
||||||
: asset.services[0].serviceEndpoint
|
|
||||||
setIsLoading(true)
|
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
|
// error if something's not right from response
|
||||||
if (!checkedFile)
|
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.')
|
throw Error('✗ No valid file detected. Check your URL and try again.')
|
||||||
|
|
||||||
// if all good, add file to formik state
|
// if all good, add file to formik state
|
||||||
helpers.setValue([{ url, ...checkedFile[0] }])
|
helpers.setValue([{ url, type: storageType, ...checkedFile[0] }])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
@ -42,7 +53,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
helpers.setTouched(false)
|
helpers.setTouched(false)
|
||||||
helpers.setValue(meta.initialValue)
|
helpers.setValue([
|
||||||
|
{ url: '', type: storageType === 'hidden' ? 'ipfs' : storageType }
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -56,7 +69,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
{...props}
|
{...props}
|
||||||
name={`${field.name}[0].url`}
|
name={`${field.name}[0].url`}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
checkUrl={true}
|
||||||
handleButtonClick={handleValidation}
|
handleButtonClick={handleValidation}
|
||||||
|
storageType={storageType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -6,6 +6,7 @@ import styles from './index.module.css'
|
|||||||
import InputGroup from '@shared/FormInput/InputGroup'
|
import InputGroup from '@shared/FormInput/InputGroup'
|
||||||
import InputElement from '@shared/FormInput/InputElement'
|
import InputElement from '@shared/FormInput/InputElement'
|
||||||
import isUrl from 'is-url-superb'
|
import isUrl from 'is-url-superb'
|
||||||
|
import { isCID } from '@utils/ipfs'
|
||||||
|
|
||||||
export interface URLInputProps {
|
export interface URLInputProps {
|
||||||
submitText: string
|
submitText: string
|
||||||
@ -13,6 +14,7 @@ export interface URLInputProps {
|
|||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
name: string
|
name: string
|
||||||
checkUrl?: boolean
|
checkUrl?: boolean
|
||||||
|
storageType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function URLInput({
|
export default function URLInput({
|
||||||
@ -21,6 +23,7 @@ export default function URLInput({
|
|||||||
isLoading,
|
isLoading,
|
||||||
name,
|
name,
|
||||||
checkUrl,
|
checkUrl,
|
||||||
|
storageType,
|
||||||
...props
|
...props
|
||||||
}: URLInputProps): ReactElement {
|
}: URLInputProps): ReactElement {
|
||||||
const [field, meta] = useField(name)
|
const [field, meta] = useField(name)
|
||||||
@ -32,7 +35,8 @@ export default function URLInput({
|
|||||||
setIsButtonDisabled(
|
setIsButtonDisabled(
|
||||||
!field?.value ||
|
!field?.value ||
|
||||||
field.value === '' ||
|
field.value === '' ||
|
||||||
(checkUrl && !isUrl(field.value)) ||
|
(checkUrl && storageType === 'url' && !isUrl(field.value)) ||
|
||||||
|
(checkUrl && storageType === 'ipfs' && !isCID(field.value)) ||
|
||||||
field.value.includes('javascript:') ||
|
field.value.includes('javascript:') ||
|
||||||
meta?.error
|
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 styles from './index.module.css'
|
||||||
import { InputProps } from '..'
|
import { InputProps } from '..'
|
||||||
import FilesInput from './FilesInput'
|
import FilesInput from './FilesInput'
|
||||||
@ -11,6 +11,7 @@ import Nft from './Nft'
|
|||||||
import InputRadio from './Radio'
|
import InputRadio from './Radio'
|
||||||
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
||||||
import TagsAutoComplete from './TagsAutoComplete'
|
import TagsAutoComplete from './TagsAutoComplete'
|
||||||
|
import TabsFile from '@shared/atoms/TabsFile'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ export default function InputElement({
|
|||||||
...props
|
...props
|
||||||
}: InputProps): ReactElement {
|
}: InputProps): ReactElement {
|
||||||
const styleClasses = cx({ select: true, [size]: size })
|
const styleClasses = cx({ select: true, [size]: size })
|
||||||
|
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'select': {
|
case 'select': {
|
||||||
const sortedOptions =
|
const sortedOptions =
|
||||||
@ -80,6 +82,26 @@ export default function InputElement({
|
|||||||
</select>
|
</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':
|
case 'textarea':
|
||||||
return <textarea id={props.name} className={styles.textarea} {...props} />
|
return <textarea id={props.name} className={styles.textarea} {...props} />
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ export interface InputProps {
|
|||||||
type?: string
|
type?: string
|
||||||
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
||||||
sortOptions?: boolean
|
sortOptions?: boolean
|
||||||
|
fields?: FieldInputProps<any>[]
|
||||||
additionalComponent?: ReactElement
|
additionalComponent?: ReactElement
|
||||||
value?: string | number
|
value?: string | number
|
||||||
onChange?(
|
onChange?(
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
|
||||||
import Conversion from './Conversion'
|
import Conversion from './Conversion'
|
||||||
import styles from './PriceUnit.module.css'
|
import styles from './PriceUnit.module.css'
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { formatNumber } from '@utils/numbers'
|
||||||
export function formatPrice(price: number, locale: string): string {
|
|
||||||
return formatCurrency(price, '', locale, false, {
|
|
||||||
// Not exactly clear what `significant figures` are for this library,
|
|
||||||
// but setting this seems to give us the formatting we want.
|
|
||||||
// See https://github.com/oceanprotocol/market/issues/70
|
|
||||||
significantFigures: 4
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PriceUnit({
|
export default function PriceUnit({
|
||||||
price,
|
price,
|
||||||
@ -19,7 +10,8 @@ export default function PriceUnit({
|
|||||||
size = 'small',
|
size = 'small',
|
||||||
conversion,
|
conversion,
|
||||||
symbol,
|
symbol,
|
||||||
type
|
type,
|
||||||
|
decimals
|
||||||
}: {
|
}: {
|
||||||
price: number
|
price: number
|
||||||
type?: string
|
type?: string
|
||||||
@ -27,6 +19,7 @@ export default function PriceUnit({
|
|||||||
size?: 'small' | 'mini' | 'large'
|
size?: 'small' | 'mini' | 'large'
|
||||||
conversion?: boolean
|
conversion?: boolean
|
||||||
symbol?: string
|
symbol?: string
|
||||||
|
decimals?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { locale } = useUserPreferences()
|
const { locale } = useUserPreferences()
|
||||||
|
|
||||||
@ -37,7 +30,7 @@ export default function PriceUnit({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
{Number.isNaN(price) ? '-' : formatPrice(price, locale)}{' '}
|
{Number.isNaN(price) ? '-' : formatNumber(price, locale, decimals)}{' '}
|
||||||
<span className={styles.symbol}>{symbol}</span>
|
<span className={styles.symbol}>{symbol}</span>
|
||||||
</div>
|
</div>
|
||||||
{conversion && <Conversion price={price} symbol={symbol} />}
|
{conversion && <Conversion price={price} symbol={symbol} />}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import * as axios from 'axios'
|
import axios from 'axios'
|
||||||
import Publisher from './'
|
import Publisher from './'
|
||||||
|
|
||||||
const account = '0x0000000000000000000000000000000000000000'
|
const account = '0x0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
const axiosMock = axios as jest.Mocked<typeof axios>
|
||||||
|
|
||||||
describe('@shared/Publisher', () => {
|
describe('@shared/Publisher', () => {
|
||||||
test('should return correct markup by default', async () => {
|
test('should return correct markup by default', async () => {
|
||||||
;(axios as any).get.mockImplementationOnce(() =>
|
axiosMock.get.mockImplementationOnce(() =>
|
||||||
Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } })
|
Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } })
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ describe('@shared/Publisher', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should truncate account by default', async () => {
|
test('should truncate account by default', async () => {
|
||||||
;(axios as any).get.mockImplementationOnce(() =>
|
axiosMock.get.mockImplementationOnce(() =>
|
||||||
Promise.resolve({ data: { name: null } })
|
Promise.resolve({ data: { name: null } })
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ describe('@shared/Publisher', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should return correct markup in minimal state', async () => {
|
test('should return correct markup in minimal state', async () => {
|
||||||
;(axios as any).get.mockImplementationOnce(() =>
|
axiosMock.get.mockImplementationOnce(() =>
|
||||||
Promise.resolve({ data: { name: null } })
|
Promise.resolve({ data: { name: null } })
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ describe('@shared/Publisher', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should return markup with empty account', async () => {
|
test('should return markup with empty account', async () => {
|
||||||
;(axios as any).get.mockImplementationOnce(() =>
|
axiosMock.get.mockImplementationOnce(() =>
|
||||||
Promise.resolve({ data: { name: null } })
|
Promise.resolve({ data: { name: null } })
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -24,4 +24,9 @@ describe('Tags', () => {
|
|||||||
it('renders WithoutLinks', () => {
|
it('renders WithoutLinks', () => {
|
||||||
render(<Tags {...argsWithoutLinks} />)
|
render(<Tags {...argsWithoutLinks} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders with faulty tags', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
render(<Tags items={'tags' as any} />)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -30,6 +30,9 @@ export default function Tags({
|
|||||||
className,
|
className,
|
||||||
noLinks
|
noLinks
|
||||||
}: TagsProps): ReactElement {
|
}: TagsProps): ReactElement {
|
||||||
|
// safeguard against faults in the metadata
|
||||||
|
if (!(items instanceof Array)) return null
|
||||||
|
|
||||||
max = max || items.length
|
max = max || items.length
|
||||||
const remainder = items.length - max
|
const remainder = items.length - max
|
||||||
// filter out empty array items, and restrict to `max`
|
// filter out empty array items, and restrict to `max`
|
||||||
|
@ -17,7 +17,8 @@ const DefaultTrigger = React.forwardRef((props, ref: any) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export default function Tooltip(props: TippyProps): ReactElement {
|
export default function Tooltip(props: TippyProps): ReactElement {
|
||||||
const { content, children, trigger, disabled, className, placement } = props
|
const { className, ...restProps } = props
|
||||||
|
const { content, children, trigger, disabled, placement } = props
|
||||||
const [styles, api] = useSpring(() => animation.from)
|
const [styles, api] = useSpring(() => animation.from)
|
||||||
|
|
||||||
function onMount() {
|
function onMount() {
|
||||||
@ -60,7 +61,7 @@ export default function Tooltip(props: TippyProps): ReactElement {
|
|||||||
onMount={onMount}
|
onMount={onMount}
|
||||||
onHide={onHide}
|
onHide={onHide}
|
||||||
// animation
|
// animation
|
||||||
{...props}
|
{...restProps}
|
||||||
>
|
>
|
||||||
<div className={styleClasses}>{children || <DefaultTrigger />}</div>
|
<div className={styleClasses}>{children || <DefaultTrigger />}</div>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
|
@ -2,7 +2,7 @@ import { useAsset } from '@context/Asset'
|
|||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import Tooltip from '@shared/atoms/Tooltip'
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
import { formatNumber } from '@utils/numbers'
|
||||||
import { getNftOwnAllocation } from '@utils/veAllocation'
|
import { getNftOwnAllocation } from '@utils/veAllocation'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
@ -33,8 +33,8 @@ export default function AssetStats() {
|
|||||||
{asset?.stats?.allocated && asset?.stats?.allocated > 0 ? (
|
{asset?.stats?.allocated && asset?.stats?.allocated > 0 ? (
|
||||||
<span className={styles.stat}>
|
<span className={styles.stat}>
|
||||||
<span className={styles.number}>
|
<span className={styles.number}>
|
||||||
{formatPrice(asset.stats.allocated, locale)}
|
{formatNumber(asset.stats.allocated, locale, '0')}
|
||||||
</span>
|
</span>{' '}
|
||||||
veOCEAN
|
veOCEAN
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
143
src/components/Asset/AssetActions/ButtonBuy/index.test.tsx
Normal file
143
src/components/Asset/AssetActions/ButtonBuy/index.test.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import ButtonBuy, { ButtonBuyProps } from './'
|
||||||
|
|
||||||
|
const downloadProps: ButtonBuyProps = {
|
||||||
|
action: 'download',
|
||||||
|
disabled: false,
|
||||||
|
hasPreviousOrder: false,
|
||||||
|
hasDatatoken: false,
|
||||||
|
btSymbol: 'btSymbol',
|
||||||
|
dtSymbol: 'dtSymbol',
|
||||||
|
dtBalance: '100000000000',
|
||||||
|
assetTimeout: '1 day',
|
||||||
|
assetType: 'Dataset',
|
||||||
|
stepText: 'TEST',
|
||||||
|
priceType: 'fixed',
|
||||||
|
isConsumable: true,
|
||||||
|
isBalanceSufficient: true,
|
||||||
|
consumableFeedback: 'TEST: consumableFeedback'
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeProps: ButtonBuyProps = {
|
||||||
|
action: 'compute',
|
||||||
|
disabled: false,
|
||||||
|
hasPreviousOrder: false,
|
||||||
|
hasDatatoken: true,
|
||||||
|
btSymbol: 'btSymbol',
|
||||||
|
dtSymbol: 'dtSymbol',
|
||||||
|
dtBalance: '100000000000',
|
||||||
|
assetTimeout: '1 day',
|
||||||
|
assetType: 'algorithm',
|
||||||
|
hasPreviousOrderSelectedComputeAsset: false,
|
||||||
|
hasDatatokenSelectedComputeAsset: true,
|
||||||
|
dtSymbolSelectedComputeAsset: 'dtSymbol',
|
||||||
|
dtBalanceSelectedComputeAsset: 'dtBalance',
|
||||||
|
selectedComputeAssetType: 'selectedComputeAssetType',
|
||||||
|
stepText: ' ',
|
||||||
|
isLoading: false,
|
||||||
|
type: 'submit',
|
||||||
|
priceType: 'fixed',
|
||||||
|
algorithmPriceType: 'free',
|
||||||
|
isBalanceSufficient: true,
|
||||||
|
isConsumable: true,
|
||||||
|
consumableFeedback: 'consumableFeedback',
|
||||||
|
isAlgorithmConsumable: true,
|
||||||
|
hasProviderFee: false,
|
||||||
|
retry: false
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Asset/AssetActions/ButtonBuy', () => {
|
||||||
|
// TESTS FOR LOADING
|
||||||
|
it('Renders Buy button without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} isLoading />)
|
||||||
|
const button = screen.getByText('TEST')
|
||||||
|
expect(button).toContainHTML('<Loader')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TESTS FOR DOWNLOAD
|
||||||
|
it('Renders Buy button without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} />)
|
||||||
|
const button = screen.getByText('Buy for 1 day')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders Buy button without crashing when hasPreviousOrder=true', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} hasPreviousOrder />)
|
||||||
|
const button = screen.getByText('Download')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders retry button for download without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} retry />)
|
||||||
|
const button = screen.getByText('Retry')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders get button for free download without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} priceType="free" hasPreviousOrder />)
|
||||||
|
const button = screen.getByText('Download')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Get" button for free assets without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} priceType="free" />)
|
||||||
|
const button = screen.getByText('Get')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders Buy button without crashing', () => {
|
||||||
|
render(
|
||||||
|
<ButtonBuy
|
||||||
|
{...downloadProps}
|
||||||
|
assetTimeout="Forever"
|
||||||
|
isConsumable={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const button = screen.getByText('Buy')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TESTS FOR COMPUTE
|
||||||
|
it('Renders "Buy Compute Job" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} />)
|
||||||
|
const button = screen.getByText('Buy Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Buy Compute Job" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} hasDatatokenSelectedComputeAsset />)
|
||||||
|
const button = screen.getByText('Buy Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Start Compute Job" button', () => {
|
||||||
|
render(
|
||||||
|
<ButtonBuy
|
||||||
|
{...computeProps}
|
||||||
|
hasPreviousOrder
|
||||||
|
hasPreviousOrderSelectedComputeAsset
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const button = screen.getByText('Start Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Order Compute Job" button', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} priceType="free" hasProviderFee />)
|
||||||
|
const button = screen.getByText('Order Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Order Compute Job" button', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} priceType="free" hasProviderFee />)
|
||||||
|
const button = screen.getByText('Order Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "retry" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} retry />)
|
||||||
|
const button = screen.getByText('Retry')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
})
|
@ -2,8 +2,10 @@ import React, { FormEvent, ReactElement } from 'react'
|
|||||||
import Button from '../../../@shared/atoms/Button'
|
import Button from '../../../@shared/atoms/Button'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Loader from '../../../@shared/atoms/Loader'
|
import Loader from '../../../@shared/atoms/Loader'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
import Web3 from 'web3'
|
||||||
|
|
||||||
interface ButtonBuyProps {
|
export interface ButtonBuyProps {
|
||||||
action: 'download' | 'compute'
|
action: 'download' | 'compute'
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
hasPreviousOrder: boolean
|
hasPreviousOrder: boolean
|
||||||
@ -28,11 +30,11 @@ interface ButtonBuyProps {
|
|||||||
priceType?: string
|
priceType?: string
|
||||||
algorithmPriceType?: string
|
algorithmPriceType?: string
|
||||||
isAlgorithmConsumable?: boolean
|
isAlgorithmConsumable?: boolean
|
||||||
|
isSupportedOceanNetwork?: boolean
|
||||||
hasProviderFee?: boolean
|
hasProviderFee?: boolean
|
||||||
|
retry?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we need to take a look at these messages
|
|
||||||
|
|
||||||
function getConsumeHelpText(
|
function getConsumeHelpText(
|
||||||
btSymbol: string,
|
btSymbol: string,
|
||||||
dtBalance: string,
|
dtBalance: string,
|
||||||
@ -42,12 +44,14 @@ function getConsumeHelpText(
|
|||||||
assetType: string,
|
assetType: string,
|
||||||
isConsumable: boolean,
|
isConsumable: boolean,
|
||||||
isBalanceSufficient: boolean,
|
isBalanceSufficient: boolean,
|
||||||
consumableFeedback: string
|
consumableFeedback: string,
|
||||||
|
isSupportedOceanNetwork: boolean,
|
||||||
|
web3: Web3
|
||||||
) {
|
) {
|
||||||
const text =
|
const text =
|
||||||
isConsumable === false
|
isConsumable === false
|
||||||
? consumableFeedback
|
? consumableFeedback
|
||||||
: hasPreviousOrder
|
: hasPreviousOrder && web3 && isSupportedOceanNetwork
|
||||||
? `You bought this ${assetType} already allowing you to use it without paying again.`
|
? `You bought this ${assetType} already allowing you to use it without paying again.`
|
||||||
: hasDatatoken
|
: hasDatatoken
|
||||||
? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.`
|
? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.`
|
||||||
@ -57,6 +61,35 @@ function getConsumeHelpText(
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAlgoHelpText(
|
||||||
|
dtSymbolSelectedComputeAsset: string,
|
||||||
|
dtBalanceSelectedComputeAsset: string,
|
||||||
|
isConsumable: boolean,
|
||||||
|
isAlgorithmConsumable: boolean,
|
||||||
|
hasPreviousOrderSelectedComputeAsset: boolean,
|
||||||
|
selectedComputeAssetType: string,
|
||||||
|
hasDatatokenSelectedComputeAsset: boolean,
|
||||||
|
isBalanceSufficient: boolean,
|
||||||
|
isSupportedOceanNetwork: boolean,
|
||||||
|
web3: Web3
|
||||||
|
) {
|
||||||
|
const text =
|
||||||
|
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
|
||||||
|
isConsumable === false ||
|
||||||
|
isAlgorithmConsumable === false
|
||||||
|
? ''
|
||||||
|
: hasPreviousOrderSelectedComputeAsset && web3 && isSupportedOceanNetwork
|
||||||
|
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
|
||||||
|
: hasDatatokenSelectedComputeAsset
|
||||||
|
? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying OCEAN again.`
|
||||||
|
: web3 && !isSupportedOceanNetwork
|
||||||
|
? `Connect to the correct network to interact with this asset.`
|
||||||
|
: isBalanceSufficient === false
|
||||||
|
? ''
|
||||||
|
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.`
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
function getComputeAssetHelpText(
|
function getComputeAssetHelpText(
|
||||||
hasPreviousOrder: boolean,
|
hasPreviousOrder: boolean,
|
||||||
hasDatatoken: boolean,
|
hasDatatoken: boolean,
|
||||||
@ -73,6 +106,8 @@ function getComputeAssetHelpText(
|
|||||||
dtBalanceSelectedComputeAsset?: string,
|
dtBalanceSelectedComputeAsset?: string,
|
||||||
selectedComputeAssetType?: string,
|
selectedComputeAssetType?: string,
|
||||||
isAlgorithmConsumable?: boolean,
|
isAlgorithmConsumable?: boolean,
|
||||||
|
isSupportedOceanNetwork?: boolean,
|
||||||
|
web3?: Web3,
|
||||||
hasProviderFee?: boolean
|
hasProviderFee?: boolean
|
||||||
) {
|
) {
|
||||||
const computeAssetHelpText = getConsumeHelpText(
|
const computeAssetHelpText = getConsumeHelpText(
|
||||||
@ -84,21 +119,24 @@ function getComputeAssetHelpText(
|
|||||||
assetType,
|
assetType,
|
||||||
isConsumable,
|
isConsumable,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
consumableFeedback
|
consumableFeedback,
|
||||||
|
isSupportedOceanNetwork,
|
||||||
|
web3
|
||||||
|
)
|
||||||
|
|
||||||
|
const computeAlgoHelpText = getAlgoHelpText(
|
||||||
|
dtSymbolSelectedComputeAsset,
|
||||||
|
dtBalanceSelectedComputeAsset,
|
||||||
|
isConsumable,
|
||||||
|
isAlgorithmConsumable,
|
||||||
|
hasPreviousOrderSelectedComputeAsset,
|
||||||
|
selectedComputeAssetType,
|
||||||
|
hasDatatokenSelectedComputeAsset,
|
||||||
|
isBalanceSufficient,
|
||||||
|
isSupportedOceanNetwork,
|
||||||
|
web3
|
||||||
)
|
)
|
||||||
|
|
||||||
const computeAlgoHelpText =
|
|
||||||
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
|
|
||||||
isConsumable === false ||
|
|
||||||
isAlgorithmConsumable === false
|
|
||||||
? ''
|
|
||||||
: hasPreviousOrderSelectedComputeAsset
|
|
||||||
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
|
|
||||||
: hasDatatokenSelectedComputeAsset
|
|
||||||
? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying ${btSymbol} again.`
|
|
||||||
: isBalanceSufficient === false
|
|
||||||
? ''
|
|
||||||
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher.`
|
|
||||||
const providerFeeHelpText = hasProviderFee
|
const providerFeeHelpText = hasProviderFee
|
||||||
? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
|
? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
|
||||||
: 'C2D resources required to start the job are available, no payment required for those fees.'
|
: 'C2D resources required to start the job are available, no payment required for those fees.'
|
||||||
@ -131,22 +169,26 @@ export default function ButtonBuy({
|
|||||||
priceType,
|
priceType,
|
||||||
algorithmPriceType,
|
algorithmPriceType,
|
||||||
isAlgorithmConsumable,
|
isAlgorithmConsumable,
|
||||||
hasProviderFee
|
hasProviderFee,
|
||||||
|
retry,
|
||||||
|
isSupportedOceanNetwork
|
||||||
}: ButtonBuyProps): ReactElement {
|
}: ButtonBuyProps): ReactElement {
|
||||||
const buttonText =
|
const { web3 } = useWeb3()
|
||||||
action === 'download'
|
const buttonText = retry
|
||||||
? hasPreviousOrder
|
? 'Retry'
|
||||||
? 'Download'
|
: action === 'download'
|
||||||
: priceType === 'free'
|
? hasPreviousOrder
|
||||||
? 'Get'
|
? 'Download'
|
||||||
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
: priceType === 'free'
|
||||||
: hasPreviousOrder &&
|
? 'Get'
|
||||||
hasPreviousOrderSelectedComputeAsset &&
|
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
||||||
!hasProviderFee
|
: hasPreviousOrder &&
|
||||||
? 'Start Compute Job'
|
hasPreviousOrderSelectedComputeAsset &&
|
||||||
: priceType === 'free' && algorithmPriceType === 'free'
|
!hasProviderFee
|
||||||
? 'Order Compute Job'
|
? 'Start Compute Job'
|
||||||
: `Buy Compute Job`
|
: priceType === 'free' && algorithmPriceType === 'free'
|
||||||
|
? 'Order Compute Job'
|
||||||
|
: `Buy Compute Job`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
@ -174,7 +216,9 @@ export default function ButtonBuy({
|
|||||||
assetType,
|
assetType,
|
||||||
isConsumable,
|
isConsumable,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
consumableFeedback
|
consumableFeedback,
|
||||||
|
isSupportedOceanNetwork,
|
||||||
|
web3
|
||||||
)
|
)
|
||||||
: getComputeAssetHelpText(
|
: getComputeAssetHelpText(
|
||||||
hasPreviousOrder,
|
hasPreviousOrder,
|
||||||
@ -192,6 +236,8 @@ export default function ButtonBuy({
|
|||||||
dtBalanceSelectedComputeAsset,
|
dtBalanceSelectedComputeAsset,
|
||||||
selectedComputeAssetType,
|
selectedComputeAssetType,
|
||||||
isAlgorithmConsumable,
|
isAlgorithmConsumable,
|
||||||
|
isSupportedOceanNetwork,
|
||||||
|
web3,
|
||||||
hasProviderFee
|
hasProviderFee
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import PriceOutput from './PriceOutput'
|
|||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import content from '../../../../../content/pages/startComputeDataset.json'
|
import content from '../../../../../content/pages/startComputeDataset.json'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset, ZERO_ADDRESS } from '@oceanprotocol/lib'
|
||||||
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
||||||
import { useMarketMetadata } from '@context/MarketMetadata'
|
import { useMarketMetadata } from '@context/MarketMetadata'
|
||||||
import Alert from '@shared/atoms/Alert'
|
import Alert from '@shared/atoms/Alert'
|
||||||
@ -43,7 +43,8 @@ export default function FormStartCompute({
|
|||||||
datasetOrderPriceAndFees,
|
datasetOrderPriceAndFees,
|
||||||
algoOrderPriceAndFees,
|
algoOrderPriceAndFees,
|
||||||
providerFeeAmount,
|
providerFeeAmount,
|
||||||
validUntil
|
validUntil,
|
||||||
|
retry
|
||||||
}: {
|
}: {
|
||||||
algorithms: AssetSelectionAsset[]
|
algorithms: AssetSelectionAsset[]
|
||||||
ddoListAlgorithms: Asset[]
|
ddoListAlgorithms: Asset[]
|
||||||
@ -71,9 +72,10 @@ export default function FormStartCompute({
|
|||||||
algoOrderPriceAndFees?: OrderPriceAndFees
|
algoOrderPriceAndFees?: OrderPriceAndFees
|
||||||
providerFeeAmount?: string
|
providerFeeAmount?: string
|
||||||
validUntil?: string
|
validUntil?: string
|
||||||
|
retry: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { siteContent } = useMarketMetadata()
|
const { siteContent } = useMarketMetadata()
|
||||||
const { accountId, balance } = useWeb3()
|
const { accountId, balance, isSupportedOceanNetwork } = useWeb3()
|
||||||
const { isValid, values }: FormikContextType<{ algorithm: string }> =
|
const { isValid, values }: FormikContextType<{ algorithm: string }> =
|
||||||
useFormikContext()
|
useFormikContext()
|
||||||
const { asset, isAssetNetwork } = useAsset()
|
const { asset, isAssetNetwork } = useAsset()
|
||||||
@ -96,7 +98,7 @@ export default function FormStartCompute({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!values.algorithm || !accountId || !isConsumable) return
|
if (!values.algorithm || !isConsumable) return
|
||||||
|
|
||||||
async function fetchAlgorithmAssetExtended() {
|
async function fetchAlgorithmAssetExtended() {
|
||||||
const algorithmAsset = getAlgorithmAsset(values.algorithm)
|
const algorithmAsset = getAlgorithmAsset(values.algorithm)
|
||||||
@ -104,7 +106,7 @@ export default function FormStartCompute({
|
|||||||
algorithmAsset.chainId,
|
algorithmAsset.chainId,
|
||||||
algorithmAsset.services[0].datatokenAddress,
|
algorithmAsset.services[0].datatokenAddress,
|
||||||
algorithmAsset.services[0].timeout,
|
algorithmAsset.services[0].timeout,
|
||||||
accountId
|
accountId || ZERO_ADDRESS // if user is not connected, use ZERO_ADDRESS as accountId
|
||||||
)
|
)
|
||||||
const extendedAlgoAsset: AssetExtended = {
|
const extendedAlgoAsset: AssetExtended = {
|
||||||
...algorithmAsset,
|
...algorithmAsset,
|
||||||
@ -196,15 +198,20 @@ export default function FormStartCompute({
|
|||||||
}
|
}
|
||||||
setTotalPrices(totalPrices)
|
setTotalPrices(totalPrices)
|
||||||
}, [
|
}, [
|
||||||
asset?.accessDetails,
|
asset,
|
||||||
selectedAlgorithmAsset?.accessDetails,
|
|
||||||
hasPreviousOrder,
|
hasPreviousOrder,
|
||||||
hasDatatoken,
|
hasDatatoken,
|
||||||
hasPreviousOrderSelectedComputeAsset,
|
hasPreviousOrderSelectedComputeAsset,
|
||||||
hasDatatokenSelectedComputeAsset,
|
hasDatatokenSelectedComputeAsset,
|
||||||
datasetOrderPriceAndFees,
|
datasetOrderPriceAndFees,
|
||||||
algoOrderPriceAndFees,
|
algoOrderPriceAndFees,
|
||||||
providerFeeAmount
|
providerFeeAmount,
|
||||||
|
isAssetNetwork,
|
||||||
|
selectedAlgorithmAsset?.accessDetails,
|
||||||
|
datasetOrderPrice,
|
||||||
|
algoOrderPrice,
|
||||||
|
algorithmSymbol,
|
||||||
|
datasetSymbol
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -214,12 +221,13 @@ export default function FormStartCompute({
|
|||||||
setIsBalanceSufficient(false)
|
setIsBalanceSufficient(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if one comparison of baseTokenBalance and token price comparison is false then the state will be false
|
// if one comparison of baseTokenBalance and token price comparison is false then the state will be false
|
||||||
setIsBalanceSufficient(
|
setIsBalanceSufficient(
|
||||||
isBalanceSufficient && compareAsBN(baseTokenBalance, `${price.value}`)
|
baseTokenBalance && compareAsBN(baseTokenBalance, `${price.value}`)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}, [balance, dtBalance, datasetSymbol, algorithmSymbol])
|
}, [balance, dtBalance, datasetSymbol, algorithmSymbol, totalPrices])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className={styles.form}>
|
<Form className={styles.form}>
|
||||||
@ -293,7 +301,9 @@ export default function FormStartCompute({
|
|||||||
isAlgorithmConsumable={
|
isAlgorithmConsumable={
|
||||||
selectedAlgorithmAsset?.accessDetails?.isPurchasable
|
selectedAlgorithmAsset?.accessDetails?.isPurchasable
|
||||||
}
|
}
|
||||||
|
isSupportedOceanNetwork={isSupportedOceanNetwork}
|
||||||
hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'}
|
hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'}
|
||||||
|
retry={retry}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import Tooltip from '@shared/atoms/Tooltip'
|
|||||||
import styles from './PriceOutput.module.css'
|
import styles from './PriceOutput.module.css'
|
||||||
import { MAX_DECIMALS } from '@utils/constants'
|
import { MAX_DECIMALS } from '@utils/constants'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
|
||||||
interface PriceOutputProps {
|
interface PriceOutputProps {
|
||||||
hasPreviousOrder: boolean
|
hasPreviousOrder: boolean
|
||||||
@ -40,13 +41,21 @@ function Row({
|
|||||||
sign?: string
|
sign?: string
|
||||||
type?: string
|
type?: string
|
||||||
}) {
|
}) {
|
||||||
|
const { isSupportedOceanNetwork } = useWeb3()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.priceRow}>
|
<div className={styles.priceRow}>
|
||||||
<div className={styles.sign}>{sign}</div>
|
<div className={styles.sign}>{sign}</div>
|
||||||
<div className={styles.type}>{type}</div>
|
<div className={styles.type}>{type}</div>
|
||||||
<div>
|
<div>
|
||||||
<PriceUnit
|
<PriceUnit
|
||||||
price={hasPreviousOrder || hasDatatoken ? 0 : Number(price)}
|
price={
|
||||||
|
!isSupportedOceanNetwork
|
||||||
|
? hasPreviousOrder || hasDatatoken
|
||||||
|
? 0
|
||||||
|
: Number(price)
|
||||||
|
: Number(price)
|
||||||
|
}
|
||||||
symbol={symbol}
|
symbol={symbol}
|
||||||
size="small"
|
size="small"
|
||||||
className={styles.price}
|
className={styles.price}
|
||||||
|
@ -45,6 +45,7 @@ import { getComputeFeedback } from '@utils/feedback'
|
|||||||
import { getDummyWeb3 } from '@utils/web3'
|
import { getDummyWeb3 } from '@utils/web3'
|
||||||
import { initializeProviderForCompute } from '@utils/provider'
|
import { initializeProviderForCompute } from '@utils/provider'
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { useAsset } from '@context/Asset'
|
||||||
|
|
||||||
const refreshInterval = 10000 // 10 sec.
|
const refreshInterval = 10000 // 10 sec.
|
||||||
export default function Compute({
|
export default function Compute({
|
||||||
@ -60,8 +61,10 @@ export default function Compute({
|
|||||||
fileIsLoading?: boolean
|
fileIsLoading?: boolean
|
||||||
consumableFeedback?: string
|
consumableFeedback?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { accountId, web3 } = useWeb3()
|
const { accountId, web3, isSupportedOceanNetwork } = useWeb3()
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
const { isAssetNetwork } = useAsset()
|
||||||
|
|
||||||
const newAbortController = useAbortController()
|
const newAbortController = useAbortController()
|
||||||
const newCancelToken = useCancelToken()
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
@ -97,6 +100,7 @@ export default function Compute({
|
|||||||
const [refetchJobs, setRefetchJobs] = useState(false)
|
const [refetchJobs, setRefetchJobs] = useState(false)
|
||||||
const [isLoadingJobs, setIsLoadingJobs] = useState(false)
|
const [isLoadingJobs, setIsLoadingJobs] = useState(false)
|
||||||
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
||||||
|
const [retry, setRetry] = useState<boolean>(false)
|
||||||
|
|
||||||
const hasDatatoken = Number(dtBalance) >= 1
|
const hasDatatoken = Number(dtBalance) >= 1
|
||||||
const isComputeButtonDisabled =
|
const isComputeButtonDisabled =
|
||||||
@ -115,7 +119,7 @@ export default function Compute({
|
|||||||
const datatokenInstance = new Datatoken(web3)
|
const datatokenInstance = new Datatoken(web3)
|
||||||
const dtBalance = await datatokenInstance.balance(
|
const dtBalance = await datatokenInstance.balance(
|
||||||
asset?.services[0].datatokenAddress,
|
asset?.services[0].datatokenAddress,
|
||||||
accountId
|
accountId || ZERO_ADDRESS // if the user is not connected, we use ZERO_ADDRESS as accountId
|
||||||
)
|
)
|
||||||
setAlgorithmDTBalance(new Decimal(dtBalance).toString())
|
setAlgorithmDTBalance(new Decimal(dtBalance).toString())
|
||||||
const hasAlgoDt = Number(dtBalance) >= 1
|
const hasAlgoDt = Number(dtBalance) >= 1
|
||||||
@ -133,9 +137,10 @@ export default function Compute({
|
|||||||
const initializedProvider = await initializeProviderForCompute(
|
const initializedProvider = await initializeProviderForCompute(
|
||||||
asset,
|
asset,
|
||||||
selectedAlgorithmAsset,
|
selectedAlgorithmAsset,
|
||||||
accountId,
|
accountId || ZERO_ADDRESS, // if the user is not connected, we use ZERO_ADDRESS as accountId
|
||||||
computeEnv
|
computeEnv
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!initializedProvider ||
|
!initializedProvider ||
|
||||||
!initializedProvider?.datasets ||
|
!initializedProvider?.datasets ||
|
||||||
@ -144,13 +149,17 @@ export default function Compute({
|
|||||||
throw new Error(`Error initializing provider for the compute job!`)
|
throw new Error(`Error initializing provider for the compute job!`)
|
||||||
|
|
||||||
setInitializedProviderResponse(initializedProvider)
|
setInitializedProviderResponse(initializedProvider)
|
||||||
setProviderFeeAmount(
|
|
||||||
await unitsToAmount(
|
const feeAmount = await unitsToAmount(
|
||||||
web3,
|
!isSupportedOceanNetwork || !isAssetNetwork
|
||||||
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken,
|
? await getDummyWeb3(asset?.chainId)
|
||||||
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeAmount
|
: web3,
|
||||||
)
|
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken,
|
||||||
|
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeAmount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setProviderFeeAmount(feeAmount)
|
||||||
|
|
||||||
const computeDuration = (
|
const computeDuration = (
|
||||||
parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) -
|
parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) -
|
||||||
Math.floor(Date.now() / 1000)
|
Math.floor(Date.now() / 1000)
|
||||||
@ -216,7 +225,7 @@ export default function Compute({
|
|||||||
}, [asset?.accessDetails, accountId, isUnsupportedPricing])
|
}, [asset?.accessDetails, accountId, isUnsupportedPricing])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedAlgorithmAsset?.accessDetails || !accountId) return
|
if (!selectedAlgorithmAsset?.accessDetails) return
|
||||||
|
|
||||||
setIsRequestingAlgoOrderPrice(true)
|
setIsRequestingAlgoOrderPrice(true)
|
||||||
setIsConsumableAlgorithmPrice(
|
setIsConsumableAlgorithmPrice(
|
||||||
@ -291,7 +300,8 @@ export default function Compute({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newError = error
|
const newError = error
|
||||||
if (!newError) return
|
if (!newError) return
|
||||||
toast.error(newError)
|
const errorMsg = newError + '. Please retry.'
|
||||||
|
toast.error(errorMsg)
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
async function startJob(): Promise<void> {
|
async function startJob(): Promise<void> {
|
||||||
@ -304,6 +314,7 @@ export default function Compute({
|
|||||||
documentId: selectedAlgorithmAsset.id,
|
documentId: selectedAlgorithmAsset.id,
|
||||||
serviceId: selectedAlgorithmAsset.services[0].id
|
serviceId: selectedAlgorithmAsset.services[0].id
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowed = await isOrderable(
|
const allowed = await isOrderable(
|
||||||
asset,
|
asset,
|
||||||
computeService.id,
|
computeService.id,
|
||||||
@ -386,6 +397,7 @@ export default function Compute({
|
|||||||
initPriceAndFees()
|
initPriceAndFees()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error.message)
|
setError(error.message)
|
||||||
|
setRetry(true)
|
||||||
LoggerInstance.error(`[compute] ${error.message} `)
|
LoggerInstance.error(`[compute] ${error.message} `)
|
||||||
} finally {
|
} finally {
|
||||||
setIsOrdering(false)
|
setIsOrdering(false)
|
||||||
@ -447,14 +459,12 @@ export default function Compute({
|
|||||||
setSelectedAlgorithm={setSelectedAlgorithmAsset}
|
setSelectedAlgorithm={setSelectedAlgorithmAsset}
|
||||||
isLoading={isOrdering || isRequestingAlgoOrderPrice}
|
isLoading={isOrdering || isRequestingAlgoOrderPrice}
|
||||||
isComputeButtonDisabled={isComputeButtonDisabled}
|
isComputeButtonDisabled={isComputeButtonDisabled}
|
||||||
hasPreviousOrder={validOrderTx !== undefined}
|
hasPreviousOrder={!!validOrderTx}
|
||||||
hasDatatoken={hasDatatoken}
|
hasDatatoken={hasDatatoken}
|
||||||
dtBalance={dtBalance}
|
dtBalance={dtBalance}
|
||||||
assetType={asset?.metadata.type}
|
assetType={asset?.metadata.type}
|
||||||
assetTimeout={secondsToString(asset?.services[0].timeout)}
|
assetTimeout={secondsToString(asset?.services[0].timeout)}
|
||||||
hasPreviousOrderSelectedComputeAsset={
|
hasPreviousOrderSelectedComputeAsset={!!validAlgorithmOrderTx}
|
||||||
validAlgorithmOrderTx !== undefined
|
|
||||||
}
|
|
||||||
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
|
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
|
||||||
datasetSymbol={asset?.accessDetails?.baseToken?.symbol || 'OCEAN'}
|
datasetSymbol={asset?.accessDetails?.baseToken?.symbol || 'OCEAN'}
|
||||||
algorithmSymbol={
|
algorithmSymbol={
|
||||||
@ -477,6 +487,7 @@ export default function Compute({
|
|||||||
algoOrderPriceAndFees={algoOrderPriceAndFees}
|
algoOrderPriceAndFees={algoOrderPriceAndFees}
|
||||||
providerFeeAmount={providerFeeAmount}
|
providerFeeAmount={providerFeeAmount}
|
||||||
validUntil={computeValidUntil}
|
validUntil={computeValidUntil}
|
||||||
|
retry={retry}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
)}
|
)}
|
||||||
|
@ -33,7 +33,7 @@ export default function Download({
|
|||||||
fileIsLoading?: boolean
|
fileIsLoading?: boolean
|
||||||
consumableFeedback?: string
|
consumableFeedback?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { accountId, web3 } = useWeb3()
|
const { accountId, web3, isSupportedOceanNetwork } = useWeb3()
|
||||||
const { getOpcFeeForToken } = useMarketMetadata()
|
const { getOpcFeeForToken } = useMarketMetadata()
|
||||||
const { isInPurgatory, isAssetNetwork } = useAsset()
|
const { isInPurgatory, isAssetNetwork } = useAsset()
|
||||||
const isMounted = useIsMounted()
|
const isMounted = useIsMounted()
|
||||||
@ -48,6 +48,7 @@ export default function Download({
|
|||||||
const [isOrderDisabled, setIsOrderDisabled] = useState(false)
|
const [isOrderDisabled, setIsOrderDisabled] = useState(false)
|
||||||
const [orderPriceAndFees, setOrderPriceAndFees] =
|
const [orderPriceAndFees, setOrderPriceAndFees] =
|
||||||
useState<OrderPriceAndFees>()
|
useState<OrderPriceAndFees>()
|
||||||
|
const [retry, setRetry] = useState<boolean>(false)
|
||||||
|
|
||||||
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
|
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
|
||||||
|
|
||||||
@ -155,9 +156,10 @@ export default function Download({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error)
|
LoggerInstance.error(error)
|
||||||
|
setRetry(true)
|
||||||
const message = isOwned
|
const message = isOwned
|
||||||
? 'Failed to download file!'
|
? 'Failed to download file!'
|
||||||
: 'An error occurred. Check console for more information.'
|
: 'An error occurred, please retry. Check console for more information.'
|
||||||
toast.error(message)
|
toast.error(message)
|
||||||
}
|
}
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -181,6 +183,8 @@ export default function Download({
|
|||||||
isConsumable={asset.accessDetails?.isPurchasable}
|
isConsumable={asset.accessDetails?.isPurchasable}
|
||||||
isBalanceSufficient={isBalanceSufficient}
|
isBalanceSufficient={isBalanceSufficient}
|
||||||
consumableFeedback={consumableFeedback}
|
consumableFeedback={consumableFeedback}
|
||||||
|
retry={retry}
|
||||||
|
isSupportedOceanNetwork={isSupportedOceanNetwork}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { compareAsBN } from '@utils/numbers'
|
|||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import Web3Feedback from '@shared/Web3Feedback'
|
import Web3Feedback from '@shared/Web3Feedback'
|
||||||
import { getFileDidInfo, getFileUrlInfo } from '@utils/provider'
|
import { getFileDidInfo, getFileInfo } from '@utils/provider'
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
@ -52,14 +52,20 @@ export default function AssetActions({
|
|||||||
formikState?.values?.services[0].providerUrl.url ||
|
formikState?.values?.services[0].providerUrl.url ||
|
||||||
asset?.services[0]?.serviceEndpoint
|
asset?.services[0]?.serviceEndpoint
|
||||||
|
|
||||||
|
const storageType = formikState?.values?.services
|
||||||
|
? formikState?.values?.services[0].files[0].type
|
||||||
|
: null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
|
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
|
||||||
.url
|
.url
|
||||||
? await getFileUrlInfo(
|
? await getFileInfo(
|
||||||
formikState?.values?.services?.[0].files?.[0].url,
|
formikState?.values?.services?.[0].files?.[0].url,
|
||||||
providerUrl
|
providerUrl,
|
||||||
|
storageType
|
||||||
)
|
)
|
||||||
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)
|
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)
|
||||||
|
|
||||||
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
||||||
|
|
||||||
// set the content type in the Dataset Schema
|
// 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 MetaItem from './MetaItem'
|
||||||
import styles from './MetaFull.module.css'
|
import styles from './MetaFull.module.css'
|
||||||
import Publisher from '@shared/Publisher'
|
import Publisher from '@shared/Publisher'
|
||||||
import { useAsset } from '@context/Asset'
|
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 {
|
export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
|
||||||
|
const [paymentCollector, setPaymentCollector] = useState<string>()
|
||||||
const { isInPurgatory } = useAsset()
|
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() {
|
function DockerImage() {
|
||||||
const containerInfo = ddo?.metadata?.algorithm?.container
|
const containerInfo = ddo?.metadata?.algorithm?.container
|
||||||
@ -23,6 +40,12 @@ export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
|
|||||||
title="Owner"
|
title="Owner"
|
||||||
content={<Publisher account={ddo?.nft?.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 && (
|
{ddo?.metadata?.type === 'algorithm' && ddo?.metadata?.algorithm && (
|
||||||
<MetaItem title="Docker Image" content={<DockerImage />} />
|
<MetaItem title="Docker Image" content={<DockerImage />} />
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
.wrapper img {
|
.wrapper img {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
@ -14,11 +14,13 @@ const openSeaTestNetworks = [4]
|
|||||||
|
|
||||||
export default function NftTooltip({
|
export default function NftTooltip({
|
||||||
nft,
|
nft,
|
||||||
|
nftImage,
|
||||||
address,
|
address,
|
||||||
chainId,
|
chainId,
|
||||||
isBlockscoutExplorer
|
isBlockscoutExplorer
|
||||||
}: {
|
}: {
|
||||||
nft: NftMetadata
|
nft: NftMetadata
|
||||||
|
nftImage: string
|
||||||
address: string
|
address: string
|
||||||
chainId: number
|
chainId: number
|
||||||
isBlockscoutExplorer: boolean
|
isBlockscoutExplorer: boolean
|
||||||
@ -39,7 +41,7 @@ export default function NftTooltip({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
{nft && <img src={nft.image_data || nft.image} alt={nft?.name} />}
|
{nftImage && <img src={nftImage} alt={nft?.name} />}
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
{nft && <h5>{nft.name}</h5>}
|
{nft && <h5>{nft.name}</h5>}
|
||||||
{address && (
|
{address && (
|
||||||
|
@ -4,14 +4,19 @@
|
|||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
width: calc(var(--spacer) * 2);
|
width: calc(var(--spacer) * 2);
|
||||||
height: calc(var(--spacer) * 2);
|
height: calc(var(--spacer) * 2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nftImage img,
|
|
||||||
.nftImage > svg:first-of-type {
|
.nftImage > svg:first-of-type {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nftImage img {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.nftImage > svg:first-of-type {
|
.nftImage > svg:first-of-type {
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ export default function Nft({
|
|||||||
content={
|
content={
|
||||||
<NftTooltip
|
<NftTooltip
|
||||||
nft={nftMetadata}
|
nft={nftMetadata}
|
||||||
|
nftImage={nftImage}
|
||||||
address={asset?.nftAddress}
|
address={asset?.nftAddress}
|
||||||
chainId={asset?.chainId}
|
chainId={asset?.chainId}
|
||||||
isBlockscoutExplorer={isBlockscoutExplorer}
|
isBlockscoutExplorer={isBlockscoutExplorer}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
import { Formik } from 'formik'
|
import { Formik } from 'formik'
|
||||||
import {
|
import {
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
Metadata,
|
Metadata,
|
||||||
FixedRateExchange,
|
FixedRateExchange,
|
||||||
Asset,
|
Asset,
|
||||||
Service
|
Service,
|
||||||
|
Datatoken
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import { validationSchema } from './_validation'
|
import { validationSchema } from './_validation'
|
||||||
import { getInitialValues } from './_constants'
|
import { getInitialValues } from './_constants'
|
||||||
@ -36,10 +37,28 @@ export default function Edit({
|
|||||||
const { accountId, web3 } = useWeb3()
|
const { accountId, web3 } = useWeb3()
|
||||||
const newAbortController = useAbortController()
|
const newAbortController = useAbortController()
|
||||||
const [success, setSuccess] = useState<string>()
|
const [success, setSuccess] = useState<string>()
|
||||||
|
const [paymentCollector, setPaymentCollector] = useState<string>()
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
const isComputeType = asset?.services[0]?.type === 'compute'
|
const isComputeType = asset?.services[0]?.type === 'compute'
|
||||||
const hasFeedback = error || success
|
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) {
|
async function updateFixedPrice(newPrice: string) {
|
||||||
const config = getOceanConfig(asset.chainId)
|
const config = getOceanConfig(asset.chainId)
|
||||||
|
|
||||||
@ -81,13 +100,22 @@ export default function Edit({
|
|||||||
values.price !== asset.accessDetails.price &&
|
values.price !== asset.accessDetails.price &&
|
||||||
(await updateFixedPrice(values.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) {
|
if (values.files[0]?.url) {
|
||||||
const file = {
|
const file = {
|
||||||
nftAddress: asset.nftAddress,
|
nftAddress: asset.nftAddress,
|
||||||
datatokenAddress: asset.services[0].datatokenAddress,
|
datatokenAddress: asset.services[0].datatokenAddress,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
type: 'url',
|
type: values.files[0].type,
|
||||||
index: 0,
|
index: 0,
|
||||||
url: values.files[0].url,
|
url: values.files[0].url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
@ -147,7 +175,8 @@ export default function Edit({
|
|||||||
initialValues={getInitialValues(
|
initialValues={getInitialValues(
|
||||||
asset?.metadata,
|
asset?.metadata,
|
||||||
asset?.services[0]?.timeout,
|
asset?.services[0]?.timeout,
|
||||||
asset?.accessDetails?.price
|
asset?.accessDetails?.price,
|
||||||
|
paymentCollector
|
||||||
)}
|
)}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={async (values, { resetForm }) => {
|
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('src/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 React, { ReactElement, useEffect } from 'react'
|
||||||
import { Field, Form, useField, useFormikContext } from 'formik'
|
import { Field, Form, useFormikContext } from 'formik'
|
||||||
import Input, { InputProps } from '@shared/FormInput'
|
import Input from '@shared/FormInput'
|
||||||
import FormActions from './FormActions'
|
import FormActions from './FormActions'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { FormPublishData } from 'src/components/Publish/_types'
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
|
import { getFieldContent } from '@utils/form'
|
||||||
|
|
||||||
export function checkIfTimeoutInPredefinedValues(
|
export function checkIfTimeoutInPredefinedValues(
|
||||||
timeout: string,
|
timeout: string,
|
||||||
@ -21,11 +22,11 @@ export default function FormEditMetadata({
|
|||||||
showPrice,
|
showPrice,
|
||||||
isComputeDataset
|
isComputeDataset
|
||||||
}: {
|
}: {
|
||||||
data: InputProps[]
|
data: FormFieldContent[]
|
||||||
showPrice: boolean
|
showPrice: boolean
|
||||||
isComputeDataset: boolean
|
isComputeDataset: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { oceanConfig, asset } = useAsset()
|
const { asset } = useAsset()
|
||||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
// 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(() => {
|
useEffect(() => {
|
||||||
// let's initiate files with empty url (we can't access the asset url) with type hidden (for UI frontend)
|
// let's initiate files with empty url (we can't access the asset url) with type hidden (for UI frontend)
|
||||||
setFieldValue('files', [
|
setTimeout(() => {
|
||||||
{
|
setFieldValue('files', [
|
||||||
url: '',
|
{
|
||||||
type: 'hidden'
|
url: '',
|
||||||
}
|
type: 'hidden'
|
||||||
])
|
}
|
||||||
|
])
|
||||||
|
}, 500)
|
||||||
|
|
||||||
const providerUrl = values?.services
|
const providerUrl = values?.services
|
||||||
? values?.services[0].providerUrl.url
|
? values?.services[0].providerUrl.url
|
||||||
: asset.services[0].serviceEndpoint
|
: asset.services[0].serviceEndpoint
|
||||||
|
|
||||||
// if we have a sample file, we need to get the files' info before setting defaults links value
|
// if we have a sample file, we need to get the files' info before setting defaults links value
|
||||||
asset?.metadata?.links?.[0] &&
|
asset?.metadata?.links?.[0] &&
|
||||||
getFileUrlInfo(asset.metadata.links[0], providerUrl).then(
|
getFileInfo(asset.metadata.links[0], providerUrl, 'url').then(
|
||||||
(checkedFile) => {
|
(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
|
// initiate link with values from asset metadata
|
||||||
setFieldValue('links', [
|
setFieldValue('links', [
|
||||||
{
|
{
|
||||||
url: asset.metadata.links[0],
|
url: asset.metadata.links[0],
|
||||||
|
type: 'url',
|
||||||
...checkedFile[0]
|
...checkedFile[0]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -74,23 +88,48 @@ export default function FormEditMetadata({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{data.map(
|
<Field {...getFieldContent('name', data)} component={Input} name="name" />
|
||||||
(field: InputProps) =>
|
|
||||||
(!showPrice && field.name === 'price') || (
|
<Field
|
||||||
<Field
|
{...getFieldContent('description', data)}
|
||||||
key={field.name}
|
component={Input}
|
||||||
options={
|
name="description"
|
||||||
field.name === 'timeout' && isComputeDataset === true
|
/>
|
||||||
? timeoutOptionsArray
|
|
||||||
: field.options
|
{showPrice && (
|
||||||
}
|
<Field
|
||||||
{...field}
|
{...getFieldContent('price', data)}
|
||||||
component={Input}
|
component={Input}
|
||||||
prefix={field.name === 'price' && oceanConfig?.oceanTokenSymbol}
|
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" />
|
||||||
|
|
||||||
<FormActions />
|
<FormActions />
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
@ -5,17 +5,19 @@ import { ComputeEditForm, MetadataEditForm } from './_types'
|
|||||||
export function getInitialValues(
|
export function getInitialValues(
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
price: string
|
price: string,
|
||||||
|
paymentCollector: string
|
||||||
): Partial<MetadataEditForm> {
|
): Partial<MetadataEditForm> {
|
||||||
return {
|
return {
|
||||||
name: metadata?.name,
|
name: metadata?.name,
|
||||||
description: metadata?.description,
|
description: metadata?.description,
|
||||||
price,
|
price,
|
||||||
links: [{ url: '', type: '' }],
|
links: [{ url: '', type: 'url' }],
|
||||||
files: [{ url: '', type: '' }],
|
files: [{ url: '', type: 'ipfs' }],
|
||||||
timeout: secondsToString(timeout),
|
timeout: secondsToString(timeout),
|
||||||
author: metadata?.author,
|
author: metadata?.author,
|
||||||
tags: metadata?.tags
|
tags: metadata?.tags,
|
||||||
|
paymentCollector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ export interface MetadataEditForm {
|
|||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
timeout: string
|
timeout: string
|
||||||
|
paymentCollector: string
|
||||||
price?: string
|
price?: string
|
||||||
files: FileInfo[]
|
files: FileInfo[]
|
||||||
links?: FileInfo[]
|
links?: FileInfo[]
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { FileInfo } from '@oceanprotocol/lib'
|
import { FileInfo } from '@oceanprotocol/lib'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
import web3 from 'web3'
|
||||||
|
import { testLinks } from '../../../@utils/yup'
|
||||||
|
|
||||||
export const validationSchema = Yup.object().shape({
|
export const validationSchema = Yup.object().shape({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
@ -10,38 +12,38 @@ export const validationSchema = Yup.object().shape({
|
|||||||
files: Yup.array<FileInfo[]>()
|
files: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks(true),
|
||||||
.url('Must be a valid URL.')
|
valid: Yup.boolean().test((value, context) => {
|
||||||
.test(
|
const { type } = context.parent
|
||||||
'GoogleNotSupported',
|
// allow user to submit if the value type is hidden
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
if (type === 'hidden') return true
|
||||||
(value) => {
|
return value || false
|
||||||
return !value?.toString().includes('drive.google')
|
})
|
||||||
}
|
|
||||||
),
|
|
||||||
valid: Yup.boolean().isTrue()
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.nullable(),
|
.nullable(),
|
||||||
links: Yup.array<FileInfo[]>()
|
links: Yup.array<FileInfo[]>().of(
|
||||||
.of(
|
Yup.object().shape({
|
||||||
Yup.object().shape({
|
url: testLinks(true),
|
||||||
url: Yup.string()
|
valid: Yup.boolean().test((value, context) => {
|
||||||
.url('Must be a valid URL.')
|
// allow user to submit if the value is null
|
||||||
.test(
|
const { valid, url } = context.parent
|
||||||
'GoogleNotSupported',
|
// allow user to continue if the url is empty
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
if (!url) return true
|
||||||
(value) => {
|
return valid
|
||||||
return !value?.toString().includes('drive.google')
|
|
||||||
}
|
|
||||||
),
|
|
||||||
valid: Yup.boolean().isTrue()
|
|
||||||
})
|
})
|
||||||
)
|
})
|
||||||
.nullable(),
|
),
|
||||||
timeout: Yup.string().required('Required'),
|
timeout: Yup.string().required('Required'),
|
||||||
author: Yup.string().nullable(),
|
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({
|
export const computeSettingsValidationSchema = Yup.object().shape({
|
||||||
|
@ -23,7 +23,6 @@ export default function RelatedAssets(): ReactElement {
|
|||||||
!asset?.nft ||
|
!asset?.nft ||
|
||||||
!asset?.metadata
|
!asset?.metadata
|
||||||
) {
|
) {
|
||||||
setIsLoading(false)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,11 +30,17 @@ export default function RelatedAssets(): ReactElement {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tagQuery = generateBaseQuery(
|
let tagResults: Asset[] = []
|
||||||
generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
|
|
||||||
)
|
// safeguard against faults in the metadata
|
||||||
const tagResults = (await queryMetadata(tagQuery, newCancelToken()))
|
if (asset.metadata.tags instanceof Array) {
|
||||||
?.results
|
const tagQuery = generateBaseQuery(
|
||||||
|
generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
|
||||||
|
)
|
||||||
|
|
||||||
|
tagResults = (await queryMetadata(tagQuery, newCancelToken()))
|
||||||
|
?.results
|
||||||
|
}
|
||||||
|
|
||||||
if (tagResults.length === 4) {
|
if (tagResults.length === 4) {
|
||||||
setRelatedAssets(tagResults)
|
setRelatedAssets(tagResults)
|
||||||
|
@ -124,8 +124,19 @@ export default function MarketStats(): ReactElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<PriceUnit price={total.veLocked} symbol="OCEAN" size="small" /> locked.{' '}
|
<PriceUnit
|
||||||
<PriceUnit price={total.veAllocated} symbol="veOCEAN" size="small" />{' '}
|
decimals="0"
|
||||||
|
price={total.veLocked}
|
||||||
|
symbol="OCEAN"
|
||||||
|
size="small"
|
||||||
|
/>{' '}
|
||||||
|
locked.{' '}
|
||||||
|
<PriceUnit
|
||||||
|
decimals="0"
|
||||||
|
price={total.veAllocated}
|
||||||
|
symbol="veOCEAN"
|
||||||
|
size="small"
|
||||||
|
/>{' '}
|
||||||
allocated.
|
allocated.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
56
src/components/Home/MostViews/index.test.tsx
Normal file
56
src/components/Home/MostViews/index.test.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import React from 'react'
|
||||||
|
import MostViews from '.'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { queryMetadata } from '@utils/aquarius'
|
||||||
|
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius'
|
||||||
|
|
||||||
|
jest.mock('axios')
|
||||||
|
jest.mock('@utils/aquarius')
|
||||||
|
|
||||||
|
const axiosMock = axios as jest.Mocked<typeof axios>
|
||||||
|
const queryMetadataMock = queryMetadata as jest.Mock
|
||||||
|
|
||||||
|
const queryMetadataBaseReturn: PagedAssets = {
|
||||||
|
results: [assetAquarius],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
totalResults: 1,
|
||||||
|
aggregations: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components/Home/MostViews', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
axiosMock.get.mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: [{ count: 666, did: assetAquarius.id }]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
queryMetadataMock.mockResolvedValue(queryMetadataBaseReturn)
|
||||||
|
render(<MostViews />)
|
||||||
|
await screen.findByText('666')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('catches errors', async () => {
|
||||||
|
queryMetadataMock.mockImplementation(() => {
|
||||||
|
throw new Error('Hello error')
|
||||||
|
})
|
||||||
|
|
||||||
|
// prevent console error from showing up in test log
|
||||||
|
const originalError = console.error
|
||||||
|
console.error = jest.fn()
|
||||||
|
|
||||||
|
try {
|
||||||
|
render(<MostViews />)
|
||||||
|
await screen.findByText('No results found')
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toEqual({ message: 'Hello error' })
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error = originalError
|
||||||
|
})
|
||||||
|
})
|
73
src/components/Home/MostViews/index.tsx
Normal file
73
src/components/Home/MostViews/index.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
|
||||||
|
import styles from '../index.module.css'
|
||||||
|
import {
|
||||||
|
generateBaseQuery,
|
||||||
|
getFilterTerm,
|
||||||
|
queryMetadata
|
||||||
|
} from '@utils/aquarius'
|
||||||
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
|
import AssetList from '@shared/AssetList'
|
||||||
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import { sortAssets } from '@utils/index'
|
||||||
|
import axios, { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
export default function MostViews(): ReactElement {
|
||||||
|
const [loading, setLoading] = useState<boolean>()
|
||||||
|
const [mostViewed, setMostViewed] = useState<AssetExtended[]>([])
|
||||||
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
|
const getMostViewed = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const response: AxiosResponse<PageViews[]> = await axios.get(
|
||||||
|
'https://market-analytics.oceanprotocol.com/pages?limit=6',
|
||||||
|
{ cancelToken: newCancelToken() }
|
||||||
|
)
|
||||||
|
const dids = response?.data?.map((x: PageViews) => x.did)
|
||||||
|
const assetsWithViews: AssetExtended[] = []
|
||||||
|
const baseParams = {
|
||||||
|
esPaginationOptions: { size: 6 },
|
||||||
|
filters: [getFilterTerm('_id', dids)]
|
||||||
|
} as BaseQueryParams
|
||||||
|
const query = generateBaseQuery(baseParams)
|
||||||
|
const result = await queryMetadata(query, newCancelToken())
|
||||||
|
|
||||||
|
if (result?.totalResults > 0) {
|
||||||
|
const sortedAssets = sortAssets(result.results, dids)
|
||||||
|
const overflow = sortedAssets.length - 6
|
||||||
|
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
||||||
|
sortedAssets.forEach((asset) => {
|
||||||
|
assetsWithViews.push({
|
||||||
|
...asset,
|
||||||
|
views: response.data.filter((x) => x.did === asset.id)?.[0]?.count
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setMostViewed(assetsWithViews)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.error(error.message)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [newCancelToken])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMostViewed()
|
||||||
|
}, [getMostViewed])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.section}>
|
||||||
|
<h3>
|
||||||
|
Most Views <span>last 30 days</span>
|
||||||
|
<Tooltip content="Assets from all supported chains. Not affected by your selected networks." />
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<AssetList
|
||||||
|
assets={mostViewed}
|
||||||
|
showPagination={false}
|
||||||
|
isLoading={loading}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
@ -1,29 +1,27 @@
|
|||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import AssetList from '@shared/AssetList'
|
import AssetList from '@shared/AssetList'
|
||||||
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
|
import Markdown from '@shared/Markdown'
|
||||||
import { queryMetadata } from '@utils/aquarius'
|
import { queryMetadata } from '@utils/aquarius'
|
||||||
|
import { sortAssets } from '@utils/index'
|
||||||
import React, { ReactElement, useState, useEffect } from 'react'
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
function sortElements(items: Asset[], sorted: string[]) {
|
|
||||||
items.sort(function (a, b) {
|
|
||||||
return sorted.indexOf(a.nftAddress) - sorted.indexOf(b.nftAddress)
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SectionQueryResult({
|
export default function SectionQueryResult({
|
||||||
title,
|
title,
|
||||||
query,
|
query,
|
||||||
action,
|
action,
|
||||||
queryData
|
queryData,
|
||||||
|
tooltip
|
||||||
}: {
|
}: {
|
||||||
title: ReactElement | string
|
title: ReactElement | string
|
||||||
query: SearchQuery
|
query: SearchQuery
|
||||||
action?: ReactElement
|
action?: ReactElement
|
||||||
queryData?: string[]
|
queryData?: string[]
|
||||||
|
tooltip?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const [result, setResult] = useState<PagedAssets>()
|
const [result, setResult] = useState<PagedAssets>()
|
||||||
@ -52,7 +50,7 @@ export default function SectionQueryResult({
|
|||||||
const result = await queryMetadata(query, newCancelToken())
|
const result = await queryMetadata(query, newCancelToken())
|
||||||
if (!isMounted()) return
|
if (!isMounted()) return
|
||||||
if (queryData && result?.totalResults > 0) {
|
if (queryData && result?.totalResults > 0) {
|
||||||
const sortedAssets = sortElements(result.results, queryData)
|
const sortedAssets = sortAssets(result.results, queryData)
|
||||||
const overflow = sortedAssets.length - 6
|
const overflow = sortedAssets.length - 6
|
||||||
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
||||||
result.results = sortedAssets
|
result.results = sortedAssets
|
||||||
@ -69,7 +67,9 @@ export default function SectionQueryResult({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<h3>{title}</h3>
|
<h3>
|
||||||
|
{title} {tooltip && <Tooltip content={<Markdown text={tooltip} />} />}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<AssetList
|
<AssetList
|
||||||
assets={result?.results}
|
assets={result?.results}
|
||||||
|
@ -13,6 +13,13 @@
|
|||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section h3 span {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
font-family: var(--font-family-base);
|
||||||
|
font-weight: var(--font-weight-base);
|
||||||
|
}
|
||||||
|
|
||||||
.section [class*='button'] {
|
.section [class*='button'] {
|
||||||
margin-top: var(--spacer);
|
margin-top: var(--spacer);
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,14 @@ import TopTags from './TopTags'
|
|||||||
import SectionQueryResult from './SectionQueryResult'
|
import SectionQueryResult from './SectionQueryResult'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Allocations from './Allocations'
|
import Allocations from './Allocations'
|
||||||
|
import MostViews from './MostViews'
|
||||||
|
|
||||||
export default function HomePage(): ReactElement {
|
export default function HomePage(): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
|
||||||
const [queryLatest, setQueryLatest] = useState<SearchQuery>()
|
const [queryLatest, setQueryLatest] = useState<SearchQuery>()
|
||||||
const [queryMostSales, setQueryMostSales] = useState<SearchQuery>()
|
const [queryMostSales, setQueryMostSales] = useState<SearchQuery>()
|
||||||
|
|
||||||
const [queryMostAllocation, setQueryMostAllocation] = useState<SearchQuery>()
|
const [queryMostAllocation, setQueryMostAllocation] = useState<SearchQuery>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -66,7 +68,7 @@ export default function HomePage(): ReactElement {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SectionQueryResult title="Most Sales" query={queryMostSales} />
|
<SectionQueryResult title="Most Sales" query={queryMostSales} />
|
||||||
|
<MostViews />
|
||||||
<TopSales title="Publishers With Most Sales" />
|
<TopSales title="Publishers With Most Sales" />
|
||||||
<TopTags title="Top Tags By Sales" />
|
<TopTags title="Top Tags By Sales" />
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import Downloads from './Downloads'
|
|||||||
import ComputeJobs from './ComputeJobs'
|
import ComputeJobs from './ComputeJobs'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { chainIds } from 'app.config'
|
|
||||||
import { getComputeJobs } from '@utils/compute'
|
import { getComputeJobs } from '@utils/compute'
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
|
@ -17,6 +17,7 @@ export default function Preview(): ReactElement {
|
|||||||
asset.accessDetails = {
|
asset.accessDetails = {
|
||||||
type: values.pricing.type,
|
type: values.pricing.type,
|
||||||
addressOrId: ZERO_ADDRESS,
|
addressOrId: ZERO_ADDRESS,
|
||||||
|
templateId: 1,
|
||||||
price: `${values.pricing.price}`,
|
price: `${values.pricing.price}`,
|
||||||
baseToken: {
|
baseToken: {
|
||||||
address: ZERO_ADDRESS,
|
address: ZERO_ADDRESS,
|
||||||
|
@ -72,8 +72,8 @@ export const initialValues: FormPublishData = {
|
|||||||
},
|
},
|
||||||
services: [
|
services: [
|
||||||
{
|
{
|
||||||
files: [{ url: '', type: '' }],
|
files: [{ url: '', type: 'ipfs' }],
|
||||||
links: [{ url: '', type: '' }],
|
links: [{ url: '', type: 'url' }],
|
||||||
dataTokenOptions: { name: '', symbol: '' },
|
dataTokenOptions: { name: '', symbol: '' },
|
||||||
timeout: '',
|
timeout: '',
|
||||||
access: 'access',
|
access: 'access',
|
||||||
|
@ -23,7 +23,8 @@ import { FormPublishData, MetadataAlgorithmContainer } from './_types'
|
|||||||
import {
|
import {
|
||||||
marketFeeAddress,
|
marketFeeAddress,
|
||||||
publisherMarketOrderFee,
|
publisherMarketOrderFee,
|
||||||
publisherMarketFixedSwapFee
|
publisherMarketFixedSwapFee,
|
||||||
|
defaultDatatokenTemplateIndex
|
||||||
} from '../../../app.config'
|
} from '../../../app.config'
|
||||||
import { sanitizeUrl } from '@utils/url'
|
import { sanitizeUrl } from '@utils/url'
|
||||||
import { getContainerChecksum } from '@utils/docker'
|
import { getContainerChecksum } from '@utils/docker'
|
||||||
@ -138,18 +139,24 @@ export async function transformPublishFormToDdo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is the default format hardcoded
|
// this is the default format hardcoded
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
nftAddress,
|
nftAddress,
|
||||||
datatokenAddress,
|
datatokenAddress,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
type: 'url',
|
type: files[0].type,
|
||||||
index: 0,
|
index: 0,
|
||||||
url: files[0].url,
|
[files[0].type === 'ipfs'
|
||||||
|
? 'hash'
|
||||||
|
: files[0].type === 'arweave'
|
||||||
|
? 'transactionId'
|
||||||
|
: 'url']: files[0].url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesEncrypted =
|
const filesEncrypted =
|
||||||
!isPreview &&
|
!isPreview &&
|
||||||
files?.length &&
|
files?.length &&
|
||||||
@ -211,7 +218,7 @@ export async function createTokensAndPricing(
|
|||||||
|
|
||||||
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
|
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
|
||||||
const ercParams: DatatokenCreateParams = {
|
const ercParams: DatatokenCreateParams = {
|
||||||
templateIndex: 2,
|
templateIndex: defaultDatatokenTemplateIndex,
|
||||||
minter: accountId,
|
minter: accountId,
|
||||||
paymentCollector: accountId,
|
paymentCollector: accountId,
|
||||||
mpFeeAddress: marketFeeAddress,
|
mpFeeAddress: marketFeeAddress,
|
||||||
|
@ -3,6 +3,7 @@ import * as Yup from 'yup'
|
|||||||
import { getMaxDecimalsValidation } from '@utils/numbers'
|
import { getMaxDecimalsValidation } from '@utils/numbers'
|
||||||
import { validateFieldSchaclSchema } from '@utils/schaclSchema'
|
import { validateFieldSchaclSchema } from '@utils/schaclSchema'
|
||||||
import { FileInfo } from '@oceanprotocol/lib'
|
import { FileInfo } from '@oceanprotocol/lib'
|
||||||
|
import { testLinks } from '../../@utils/yup'
|
||||||
|
|
||||||
// TODO: conditional validation
|
// TODO: conditional validation
|
||||||
// e.g. when algo is selected, Docker image is required
|
// e.g. when algo is selected, Docker image is required
|
||||||
@ -37,16 +38,7 @@ const validationService = {
|
|||||||
files: Yup.array<FileInfo[]>()
|
files: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks().required('Required'),
|
||||||
.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'),
|
|
||||||
|
|
||||||
valid: Yup.boolean().isTrue().required('File must be valid.')
|
valid: Yup.boolean().isTrue().required('File must be valid.')
|
||||||
})
|
})
|
||||||
@ -56,16 +48,7 @@ const validationService = {
|
|||||||
links: Yup.array<FileInfo[]>()
|
links: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks(),
|
||||||
.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
|
|
||||||
valid: Yup.boolean()
|
valid: Yup.boolean()
|
||||||
// valid: Yup.boolean().isTrue('File must be valid.')
|
// valid: Yup.boolean().isTrue('File must be valid.')
|
||||||
})
|
})
|
||||||
|
@ -79,8 +79,6 @@ export default function SearchPage({
|
|||||||
fetchAssets(parsed, chainIds)
|
fetchAssets(parsed, chainIds)
|
||||||
}, [parsed, chainIds, newCancelToken, fetchAssets])
|
}, [parsed, chainIds, newCancelToken, fetchAssets])
|
||||||
|
|
||||||
console.log(queryResult?.results)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.search}>
|
<div className={styles.search}>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user