From 34e424b13fcb9ce69f1f1114c865027f6863d905 Mon Sep 17 00:00:00 2001 From: claudiaHash <49017601+claudiaHash@users.noreply.github.com> Date: Tue, 7 Dec 2021 11:43:33 +0200 Subject: [PATCH] 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 --- .../molecules/AccountTeaser.module.css | 46 +++++++++++ src/components/molecules/AccountTeaser.tsx | 69 ++++++++++++++++ src/components/organisms/AccountList.tsx | 55 +++++++++++++ src/components/pages/Home.tsx | 47 ++++++++++- .../pages/Profile/Header/Stats.module.css | 6 ++ src/components/pages/Profile/Header/Stats.tsx | 1 + src/providers/Profile.tsx | 4 +- src/utils/subgraph.ts | 80 +++++++++++++++---- 8 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 src/components/molecules/AccountTeaser.module.css create mode 100644 src/components/molecules/AccountTeaser.tsx create mode 100644 src/components/organisms/AccountList.tsx diff --git a/src/components/molecules/AccountTeaser.module.css b/src/components/molecules/AccountTeaser.module.css new file mode 100644 index 000000000..47af5f397 --- /dev/null +++ b/src/components/molecules/AccountTeaser.module.css @@ -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); +} diff --git a/src/components/molecules/AccountTeaser.tsx b/src/components/molecules/AccountTeaser.tsx new file mode 100644 index 000000000..46700bcb1 --- /dev/null +++ b/src/components/molecules/AccountTeaser.tsx @@ -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 = ({ account, place }) => { + const [profile, setProfile] = useState() + 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 ( +
+ +
+ {place && {place}} + {profile?.image ? ( + + ) : ( + + )} +
+ +

+ {profile?.name ? profile?.name : accountTruncate(account)} +

+
+

+ {`${sales} ${sales === 1 ? 'sale' : 'sales'}`} +

+
+
+ +
+ ) +} + +export default AccountTeaser diff --git a/src/components/organisms/AccountList.tsx b/src/components/organisms/AccountList.tsx new file mode 100644 index 000000000..01a2349e0 --- /dev/null +++ b/src/components/organisms/AccountList.tsx @@ -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 ( +
+ +
+ ) +} + +declare type AccountListProps = { + accounts: string[] + isLoading: boolean + className?: string +} + +const AccountList: React.FC = ({ + accounts, + isLoading, + className +}) => { + const { chainIds } = useUserPreferences() + + const styleClasses = cx({ + assetList: true, + [className]: className + }) + + return accounts && (isLoading === undefined || isLoading === false) ? ( + <> +
+ {accounts.length > 0 ? ( + accounts.map((account, index) => ( + + )) + ) : chainIds.length === 0 ? ( +
No network selected.
+ ) : ( +
No results found.
+ )} +
+ + ) : ( + + ) +} + +export default AccountList diff --git a/src/components/pages/Home.tsx b/src/components/pages/Home.tsx index 9fac2b81a..3ce623151 100644 --- a/src/components/pages/Home.tsx +++ b/src/components/pages/Home.tsx @@ -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([]) + const [loading, setLoading] = useState() + + 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 ( +
+

{title}

+ + {action && action} +
+ ) +} + function SectionQueryResult({ title, query, @@ -154,6 +198,7 @@ export default function HomePage(): ReactElement { } /> )} + ) diff --git a/src/components/pages/Profile/Header/Stats.module.css b/src/components/pages/Profile/Header/Stats.module.css index 67a6a0c7c..ff1f2694c 100644 --- a/src/components/pages/Profile/Header/Stats.module.css +++ b/src/components/pages/Profile/Header/Stats.module.css @@ -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; +} diff --git a/src/components/pages/Profile/Header/Stats.tsx b/src/components/pages/Profile/Header/Stats.tsx index 7f22b9b27..05ecf606a 100644 --- a/src/components/pages/Profile/Header/Stats.tsx +++ b/src/components/pages/Profile/Header/Stats.tsx @@ -92,6 +92,7 @@ export default function Stats({ } /> + } diff --git a/src/providers/Profile.tsx b/src/providers/Profile.tsx index 026c63d50..1a5f63628 100644 --- a/src/providers/Profile.tsx +++ b/src/providers/Profile.tsx @@ -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 ) }, diff --git a/src/utils/subgraph.ts b/src/utils/subgraph.ts index 4658b0076..4a8d35b29 100644 --- a/src/utils/subgraph.ts +++ b/src/utils/subgraph.ts @@ -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 { + const data: string[] = [] + const publisherSales: UserSales[] = [] + + for (const chain of chainIds) { + const queryContext = getQueryContext(Number(chain)) + const fetchedUsers: OperationResult = 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 +}