1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Publishers with most sales, using AccountTeaser component (#864)

* AccountTeaser used in Publishers with most sales, against /account_page

* explorer link fixes

* reduced stats values displayed, used Blockies  component

* sales number taken from subgraph

* logs deleted, function renamed

* sort publishers by sales from more networks

* removed unused pagination

* display & lint fixes

* ranking added, useless css removed

* unused query removed

* types fix

* pr fixes

* loading fix

* removed unused imports

* css fixes, sales correct ordering

* get publishers and all sales, css fix

* err fix

* get publishers and sales optimization

* no duplicates fix

Co-authored-by: ClaudiaHolhos <claudia@oceanprotocol.com>
This commit is contained in:
claudiaHash 2021-12-07 11:43:33 +02:00 committed by GitHub
parent 28a2ba88c1
commit 34e424b13f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 291 additions and 17 deletions

View File

@ -0,0 +1,46 @@
.blockies {
aspect-ratio: 1/1;
width: 13%;
height: 13%;
border-radius: 50%;
margin-left: 0;
margin-right: calc(var(--spacer) / 4);
}
.teaser {
max-width: 40rem;
height: 100%;
}
.link {
composes: box from '../atoms/Box.module.css';
padding: calc(var(--spacer) / 2) !important;
font-size: var(--font-size-mini);
height: 90%;
color: var(--color-secondary);
position: relative;
display: flex;
align-items: center;
}
.link span {
font-size: var(--font-size-large);
margin-right: calc(var(--spacer) / 3);
}
.name {
margin-bottom: 0;
font-size: var(--font-size-base) !important;
padding-top: calc(var(--spacer) / 4);
}
.header {
display: flex;
flex-direction: row;
align-items: center;
}
.sales {
font-size: small;
margin-top: -5px !important;
margin-bottom: clac(var(--spacer) / 2);
}

View File

@ -0,0 +1,69 @@
import { Link } from 'gatsby'
import React, { useEffect, useState } from 'react'
import Dotdotdot from 'react-dotdotdot'
import { Profile } from '../../models/Profile'
import { accountTruncate } from '../../utils/web3'
import get3BoxProfile from '../../utils/profile'
import styles from './AccountTeaser.module.css'
import Blockies from '../atoms/Blockies'
import { useCancelToken } from '../../hooks/useCancelToken'
import { getUserSales } from '../../utils/subgraph'
import { useUserPreferences } from '../../providers/UserPreferences'
declare type AccountTeaserProps = {
account: string
place?: number
}
const AccountTeaser: React.FC<AccountTeaserProps> = ({ account, place }) => {
const [profile, setProfile] = useState<Profile>()
const [sales, setSales] = useState(0)
const newCancelToken = useCancelToken()
const { chainIds } = useUserPreferences()
useEffect(() => {
if (!account) return
async function getProfileData() {
const profile = await get3BoxProfile(account, newCancelToken())
if (!profile) return
setProfile(profile)
}
getProfileData()
}, [account, newCancelToken])
useEffect(() => {
if (!account) return
async function getProfileSales() {
const userSales = await getUserSales(account, chainIds)
setSales(userSales)
}
getProfileSales()
}, [account, chainIds])
return (
<article className={styles.teaser}>
<Link to={`/profile/${account}`} className={styles.link}>
<header className={styles.header}>
{place && <span>{place}</span>}
{profile?.image ? (
<img src={profile.image} className={styles.blockies} />
) : (
<Blockies accountId={account} className={styles.blockies} />
)}
<div>
<Dotdotdot clamp={3}>
<h3 className={styles.name}>
{profile?.name ? profile?.name : accountTruncate(account)}
</h3>
</Dotdotdot>
<p className={styles.sales}>
{`${sales} ${sales === 1 ? 'sale' : 'sales'}`}
</p>
</div>
</header>
</Link>
</article>
)
}
export default AccountTeaser

View File

@ -0,0 +1,55 @@
import AccountTeaser from '../molecules/AccountTeaser'
import React from 'react'
import styles from './AssetList.module.css'
import classNames from 'classnames/bind'
import Loader from '../atoms/Loader'
import { useUserPreferences } from '../../providers/UserPreferences'
const cx = classNames.bind(styles)
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
declare type AccountListProps = {
accounts: string[]
isLoading: boolean
className?: string
}
const AccountList: React.FC<AccountListProps> = ({
accounts,
isLoading,
className
}) => {
const { chainIds } = useUserPreferences()
const styleClasses = cx({
assetList: true,
[className]: className
})
return accounts && (isLoading === undefined || isLoading === false) ? (
<>
<div className={styleClasses}>
{accounts.length > 0 ? (
accounts.map((account, index) => (
<AccountTeaser account={account} key={account} place={index + 1} />
))
) : chainIds.length === 0 ? (
<div className={styles.empty}>No network selected.</div>
) : (
<div className={styles.empty}>No results found.</div>
)}
</div>
</>
) : (
<LoaderArea />
)
}
export default AccountList

View File

@ -8,10 +8,14 @@ import {
queryMetadata
} from '../../utils/aquarius'
import Permission from '../organisms/Permission'
import { getHighestLiquidityDatatokens } from '../../utils/subgraph'
import {
getTopAssetsPublishers,
getHighestLiquidityDatatokens
} from '../../utils/subgraph'
import { DDO, Logger } from '@oceanprotocol/lib'
import { useUserPreferences } from '../../providers/UserPreferences'
import styles from './Home.module.css'
import AccountList from '../organisms/AccountList'
import { useIsMounted } from '../../hooks/useIsMounted'
import { useCancelToken } from '../../hooks/useCancelToken'
import { SearchQuery } from '../../models/aquarius/SearchQuery'
@ -45,6 +49,46 @@ function sortElements(items: DDO[], sorted: string[]) {
return items
}
function PublishersWithMostSales({
title,
action
}: {
title: ReactElement | string
action?: ReactElement
}) {
const { chainIds } = useUserPreferences()
const [result, setResult] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>()
useEffect(() => {
async function init() {
if (chainIds.length === 0) {
const result: string[] = []
setResult(result)
setLoading(false)
} else {
try {
setLoading(true)
const publishers = await getTopAssetsPublishers(chainIds)
setResult(publishers)
setLoading(false)
} catch (error) {
Logger.error(error.message)
}
}
}
init()
}, [chainIds])
return (
<section className={styles.section}>
<h3>{title}</h3>
<AccountList accounts={result} isLoading={loading} />
{action && action}
</section>
)
}
function SectionQueryResult({
title,
query,
@ -154,6 +198,7 @@ export default function HomePage(): ReactElement {
}
/>
)}
<PublishersWithMostSales title="Publishers with most sales" />
</>
</Permission>
)

