mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Various veOCEAN features (#1743)
* change top message, fixes, allocations * lock ocean tooltip * asset stats tweaks * front page allocations refactor * wording, run empty table messages through markdown * footer stats tweaks * profile tinkering * fix allocations in table * Tweak lock action logic * fix allocation 0 * sort by allocation * search ordering fixes Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
2fb4ee01c4
commit
67621f5639
@ -14,7 +14,7 @@
|
||||
"link": "/profile"
|
||||
}
|
||||
],
|
||||
"announcement": "Explore [OceanONDA V4](https://blog.oceanprotocol.com/how-to-publish-a-data-nft-f58ad2a622a9).",
|
||||
"announcement": "[Lock your OCEAN](https://df.oceandao.org/) to get veOCEAN, earn rewards and curate data.",
|
||||
"warning": {
|
||||
"ctd": "Compute-to-Data is still in a testing phase, please use it only on test networks."
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ export enum SortTermOptions {
|
||||
Created = 'nft.created',
|
||||
Relevance = '_score',
|
||||
Orders = 'stats.orders',
|
||||
Allocated = 'stats.allocated'
|
||||
Allocated = 'stats.allocated',
|
||||
Price = 'stats.price.value'
|
||||
}
|
||||
|
||||
// Note: could not figure out how to get `enum` to be ambiant
|
||||
|
@ -215,6 +215,28 @@ export async function getAssetsFromDtList(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAssetsFromNftList(
|
||||
nftList: string[],
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<Asset[]> {
|
||||
try {
|
||||
if (!(nftList.length > 0)) return
|
||||
|
||||
const baseParams = {
|
||||
chainIds,
|
||||
filters: [getFilterTerm('nftAddress', nftList)],
|
||||
ignorePurgatory: true
|
||||
} as BaseQueryParams
|
||||
const query = generateBaseQuery(baseParams)
|
||||
|
||||
const queryResult = await queryMetadata(query, cancelToken)
|
||||
return queryResult?.results
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export async function retrieveDDOListByDIDs(
|
||||
didList: string[],
|
||||
chainIds: number[],
|
||||
|
@ -1,7 +1,19 @@
|
||||
import { AllLocked } from 'src/@types/subgraph/AllLocked'
|
||||
import { OwnAllocations } from 'src/@types/subgraph/OwnAllocations'
|
||||
import { NftOwnAllocation } from 'src/@types/subgraph/NftOwnAllocation'
|
||||
import { OceanLocked } from 'src/@types/subgraph/OceanLocked'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { fetchData, getQueryContext } from './subgraph'
|
||||
import axios from 'axios'
|
||||
import networkdata from '../../content/networks-metadata.json'
|
||||
import {
|
||||
getNetworkDataById,
|
||||
getNetworkType,
|
||||
NetworkType
|
||||
} from '@hooks/useNetworkMetadata'
|
||||
import { getAssetsFromNftList } from './aquarius'
|
||||
import { chainIdsSupported } from 'app.config'
|
||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
||||
|
||||
const AllLocked = gql`
|
||||
query AllLocked {
|
||||
@ -11,10 +23,82 @@ const AllLocked = gql`
|
||||
}
|
||||
`
|
||||
|
||||
interface TotalVe {
|
||||
const OwnAllocations = gql`
|
||||
query OwnAllocations($address: String) {
|
||||
veAllocations(where: { allocationUser: $address }) {
|
||||
id
|
||||
nftAddress
|
||||
allocated
|
||||
}
|
||||
}
|
||||
`
|
||||
const NftOwnAllocation = gql`
|
||||
query NftOwnAllocation($address: String, $nftAddress: String) {
|
||||
veAllocations(
|
||||
where: { allocationUser: $address, nftAddress: $nftAddress }
|
||||
) {
|
||||
allocated
|
||||
}
|
||||
}
|
||||
`
|
||||
const OceanLocked = gql`
|
||||
query OceanLocked($address: String) {
|
||||
veOCEAN(id: $address) {
|
||||
id
|
||||
lockedAmount
|
||||
unlockTime
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export interface TotalVe {
|
||||
totalLocked: number
|
||||
totalAllocated: number
|
||||
}
|
||||
export interface Allocation {
|
||||
nftAddress: string
|
||||
allocation: number
|
||||
}
|
||||
|
||||
export interface AssetWithOwnAllocation {
|
||||
asset: AssetExtended
|
||||
allocation: string
|
||||
}
|
||||
|
||||
export function getVeChainNetworkId(assetNetworkId: number): number {
|
||||
const networkData = getNetworkDataById(networkdata, assetNetworkId)
|
||||
const networkType = getNetworkType(networkData)
|
||||
if (networkType === NetworkType.Mainnet) return 1
|
||||
else return 5
|
||||
}
|
||||
|
||||
export function getVeChainNetworkIds(assetNetworkIds: number[]): number[] {
|
||||
const veNetworkIds: number[] = []
|
||||
assetNetworkIds.forEach((x) => {
|
||||
const id = getVeChainNetworkId(x)
|
||||
veNetworkIds.indexOf(id) === -1 && veNetworkIds.push(id)
|
||||
})
|
||||
return veNetworkIds
|
||||
}
|
||||
export async function getNftOwnAllocation(
|
||||
userAddress: string,
|
||||
nftAddress: string,
|
||||
networkId: number
|
||||
): Promise<number> {
|
||||
const veNetworkId = getVeChainNetworkId(networkId)
|
||||
const queryContext = getQueryContext(veNetworkId)
|
||||
const fetchedAllocation: OperationResult<NftOwnAllocation, any> =
|
||||
await fetchData(
|
||||
NftOwnAllocation,
|
||||
{
|
||||
address: userAddress.toLowerCase(),
|
||||
nftAddress: nftAddress.toLowerCase()
|
||||
},
|
||||
queryContext
|
||||
)
|
||||
|
||||
return fetchedAllocation.data?.veAllocations[0]?.allocated
|
||||
}
|
||||
|
||||
export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
||||
const totals = {
|
||||
@ -26,7 +110,7 @@ export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
||||
|
||||
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: string }) =>
|
||||
previousValue + Number(currentValue.ve_allocated),
|
||||
0
|
||||
)
|
||||
@ -43,3 +127,66 @@ export async function getTotalAllocatedAndLocked(): Promise<TotalVe> {
|
||||
)
|
||||
return totals
|
||||
}
|
||||
|
||||
export async function getLocked(
|
||||
userAddress: string,
|
||||
networkIds: number[]
|
||||
): Promise<number> {
|
||||
let total = 0
|
||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||
const queryContext = getQueryContext(veNetworkIds[i])
|
||||
const fetchedLocked: OperationResult<OceanLocked, any> = await fetchData(
|
||||
OceanLocked,
|
||||
{ address: userAddress.toLowerCase() },
|
||||
queryContext
|
||||
)
|
||||
|
||||
fetchedLocked.data?.veOCEAN?.lockedAmount &&
|
||||
(total += Number(fetchedLocked.data?.veOCEAN?.lockedAmount))
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
export async function getOwnAllocations(
|
||||
networkIds: number[],
|
||||
userAddress: string
|
||||
): Promise<Allocation[]> {
|
||||
const allocations: Allocation[] = []
|
||||
const veNetworkIds = getVeChainNetworkIds(networkIds)
|
||||
for (let i = 0; i < veNetworkIds.length; i++) {
|
||||
const queryContext = getQueryContext(veNetworkIds[i])
|
||||
const fetchedAllocations: OperationResult<OwnAllocations, any> =
|
||||
await fetchData(
|
||||
OwnAllocations,
|
||||
{ address: userAddress.toLowerCase() },
|
||||
queryContext
|
||||
)
|
||||
|
||||
fetchedAllocations.data?.veAllocations.forEach(
|
||||
(x) =>
|
||||
x.allocated !== '0' &&
|
||||
allocations.push({
|
||||
nftAddress: x.nftAddress,
|
||||
allocation: x.allocated / 100
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return allocations
|
||||
}
|
||||
|
||||
export async function getOwnAssetsWithAllocation(
|
||||
networkIds: number[],
|
||||
userAddress: string
|
||||
): Promise<Asset[]> {
|
||||
const allocations = await getOwnAllocations(networkIds, userAddress)
|
||||
const assets = await getAssetsFromNftList(
|
||||
allocations.map((x) => x.nftAddress),
|
||||
chainIdsSupported,
|
||||
null
|
||||
)
|
||||
|
||||
return assets
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export default function Conversion({
|
||||
const { prices } = usePrices()
|
||||
const { currency, locale } = useUserPreferences()
|
||||
|
||||
const [priceConverted, setPriceConverted] = useState('0.00')
|
||||
const [priceConverted, setPriceConverted] = useState('0')
|
||||
// detect fiat, only have those kick in full @coingecko/cryptoformat formatting
|
||||
const isFiat = !isCrypto(currency)
|
||||
// isCrypto() only checks for BTC & ETH & unknown but seems sufficient for now
|
||||
@ -28,7 +28,7 @@ export default function Conversion({
|
||||
const priceTokenId = getCoingeckoTokenId(symbol)
|
||||
|
||||
useEffect(() => {
|
||||
if (!prices || !price || !priceTokenId || !prices[priceTokenId]) {
|
||||
if (!prices || !priceTokenId || !prices[priceTokenId]) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export default function Conversion({
|
||||
isFiat ? currency : '',
|
||||
locale,
|
||||
false,
|
||||
{ decimalPlaces: 2 }
|
||||
{ decimalPlaces: price === 0 ? 0 : 2 }
|
||||
)
|
||||
// It's a hack! Wrap everything in the string which is not a number or `.` or `,`
|
||||
// with a span for consistent visual symbol formatting.
|
||||
|
@ -7,7 +7,7 @@
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.price > div:firt-child {
|
||||
.price > div:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,14 @@
|
||||
import { markdownToHtml } from '@utils/markdown'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Empty.module.css'
|
||||
|
||||
export default function Empty({ message }: { message?: string }): ReactElement {
|
||||
return <div className={styles.empty}>{message || 'No results found'}</div>
|
||||
return (
|
||||
<div
|
||||
className={styles.empty}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: markdownToHtml(message) || 'No results found'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,44 +1,61 @@
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import { getNftOwnAllocation } from '@utils/veAllocation'
|
||||
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)
|
||||
const { accountId } = useWeb3()
|
||||
|
||||
const [ownAllocation, setOwnAllocation] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!asset) return
|
||||
if (!asset || !accountId) return
|
||||
|
||||
const { orders, allocated } = asset.stats
|
||||
|
||||
setOrders(orders)
|
||||
setAllocated(allocated)
|
||||
}, [asset])
|
||||
async function init() {
|
||||
const allocation = await getNftOwnAllocation(
|
||||
accountId,
|
||||
asset.nftAddress,
|
||||
asset.chainId
|
||||
)
|
||||
setOwnAllocation(allocation / 100)
|
||||
}
|
||||
init()
|
||||
}, [accountId, asset])
|
||||
|
||||
return (
|
||||
<footer className={styles.stats}>
|
||||
{allocated && allocated > 0 ? (
|
||||
{asset?.stats?.allocated && asset?.stats?.allocated > 0 ? (
|
||||
<span className={styles.stat}>
|
||||
<span className={styles.number}>
|
||||
{formatPrice(allocated, locale)}
|
||||
{formatPrice(asset.stats.allocated, locale)}
|
||||
</span>
|
||||
veOCEAN
|
||||
</span>
|
||||
) : null}
|
||||
{!asset || !asset?.stats || orders < 0 ? (
|
||||
{!asset?.stats || asset?.stats?.orders < 0 ? (
|
||||
'N/A'
|
||||
) : orders === 0 ? (
|
||||
) : asset?.stats?.orders === 0 ? (
|
||||
'No sales yet'
|
||||
) : (
|
||||
<span className={styles.stat}>
|
||||
<span className={styles.number}>{orders}</span> sale
|
||||
{orders === 1 ? '' : 's'}
|
||||
<span className={styles.number}>{asset.stats.orders}</span> sale
|
||||
{asset.stats.orders === 1 ? '' : 's'}
|
||||
</span>
|
||||
)}
|
||||
{ownAllocation && ownAllocation > 0 ? (
|
||||
<span className={styles.stat}>
|
||||
<span className={styles.number}>{ownAllocation}</span>% allocated
|
||||
<Tooltip
|
||||
content={`You have ${ownAllocation}% of your total veOCEAN allocated to this asset.`}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
@ -11,10 +11,7 @@ export default function MarketStatsTotal({
|
||||
<>
|
||||
<PriceUnit price={total.orders} size="small" /> orders across{' '}
|
||||
<PriceUnit price={total.nfts} size="small" /> assets with{' '}
|
||||
<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.
|
||||
<PriceUnit price={total.datatokens} size="small" /> different datatokens.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import Tooltip from '@shared/atoms/Tooltip'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import content from '../../../../content/footer.json'
|
||||
import { getTotalAllocatedAndLocked } from '@utils/veAllocation'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
|
||||
const initialTotal: StatsTotal = {
|
||||
nfts: 0,
|
||||
@ -113,7 +114,7 @@ export default function MarketStats(): ReactElement {
|
||||
|
||||
return (
|
||||
<div className={styles.stats}>
|
||||
<>
|
||||
<div>
|
||||
<MarketStatsTotal total={total} />{' '}
|
||||
<Tooltip
|
||||
className={styles.info}
|
||||
@ -121,7 +122,12 @@ export default function MarketStats(): ReactElement {
|
||||
<Markdown className={styles.note} text={content.stats.note} />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
<div>
|
||||
<PriceUnit price={total.veLocked} symbol="OCEAN" size="small" /> locked.{' '}
|
||||
<PriceUnit price={total.veAllocated} symbol="veOCEAN" size="small" />{' '}
|
||||
allocated.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
47
src/components/Home/Allocations/AssetListTable.tsx
Normal file
47
src/components/Home/Allocations/AssetListTable.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
import Table, { TableOceanColumn } from '@shared/atoms/Table'
|
||||
import AssetTitle from '@shared/AssetList/AssetListTitle'
|
||||
import { AssetWithOwnAllocation } from '@utils/veAllocation'
|
||||
|
||||
const columns: TableOceanColumn<AssetWithOwnAllocation>[] = [
|
||||
{
|
||||
name: 'Dataset',
|
||||
selector: (row) => {
|
||||
const { metadata } = row.asset
|
||||
return <AssetTitle title={metadata.name} asset={row.asset} />
|
||||
},
|
||||
maxWidth: '45rem',
|
||||
grow: 1
|
||||
},
|
||||
{
|
||||
name: 'Datatoken Symbol',
|
||||
selector: (row) => row.asset.datatokens[0].symbol,
|
||||
maxWidth: '10rem'
|
||||
},
|
||||
{
|
||||
name: 'Allocated',
|
||||
selector: (row) => row.allocation,
|
||||
right: true,
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
|
||||
export default function AssetListTable({
|
||||
data,
|
||||
isLoading
|
||||
}: {
|
||||
data: AssetWithOwnAllocation[]
|
||||
isLoading: boolean
|
||||
}) {
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={data}
|
||||
defaultSortFieldId={3}
|
||||
sortAsc={false}
|
||||
isLoading={isLoading}
|
||||
emptyMessage={`Your allocated assets will appear here. [Lock your OCEAN](https://df.oceandao.org) to get started.`}
|
||||
noTableHead
|
||||
/>
|
||||
)
|
||||
}
|
3
src/components/Home/Allocations/index.module.css
Normal file
3
src/components/Home/Allocations/index.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.section {
|
||||
composes: section from '../index.module.css';
|
||||
}
|
93
src/components/Home/Allocations/index.tsx
Normal file
93
src/components/Home/Allocations/index.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { AssetWithOwnAllocation, getOwnAllocations } from '@utils/veAllocation'
|
||||
import styles from './index.module.css'
|
||||
import {
|
||||
getFilterTerm,
|
||||
generateBaseQuery,
|
||||
queryMetadata
|
||||
} from '@utils/aquarius'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import AssetListTable from './AssetListTable'
|
||||
|
||||
export default function Allocations(): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { chainIds } = useUserPreferences()
|
||||
const isMounted = useIsMounted()
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [data, setData] = useState<AssetWithOwnAllocation[]>()
|
||||
const [hasAllocations, setHasAllocations] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId) return
|
||||
|
||||
async function checkAllocations() {
|
||||
try {
|
||||
const allocations = await getOwnAllocations(chainIds, accountId)
|
||||
setHasAllocations(allocations && allocations.length > 0)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
}
|
||||
}
|
||||
checkAllocations()
|
||||
}, [accountId, chainIds])
|
||||
|
||||
useEffect(() => {
|
||||
async function getAllocationAssets() {
|
||||
if (!hasAllocations) return
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
const allocations = await getOwnAllocations(chainIds, accountId)
|
||||
setHasAllocations(allocations && allocations.length > 0)
|
||||
|
||||
const baseParams = {
|
||||
chainIds,
|
||||
filters: [
|
||||
getFilterTerm(
|
||||
'nftAddress',
|
||||
allocations.map((x) => x.nftAddress)
|
||||
)
|
||||
],
|
||||
ignorePurgatory: true
|
||||
} as BaseQueryParams
|
||||
|
||||
const query = generateBaseQuery(baseParams)
|
||||
|
||||
const result = await queryMetadata(query, newCancelToken())
|
||||
|
||||
const assetsWithAllocation: AssetWithOwnAllocation[] = []
|
||||
|
||||
result?.results.forEach((asset) => {
|
||||
const allocation = allocations.find(
|
||||
(x) => x.nftAddress.toLowerCase() === asset.nftAddress.toLowerCase()
|
||||
)
|
||||
assetsWithAllocation.push({
|
||||
asset,
|
||||
allocation: `${allocation.allocation} %`
|
||||
})
|
||||
})
|
||||
|
||||
if (!isMounted()) return
|
||||
setData(assetsWithAllocation)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
}
|
||||
}
|
||||
getAllocationAssets()
|
||||
}, [hasAllocations, accountId, chainIds, isMounted, newCancelToken])
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<h3>Your Allocated Assets</h3>
|
||||
<AssetListTable data={data} isLoading={loading} />
|
||||
</section>
|
||||
)
|
||||
}
|
@ -8,12 +8,14 @@ import TopSales from './TopSales'
|
||||
import TopTags from './TopTags'
|
||||
import SectionQueryResult from './SectionQueryResult'
|
||||
import styles from './index.module.css'
|
||||
import Allocations from './Allocations'
|
||||
|
||||
export default function HomePage(): ReactElement {
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
const [queryLatest, setQueryLatest] = useState<SearchQuery>()
|
||||
const [queryMostSales, setQueryMostSales] = useState<SearchQuery>()
|
||||
const [queryMostAllocation, setQueryMostAllocation] = useState<SearchQuery>()
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
useEffect(() => {
|
||||
const baseParams = {
|
||||
@ -52,10 +54,12 @@ export default function HomePage(): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<section className={styles.section}>
|
||||
<h3>Bookmarks</h3>
|
||||
<h3>Your Bookmarks</h3>
|
||||
<Bookmarks />
|
||||
</section>
|
||||
|
||||
<Allocations />
|
||||
|
||||
<SectionQueryResult
|
||||
title="Highest veOCEAN Allocations"
|
||||
query={queryMostAllocation}
|
||||
|
@ -9,7 +9,7 @@
|
||||
.number {
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import Tooltip from '@shared/atoms/Tooltip'
|
||||
import styles from './NumberUnit.module.css'
|
||||
|
||||
interface NumberUnitProps {
|
||||
label: string
|
||||
label: string | ReactElement
|
||||
value: number | string | Element | ReactElement
|
||||
small?: boolean
|
||||
icon?: Element | ReactElement
|
||||
|
@ -4,3 +4,24 @@
|
||||
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.stats [class^='PriceUnit_symbol'] {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--font-weight-base);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.stats .link,
|
||||
.stats .link:hover {
|
||||
color: var(--color-primary);
|
||||
font-size: var(--font-size-small);
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.stats [class^='PriceUnit_price'] {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--font-weight-base);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
@ -6,16 +6,31 @@ import NumberUnit from './NumberUnit'
|
||||
import styles from './Stats.module.css'
|
||||
import { useProfile } from '@context/Profile'
|
||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||
import { getLocked } from '@utils/veAllocation'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
export default function Stats({
|
||||
accountId
|
||||
}: {
|
||||
accountId: string
|
||||
}): ReactElement {
|
||||
const web3 = useWeb3()
|
||||
const { chainIds } = useUserPreferences()
|
||||
const { assets, assetsTotal, sales } = useProfile()
|
||||
|
||||
const [totalSales, setTotalSales] = useState(0)
|
||||
const [lockedOcean, setLockedOcean] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
async function getLockedOcean() {
|
||||
if (!accountId) return
|
||||
const locked = await getLocked(accountId, chainIds)
|
||||
setLockedOcean(locked)
|
||||
}
|
||||
getLockedOcean()
|
||||
}, [accountId, chainIds])
|
||||
|
||||
useEffect(() => {
|
||||
if (!assets || !accountId || !chainIds) return
|
||||
@ -59,6 +74,30 @@ export default function Stats({
|
||||
value={sales < 0 ? 0 : sales}
|
||||
/>
|
||||
<NumberUnit label="Published" value={assetsTotal} />
|
||||
<NumberUnit
|
||||
label={
|
||||
lockedOcean === 0 && accountId === web3.accountId ? (
|
||||
<Button
|
||||
className={styles.link}
|
||||
style="text"
|
||||
href="https://df.oceandao.org"
|
||||
>
|
||||
Lock OCEAN
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<PriceUnit price={lockedOcean} symbol="OCEAN" /> locked
|
||||
</>
|
||||
)
|
||||
}
|
||||
value={
|
||||
<Conversion
|
||||
price={lockedOcean > 0 ? lockedOcean : 0}
|
||||
symbol="OCEAN"
|
||||
hideApproximateSymbol
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ div.filterList {
|
||||
white-space: normal;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
gap: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.filter {
|
||||
.filter,
|
||||
.filterList > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
.row {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
|
@ -8,15 +8,9 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 55rem) {
|
||||
.sortList {
|
||||
align-self: flex-end;
|
||||
overflow-y: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.sortLabel {
|
||||
composes: label from '@shared/FormInput/Label.module.css';
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 1.5);
|
||||
@ -26,7 +20,7 @@
|
||||
}
|
||||
|
||||
.sorted {
|
||||
display: flex;
|
||||
display: inline-block;
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
color: var(--color-secondary);
|
||||
@ -35,6 +29,7 @@
|
||||
font-weight: var(--font-weight-base);
|
||||
background: var(--background-content);
|
||||
box-shadow: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sorted:hover,
|
||||
@ -47,7 +42,6 @@
|
||||
}
|
||||
|
||||
.direction {
|
||||
display: flex;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
|
@ -13,7 +13,10 @@ const cx = classNames.bind(styles)
|
||||
|
||||
const sortItems = [
|
||||
{ display: 'Relevance', value: SortTermOptions.Relevance },
|
||||
{ display: 'Published', value: SortTermOptions.Created }
|
||||
{ display: 'Published', value: SortTermOptions.Created },
|
||||
{ display: 'Sales', value: SortTermOptions.Orders },
|
||||
{ display: 'Total allocation', value: SortTermOptions.Allocated },
|
||||
{ display: 'Price', value: SortTermOptions.Price }
|
||||
]
|
||||
|
||||
export default function Sort({
|
||||
|
Loading…
Reference in New Issue
Block a user