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

add account teaser, various refactors (#957)

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>
This commit is contained in:
mihaisc 2021-12-10 01:50:00 -08:00 committed by GitHub
parent 8b47cf2275
commit b0adbf8071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 280 additions and 28 deletions

View File

@ -0,0 +1,4 @@
interface AccountTeaserVM {
address: string
nrSales: number
}

View File

@ -24,6 +24,10 @@ import {
PoolShares_poolShares as PoolShare
} from '../@types/apollo/PoolShares'
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
@ -165,19 +169,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 }) {
@ -259,6 +250,20 @@ const UserSalesQuery = gql`
}
`
const TopSalesQuery = gql`
query TopSalesQuery {
users(
first: 20
orderBy: nrSales
orderDirection: desc
where: { nrSales_not: 0 }
) {
id
nrSales
}
}
`
export function getSubgraphUri(chainId: number): string {
const config = getOceanConfig(chainId)
return config.subgraphUri
@ -723,3 +728,38 @@ export async function getUserSales(
Logger.log(error.message)
}
}
export async function getTopAssetsPublishers(
chainIds: number[],
nrItems = 9
): Promise<AccountTeaserVM[]> {
const publisherSales: AccountTeaserVM[] = []
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.address
)
if (publishersIndex === -1) {
const publisher: AccountTeaserVM = {
address: fetchedUsers.data.users[i].id,
nrSales: fetchedUsers.data.users[i].nrSales
}
publisherSales.push(publisher)
} else {
publisherSales[publishersIndex].nrSales +=
publisherSales[publishersIndex].nrSales
}
}
}
publisherSales.sort((a, b) => b.nrSales - a.nrSales)
return publisherSales.slice(0, nrItems)
}

View File

@ -0,0 +1,57 @@
import React, { ReactElement } from 'react'
import styles from './AssetList.module.css'
import classNames from 'classnames/bind'
import Loader from '../atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences'
import AccountTeaser from '@shared/AccountTeaser/AccountTeaser'
const cx = classNames.bind(styles)
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
declare type AccountListProps = {
accounts: AccountTeaserVM[]
isLoading: boolean
className?: string
}
export default function AccountList({
accounts,
isLoading,
className
}: AccountListProps): ReactElement {
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
accountTeaserVM={account}
key={index + 1}
place={index + 1}
/>
))
) : chainIds.length === 0 ? (
<div className={styles.empty}>No network selected.</div>
) : (
<div className={styles.empty}>No results found.</div>
)}
</div>
</>
) : (
<LoaderArea />
)
}

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,66 @@
import React, { ReactElement, useEffect, useState } from 'react'
import Dotdotdot from 'react-dotdotdot'
import Link from 'next/link'
import styles from './AccountTeaser.module.css'
import Blockies from '../atoms/Blockies'
import { useCancelToken } from '@hooks/useCancelToken'
import get3BoxProfile from '@utils/profile'
import { accountTruncate } from '@utils/web3'
declare type AccountTeaserProps = {
accountTeaserVM: AccountTeaserVM
place?: number
}
export default function AccountTeaser({
accountTeaserVM,
place
}: AccountTeaserProps): ReactElement {
const [profile, setProfile] = useState<Profile>()
const newCancelToken = useCancelToken()
useEffect(() => {
if (!accountTeaserVM) return
async function getProfileData() {
const profile = await get3BoxProfile(
accountTeaserVM.address,
newCancelToken()
)
if (!profile) return
setProfile(profile)
}
getProfileData()
}, [accountTeaserVM, newCancelToken])
return (
<article className={styles.teaser}>
<Link href={`/profile/${accountTeaserVM.address}`}>
<header className={styles.header}>
{place && <span>{place}</span>}
{profile?.image ? (
<img src={profile.image} className={styles.blockies} />
) : (
<Blockies
accountId={accountTeaserVM.address}
className={styles.blockies}
/>
)}
<div>
<Dotdotdot clamp={3}>
<h3 className={styles.name}>
{profile?.name
? profile?.name
: accountTruncate(accountTeaserVM.address)}
</h3>
</Dotdotdot>
<p className={styles.sales}>
{`${accountTeaserVM.nrSales} ${
accountTeaserVM.nrSales === 1 ? 'sale' : 'sales'
}`}
</p>
</div>
</header>
</Link>
</article>
)
}

View File

@ -1,5 +1,5 @@
import AssetTeaser from '@shared/AssetTeaser/AssetTeaser'
import React, { useEffect, useState } from 'react'
import React, { ReactElement, useEffect, useState } from 'react'
import Pagination from '@shared/Pagination'
import styles from './index.module.css'
import classNames from 'classnames/bind'
@ -29,7 +29,7 @@ declare type AssetListProps = {
noPublisher?: boolean
}
const AssetList: React.FC<AssetListProps> = ({
export default function AssetList({
assets,
showPagination,
page,
@ -38,7 +38,7 @@ const AssetList: React.FC<AssetListProps> = ({
onPageChange,
className,
noPublisher
}) => {
}: AssetListProps): ReactElement {
const { chainIds } = useUserPreferences()
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetListPrices[]>()
const [loading, setLoading] = useState<boolean>(isLoading)
@ -105,5 +105,3 @@ const AssetList: React.FC<AssetListProps> = ({
<LoaderArea />
)
}
export default AssetList

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { ReactElement } from 'react'
import Link from 'next/link'
import Dotdotdot from 'react-dotdotdot'
import Price from '@shared/Price'
@ -15,11 +15,11 @@ declare type AssetTeaserProps = {
noPublisher?: boolean
}
const AssetTeaser: React.FC<AssetTeaserProps> = ({
export default function AssetTeaser({
ddo,
price,
noPublisher
}: AssetTeaserProps) => {
}: AssetTeaserProps): ReactElement {
const { name, type, description } = ddo.metadata
const { dataTokenInfo } = ddo
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
@ -61,5 +61,3 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
</article>
)
}
export default AssetTeaser

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { ReactElement } from 'react'
import Link from 'next/link'
import styles from './Tags.module.css'
@ -23,13 +23,13 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
)
}
const Tags: React.FC<TagsProps> = ({
export default function Tags({
items,
max,
showMore,
className,
noLinks
}) => {
}: TagsProps): ReactElement {
max = max || items.length
const remainder = items.length - max
// filter out empty array items, and restrict to `max`
@ -48,5 +48,3 @@ const Tags: React.FC<TagsProps> = ({
</div>
)
}
export default Tags

View File

@ -0,0 +1,45 @@
import { useUserPreferences } from '@context/UserPreferences'
import AccountList from '@shared/AccountList/AccountList'
import { getTopAssetsPublishers } from '@utils/subgraph'
import React, { ReactElement, useEffect, useState } from 'react'
import styles from './Home.module.css'
export default function PublishersWithMostSales({
title,
action
}: {
title: ReactElement | string
action?: ReactElement
}): ReactElement {
const { chainIds } = useUserPreferences()
const [result, setResult] = useState<AccountTeaserVM[]>([])
const [loading, setLoading] = useState<boolean>()
useEffect(() => {
async function init() {
if (chainIds.length === 0) {
const result: AccountTeaserVM[] = []
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>
)
}