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:
parent
8b47cf2275
commit
b0adbf8071
src
@types/viewModels
@utils
components
@shared
AccountList
AccountTeaser
AssetList
AssetTeaser
atoms
Home
4
src/@types/viewModels/AccountTeaserVM.d.ts
vendored
Normal file
4
src/@types/viewModels/AccountTeaserVM.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
interface AccountTeaserVM {
|
||||||
|
address: string
|
||||||
|
nrSales: number
|
||||||
|
}
|
@ -24,6 +24,10 @@ import {
|
|||||||
PoolShares_poolShares as PoolShare
|
PoolShares_poolShares as PoolShare
|
||||||
} from '../@types/apollo/PoolShares'
|
} from '../@types/apollo/PoolShares'
|
||||||
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
|
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
|
||||||
|
import {
|
||||||
|
UserSalesQuery_users as UserSales,
|
||||||
|
UserSalesQuery as UsersSalesList
|
||||||
|
} from '../@types/apollo/UserSalesQuery'
|
||||||
|
|
||||||
export interface UserLiquidity {
|
export interface UserLiquidity {
|
||||||
price: string
|
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`
|
const UserSharesQuery = gql`
|
||||||
query UserSharesQuery($user: String, $pools: [String!]) {
|
query UserSharesQuery($user: String, $pools: [String!]) {
|
||||||
poolShares(where: { userAddress: $user, poolId_in: $pools }) {
|
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 {
|
export function getSubgraphUri(chainId: number): string {
|
||||||
const config = getOceanConfig(chainId)
|
const config = getOceanConfig(chainId)
|
||||||
return config.subgraphUri
|
return config.subgraphUri
|
||||||
@ -723,3 +728,38 @@ export async function getUserSales(
|
|||||||
Logger.log(error.message)
|
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)
|
||||||
|
}
|
||||||
|
57
src/components/@shared/AccountList/AccountList.tsx
Normal file
57
src/components/@shared/AccountList/AccountList.tsx
Normal 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 />
|
||||||
|
)
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
66
src/components/@shared/AccountTeaser/AccountTeaser.tsx
Normal file
66
src/components/@shared/AccountTeaser/AccountTeaser.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import AssetTeaser from '@shared/AssetTeaser/AssetTeaser'
|
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 Pagination from '@shared/Pagination'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
@ -29,7 +29,7 @@ declare type AssetListProps = {
|
|||||||
noPublisher?: boolean
|
noPublisher?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetList: React.FC<AssetListProps> = ({
|
export default function AssetList({
|
||||||
assets,
|
assets,
|
||||||
showPagination,
|
showPagination,
|
||||||
page,
|
page,
|
||||||
@ -38,7 +38,7 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
className,
|
className,
|
||||||
noPublisher
|
noPublisher
|
||||||
}) => {
|
}: AssetListProps): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetListPrices[]>()
|
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetListPrices[]>()
|
||||||
const [loading, setLoading] = useState<boolean>(isLoading)
|
const [loading, setLoading] = useState<boolean>(isLoading)
|
||||||
@ -105,5 +105,3 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||||||
<LoaderArea />
|
<LoaderArea />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AssetList
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import Dotdotdot from 'react-dotdotdot'
|
import Dotdotdot from 'react-dotdotdot'
|
||||||
import Price from '@shared/Price'
|
import Price from '@shared/Price'
|
||||||
@ -15,11 +15,11 @@ declare type AssetTeaserProps = {
|
|||||||
noPublisher?: boolean
|
noPublisher?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
export default function AssetTeaser({
|
||||||
ddo,
|
ddo,
|
||||||
price,
|
price,
|
||||||
noPublisher
|
noPublisher
|
||||||
}: AssetTeaserProps) => {
|
}: AssetTeaserProps): ReactElement {
|
||||||
const { name, type, description } = ddo.metadata
|
const { name, type, description } = ddo.metadata
|
||||||
const { dataTokenInfo } = ddo
|
const { dataTokenInfo } = ddo
|
||||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||||
@ -61,5 +61,3 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
|||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AssetTeaser
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import styles from './Tags.module.css'
|
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,
|
items,
|
||||||
max,
|
max,
|
||||||
showMore,
|
showMore,
|
||||||
className,
|
className,
|
||||||
noLinks
|
noLinks
|
||||||
}) => {
|
}: TagsProps): ReactElement {
|
||||||
max = max || items.length
|
max = max || items.length
|
||||||
const remainder = items.length - max
|
const remainder = items.length - max
|
||||||
// filter out empty array items, and restrict to `max`
|
// filter out empty array items, and restrict to `max`
|
||||||
@ -48,5 +48,3 @@ const Tags: React.FC<TagsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Tags
|
|
||||||
|
45
src/components/Home/PublishersWithMostSales.tsx
Normal file
45
src/components/Home/PublishersWithMostSales.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user