View File

@ -4,3 +4,9 @@
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
margin-top: var(--spacer);
}
.accountTeaserStats {
grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr)) !important;
margin-top: calc(var(--spacer) / 2);
justify-items: center;
}

View File

@ -92,6 +92,7 @@ export default function Stats({
<Conversion price={publisherLiquidity?.price} hideApproximateSymbol />
}
/>
<NumberUnit
label="Total Liquidity"
value={<Conversion price={`${totalLiquidity}`} hideApproximateSymbol />}

View File

@ -240,9 +240,9 @@ function ProfileProvider({
cancelToken
)
setDownloads(downloads)
setDownloadsTotal(downloads.length)
setDownloadsTotal(downloads?.length)
Logger.log(
`[profile] Fetched ${downloads.length} download orders.`,
`[profile] Fetched ${downloads?.length} download orders.`,
downloads
)
},

View File

@ -2,7 +2,6 @@ import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
import { DDO, Logger } from '@oceanprotocol/lib'
import { getUrqlClientInstance } from '../providers/UrqlProvider'
import { getOceanConfig } from './ocean'
import web3 from 'web3'
import {
AssetsPoolPrice,
AssetsPoolPrice_pools as AssetsPoolPricePool
@ -26,6 +25,10 @@ import {
} from '../@types/apollo/PoolShares'
import { BestPrice } from '../models/BestPrice'
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
import {
UserSalesQuery_users as UserSales,
UserSalesQuery as UsersSalesList
} from '../@types/apollo/UserSalesQuery'
export interface UserLiquidity {
price: string
@ -167,19 +170,6 @@ const HighestLiquidityAssets = gql`
}
`
const TotalAccountOrders = gql`
query TotalAccountOrders($datatokenId_in: [String!]) {
tokenOrders(where: { datatokenId_in: $datatokenId_in }) {
payer {
id
}
datatokenId {
id
}
}
}
`
const UserSharesQuery = gql`
query UserSharesQuery($user: String, $pools: [String!]) {
poolShares(where: { userAddress: $user, poolId_in: $pools }) {
@ -252,6 +242,19 @@ const UserTokenOrders = gql`
}
}
`
const TopSalesQuery = gql`
query TopSalesQuery {
users(
first: 20
orderBy: nrSales
orderDirection: desc
where: { nrSales_not: 0 }
) {
id
nrSales
}
}
`
const UserSalesQuery = gql`
query UserSalesQuery($userSalesId: String) {
users(where: { id: $userSalesId }) {
@ -721,3 +724,52 @@ export async function getUserSales(
Logger.log(error.message)
}
}
export async function getTopAssetsPublishers(
chainIds: number[]
): Promise<string[]> {
const data: string[] = []
const publisherSales: UserSales[] = []
for (const chain of chainIds) {
const queryContext = getQueryContext(Number(chain))
const fetchedUsers: OperationResult<UsersSalesList> = await fetchData(
TopSalesQuery,
null,
queryContext
)
for (let i = 0; i < fetchedUsers.data.users.length; i++) {
const publishersIndex = publisherSales.findIndex(
(user) => fetchedUsers.data.users[i].id === user.id
)
if (publishersIndex === -1) {
const publisher: UserSales = {
id: fetchedUsers.data.users[i].id,
nrSales: fetchedUsers.data.users[i].nrSales,
__typename: 'User'
}
publisherSales.push(publisher)
} else {
const publisher: UserSales = {
id: fetchedUsers.data.users[i].id,
nrSales:
fetchedUsers.data.users[i].nrSales +
publisherSales[publishersIndex].nrSales,
__typename: 'User'
}
publisherSales[publishersIndex] = publisher
}
}
}
publisherSales.sort((a, b) => b.nrSales - a.nrSales)
for (let i = 0; i < publisherSales.length; i++) {
if (data.length < 9) {
data.push(publisherSales[i].id)
} else {
return data
}
}
return data
}