From b6dbf2c7656bf5ab9245a2b995896fc04af62372 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Fri, 9 Dec 2022 13:53:27 -0500 Subject: [PATCH 01/14] initial paginate work on homepage --- @filter/SelectedFilters.tsx | 2 +- .../hooks/useNounishAuctionQueries.ts | 2 +- @shared/hooks/usePagination.ts | 119 ++++++ .../CollectionMenu/CollectionMenu.tsx | 5 +- constants/index.ts | 1 + constants/pagination.ts | 2 + data/nounsDaos.ts | 9 +- graphql.schema.json | 382 ++++++++++++++++++ hooks/useNounsDaos.ts | 22 +- hooks/useToken.ts | 4 +- pages/_app.tsx | 5 +- pages/index.tsx | 54 ++- providers/CollectionsProvider.tsx | 4 +- types/zora.api.generated.ts | 49 +++ 14 files changed, 632 insertions(+), 28 deletions(-) create mode 100644 @shared/hooks/usePagination.ts create mode 100644 constants/pagination.ts diff --git a/@filter/SelectedFilters.tsx b/@filter/SelectedFilters.tsx index 222772d3..88c50280 100644 --- a/@filter/SelectedFilters.tsx +++ b/@filter/SelectedFilters.tsx @@ -107,7 +107,7 @@ export function SelectedFilters() { showCloseIcon useBorder rightPad - onClick={() => toggleOptionHandler(options.label, option.value)} + onClick={toggleOptionHandler(options.label, option.value)} /> ))} diff --git a/@noun-auction/hooks/useNounishAuctionQueries.ts b/@noun-auction/hooks/useNounishAuctionQueries.ts index e3f4ff16..44aebf08 100644 --- a/@noun-auction/hooks/useNounishAuctionQueries.ts +++ b/@noun-auction/hooks/useNounishAuctionQueries.ts @@ -28,7 +28,7 @@ export function useNounishAuctionQuery({ setTimeout(() => revalidate({ retryCount }), 5000) }, dedupingInterval: 5000, - refreshInterval: 5000, + refreshInterval: 30000, } ) diff --git a/@shared/hooks/usePagination.ts b/@shared/hooks/usePagination.ts new file mode 100644 index 00000000..b0529ee7 --- /dev/null +++ b/@shared/hooks/usePagination.ts @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' + +export type PaginationProps = { + // The initial index + initialIndex?: number + // The total number of results + length: number + // Allow to loop backwards and forwards + loop?: boolean + // The number of items per page + maxPerPage?: number + // number of proximity pages to show + proximityMax?: number +} + +export type PaginationActions = { + first: () => void + last: () => void + next: () => void + previous: () => void + set: (index: number) => void +} + +export type PaginationState = { + actions: PaginationActions + displayIndex: number + index: number + previous: number + next: number + isFirst: boolean + isLast: boolean + percent: number + proximity: number[] + totalPages: number +} + +// List of page numbers to show + +function getProximityList(index: number, totalPages: number, max: number): number[] { + const items: number[] = Array.from(Array(totalPages).keys()) + const last: number = totalPages - 1 + + // Placeholder to id the gap + const placeholder: number = -1 + + // small list + if (totalPages - 1 <= max) return items + + // start + if (index < max) return [...items.slice(0, max), placeholder, last] + + // end + if (index > totalPages - (max + 1)) + return [0, placeholder, ...items.slice(totalPages - max, totalPages)] + + // middle + return [...items.slice(index - (max - 1), index + 1), placeholder, last] +} + +export function usePagination({ + initialIndex = 0, + length, + loop = false, + maxPerPage = 1, + proximityMax = 3, +}: PaginationProps): PaginationState { + const [index, setIndex] = useState(initialIndex) + + const totalPages: number = useMemo( + () => Math.ceil(length / maxPerPage), + [length, maxPerPage] + ) + + // If length changes, reset index + useEffect(() => setIndex(0), [length]) + + const isFirst: boolean = index === 0 + const isLast: boolean = index === totalPages - 1 + + const prevIndex: number = index - 1 + const loopPrevious: number = (prevIndex + totalPages) % totalPages + const previous: number = loop ? loopPrevious : isFirst ? index : prevIndex + + const nextIndex: number = index + 1 + const loopNext: number = nextIndex % totalPages + const next: number = loop ? loopNext : isLast ? index : nextIndex + + const percent: number = index / totalPages + + const proximity: number[] = useMemo( + () => getProximityList(index, totalPages, proximityMax), + [index, proximityMax, totalPages] + ) + + // Actions + const firstPage = useCallback(() => setIndex(0), []) + const lastPage = useCallback(() => setIndex(totalPages - 1), [totalPages]) + const nextPage = useCallback(() => setIndex(next), [next]) + const previousPage = useCallback(() => setIndex(previous), [previous]) + + return { + index, + displayIndex: index + 1, + previous, + next, + isFirst, + isLast, + percent, + proximity, + totalPages, + actions: { + set: setIndex, + first: firstPage, + last: lastPage, + next: nextPage, + previous: previousPage, + }, + } +} diff --git a/compositions/CollectionMenu/CollectionMenu.tsx b/compositions/CollectionMenu/CollectionMenu.tsx index 66a9d4fa..28956c8f 100644 --- a/compositions/CollectionMenu/CollectionMenu.tsx +++ b/compositions/CollectionMenu/CollectionMenu.tsx @@ -17,8 +17,7 @@ import { CollectionNavList } from './CollectionNavList' export function CollectionMenu() { const { currentCollection, currentCollectionCount } = useCollectionsContext() - const daos = useNounsDaos({ limit: 50 }) - const daoCount = useMemo(() => daos.length, [daos]) + const { daos } = useNounsDaos({ limit: 50, keyModifier: 'collectionMenu' }) const { hasScrolled, scrollEvent } = useHasScrolled() const [filter, setFilter] = useState('') const filteredItems = useMemo( @@ -75,7 +74,7 @@ export function CollectionMenu() { Browse - {daoCount} + {daos.length} ( - [`nounsDaos`], + [keyModifier ? `nounsDaos-${keyModifier}` : `nounsDaos`], () => zoraApiFetcher(NOUNS_DAOS_QUERY, { limit, + after, }), { onErrorRetry: (_, _1, _2, revalidate, { retryCount }) => { // Only retry up to 10 times. if (retryCount >= 10) return // Retry after 5 seconds. - setTimeout(() => revalidate({ retryCount }), 5000) + setTimeout(() => revalidate({ retryCount }), 30000) }, } ) - return useMemo(() => { + const daos = useMemo(() => { const newData = data?.nouns?.nounsDaos?.nodes if (newData && newData.length > 0) { @@ -51,4 +59,10 @@ export function useNounsDaos({ limit = 30 }: NounsDaos) { // no need to update when cache changed! // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.nouns?.nounsDaos?.nodes]) + + return { + response: data, + daos, + pageInfo: data?.nouns.nounsDaos.pageInfo, + } } diff --git a/hooks/useToken.ts b/hooks/useToken.ts index 7c44b429..e2ce9d45 100644 --- a/hooks/useToken.ts +++ b/hooks/useToken.ts @@ -27,10 +27,10 @@ export function useToken({ collectionAddress, tokenId }: Params) { // Only retry up to 1 times. if (retryCount >= 2) return // Retry after 5 seconds. - setTimeout(() => revalidate({ retryCount }), 5000) + setTimeout(() => revalidate({ retryCount }), 30000) }, dedupingInterval: 10000, - refreshInterval: 5000, + refreshInterval: 30000, } ) diff --git a/pages/_app.tsx b/pages/_app.tsx index 14e5dd54..e4daa20a 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -51,7 +51,6 @@ const wagmiClient = createClient({ export const strategy = new ZDKFetchStrategy('1', GALACTUS_BASE_URL) function MyApp({ Component, pageProps }: AppProps) { - const { collections, daos } = useCollections() const router = useRouter() useEffect(() => { @@ -83,7 +82,9 @@ function MyApp({ Component, pageProps }: AppProps) { })} > - + diff --git a/pages/index.tsx b/pages/index.tsx index b7c83a6b..b8ffcf4e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,27 +1,45 @@ import { HomePageHeader, PageWrapper, Seo } from 'components' import { DaoTable } from 'compositions' +import { DAO_PAGE_LIMIT, TOTAL_DAO_COUNT_ESTIMATE } from 'constants/pagination' import * as styles from 'styles/styles.css' import { NOUNS_DAOS_QUERY } from 'data/nounsDaos' import { useNounsDaos } from 'hooks/useNounsDaos' -import React, { useMemo } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { TypeSafeDao } from 'validators/dao' import * as Sentry from '@sentry/react' import { zoraApiFetcher } from '@shared' +import { usePagination } from '@shared/hooks/usePagination' import { CollectionsQuery } from '@zoralabs/zdk/dist/queries/queries-sdk' -import { Grid } from '@zoralabs/zord' +import { Button, Grid, Pagination, PaginationProximityList } from '@zoralabs/zord' export type CollectionParsed = CollectionsQuery['collections']['nodes'] function Home(props: { daos: TypeSafeDao[] }) { - const clientDaos = useNounsDaos({ limit: 30 }) - const hasDaos = useMemo( - () => props.daos?.length > 0 || clientDaos?.length > 0, - [clientDaos, props.daos] - ) + // const { collections } = useCollections(pagination.index) + + const [paginationCursor, setPaginationCursor] = useState('') + + const { daos: clientDaos, pageInfo } = useNounsDaos({ + limit: DAO_PAGE_LIMIT, + after: paginationCursor, + }) + const pagination = usePagination({ + length: TOTAL_DAO_COUNT_ESTIMATE, + maxPerPage: DAO_PAGE_LIMIT, + }) + const daos = useMemo(() => clientDaos ?? props.daos, [clientDaos, props.daos]) + const hasDaos = useMemo(() => daos?.length > 0, [daos]) + + const pageNext = useCallback(() => { + pageInfo?.endCursor && setPaginationCursor(pageInfo?.endCursor) + }, []) + + console.log('DAOS', daos, daos.length) + console.log('AFTER', pageInfo?.endCursor) return ( @@ -37,17 +55,31 @@ function Home(props: { daos: TypeSafeDao[] }) { mt={{ '@initial': 'x6', '@1024': 'x24' }} mb={{ '@initial': 'x8', '@1024': 'x24' }} /> - {hasDaos && ( - - )} + {hasDaos && } + + {/* + + */} + ) } export async function getServerSideProps() { try { - const daos = await zoraApiFetcher(NOUNS_DAOS_QUERY, { limit: 30 }) + const daos = await zoraApiFetcher(NOUNS_DAOS_QUERY, { limit: DAO_PAGE_LIMIT }) return { props: { diff --git a/providers/CollectionsProvider.tsx b/providers/CollectionsProvider.tsx index 4dd7f728..5a03f695 100644 --- a/providers/CollectionsProvider.tsx +++ b/providers/CollectionsProvider.tsx @@ -33,8 +33,8 @@ const CollectionsContext = createContext<{ type CollectionsProps = { children?: ReactNode - collections: CollectionsData[] | undefined - daos: CollectionsData[] | undefined + collections?: CollectionsData[] | undefined + daos?: CollectionsData[] | undefined } export function useCollectionsContext() { diff --git a/types/zora.api.generated.ts b/types/zora.api.generated.ts index f31ab753..34ca1430 100644 --- a/types/zora.api.generated.ts +++ b/types/zora.api.generated.ts @@ -1909,9 +1909,17 @@ export type V3AskEventProperties = | V3AskCreatedEventProperties | V3AskFilledEventProperties | V3AskPriceUpdatedEventProperties + | V3AsksCoreEthAskEventProperties + | V3AsksCoreEthAskFilledEventProperties + | V3AsksCoreEthRoyaltyPayoutEventProperties | V3PrivateAskEventProperties export enum V3AskEventType { + V3AsksCoreEthCanceled = 'V3_ASKS_CORE_ETH_CANCELED', + V3AsksCoreEthCreated = 'V3_ASKS_CORE_ETH_CREATED', + V3AsksCoreEthFilled = 'V3_ASKS_CORE_ETH_FILLED', + V3AsksCoreEthPriceUpdated = 'V3_ASKS_CORE_ETH_PRICE_UPDATED', + V3AsksCoreEthRoyaltyPayout = 'V3_ASKS_CORE_ETH_ROYALTY_PAYOUT', V3AskCanceled = 'V3_ASK_CANCELED', V3AskCreated = 'V3_ASK_CREATED', V3AskFilled = 'V3_ASK_FILLED', @@ -1944,6 +1952,37 @@ export type V3AskPriceUpdatedEventProperties = { sellerFundsRecipient: Scalars['String'] } +export type V3AsksCoreEthAskEventProperties = { + __typename?: 'V3AsksCoreEthAskEventProperties' + askCurrency: Scalars['String'] + askPrice: Scalars['String'] + price: PriceAtTime + seller: Scalars['String'] + tokenContract: Scalars['String'] + tokenId: Scalars['String'] +} + +export type V3AsksCoreEthAskFilledEventProperties = { + __typename?: 'V3AsksCoreEthAskFilledEventProperties' + askCurrency: Scalars['String'] + askPrice: Scalars['String'] + buyer: Scalars['String'] + price: PriceAtTime + seller: Scalars['String'] + tokenContract: Scalars['String'] + tokenId: Scalars['String'] +} + +export type V3AsksCoreEthRoyaltyPayoutEventProperties = { + __typename?: 'V3AsksCoreEthRoyaltyPayoutEventProperties' + amount: PriceAtTime + askCurrency: Scalars['String'] + askPrice: Scalars['String'] + recipient: Scalars['String'] + tokenContract: Scalars['String'] + tokenId: Scalars['String'] +} + export type V3ModuleManagerEvent = { __typename?: 'V3ModuleManagerEvent' address: Scalars['String'] @@ -2189,6 +2228,8 @@ export type NounishAuctionsQuery = { export type NounsDaosQueryVariables = Exact<{ network: NetworkInput + limit: Scalars['Int'] + after?: InputMaybe }> export type NounsDaosQuery = { @@ -2211,6 +2252,12 @@ export type NounsDaosQuery = { contractAddress?: string | null networkInfo: { __typename?: 'NetworkInfo'; chain: Chain; network: Network } }> + pageInfo: { + __typename?: 'PageInfo' + hasNextPage: boolean + endCursor?: string | null + limit: number + } } } } @@ -2218,6 +2265,8 @@ export type NounsDaosQuery = { export type OffchainOrderForTokenQueryVariables = Exact<{ tokenId: Scalars['String'] tokenAddress: Scalars['String'] + network: Network + chain: Chain }> export type OffchainOrderForTokenQuery = { From 90520af5286e1aa70369add556de0a3110386ae4 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Fri, 9 Dec 2022 13:56:33 -0500 Subject: [PATCH 02/14] fix build --- @filter/SelectedFilters.tsx | 2 +- pages/index.tsx | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/@filter/SelectedFilters.tsx b/@filter/SelectedFilters.tsx index 88c50280..222772d3 100644 --- a/@filter/SelectedFilters.tsx +++ b/@filter/SelectedFilters.tsx @@ -107,7 +107,7 @@ export function SelectedFilters() { showCloseIcon useBorder rightPad - onClick={toggleOptionHandler(options.label, option.value)} + onClick={() => toggleOptionHandler(options.label, option.value)} /> ))} diff --git a/pages/index.tsx b/pages/index.tsx index b8ffcf4e..abc965d5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -14,13 +14,11 @@ import * as Sentry from '@sentry/react' import { zoraApiFetcher } from '@shared' import { usePagination } from '@shared/hooks/usePagination' import { CollectionsQuery } from '@zoralabs/zdk/dist/queries/queries-sdk' -import { Button, Grid, Pagination, PaginationProximityList } from '@zoralabs/zord' +import { Button, Grid } from '@zoralabs/zord' export type CollectionParsed = CollectionsQuery['collections']['nodes'] function Home(props: { daos: TypeSafeDao[] }) { - // const { collections } = useCollections(pagination.index) - const [paginationCursor, setPaginationCursor] = useState('') const { daos: clientDaos, pageInfo } = useNounsDaos({ @@ -36,7 +34,7 @@ function Home(props: { daos: TypeSafeDao[] }) { const pageNext = useCallback(() => { pageInfo?.endCursor && setPaginationCursor(pageInfo?.endCursor) - }, []) + }, [pageInfo?.endCursor]) console.log('DAOS', daos, daos.length) console.log('AFTER', pageInfo?.endCursor) From 24536616674b6988caf044e239f4ee13c08756ec Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Fri, 9 Dec 2022 14:11:53 -0500 Subject: [PATCH 03/14] cleaner loadmore handling --- pages/index.tsx | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index abc965d5..4b7cdbaf 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,6 @@ import { HomePageHeader, PageWrapper, Seo } from 'components' import { DaoTable } from 'compositions' -import { DAO_PAGE_LIMIT, TOTAL_DAO_COUNT_ESTIMATE } from 'constants/pagination' +import { DAO_PAGE_LIMIT } from 'constants/pagination' import * as styles from 'styles/styles.css' import { NOUNS_DAOS_QUERY } from 'data/nounsDaos' @@ -12,23 +12,17 @@ import { TypeSafeDao } from 'validators/dao' import * as Sentry from '@sentry/react' import { zoraApiFetcher } from '@shared' -import { usePagination } from '@shared/hooks/usePagination' import { CollectionsQuery } from '@zoralabs/zdk/dist/queries/queries-sdk' -import { Button, Grid } from '@zoralabs/zord' +import { Button, Eyebrow, Grid } from '@zoralabs/zord' export type CollectionParsed = CollectionsQuery['collections']['nodes'] function Home(props: { daos: TypeSafeDao[] }) { const [paginationCursor, setPaginationCursor] = useState('') - const { daos: clientDaos, pageInfo } = useNounsDaos({ limit: DAO_PAGE_LIMIT, after: paginationCursor, }) - const pagination = usePagination({ - length: TOTAL_DAO_COUNT_ESTIMATE, - maxPerPage: DAO_PAGE_LIMIT, - }) const daos = useMemo(() => clientDaos ?? props.daos, [clientDaos, props.daos]) const hasDaos = useMemo(() => daos?.length > 0, [daos]) @@ -54,23 +48,17 @@ function Home(props: { daos: TypeSafeDao[] }) { mb={{ '@initial': 'x8', '@1024': 'x24' }} /> {hasDaos && } - - {/* - - */} - + + {hasDaos && ( + <> + {pageInfo?.hasNextPage ? ( + + ) : ( + All Daos Loaded + )} + + )} ) } From 135d2a28f7b9d532cbc458e7ce709a17a1940ca4 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Fri, 9 Dec 2022 14:30:43 -0500 Subject: [PATCH 04/14] add loading state to loadmore button --- hooks/useNounsDaos.ts | 3 ++- pages/index.tsx | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/hooks/useNounsDaos.ts b/hooks/useNounsDaos.ts index c3430278..8e18dc5e 100644 --- a/hooks/useNounsDaos.ts +++ b/hooks/useNounsDaos.ts @@ -29,7 +29,7 @@ export function useNounsDaos({ keyModifier = '', }: NounsDaos) { const [cached, setCache] = useState([] as TypeSafeDao[]) - const { data } = useSWR( + const { data, isValidating } = useSWR( [keyModifier ? `nounsDaos-${keyModifier}` : `nounsDaos`], () => zoraApiFetcher(NOUNS_DAOS_QUERY, { @@ -64,5 +64,6 @@ export function useNounsDaos({ response: data, daos, pageInfo: data?.nouns.nounsDaos.pageInfo, + isValidating, } } diff --git a/pages/index.tsx b/pages/index.tsx index 4b7cdbaf..b6f7e8f7 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -19,7 +19,11 @@ export type CollectionParsed = CollectionsQuery['collections']['nodes'] function Home(props: { daos: TypeSafeDao[] }) { const [paginationCursor, setPaginationCursor] = useState('') - const { daos: clientDaos, pageInfo } = useNounsDaos({ + const { + daos: clientDaos, + pageInfo, + isValidating, + } = useNounsDaos({ limit: DAO_PAGE_LIMIT, after: paginationCursor, }) @@ -27,7 +31,9 @@ function Home(props: { daos: TypeSafeDao[] }) { const hasDaos = useMemo(() => daos?.length > 0, [daos]) const pageNext = useCallback(() => { - pageInfo?.endCursor && setPaginationCursor(pageInfo?.endCursor) + pageInfo?.endCursor && + pageInfo?.endCursor !== paginationCursor && + setPaginationCursor(pageInfo?.endCursor) }, [pageInfo?.endCursor]) console.log('DAOS', daos, daos.length) @@ -53,7 +59,9 @@ function Home(props: { daos: TypeSafeDao[] }) { {hasDaos && ( <> {pageInfo?.hasNextPage ? ( - + ) : ( All Daos Loaded )} From c08ead56319b0005c1bf15256a778900ab89388e Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Mon, 12 Dec 2022 14:21:05 -0500 Subject: [PATCH 05/14] clean up fetcher --- hooks/useNounsDaos.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/hooks/useNounsDaos.ts b/hooks/useNounsDaos.ts index 8e18dc5e..73f53770 100644 --- a/hooks/useNounsDaos.ts +++ b/hooks/useNounsDaos.ts @@ -9,14 +9,6 @@ import { TypeSafeDao, verifyDao } from 'validators/dao' import { zoraApiFetcher } from '@shared' -export type AuctionVolumeReturnType = - | { - chainTokenPrice: number - totalCount: number - usdcPrice: number - } - | undefined - interface NounsDaos { limit?: number after?: string From 4bbaca35c5ab1f466347f9cf418c88049e6c5a6b Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Wed, 14 Dec 2022 18:05:19 -0500 Subject: [PATCH 06/14] re-jig dao pagination for next/prev --- compositions/Daos/{Daos.tsx => DaoTable.tsx} | 0 compositions/Daos/index.ts | 2 +- hooks/useNounsDaos.ts | 26 +++ pages/index.tsx | 162 ++++++++++++++++--- 4 files changed, 165 insertions(+), 25 deletions(-) rename compositions/Daos/{Daos.tsx => DaoTable.tsx} (100%) diff --git a/compositions/Daos/Daos.tsx b/compositions/Daos/DaoTable.tsx similarity index 100% rename from compositions/Daos/Daos.tsx rename to compositions/Daos/DaoTable.tsx diff --git a/compositions/Daos/index.ts b/compositions/Daos/index.ts index cbab7ba6..9b30dd13 100644 --- a/compositions/Daos/index.ts +++ b/compositions/Daos/index.ts @@ -1 +1 @@ -export * from './Daos' +export * from './DaoTable' diff --git a/hooks/useNounsDaos.ts b/hooks/useNounsDaos.ts index 73f53770..a7ccc1cc 100644 --- a/hooks/useNounsDaos.ts +++ b/hooks/useNounsDaos.ts @@ -13,14 +13,20 @@ interface NounsDaos { limit?: number after?: string keyModifier?: string // optionally add a specific modifier to ensure that the SWR results cache does not inadvertently change the result counts in other places on the site + refreshInterval?: number + fallbackData?: NounsDaosQuery } export function useNounsDaos({ limit = DAO_PAGE_LIMIT, after = '', keyModifier = '', + refreshInterval = 30000, + fallbackData, }: NounsDaos) { const [cached, setCache] = useState([] as TypeSafeDao[]) + // const [cached, setCache] = useState(undefined) + const { data, isValidating } = useSWR( [keyModifier ? `nounsDaos-${keyModifier}` : `nounsDaos`], () => @@ -29,6 +35,8 @@ export function useNounsDaos({ after, }), { + fallbackData, + refreshInterval, onErrorRetry: (_, _1, _2, revalidate, { retryCount }) => { // Only retry up to 10 times. if (retryCount >= 10) return @@ -52,6 +60,24 @@ export function useNounsDaos({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.nouns?.nounsDaos?.nodes]) + // const daos = useMemo(() => { + // return data?.nouns?.nounsDaos?.nodes.map(verifyDao) || [] + + // // setCache(data) + // // setCache(newData) + // // return newData && newData?.length > 0 ? newData : [] + + // // if (newData && newData.length > 0) { + // // const verifiedData = newData.map(verifyDao) + // // setCache(verifiedData) + // // return verifiedData + // // } else { + // // return cached.length > 0 ? cached : [] + // // } + // // no need to update when cache changed! + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [data?.nouns?.nounsDaos?.nodes]) + return { response: data, daos, diff --git a/pages/index.tsx b/pages/index.tsx index b6f7e8f7..a1d1686b 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -2,42 +2,133 @@ import { HomePageHeader, PageWrapper, Seo } from 'components' import { DaoTable } from 'compositions' import { DAO_PAGE_LIMIT } from 'constants/pagination' import * as styles from 'styles/styles.css' +import { NounsDaosQuery } from 'types/zora.api.generated' import { NOUNS_DAOS_QUERY } from 'data/nounsDaos' import { useNounsDaos } from 'hooks/useNounsDaos' -import React, { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { TypeSafeDao } from 'validators/dao' import * as Sentry from '@sentry/react' import { zoraApiFetcher } from '@shared' import { CollectionsQuery } from '@zoralabs/zdk/dist/queries/queries-sdk' -import { Button, Eyebrow, Grid } from '@zoralabs/zord' +import { Button, Eyebrow, Flex, Grid, Paragraph, Stack } from '@zoralabs/zord' export type CollectionParsed = CollectionsQuery['collections']['nodes'] -function Home(props: { daos: TypeSafeDao[] }) { - const [paginationCursor, setPaginationCursor] = useState('') +function Home(props: { + // ssrDAOQuery: NounsDaosQuery, + daos: TypeSafeDao[] +}) { const { + daos: ssrDAOS, + // ssrDAOQuery + } = props + const [currentCursor, setCurrentCursor] = useState('') + // const [nextCursor, setNextCursor] = useState('') + // const [prevCursor, setPrevCursor] = useState('') + const [cursorCache, setCursorCache] = useState([]) + // const [daoCache, setDaoCache] = useState(ssrDAOQuery) + + const { + response: daoQueryResponse, daos: clientDaos, pageInfo, isValidating, } = useNounsDaos({ limit: DAO_PAGE_LIMIT, - after: paginationCursor, + after: currentCursor, + // fallbackData: currentCursor === '' ? ssrDAOQuery : undefined, + // fallbackData: !paginationCursor ? ssrDAOQuery : daoCache, + // fallbackData: daoCache, }) - const daos = useMemo(() => clientDaos ?? props.daos, [clientDaos, props.daos]) + + const daos = useMemo(() => clientDaos ?? ssrDAOS, [clientDaos, ssrDAOS]) const hasDaos = useMemo(() => daos?.length > 0, [daos]) + const lastItem = useMemo( + () => (cursorCache.length === 0 ? '' : cursorCache[cursorCache.length - 1]), + [cursorCache] + ) + + // new: ddddddd + + // act + // ['aaaaaaaa','bbbbbbbbb','ccccccccc'] + + useEffect(() => { + if (!pageInfo?.endCursor || pageInfo.endCursor === currentCursor) { + console.log('CONDITIONAL RETURN') + return + } + const fetchedCursor = pageInfo.endCursor + let newCursorCache: string[] = cursorCache + + // Add new cursor to pagination cache if it's not already there + if (cursorCache.includes(fetchedCursor)) { + console.log('ALREADY IN CACHE') + // setPrevCursor(cursorCache[cursorCache.indexOf(fetchedCursor) - 1]) + // setNextCursor(cursorCache[cursorCache.indexOf(fetchedCursor) + 1]) + + // setCurrentCursor(cursorCache[cursorCache.indexOf(fetchedCursor) - 2]) + } else { + console.log('ADDING TO CACHE:', fetchedCursor, cursorCache.length) + + // const prevItem = newCursorCache[newCursorCache.length - 1] + + newCursorCache.push(fetchedCursor) + // setPrevCursor(lastItem) + // if (cursorCache.length !== 0) { + // console.log('SETTING PREV TO ', cursorCache[cursorCache.length - 1]) + // } + // if (cursorCache.length !== 0) { + // console.log('SETTING PREV TO ', lastItem) + // } + + // console.log('PREV:', cursorCache[cursorCache.length - 1]) + // console.log('PREV:', prevCursor) + // setNextCursor(fetchedCursor) + setCursorCache(newCursorCache) + console.log('NEXT:', fetchedCursor) + } + }, [ + pageInfo?.endCursor, + // , cursorCache, currentCursor + ]) + + useEffect(() => console.log(cursorCache), [cursorCache]) + + const prevCursor = + cursorCache.indexOf(currentCursor) <= 1 + ? '' + : cursorCache[cursorCache.indexOf(currentCursor)] + + const nextCursor = cursorCache[cursorCache.indexOf(currentCursor) + 1] + const pageNext = useCallback(() => { - pageInfo?.endCursor && - pageInfo?.endCursor !== paginationCursor && - setPaginationCursor(pageInfo?.endCursor) - }, [pageInfo?.endCursor]) + // if (nextCursor) setCurrentCursor(nextCursor) + // setCurrentCursor(nextCursor) - console.log('DAOS', daos, daos.length) - console.log('AFTER', pageInfo?.endCursor) + console.log('NEXT', nextCursor) + setCurrentCursor(nextCursor) + }, []) + + const pagePrev = useCallback(() => { + // setCurrentCursor(prevCursor) + // setCurrentCursor(prevCursor) + + // const prevCursor = + // cursorCache.indexOf(currentCursor) <= 1 + // ? '' + // : cursorCache[cursorCache.indexOf(currentCursor) - 1] + console.log('PREV', prevCursor) + setCurrentCursor(prevCursor) + }, []) + + // console.log('DAOS', daos, daos.length) + // console.log('AFTER', pageInfo?.endCursor) return ( @@ -53,19 +144,38 @@ function Home(props: { daos: TypeSafeDao[] }) { mt={{ '@initial': 'x6', '@1024': 'x24' }} mb={{ '@initial': 'x8', '@1024': 'x24' }} /> + {hasDaos && } - {hasDaos && ( - <> - {pageInfo?.hasNextPage ? ( - - ) : ( - All Daos Loaded - )} - + + + {currentCursor !== '' && ( + + )} + {/* {cursorCache.length > 0 ? ( */} + {nextCursor && ( + + )} + {pageInfo && !pageInfo?.hasNextPage && All DAOs Loaded} + + {cursorCache.toString()} + {`prev: ${ + prevCursor === '' ? 'INITIAL' : prevCursor + }`} + {`current: ${currentCursor}`} + {`next: ${nextCursor}`} + )} ) @@ -73,11 +183,15 @@ function Home(props: { daos: TypeSafeDao[] }) { export async function getServerSideProps() { try { - const daos = await zoraApiFetcher(NOUNS_DAOS_QUERY, { limit: DAO_PAGE_LIMIT }) + const ssrDAOQuery: NounsDaosQuery = await zoraApiFetcher(NOUNS_DAOS_QUERY, { + limit: DAO_PAGE_LIMIT, + }) return { props: { - daos: daos?.nouns?.nounsDaos?.nodes ?? null, + // daos: daos?.nouns?.nounsDaos?.nodes ?? null, + // ssrDAOQuery, + daos: ssrDAOQuery?.nouns?.nounsDaos?.nodes ?? null, }, } } catch (err) { From 508afda476bc7547ecde99bdc8bcf3b545bf3db4 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Thu, 15 Dec 2022 14:05:02 -0500 Subject: [PATCH 07/14] pagination optimizations + minor style edits --- .../CollectionMenu/CollectionMenu.tsx | 5 +- compositions/Daos/DaoRow.css.ts | 6 - compositions/Daos/DaoRow.tsx | 7 +- compositions/Daos/DaoTable.tsx | 12 +- compositions/Daos/Daos.css.ts | 6 + compositions/Header/Header.tsx | 2 +- hooks/useNounsDaos.ts | 8 +- pages/index.tsx | 103 +++++------------- 8 files changed, 49 insertions(+), 100 deletions(-) diff --git a/compositions/CollectionMenu/CollectionMenu.tsx b/compositions/CollectionMenu/CollectionMenu.tsx index 28956c8f..dd259cb0 100644 --- a/compositions/CollectionMenu/CollectionMenu.tsx +++ b/compositions/CollectionMenu/CollectionMenu.tsx @@ -21,7 +21,8 @@ export function CollectionMenu() { const { hasScrolled, scrollEvent } = useHasScrolled() const [filter, setFilter] = useState('') const filteredItems = useMemo( - () => daos.filter((item) => item?.name?.toLowerCase().includes(filter.toLowerCase())), + () => + daos?.filter((item) => item?.name?.toLowerCase().includes(filter.toLowerCase())), [filter, daos] ) const hasResults = useMemo(() => filteredItems.length > 0, [filteredItems]) @@ -74,7 +75,7 @@ export function CollectionMenu() { Browse - {daos.length} + {daos?.length} { if (!token || !activeAuction) return null - const highestBid = activeAuction.highestBidPrice?.chainTokenPrice?.raw || '0' - const tokenId = activeAuction.tokenId - const collectionAddress = activeAuction.collectionAddress + const { tokenId, collectionAddress, highestBidPrice } = activeAuction + const highestBid = highestBidPrice?.chainTokenPrice?.raw || '0' const auctionStatus = Date.now() - parseInt(activeAuction.endTime) * 1000 > 0 ? 'Settling' : 'Live' @@ -136,7 +135,7 @@ export const DaoRowComponent = ({ } return ( - + )} - - {(daos ?? []).map((dao, index) => ( - - ))} +
    + {(daos ?? []).map((dao, index) => ( + + ))} +
) diff --git a/compositions/Daos/Daos.css.ts b/compositions/Daos/Daos.css.ts index d90d7e72..c9eb6324 100644 --- a/compositions/Daos/Daos.css.ts +++ b/compositions/Daos/Daos.css.ts @@ -49,3 +49,9 @@ export const daosRow = style([ }, }), ]) + +export const header = style([ + { + height: '40px!important', + }, +]) diff --git a/compositions/Header/Header.tsx b/compositions/Header/Header.tsx index 2b964e07..5643f9b3 100644 --- a/compositions/Header/Header.tsx +++ b/compositions/Header/Header.tsx @@ -29,7 +29,7 @@ export function Header() { - + {/* */} (undefined) @@ -41,7 +41,7 @@ export function useNounsDaos({ // Only retry up to 10 times. if (retryCount >= 10) return // Retry after 5 seconds. - setTimeout(() => revalidate({ retryCount }), 30000) + setTimeout(() => revalidate({ retryCount }), 10000) }, } ) @@ -78,8 +78,10 @@ export function useNounsDaos({ // // eslint-disable-next-line react-hooks/exhaustive-deps // }, [data?.nouns?.nounsDaos?.nodes]) + console.log('HOOK DAOS', daos) + return { - response: data, + // response: data, daos, pageInfo: data?.nouns.nounsDaos.pageInfo, isValidating, diff --git a/pages/index.tsx b/pages/index.tsx index a1d1686b..024230ae 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -18,45 +18,27 @@ import { Button, Eyebrow, Flex, Grid, Paragraph, Stack } from '@zoralabs/zord' export type CollectionParsed = CollectionsQuery['collections']['nodes'] -function Home(props: { - // ssrDAOQuery: NounsDaosQuery, - daos: TypeSafeDao[] -}) { - const { - daos: ssrDAOS, - // ssrDAOQuery - } = props +function Home(props: { ssrDAOQuery: NounsDaosQuery; daos: TypeSafeDao[] }) { + const { daos: ssrDAOS, ssrDAOQuery } = props const [currentCursor, setCurrentCursor] = useState('') - // const [nextCursor, setNextCursor] = useState('') - // const [prevCursor, setPrevCursor] = useState('') const [cursorCache, setCursorCache] = useState([]) - // const [daoCache, setDaoCache] = useState(ssrDAOQuery) const { - response: daoQueryResponse, + // response: daoQueryResponse, daos: clientDaos, pageInfo, - isValidating, + // isValidating, } = useNounsDaos({ limit: DAO_PAGE_LIMIT, after: currentCursor, - // fallbackData: currentCursor === '' ? ssrDAOQuery : undefined, - // fallbackData: !paginationCursor ? ssrDAOQuery : daoCache, - // fallbackData: daoCache, + fallbackData: currentCursor === '' ? ssrDAOQuery : undefined, + // fallbackData: ssrDAOQuery, }) const daos = useMemo(() => clientDaos ?? ssrDAOS, [clientDaos, ssrDAOS]) const hasDaos = useMemo(() => daos?.length > 0, [daos]) - - const lastItem = useMemo( - () => (cursorCache.length === 0 ? '' : cursorCache[cursorCache.length - 1]), - [cursorCache] - ) - - // new: ddddddd - - // act - // ['aaaaaaaa','bbbbbbbbb','ccccccccc'] + console.log('daos.length', daos?.length) + console.log('daos', daos) useEffect(() => { if (!pageInfo?.endCursor || pageInfo.endCursor === currentCursor) { @@ -67,68 +49,34 @@ function Home(props: { let newCursorCache: string[] = cursorCache // Add new cursor to pagination cache if it's not already there - if (cursorCache.includes(fetchedCursor)) { - console.log('ALREADY IN CACHE') - // setPrevCursor(cursorCache[cursorCache.indexOf(fetchedCursor) - 1]) - // setNextCursor(cursorCache[cursorCache.indexOf(fetchedCursor) + 1]) - - // setCurrentCursor(cursorCache[cursorCache.indexOf(fetchedCursor) - 2]) - } else { - console.log('ADDING TO CACHE:', fetchedCursor, cursorCache.length) - - // const prevItem = newCursorCache[newCursorCache.length - 1] - + if (!cursorCache.includes(fetchedCursor)) { + // console.log('ADDING TO CACHE:', fetchedCursor, cursorCache.length) newCursorCache.push(fetchedCursor) - // setPrevCursor(lastItem) - // if (cursorCache.length !== 0) { - // console.log('SETTING PREV TO ', cursorCache[cursorCache.length - 1]) - // } - // if (cursorCache.length !== 0) { - // console.log('SETTING PREV TO ', lastItem) - // } - - // console.log('PREV:', cursorCache[cursorCache.length - 1]) - // console.log('PREV:', prevCursor) - // setNextCursor(fetchedCursor) setCursorCache(newCursorCache) console.log('NEXT:', fetchedCursor) } - }, [ - pageInfo?.endCursor, - // , cursorCache, currentCursor - ]) + }, [pageInfo?.endCursor, cursorCache, currentCursor]) useEffect(() => console.log(cursorCache), [cursorCache]) const prevCursor = - cursorCache.indexOf(currentCursor) <= 1 + cursorCache.indexOf(currentCursor) < 1 ? '' - : cursorCache[cursorCache.indexOf(currentCursor)] + : cursorCache[cursorCache.indexOf(currentCursor) - 1] - const nextCursor = cursorCache[cursorCache.indexOf(currentCursor) + 1] - - const pageNext = useCallback(() => { - // if (nextCursor) setCurrentCursor(nextCursor) - // setCurrentCursor(nextCursor) - - console.log('NEXT', nextCursor) - setCurrentCursor(nextCursor) - }, []) + const nextCursor = pageInfo?.hasNextPage + ? cursorCache[cursorCache.indexOf(currentCursor) + 1] + : '' const pagePrev = useCallback(() => { - // setCurrentCursor(prevCursor) - // setCurrentCursor(prevCursor) - - // const prevCursor = - // cursorCache.indexOf(currentCursor) <= 1 - // ? '' - // : cursorCache[cursorCache.indexOf(currentCursor) - 1] - console.log('PREV', prevCursor) + console.log('SETTING PREV TO: ', prevCursor) setCurrentCursor(prevCursor) - }, []) + }, [prevCursor]) - // console.log('DAOS', daos, daos.length) - // console.log('AFTER', pageInfo?.endCursor) + const pageNext = useCallback(() => { + console.log('SETTING NEXT TO: ', nextCursor) + setCurrentCursor(nextCursor) + }, [nextCursor]) return ( @@ -158,10 +106,9 @@ function Home(props: { Prev {prevCursor === '' ? 'INITIAL' : prevCursor} )} - {/* {cursorCache.length > 0 ? ( */} {nextCursor && ( + {hasDaos && ( + + + + {currentIndex !== 0 && ( + + )} + + + {pageInfo?.hasNextPage && ( + + )} + + + {DEBUG && ( + + + {hasDaos && + daos.map((dao) => ( + + {dao.collectionAddress} + + ))} + + cursorCache: [{cursorCache.toString()}] + {`prev: ${ + currentIndex === 0 ? 'INITIAL' : cursorCache[currentIndex - 1] + }`} + {`current: ${cursorCache[currentIndex]}`} + {`next: ${ + cursorCache[currentIndex] + 1 + }`} + {`daos.length: ${daos?.length}`} + )} - {nextCursor && ( - - )} - {pageInfo && !pageInfo?.hasNextPage && All DAOs Loaded} - - cursorCache: [{cursorCache.toString()}] - {`prev: ${ - prevCursor === '' ? 'INITIAL' : prevCursor - }`} - {`current: ${currentCursor}`} - {`next: ${nextCursor}`} - {`daos.length: ${daos?.length}`} - - )} + + )} + ) } From e92650f8ba6f5eabcb5a18126054189d4e8464b3 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Tue, 20 Dec 2022 15:02:41 -0500 Subject: [PATCH 13/14] WIP pagination debug --- compositions/Daos/DaoRow.tsx | 45 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/compositions/Daos/DaoRow.tsx b/compositions/Daos/DaoRow.tsx index 5226152e..d161c5f3 100644 --- a/compositions/Daos/DaoRow.tsx +++ b/compositions/Daos/DaoRow.tsx @@ -26,8 +26,9 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { const { activeAuction } = useNounishAuctionQuery({ collectionAddress: dao.collectionAddress, }) - + // dao.contractAddress ?? const { token } = useToken({ + // collectionAddress: dao.collectionAddress, collectionAddress: dao.collectionAddress, // FIXME: @oleg tokenId: activeAuction?.tokenId ?? '', @@ -39,16 +40,17 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { // return null return ( + {/* {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} */} {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} ) } - const { tokenId, collectionAddress, highestBidPrice } = activeAuction - const highestBid = highestBidPrice?.chainTokenPrice?.raw || '0' + // const { tokenId, collectionAddress, highestBidPrice } = activeAuction! + // const highestBid = highestBidPrice?.chainTokenPrice?.raw || '0' - const auctionStatus = - Date.now() - parseInt(activeAuction.endTime) * 1000 > 0 ? 'Settling' : 'Live' + // const auctionStatus = + // Date.now() - parseInt(activeAuction!.endTime) * 1000 > 0 ? 'Settling' : 'Live' if ( isAddressMatch('0xd310a3041dfcf14def5ccbc508668974b5da7174', dao.collectionAddress) @@ -56,18 +58,29 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { console.log('DUPE?') } + // return ( + // + // {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} + // + // ) + return ( - + (token && activeAuction && ( + 0 ? 'Settling' : 'Live' + } + /> + )) || + null ) } From 970515cbace89484e4c04c084a93e32afa461664 Mon Sep 17 00:00:00 2001 From: BJ Vicks Date: Thu, 12 Jan 2023 14:03:03 -0500 Subject: [PATCH 14/14] Alternate DaoRow rendering for inactive auctions --- compositions/Daos/DaoRow.tsx | 85 +++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/compositions/Daos/DaoRow.tsx b/compositions/Daos/DaoRow.tsx index d161c5f3..ceb48d53 100644 --- a/compositions/Daos/DaoRow.tsx +++ b/compositions/Daos/DaoRow.tsx @@ -34,17 +34,17 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { tokenId: activeAuction?.tokenId ?? '', }) - if (!token || !activeAuction) { - console.log('NOT RENDERING DAO', dao.collectionAddress) - console.log('TOKEN', token, 'AUCTION', activeAuction) - // return null - return ( - - {/* {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} */} - {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} - - ) - } + // if (!token || !activeAuction) { + // console.log('NOT RENDERING DAO', dao.collectionAddress) + // console.log('TOKEN', token, 'AUCTION', activeAuction) + // // return null + // return ( + // + // {/* {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} */} + // {dao.collectionAddress} {token?.tokenId} {activeAuction?.address} + // + // ) + // } // const { tokenId, collectionAddress, highestBidPrice } = activeAuction! // const highestBid = highestBidPrice?.chainTokenPrice?.raw || '0' @@ -52,11 +52,11 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { // const auctionStatus = // Date.now() - parseInt(activeAuction!.endTime) * 1000 > 0 ? 'Settling' : 'Live' - if ( - isAddressMatch('0xd310a3041dfcf14def5ccbc508668974b5da7174', dao.collectionAddress) - ) { - console.log('DUPE?') - } + // if ( + // isAddressMatch('0xd310a3041dfcf14def5ccbc508668974b5da7174', dao.collectionAddress) + // ) { + // console.log('DUPE?') + // } // return ( // @@ -64,33 +64,48 @@ export const DaoRow = ({ dao, index }: { dao: TypeSafeDao; index: number }) => { // // ) + console.log( + 'DAO: ', + dao.collectionAddress, + 'AUCTION: ', + activeAuction?.collectionAddress + ) + + const auctionStatus = !activeAuction + ? 'Inactive' + : Date.now() - parseInt(activeAuction!.endTime) * 1000 > 0 + ? 'Settling' + : 'Live' + return ( - (token && activeAuction && ( - 0 ? 'Settling' : 'Live' - } - /> - )) || - null + // (token && activeAuction && ( + 0 ? 'Settling' : 'Live' + } + /> + // )) || + // null ) } type DaoRowProps = { index: number - tokenId: string + tokenId?: string collectionAddress: string tokenImage?: string collectionName: string - tokenName: string + tokenName?: string highestBid: string treasuryAddress: string auctionStatus: string @@ -99,7 +114,7 @@ type DaoRowProps = { export const DaoRowComponent = ({ index, collectionAddress, - tokenId, + tokenId = '1', tokenImage, tokenName, collectionName,