mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into feature/enforce-docker-containers
This commit is contained in:
commit
7eb3f2f479
14
package-lock.json
generated
14
package-lock.json
generated
@ -13,7 +13,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.1.1",
|
"@oceanprotocol/lib": "^2.2.1",
|
||||||
"@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",
|
||||||
@ -4539,9 +4539,9 @@
|
|||||||
"integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q=="
|
"integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@oceanprotocol/lib": {
|
"node_modules/@oceanprotocol/lib": {
|
||||||
"version": "2.1.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz",
|
||||||
"integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==",
|
"integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oceanprotocol/contracts": "^1.1.7",
|
"@oceanprotocol/contracts": "^1.1.7",
|
||||||
"bignumber.js": "^9.1.0",
|
"bignumber.js": "^9.1.0",
|
||||||
@ -44803,9 +44803,9 @@
|
|||||||
"integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q=="
|
"integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q=="
|
||||||
},
|
},
|
||||||
"@oceanprotocol/lib": {
|
"@oceanprotocol/lib": {
|
||||||
"version": "2.1.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz",
|
||||||
"integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==",
|
"integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@oceanprotocol/contracts": "^1.1.7",
|
"@oceanprotocol/contracts": "^1.1.7",
|
||||||
"bignumber.js": "^9.1.0",
|
"bignumber.js": "^9.1.0",
|
||||||
|
@ -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.1.1",
|
"@oceanprotocol/lib": "^2.2.1",
|
||||||
"@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",
|
||||||
|
@ -6,7 +6,8 @@ export enum SortDirectionOptions {
|
|||||||
export enum SortTermOptions {
|
export enum SortTermOptions {
|
||||||
Created = 'nft.created',
|
Created = 'nft.created',
|
||||||
Relevance = '_score',
|
Relevance = '_score',
|
||||||
Stats = 'stats.orders'
|
Stats = 'stats.orders',
|
||||||
|
Allocated = 'stats.allocated'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: could not figure out how to get `enum` to be ambiant
|
// Note: could not figure out how to get `enum` to be ambiant
|
||||||
|
@ -53,7 +53,9 @@ export function generateBaseQuery(
|
|||||||
...baseQueryParams.nestedQuery,
|
...baseQueryParams.nestedQuery,
|
||||||
filter: [
|
filter: [
|
||||||
...(baseQueryParams.filters || []),
|
...(baseQueryParams.filters || []),
|
||||||
getFilterTerm('chainId', baseQueryParams.chainIds),
|
baseQueryParams.chainIds
|
||||||
|
? getFilterTerm('chainId', baseQueryParams.chainIds)
|
||||||
|
: [],
|
||||||
getFilterTerm('_index', 'aquarius'),
|
getFilterTerm('_index', 'aquarius'),
|
||||||
...(baseQueryParams.ignorePurgatory
|
...(baseQueryParams.ignorePurgatory
|
||||||
? []
|
? []
|
||||||
|
45
src/@utils/veAllocation.ts
Normal file
45
src/@utils/veAllocation.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { AllLocked } from 'src/@types/subgraph/AllLocked'
|
||||||
|
import { gql, OperationResult } from 'urql'
|
||||||
|
import { fetchData, getQueryContext } from './subgraph'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const AllLocked = gql`
|
||||||
|
query AllLocked {
|
||||||
|
veOCEANs(first: 1000) {
|
||||||
|
lockedAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
interface TotalVe {
|
||||||
|
totalLocked: number
|
||||||
|
totalAllocated: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
||||||
|
const totals = {
|
||||||
|
totalLocked: 0,
|
||||||
|
totalAllocated: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryContext = getQueryContext(1)
|
||||||
|
|
||||||
|
const response = await axios.post(`https://df-sql.oceandao.org/nftinfo`)
|
||||||
|
totals.totalAllocated = response.data?.reduce(
|
||||||
|
(previousValue: number, currentValue: { ve_allocated: any }) =>
|
||||||
|
previousValue + Number(currentValue.ve_allocated),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetchedLocked: OperationResult<AllLocked, any> = await fetchData(
|
||||||
|
AllLocked,
|
||||||
|
null,
|
||||||
|
queryContext
|
||||||
|
)
|
||||||
|
totals.totalLocked = fetchedLocked.data?.veOCEANs.reduce(
|
||||||
|
(previousValue, currentValue) =>
|
||||||
|
previousValue + Number(currentValue.lockedAmount),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return totals
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import AssetTeaser from '@shared/AssetTeaser/AssetTeaser'
|
import AssetTeaser from '@shared/AssetTeaser'
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import Pagination from '@shared/Pagination'
|
import Pagination from '@shared/Pagination'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import React, { ReactElement } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
|
||||||
import Price from '@shared/Price'
|
|
||||||
import removeMarkdown from 'remove-markdown'
|
|
||||||
import Publisher from '@shared/Publisher'
|
|
||||||
import AssetType from '@shared/AssetType'
|
|
||||||
import NetworkName from '@shared/NetworkName'
|
|
||||||
import styles from './AssetTeaser.module.css'
|
|
||||||
import { getServiceByName } from '@utils/ddo'
|
|
||||||
|
|
||||||
declare type AssetTeaserProps = {
|
|
||||||
asset: AssetExtended
|
|
||||||
noPublisher?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AssetTeaser({
|
|
||||||
asset,
|
|
||||||
noPublisher
|
|
||||||
}: AssetTeaserProps): ReactElement {
|
|
||||||
const { name, type, description } = asset.metadata
|
|
||||||
const { datatokens } = asset
|
|
||||||
const isCompute = Boolean(getServiceByName(asset, 'compute'))
|
|
||||||
const accessType = isCompute ? 'compute' : 'access'
|
|
||||||
const { owner } = asset.nft
|
|
||||||
const { orders } = asset.stats
|
|
||||||
return (
|
|
||||||
<article className={`${styles.teaser} ${styles[type]}`}>
|
|
||||||
<Link href={`/asset/${asset.id}`}>
|
|
||||||
<a className={styles.link}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<div className={styles.symbol}>{datatokens[0]?.symbol}</div>
|
|
||||||
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
|
||||||
{name.slice(0, 200)}
|
|
||||||
</Dotdotdot>
|
|
||||||
{!noPublisher && (
|
|
||||||
<Publisher account={owner} minimal className={styles.publisher} />
|
|
||||||
)}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<AssetType
|
|
||||||
type={type}
|
|
||||||
accessType={accessType}
|
|
||||||
className={styles.typeDetails}
|
|
||||||
totalSales={orders}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.content}>
|
|
||||||
<Dotdotdot tagName="p" clamp={3}>
|
|
||||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
|
||||||
</Dotdotdot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer className={styles.foot}>
|
|
||||||
<Price accessDetails={asset.accessDetails} size="small" />
|
|
||||||
<NetworkName networkId={asset.chainId} className={styles.network} />
|
|
||||||
</footer>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
}
|
|
@ -9,6 +9,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-top: calc(var(--spacer) / 2);
|
||||||
|
padding-bottom: calc(var(--spacer) / 2);
|
||||||
/* for sticking footer to bottom */
|
/* for sticking footer to bottom */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -18,8 +20,12 @@
|
|||||||
background-color: var(--background-body);
|
background-color: var(--background-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detailLine {
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
margin-top: calc(var(--spacer) / 2);
|
margin-top: calc(var(--spacer) / 3);
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
/* for sticking footer to bottom */
|
/* for sticking footer to bottom */
|
||||||
@ -27,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content p {
|
.content p {
|
||||||
margin-bottom: calc(var(--spacer) / 4);
|
margin-bottom: calc(var(--spacer) / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@ -37,36 +43,20 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.publisher {
|
.footer {
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.foot {
|
|
||||||
margin-top: calc(var(--spacer) / 4);
|
margin-top: calc(var(--spacer) / 4);
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.foot p {
|
.typeLabel {
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.symbol {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeDetails {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--spacer) / 3);
|
|
||||||
right: calc(var(--spacer) / 3);
|
|
||||||
width: auto;
|
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
|
display: inline-block;
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
padding-left: calc(var(--spacer) / 3.5);
|
||||||
|
margin-left: calc(var(--spacer) / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.network {
|
.typeLabel:first-child {
|
||||||
font-size: var(--font-size-mini);
|
border-left: none;
|
||||||
position: absolute;
|
padding-left: 0;
|
||||||
right: calc(var(--spacer) / 3);
|
margin-left: 0;
|
||||||
bottom: calc(var(--spacer) / 3);
|
|
||||||
}
|
}
|
84
src/components/@shared/AssetTeaser/index.tsx
Normal file
84
src/components/@shared/AssetTeaser/index.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import Dotdotdot from 'react-dotdotdot'
|
||||||
|
import Price from '@shared/Price'
|
||||||
|
import removeMarkdown from 'remove-markdown'
|
||||||
|
import Publisher from '@shared/Publisher'
|
||||||
|
import AssetType from '@shared/AssetType'
|
||||||
|
import NetworkName from '@shared/NetworkName'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import { getServiceByName } from '@utils/ddo'
|
||||||
|
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||||
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
|
||||||
|
declare type AssetTeaserProps = {
|
||||||
|
asset: AssetExtended
|
||||||
|
noPublisher?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AssetTeaser({
|
||||||
|
asset,
|
||||||
|
noPublisher
|
||||||
|
}: AssetTeaserProps): ReactElement {
|
||||||
|
const { name, type, description } = asset.metadata
|
||||||
|
const { datatokens } = asset
|
||||||
|
const isCompute = Boolean(getServiceByName(asset, 'compute'))
|
||||||
|
const accessType = isCompute ? 'compute' : 'access'
|
||||||
|
const { owner } = asset.nft
|
||||||
|
const { orders, allocated } = asset.stats
|
||||||
|
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
|
||||||
|
const { locale } = useUserPreferences()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className={`${styles.teaser} ${styles[type]}`}>
|
||||||
|
<Link href={`/asset/${asset.id}`}>
|
||||||
|
<a className={styles.link}>
|
||||||
|
<aside className={styles.detailLine}>
|
||||||
|
<AssetType
|
||||||
|
className={styles.typeLabel}
|
||||||
|
type={type}
|
||||||
|
accessType={accessType}
|
||||||
|
/>
|
||||||
|
<span className={styles.typeLabel}>{datatokens[0]?.symbol}</span>
|
||||||
|
<NetworkName
|
||||||
|
networkId={asset.chainId}
|
||||||
|
className={styles.typeLabel}
|
||||||
|
/>
|
||||||
|
</aside>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Dotdotdot tagName="h1" clamp={3} className={styles.title}>
|
||||||
|
{name.slice(0, 200)}
|
||||||
|
</Dotdotdot>
|
||||||
|
{!noPublisher && <Publisher account={owner} minimal />}
|
||||||
|
</header>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Dotdotdot tagName="p" clamp={3}>
|
||||||
|
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||||
|
</Dotdotdot>
|
||||||
|
</div>
|
||||||
|
{isUnsupportedPricing ? (
|
||||||
|
<strong>No pricing schema available</strong>
|
||||||
|
) : (
|
||||||
|
<Price accessDetails={asset.accessDetails} size="small" />
|
||||||
|
)}
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
{allocated && allocated > 0 ? (
|
||||||
|
<span className={styles.typeLabel}>
|
||||||
|
{allocated < 0
|
||||||
|
? ''
|
||||||
|
: `${formatPrice(allocated, locale)} veOCEAN`}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{orders && orders > 0 ? (
|
||||||
|
<span className={styles.typeLabel}>
|
||||||
|
{orders < 0
|
||||||
|
? 'N/A'
|
||||||
|
: `${orders} ${orders === 1 ? 'sale' : 'sales'}`}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</footer>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
@ -7,13 +7,11 @@ import Lock from '@images/lock.svg'
|
|||||||
export default function AssetType({
|
export default function AssetType({
|
||||||
type,
|
type,
|
||||||
accessType,
|
accessType,
|
||||||
className,
|
className
|
||||||
totalSales
|
|
||||||
}: {
|
}: {
|
||||||
type: string
|
type: string
|
||||||
accessType: string
|
accessType: string
|
||||||
className?: string
|
className?: string
|
||||||
totalSales?: number
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className={className || null}>
|
<div className={className || null}>
|
||||||
@ -28,14 +26,6 @@ export default function AssetType({
|
|||||||
<div className={styles.typeLabel}>
|
<div className={styles.typeLabel}>
|
||||||
{type === 'dataset' ? 'dataset' : 'algorithm'}
|
{type === 'dataset' ? 'dataset' : 'algorithm'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(totalSales || totalSales === 0) && (
|
|
||||||
<div className={styles.typeLabel}>
|
|
||||||
{totalSales < 0
|
|
||||||
? 'N/A'
|
|
||||||
: `${totalSales} ${totalSales === 1 ? 'sale' : 'sales'}`}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
padding-left: calc(var(--spacer) / 3.5);
|
||||||
|
margin-left: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat:first-child {
|
||||||
|
border-left: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
color: var(--font-color-heading);
|
color: var(--font-color-heading);
|
||||||
|
@ -1,21 +1,43 @@
|
|||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import React from 'react'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export default function AssetStats() {
|
export default function AssetStats() {
|
||||||
|
const { locale } = useUserPreferences()
|
||||||
const { asset } = useAsset()
|
const { asset } = useAsset()
|
||||||
|
const [orders, setOrders] = useState(0)
|
||||||
|
const [allocated, setAllocated] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!asset) return
|
||||||
|
|
||||||
|
const { orders, allocated } = asset.stats
|
||||||
|
|
||||||
|
setOrders(orders)
|
||||||
|
setAllocated(allocated)
|
||||||
|
}, [asset])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className={styles.stats}>
|
<footer className={styles.stats}>
|
||||||
{!asset || !asset?.stats || asset?.stats?.orders < 0 ? (
|
{allocated && allocated > 0 ? (
|
||||||
|
<span className={styles.stat}>
|
||||||
|
<span className={styles.number}>
|
||||||
|
{formatPrice(allocated, locale)}
|
||||||
|
</span>
|
||||||
|
veOCEAN
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{!asset || !asset?.stats || orders < 0 ? (
|
||||||
'N/A'
|
'N/A'
|
||||||
) : asset?.stats?.orders === 0 ? (
|
) : orders === 0 ? (
|
||||||
'No sales yet'
|
'No sales yet'
|
||||||
) : (
|
) : (
|
||||||
<>
|
<span className={styles.stat}>
|
||||||
<span className={styles.number}>{asset.stats.orders}</span> sale
|
<span className={styles.number}>{orders}</span> sale
|
||||||
{asset.stats.orders === 1 ? '' : 's'}
|
{orders === 1 ? '' : 's'}
|
||||||
</>
|
</span>
|
||||||
)}
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import PriceUnit from '@shared/Price/PriceUnit'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { StatsTotal } from './_types'
|
import { StatsTotal } from './_types'
|
||||||
|
|
||||||
@ -8,9 +9,12 @@ export default function MarketStatsTotal({
|
|||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<strong>{total.orders}</strong> orders across{' '}
|
<PriceUnit price={total.orders} size="small" /> orders across{' '}
|
||||||
<strong>{total.nfts}</strong> assets with{' '}
|
<PriceUnit price={total.nfts} size="small" /> assets with{' '}
|
||||||
<strong>{total.datatokens}</strong> different datatokens.
|
<PriceUnit price={total.datatokens} size="small" /> different datatokens.{' '}
|
||||||
|
<PriceUnit price={total.veAllocated} symbol="veOCEAN" size="small" />{' '}
|
||||||
|
allocated.{' '}
|
||||||
|
<PriceUnit price={total.veLocked} symbol="OCEAN" size="small" /> locked.
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,6 @@ export interface StatsTotal {
|
|||||||
nfts: number
|
nfts: number
|
||||||
datatokens: number
|
datatokens: number
|
||||||
orders: number
|
orders: number
|
||||||
|
veAllocated: number
|
||||||
|
veLocked: number
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,14 @@ import { useMarketMetadata } from '@context/MarketMetadata'
|
|||||||
import Tooltip from '@shared/atoms/Tooltip'
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
import Markdown from '@shared/Markdown'
|
import Markdown from '@shared/Markdown'
|
||||||
import content from '../../../../content/footer.json'
|
import content from '../../../../content/footer.json'
|
||||||
|
import { getTotalAllocatedAndLocked } from '@utils/veAllocation'
|
||||||
|
|
||||||
const initialTotal: StatsTotal = {
|
const initialTotal: StatsTotal = {
|
||||||
nfts: 0,
|
nfts: 0,
|
||||||
datatokens: 0,
|
datatokens: 0,
|
||||||
orders: 0
|
orders: 0,
|
||||||
|
veAllocated: 0,
|
||||||
|
veLocked: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MarketStats(): ReactElement {
|
export default function MarketStats(): ReactElement {
|
||||||
@ -34,15 +37,15 @@ export default function MarketStats(): ReactElement {
|
|||||||
// Set the main chain ids we want to display stats for
|
// Set the main chain ids we want to display stats for
|
||||||
//
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!networksList || !appConfig || !appConfig?.chainIdsSupported) return
|
if (!networksList || !appConfig || !appConfig?.chainIds) return
|
||||||
|
|
||||||
const mainChainIdsList = filterNetworksByType(
|
const mainChainIdsList = filterNetworksByType(
|
||||||
'mainnet',
|
'mainnet',
|
||||||
appConfig.chainIdsSupported,
|
appConfig.chainIds,
|
||||||
networksList
|
networksList
|
||||||
)
|
)
|
||||||
setMainChainIds(mainChainIdsList)
|
setMainChainIds(mainChainIdsList)
|
||||||
}, [appConfig, appConfig?.chainIdsSupported, networksList])
|
}, [appConfig, appConfig?.chainIds, networksList])
|
||||||
|
|
||||||
//
|
//
|
||||||
// Helper: fetch data from subgraph
|
// Helper: fetch data from subgraph
|
||||||
@ -68,6 +71,12 @@ export default function MarketStats(): ReactElement {
|
|||||||
LoggerInstance.error('Error fetching global stats: ', error.message)
|
LoggerInstance.error('Error fetching global stats: ', error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const veTotals = await getTotalAllocatedAndLocked()
|
||||||
|
total.veAllocated = veTotals.totalAllocated
|
||||||
|
total.veLocked = veTotals.totalLocked
|
||||||
|
setTotal(total)
|
||||||
|
|
||||||
setData(newData)
|
setData(newData)
|
||||||
}, [mainChainIds])
|
}, [mainChainIds])
|
||||||
|
|
||||||
@ -83,9 +92,7 @@ export default function MarketStats(): ReactElement {
|
|||||||
//
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data || !mainChainIds?.length) return
|
if (!data || !mainChainIds?.length) return
|
||||||
const newTotal: StatsTotal = {
|
const newTotal: StatsTotal = total
|
||||||
...initialTotal // always start calculating beginning from initial 0 values
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const chainId of mainChainIds) {
|
for (const chainId of mainChainIds) {
|
||||||
try {
|
try {
|
||||||
|
83
src/components/Home/SectionQueryResult.tsx
Normal file
83
src/components/Home/SectionQueryResult.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
|
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import AssetList from '@shared/AssetList'
|
||||||
|
import { queryMetadata } from '@utils/aquarius'
|
||||||
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
|
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({
|
||||||
|
title,
|
||||||
|
query,
|
||||||
|
action,
|
||||||
|
queryData
|
||||||
|
}: {
|
||||||
|
title: ReactElement | string
|
||||||
|
query: SearchQuery
|
||||||
|
action?: ReactElement
|
||||||
|
queryData?: string[]
|
||||||
|
}): ReactElement {
|
||||||
|
const { chainIds } = useUserPreferences()
|
||||||
|
const [result, setResult] = useState<PagedAssets>()
|
||||||
|
const [loading, setLoading] = useState<boolean>()
|
||||||
|
const isMounted = useIsMounted()
|
||||||
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!query) return
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
if (chainIds.length === 0) {
|
||||||
|
const result: PagedAssets = {
|
||||||
|
results: [],
|
||||||
|
page: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
totalResults: 0,
|
||||||
|
aggregations: undefined
|
||||||
|
}
|
||||||
|
setResult(result)
|
||||||
|
setLoading(false)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
const result = await queryMetadata(query, newCancelToken())
|
||||||
|
if (!isMounted()) return
|
||||||
|
if (queryData && result?.totalResults > 0) {
|
||||||
|
const sortedAssets = sortElements(result.results, queryData)
|
||||||
|
const overflow = sortedAssets.length - 6
|
||||||
|
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
||||||
|
result.results = sortedAssets
|
||||||
|
}
|
||||||
|
setResult(result)
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
}, [chainIds.length, isMounted, newCancelToken, query, queryData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.section}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
|
||||||
|
<AssetList
|
||||||
|
assets={result?.results}
|
||||||
|
showPagination={false}
|
||||||
|
isLoading={loading || !query}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{action && action}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
@ -1,103 +1,24 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import AssetList from '@shared/AssetList'
|
|
||||||
import Button from '@shared/atoms/Button'
|
import Button from '@shared/atoms/Button'
|
||||||
import Bookmarks from './Bookmarks'
|
import Bookmarks from './Bookmarks'
|
||||||
import { generateBaseQuery, queryMetadata } from '@utils/aquarius'
|
import { generateBaseQuery } from '@utils/aquarius'
|
||||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
|
||||||
import { SortTermOptions } from '../../@types/aquarius/SearchQuery'
|
import { SortTermOptions } from '../../@types/aquarius/SearchQuery'
|
||||||
import TopSales from './TopSales'
|
import TopSales from './TopSales'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
import SectionQueryResult from './SectionQueryResult'
|
||||||
function sortElements(items: Asset[], sorted: string[]) {
|
|
||||||
items.sort(function (a, b) {
|
|
||||||
return (
|
|
||||||
sorted.indexOf(a.services[0].datatokenAddress.toLowerCase()) -
|
|
||||||
sorted.indexOf(b.services[0].datatokenAddress.toLowerCase())
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
function SectionQueryResult({
|
|
||||||
title,
|
|
||||||
query,
|
|
||||||
action,
|
|
||||||
queryData
|
|
||||||
}: {
|
|
||||||
title: ReactElement | string
|
|
||||||
query: SearchQuery
|
|
||||||
action?: ReactElement
|
|
||||||
queryData?: string[]
|
|
||||||
}) {
|
|
||||||
const { chainIds } = useUserPreferences()
|
|
||||||
const [result, setResult] = useState<PagedAssets>()
|
|
||||||
const [loading, setLoading] = useState<boolean>()
|
|
||||||
const isMounted = useIsMounted()
|
|
||||||
const newCancelToken = useCancelToken()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!query) return
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
if (chainIds.length === 0) {
|
|
||||||
const result: PagedAssets = {
|
|
||||||
results: [],
|
|
||||||
page: 0,
|
|
||||||
totalPages: 0,
|
|
||||||
totalResults: 0,
|
|
||||||
aggregations: undefined
|
|
||||||
}
|
|
||||||
setResult(result)
|
|
||||||
setLoading(false)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
setLoading(true)
|
|
||||||
const result = await queryMetadata(query, newCancelToken())
|
|
||||||
if (!isMounted()) return
|
|
||||||
if (queryData && result?.totalResults > 0) {
|
|
||||||
const sortedAssets = sortElements(result.results, queryData)
|
|
||||||
const overflow = sortedAssets.length - 9
|
|
||||||
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
|
||||||
result.results = sortedAssets
|
|
||||||
}
|
|
||||||
setResult(result)
|
|
||||||
setLoading(false)
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
init()
|
|
||||||
}, [chainIds.length, isMounted, newCancelToken, query, queryData])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.section}>
|
|
||||||
<h3>{title}</h3>
|
|
||||||
|
|
||||||
<AssetList
|
|
||||||
assets={result?.results}
|
|
||||||
showPagination={false}
|
|
||||||
isLoading={loading || !query}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{action && action}
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomePage(): ReactElement {
|
export default function HomePage(): ReactElement {
|
||||||
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 { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const baseParams = {
|
const baseParams = {
|
||||||
chainIds,
|
chainIds,
|
||||||
esPaginationOptions: {
|
esPaginationOptions: {
|
||||||
size: 9
|
size: 6
|
||||||
},
|
},
|
||||||
sortOptions: {
|
sortOptions: {
|
||||||
sortBy: SortTermOptions.Created
|
sortBy: SortTermOptions.Created
|
||||||
@ -108,13 +29,23 @@ export default function HomePage(): ReactElement {
|
|||||||
const baseParamsSales = {
|
const baseParamsSales = {
|
||||||
chainIds,
|
chainIds,
|
||||||
esPaginationOptions: {
|
esPaginationOptions: {
|
||||||
size: 9
|
size: 6
|
||||||
},
|
},
|
||||||
sortOptions: {
|
sortOptions: {
|
||||||
sortBy: SortTermOptions.Stats
|
sortBy: SortTermOptions.Stats
|
||||||
} as SortOptions
|
} as SortOptions
|
||||||
} as BaseQueryParams
|
} as BaseQueryParams
|
||||||
setQueryMostSales(generateBaseQuery(baseParamsSales))
|
setQueryMostSales(generateBaseQuery(baseParamsSales))
|
||||||
|
const baseParamsAllocation = {
|
||||||
|
chainIds,
|
||||||
|
esPaginationOptions: {
|
||||||
|
size: 6
|
||||||
|
},
|
||||||
|
sortOptions: {
|
||||||
|
sortBy: SortTermOptions.Allocated
|
||||||
|
} as SortOptions
|
||||||
|
} as BaseQueryParams
|
||||||
|
setQueryMostAllocation(generateBaseQuery(baseParamsAllocation))
|
||||||
}, [chainIds])
|
}, [chainIds])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -124,8 +55,15 @@ export default function HomePage(): ReactElement {
|
|||||||
<Bookmarks />
|
<Bookmarks />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<SectionQueryResult
|
||||||
|
title="Highest veOCEAN Allocations"
|
||||||
|
query={queryMostAllocation}
|
||||||
|
/>
|
||||||
|
|
||||||
<SectionQueryResult title="Most Sales" query={queryMostSales} />
|
<SectionQueryResult title="Most Sales" query={queryMostSales} />
|
||||||
|
|
||||||
|
<TopSales title="Publishers With Most Sales" />
|
||||||
|
|
||||||
<SectionQueryResult
|
<SectionQueryResult
|
||||||
title="Recently Published"
|
title="Recently Published"
|
||||||
query={queryLatest}
|
query={queryLatest}
|
||||||
@ -135,8 +73,6 @@ export default function HomePage(): ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TopSales title="Publishers With Most Sales" />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ export default function AvailableNetworks(): ReactElement {
|
|||||||
{ title: 'Main', data: networksMain },
|
{ title: 'Main', data: networksMain },
|
||||||
{ title: 'Test', data: networksTest }
|
{ title: 'Test', data: networksTest }
|
||||||
]
|
]
|
||||||
|
|
||||||
const networkList = (networks: number[]) =>
|
const networkList = (networks: number[]) =>
|
||||||
networks.map((chainId) => (
|
networks.map((chainId) => (
|
||||||
<li key={chainId}>
|
<li key={chainId}>
|
||||||
|
Loading…
Reference in New Issue
Block a user