diff --git a/package-lock.json b/package-lock.json index 14589e725..11fcc6297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", "@oceanprotocol/art": "^3.2.0", - "@oceanprotocol/lib": "^2.1.1", + "@oceanprotocol/lib": "^2.2.1", "@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/use-dark-mode": "^2.4.3", "@tippyjs/react": "^4.2.6", @@ -4539,9 +4539,9 @@ "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "node_modules/@oceanprotocol/lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz", - "integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz", + "integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==", "dependencies": { "@oceanprotocol/contracts": "^1.1.7", "bignumber.js": "^9.1.0", @@ -44803,9 +44803,9 @@ "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "@oceanprotocol/lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz", - "integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz", + "integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==", "requires": { "@oceanprotocol/contracts": "^1.1.7", "bignumber.js": "^9.1.0", diff --git a/package.json b/package.json index 60ad602b2..d9dc37b96 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", "@oceanprotocol/art": "^3.2.0", - "@oceanprotocol/lib": "^2.1.1", + "@oceanprotocol/lib": "^2.2.1", "@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/use-dark-mode": "^2.4.3", "@tippyjs/react": "^4.2.6", diff --git a/src/@types/aquarius/SearchQuery.ts b/src/@types/aquarius/SearchQuery.ts index c4e0dea5c..39ec650ee 100644 --- a/src/@types/aquarius/SearchQuery.ts +++ b/src/@types/aquarius/SearchQuery.ts @@ -6,7 +6,8 @@ export enum SortDirectionOptions { export enum SortTermOptions { Created = 'nft.created', Relevance = '_score', - Stats = 'stats.orders' + Stats = 'stats.orders', + Allocated = 'stats.allocated' } // Note: could not figure out how to get `enum` to be ambiant diff --git a/src/@utils/aquarius.ts b/src/@utils/aquarius.ts index 1491144bc..50f17c0da 100644 --- a/src/@utils/aquarius.ts +++ b/src/@utils/aquarius.ts @@ -53,7 +53,9 @@ export function generateBaseQuery( ...baseQueryParams.nestedQuery, filter: [ ...(baseQueryParams.filters || []), - getFilterTerm('chainId', baseQueryParams.chainIds), + baseQueryParams.chainIds + ? getFilterTerm('chainId', baseQueryParams.chainIds) + : [], getFilterTerm('_index', 'aquarius'), ...(baseQueryParams.ignorePurgatory ? [] diff --git a/src/@utils/veAllocation.ts b/src/@utils/veAllocation.ts new file mode 100644 index 000000000..351d5e25f --- /dev/null +++ b/src/@utils/veAllocation.ts @@ -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 { + 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 = await fetchData( + AllLocked, + null, + queryContext + ) + totals.totalLocked = fetchedLocked.data?.veOCEANs.reduce( + (previousValue, currentValue) => + previousValue + Number(currentValue.lockedAmount), + 0 + ) + return totals +} diff --git a/src/components/@shared/AssetList/index.tsx b/src/components/@shared/AssetList/index.tsx index 6fa0cc51e..7de0b25b8 100644 --- a/src/components/@shared/AssetList/index.tsx +++ b/src/components/@shared/AssetList/index.tsx @@ -1,4 +1,4 @@ -import AssetTeaser from '@shared/AssetTeaser/AssetTeaser' +import AssetTeaser from '@shared/AssetTeaser' import React, { ReactElement, useEffect, useState } from 'react' import Pagination from '@shared/Pagination' import styles from './index.module.css' diff --git a/src/components/@shared/AssetTeaser/AssetTeaser.tsx b/src/components/@shared/AssetTeaser/AssetTeaser.tsx deleted file mode 100644 index d1dbeea34..000000000 --- a/src/components/@shared/AssetTeaser/AssetTeaser.tsx +++ /dev/null @@ -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 ( - - ) -} diff --git a/src/components/@shared/AssetTeaser/AssetTeaser.module.css b/src/components/@shared/AssetTeaser/index.module.css similarity index 58% rename from src/components/@shared/AssetTeaser/AssetTeaser.module.css rename to src/components/@shared/AssetTeaser/index.module.css index daa73e675..9069d7647 100644 --- a/src/components/@shared/AssetTeaser/AssetTeaser.module.css +++ b/src/components/@shared/AssetTeaser/index.module.css @@ -9,6 +9,8 @@ height: 100%; color: var(--color-secondary); position: relative; + padding-top: calc(var(--spacer) / 2); + padding-bottom: calc(var(--spacer) / 2); /* for sticking footer to bottom */ display: flex; flex-direction: column; @@ -18,8 +20,12 @@ background-color: var(--background-body); } +.detailLine { + margin-bottom: calc(var(--spacer) / 2); +} + .content { - margin-top: calc(var(--spacer) / 2); + margin-top: calc(var(--spacer) / 3); overflow-wrap: break-word; hyphens: auto; /* for sticking footer to bottom */ @@ -27,7 +33,7 @@ } .content p { - margin-bottom: calc(var(--spacer) / 4); + margin-bottom: calc(var(--spacer) / 3); } .title { @@ -37,36 +43,20 @@ overflow-wrap: break-word; } -.publisher { - display: block; -} - -.foot { +.footer { margin-top: calc(var(--spacer) / 4); - display: flex; - justify-content: space-between; - align-items: flex-end; } -.foot p { - margin: 0; -} - -.symbol { - display: block; -} - -.typeDetails { - position: absolute; - top: calc(var(--spacer) / 3); - right: calc(var(--spacer) / 3); - width: auto; +.typeLabel { 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 { - font-size: var(--font-size-mini); - position: absolute; - right: calc(var(--spacer) / 3); - bottom: calc(var(--spacer) / 3); +.typeLabel:first-child { + border-left: none; + padding-left: 0; + margin-left: 0; } diff --git a/src/components/@shared/AssetTeaser/index.tsx b/src/components/@shared/AssetTeaser/index.tsx new file mode 100644 index 000000000..4099b4137 --- /dev/null +++ b/src/components/@shared/AssetTeaser/index.tsx @@ -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 ( + + ) +} diff --git a/src/components/@shared/AssetType/index.tsx b/src/components/@shared/AssetType/index.tsx index 7d3384c02..14be396dc 100644 --- a/src/components/@shared/AssetType/index.tsx +++ b/src/components/@shared/AssetType/index.tsx @@ -7,13 +7,11 @@ import Lock from '@images/lock.svg' export default function AssetType({ type, accessType, - className, - totalSales + className }: { type: string accessType: string className?: string - totalSales?: number }): ReactElement { return (
@@ -28,14 +26,6 @@ export default function AssetType({
{type === 'dataset' ? 'dataset' : 'algorithm'}
- - {(totalSales || totalSales === 0) && ( -
- {totalSales < 0 - ? 'N/A' - : `${totalSales} ${totalSales === 1 ? 'sale' : 'sales'}`} -
- )}
) } diff --git a/src/components/Asset/AssetActions/AssetStats/index.module.css b/src/components/Asset/AssetActions/AssetStats/index.module.css index 0519db863..a8d2ae270 100644 --- a/src/components/Asset/AssetActions/AssetStats/index.module.css +++ b/src/components/Asset/AssetActions/AssetStats/index.module.css @@ -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 { font-weight: var(--font-weight-bold); color: var(--font-color-heading); diff --git a/src/components/Asset/AssetActions/AssetStats/index.tsx b/src/components/Asset/AssetActions/AssetStats/index.tsx index 4ee046802..dd6e79991 100644 --- a/src/components/Asset/AssetActions/AssetStats/index.tsx +++ b/src/components/Asset/AssetActions/AssetStats/index.tsx @@ -1,21 +1,43 @@ 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' export default function AssetStats() { + const { locale } = useUserPreferences() 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 (
- {!asset || !asset?.stats || asset?.stats?.orders < 0 ? ( + {allocated && allocated > 0 ? ( + + + {formatPrice(allocated, locale)} + + veOCEAN + + ) : null} + {!asset || !asset?.stats || orders < 0 ? ( 'N/A' - ) : asset?.stats?.orders === 0 ? ( + ) : orders === 0 ? ( 'No sales yet' ) : ( - <> - {asset.stats.orders} sale - {asset.stats.orders === 1 ? '' : 's'} - + + {orders} sale + {orders === 1 ? '' : 's'} + )}
) diff --git a/src/components/Footer/MarketStats/Total.tsx b/src/components/Footer/MarketStats/Total.tsx index e23a426c1..01b883ab5 100644 --- a/src/components/Footer/MarketStats/Total.tsx +++ b/src/components/Footer/MarketStats/Total.tsx @@ -1,3 +1,4 @@ +import PriceUnit from '@shared/Price/PriceUnit' import React, { ReactElement } from 'react' import { StatsTotal } from './_types' @@ -8,9 +9,12 @@ export default function MarketStatsTotal({ }): ReactElement { return ( <> - {total.orders} orders across{' '} - {total.nfts} assets with{' '} - {total.datatokens} different datatokens. + orders across{' '} + assets with{' '} + different datatokens.{' '} + {' '} + allocated.{' '} + locked. ) } diff --git a/src/components/Footer/MarketStats/_types.ts b/src/components/Footer/MarketStats/_types.ts index 7f3d89939..5d2cc2295 100644 --- a/src/components/Footer/MarketStats/_types.ts +++ b/src/components/Footer/MarketStats/_types.ts @@ -6,4 +6,6 @@ export interface StatsTotal { nfts: number datatokens: number orders: number + veAllocated: number + veLocked: number } diff --git a/src/components/Footer/MarketStats/index.tsx b/src/components/Footer/MarketStats/index.tsx index 665be6cd1..563be5839 100644 --- a/src/components/Footer/MarketStats/index.tsx +++ b/src/components/Footer/MarketStats/index.tsx @@ -14,11 +14,14 @@ import { useMarketMetadata } from '@context/MarketMetadata' import Tooltip from '@shared/atoms/Tooltip' import Markdown from '@shared/Markdown' import content from '../../../../content/footer.json' +import { getTotalAllocatedAndLocked } from '@utils/veAllocation' const initialTotal: StatsTotal = { nfts: 0, datatokens: 0, - orders: 0 + orders: 0, + veAllocated: 0, + veLocked: 0 } 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 // useEffect(() => { - if (!networksList || !appConfig || !appConfig?.chainIdsSupported) return + if (!networksList || !appConfig || !appConfig?.chainIds) return const mainChainIdsList = filterNetworksByType( 'mainnet', - appConfig.chainIdsSupported, + appConfig.chainIds, networksList ) setMainChainIds(mainChainIdsList) - }, [appConfig, appConfig?.chainIdsSupported, networksList]) + }, [appConfig, appConfig?.chainIds, networksList]) // // Helper: fetch data from subgraph @@ -68,6 +71,12 @@ export default function MarketStats(): ReactElement { LoggerInstance.error('Error fetching global stats: ', error.message) } } + + const veTotals = await getTotalAllocatedAndLocked() + total.veAllocated = veTotals.totalAllocated + total.veLocked = veTotals.totalLocked + setTotal(total) + setData(newData) }, [mainChainIds]) @@ -83,9 +92,7 @@ export default function MarketStats(): ReactElement { // useEffect(() => { if (!data || !mainChainIds?.length) return - const newTotal: StatsTotal = { - ...initialTotal // always start calculating beginning from initial 0 values - } + const newTotal: StatsTotal = total for (const chainId of mainChainIds) { try { diff --git a/src/components/Home/SectionQueryResult.tsx b/src/components/Home/SectionQueryResult.tsx new file mode 100644 index 000000000..7efc796f1 --- /dev/null +++ b/src/components/Home/SectionQueryResult.tsx @@ -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() + const [loading, setLoading] = useState() + 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 ( +
+

{title}

+ + + + {action && action} +
+ ) +} diff --git a/src/components/Home/index.tsx b/src/components/Home/index.tsx index e256ee102..ce98751d6 100644 --- a/src/components/Home/index.tsx +++ b/src/components/Home/index.tsx @@ -1,103 +1,24 @@ import React, { ReactElement, useEffect, useState } from 'react' -import AssetList from '@shared/AssetList' import Button from '@shared/atoms/Button' import Bookmarks from './Bookmarks' -import { generateBaseQuery, queryMetadata } from '@utils/aquarius' -import { Asset, LoggerInstance } from '@oceanprotocol/lib' +import { generateBaseQuery } from '@utils/aquarius' import { useUserPreferences } from '@context/UserPreferences' -import { useIsMounted } from '@hooks/useIsMounted' -import { useCancelToken } from '@hooks/useCancelToken' import { SortTermOptions } from '../../@types/aquarius/SearchQuery' import TopSales from './TopSales' import styles from './index.module.css' - -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() - const [loading, setLoading] = useState() - 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 ( -
-

{title}

- - - - {action && action} -
- ) -} +import SectionQueryResult from './SectionQueryResult' export default function HomePage(): ReactElement { const [queryLatest, setQueryLatest] = useState() const [queryMostSales, setQueryMostSales] = useState() + const [queryMostAllocation, setQueryMostAllocation] = useState() const { chainIds } = useUserPreferences() useEffect(() => { const baseParams = { chainIds, esPaginationOptions: { - size: 9 + size: 6 }, sortOptions: { sortBy: SortTermOptions.Created @@ -108,13 +29,23 @@ export default function HomePage(): ReactElement { const baseParamsSales = { chainIds, esPaginationOptions: { - size: 9 + size: 6 }, sortOptions: { sortBy: SortTermOptions.Stats } as SortOptions } as BaseQueryParams setQueryMostSales(generateBaseQuery(baseParamsSales)) + const baseParamsAllocation = { + chainIds, + esPaginationOptions: { + size: 6 + }, + sortOptions: { + sortBy: SortTermOptions.Allocated + } as SortOptions + } as BaseQueryParams + setQueryMostAllocation(generateBaseQuery(baseParamsAllocation)) }, [chainIds]) return ( @@ -124,8 +55,15 @@ export default function HomePage(): ReactElement { + + + + } /> - - ) } diff --git a/src/components/Publish/AvailableNetworks/index.tsx b/src/components/Publish/AvailableNetworks/index.tsx index d6bc5e97b..8e74dad5d 100644 --- a/src/components/Publish/AvailableNetworks/index.tsx +++ b/src/components/Publish/AvailableNetworks/index.tsx @@ -26,7 +26,6 @@ export default function AvailableNetworks(): ReactElement { { title: 'Main', data: networksMain }, { title: 'Test', data: networksTest } ] - const networkList = (networks: number[]) => networks.map((chainId) => (