From ca185c0f3665b0d0931fdcb108a0fb7caa7c521a Mon Sep 17 00:00:00 2001 From: Camila Sosa Morales Date: Thu, 11 May 2023 11:38:03 -0300 Subject: [PATCH] feat: infinite scroll for hosted NFAs list --- ui/graphql/queries.graphql | 58 ++++---- ui/src/components/nfa-card/nfa-card.tsx | 3 +- ui/src/providers/apollo-provider.tsx | 4 + ui/src/utils/format.ts | 4 +- .../explore-list/nfa-list.fragment.tsx | 4 +- ui/src/views/explore/explore.context.tsx | 4 +- .../fragments/main/main-ap-list.fragment.tsx | 139 ++++++++++++------ .../fragments/main/main-header.fragment.tsx | 18 ++- .../fragments/main/skeleton.ap-list.tsx | 21 +++ .../fragments/skeleton.fragment.tsx | 6 +- .../views/indexed-nfa/indexed-nfa.context.tsx | 26 +++- 11 files changed, 204 insertions(+), 83 deletions(-) create mode 100644 ui/src/views/indexed-nfa/fragments/main/skeleton.ap-list.tsx diff --git a/ui/graphql/queries.graphql b/ui/graphql/queries.graphql index e68bcd3..ae26842 100644 --- a/ui/graphql/queries.graphql +++ b/ui/graphql/queries.graphql @@ -22,50 +22,56 @@ query lastNFAsPaginated( accessPoints { id } - } -} - -query totalTokens { - tokens { - id - } -} - -query getLatestNFAs { - tokens { - id - name + verified } } query getNFADetail($id: ID!) { token(id: $id) { - tokenId - owner { + accessPoints { id } - name description - ENS - externalURL - logo color createdAt - accessPoints { - createdAt - contentVerified - owner { - id - } + ENS + externalURL + gitRepository { + id + } + logo + name + owner { id } verified verifier { id } - gitRepository { + tokenId + } +} + +query getAccessPointsNFA( + $tokenId: String! + $orderBy: AccessPoint_orderBy + $orderDirection: OrderDirection + $pageSize: Int + $skip: Int +) { + accessPoints( + where: { token: $tokenId } + orderDirection: $orderDirection + orderBy: $orderBy + first: $pageSize + skip: $skip + ) { + contentVerified + createdAt + owner { id } + id } } diff --git a/ui/src/components/nfa-card/nfa-card.tsx b/ui/src/components/nfa-card/nfa-card.tsx index 3883dd0..4f95991 100644 --- a/ui/src/components/nfa-card/nfa-card.tsx +++ b/ui/src/components/nfa-card/nfa-card.tsx @@ -61,8 +61,7 @@ export const NFACard: React.FC = forwardStyledRef< }} > {data.name} - {/* TODO: set correct value when it gets available on contract side */} - 0.5} /> + diff --git a/ui/src/providers/apollo-provider.tsx b/ui/src/providers/apollo-provider.tsx index 77d2d5b..5e9e9f0 100644 --- a/ui/src/providers/apollo-provider.tsx +++ b/ui/src/providers/apollo-provider.tsx @@ -46,6 +46,10 @@ const client = new ApolloClient({ keyArgs: ['where', 'orderBy', 'orderDirection'], merge: mergeByKey('id'), }, + accessPoints: { + keyArgs: ['where', 'orderBy', 'orderDirection'], + merge: mergeByKey('id'), + }, }, }, }, diff --git a/ui/src/utils/format.ts b/ui/src/utils/format.ts index d5eeb18..653b946 100644 --- a/ui/src/utils/format.ts +++ b/ui/src/utils/format.ts @@ -13,7 +13,9 @@ export const contractAddress = (address: string): string => { export const getRepositoryFromURL = (url: string): string => { const urlSplitted = url.split('/'); - return `${urlSplitted[3]}/${urlSplitted[4]}`; + return urlSplitted[3] && urlSplitted[4] + ? `${urlSplitted[3]}/${urlSplitted[4]}` + : ''; }; export const getDate = (date: number): string => { diff --git a/ui/src/views/explore/explore-list/nfa-list.fragment.tsx b/ui/src/views/explore/explore-list/nfa-list.fragment.tsx index 22593cf..bfe5df1 100644 --- a/ui/src/views/explore/explore-list/nfa-list.fragment.tsx +++ b/ui/src/views/explore/explore-list/nfa-list.fragment.tsx @@ -46,7 +46,9 @@ export const NFAListFragment: React.FC = () => { skip: pageNumber * pageSize, //skip is for the pagination }, onCompleted: (data) => { - if (data.tokens.length - tokens.length < pageSize) setEndReached(true); + if (data.tokens.length - tokens.length < pageSize) { + setEndReached(true); + } }, }); diff --git a/ui/src/views/explore/explore.context.tsx b/ui/src/views/explore/explore.context.tsx index a02d2e2..ea04e78 100644 --- a/ui/src/views/explore/explore.context.tsx +++ b/ui/src/views/explore/explore.context.tsx @@ -10,7 +10,7 @@ export type ExploreContext = { pageNumber: number; endReached: boolean; setSearch: (search: string) => void; - setOrderBy: (orderBy: string) => void; + setOrderBy: (orderBy: Token_orderBy) => void; setOrderDirection: (orderDirection: OrderDirection) => void; setPageNumber: (pageNumber: number) => void; setEndReached: (isEndReaced: boolean) => void; @@ -29,7 +29,7 @@ export abstract class Explore { children, }: Explore.ProviderProps) => { const [search, setSearch] = useState(''); - const [orderBy, setOrderBy] = useState('tokenId'); + const [orderBy, setOrderBy] = useState('tokenId'); const [orderDirection, setOrderDirection] = useState('desc'); const [pageNumber, setPageNumber] = useState(0); diff --git a/ui/src/views/indexed-nfa/fragments/main/main-ap-list.fragment.tsx b/ui/src/views/indexed-nfa/fragments/main/main-ap-list.fragment.tsx index 00b1453..7b2f429 100644 --- a/ui/src/views/indexed-nfa/fragments/main/main-ap-list.fragment.tsx +++ b/ui/src/views/indexed-nfa/fragments/main/main-ap-list.fragment.tsx @@ -1,57 +1,114 @@ +import { useQuery } from '@apollo/client'; +import { ethers } from 'ethers'; +import { useEffect } from 'react'; + import Rectangle1 from '@/assets/Rectangle-199.png'; -import Rectangle2 from '@/assets/Rectangle-200.png'; -import Rectangle3 from '@/assets/Rectangle-201.png'; import { Flex, ResolvedAddress, Text } from '@/components'; -import { getTimeSince } from '@/utils'; +import { + AccessPoint as AccessPointType, + getAccessPointsNFADocument, + Owner, +} from '@/graphclient'; +import { useWindowScrollEnd } from '@/hooks'; +import { AppLog, getTimeSince } from '@/utils'; import { IndexedNFA } from '../../indexed-nfa.context'; import { IndexedNFAStyles as S } from '../../indexed-nfa.styles'; +import { SkeletonAccessPointsListFragment } from './skeleton.ap-list'; -//TODO remove -const thumbnailMocks = [Rectangle1, Rectangle2, Rectangle3]; +type AccessPointProps = { + data: Pick & { + owner: Pick; + }; +}; + +const AccessPoint: React.FC = ({ + data, +}: AccessPointProps) => { + const { id: name, owner, createdAt } = data; + return ( + + + + + + {name} + + + {owner.id} + + + {/* TODO get from bunny CDN */} + 220 views + + {getTimeSince(createdAt)} + + + + ); +}; + +const pageSize = 10; //Set this size to test pagination export const AccessPointsListFragment: React.FC = () => { const { - nfa: { accessPoints }, + nfa: { tokenId }, + orderDirection, + pageNumber, + endReached, + setEndReached, + setPageNumber, } = IndexedNFA.useContext(); + const handleError = (error: unknown): void => { + AppLog.errorToast( + 'There was an error trying to get the access points', + error + ); + }; + + const { + loading: isLoading, + data: { accessPoints } = { accessPoints: [] }, + error: queryError, + } = useQuery(getAccessPointsNFADocument, { + skip: tokenId === undefined, + fetchPolicy: 'cache-and-network', + variables: { + tokenId: ethers.utils.hexlify(Number(tokenId)), + orderDirection: orderDirection, + orderBy: 'createdAt', + pageSize, + skip: pageNumber * pageSize, //skip is for the pagination + }, + onCompleted(data) { + if (data.accessPoints.length - accessPoints.length < pageSize) + setEndReached(true); + }, + onError(error) { + handleError(error); + }, + }); + + useEffect(() => { + // Update page number when there are cached tokens + setPageNumber(Math.ceil(accessPoints.length / pageSize)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useWindowScrollEnd(() => { + // debugger; + if (isLoading || endReached || queryError) return; + setPageNumber(pageNumber + 1); + }); + return ( - {accessPoints && accessPoints?.length > 0 ? ( - accessPoints.map((item, index) => ( - - - - - - {item.id} - - - {item.owner.id} - - - {/* TODO get from bunny CDN */} - 220 views - - - {getTimeSince(item.createdAt)} - - - - - )) - ) : ( + {accessPoints.map((item, index) => ( + + ))} + {isLoading && } + {!isLoading && accessPoints.length === 0 && (

No hosted NFAs

diff --git a/ui/src/views/indexed-nfa/fragments/main/main-header.fragment.tsx b/ui/src/views/indexed-nfa/fragments/main/main-header.fragment.tsx index c866d75..feb44a8 100644 --- a/ui/src/views/indexed-nfa/fragments/main/main-header.fragment.tsx +++ b/ui/src/views/indexed-nfa/fragments/main/main-header.fragment.tsx @@ -1,28 +1,38 @@ import { useState } from 'react'; +import { OrderDirection } from '@/../.graphclient'; import { Combobox, Flex } from '@/components'; +import { AppLog } from '@/utils'; +import { IndexedNFA } from '../../indexed-nfa.context'; import { IndexedNFAStyles as S } from '../../indexed-nfa.styles'; type SortItem = { - value: string; + value: OrderDirection; label: string; }; const orderResults: SortItem[] = [ - { value: 'newest', label: 'Newest' }, - { value: 'oldest', label: 'Oldest' }, + { value: 'desc', label: 'Newest' }, + { value: 'asc', label: 'Oldest' }, ]; export const Header: React.FC = () => { + const { setPageNumber, setOrderDirection, setEndReached } = + IndexedNFA.useContext(); const [selectedValue, setSelectedValue] = useState(orderResults[0]); const handleSortChange = (item: SortItem | undefined): void => { - //TODO integrate with context and sort if (item) { setSelectedValue(item); + setPageNumber(0); + setEndReached(false); + setOrderDirection(item.value); + } else { + AppLog.errorToast('Error selecting sort option. Try again'); } }; + return ( <> diff --git a/ui/src/views/indexed-nfa/fragments/main/skeleton.ap-list.tsx b/ui/src/views/indexed-nfa/fragments/main/skeleton.ap-list.tsx new file mode 100644 index 0000000..ce64a83 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/main/skeleton.ap-list.tsx @@ -0,0 +1,21 @@ +import { IndexedNFAStyles as S } from '../../indexed-nfa.styles'; + +const SkeletonAccessPoint: React.FC = () => ( + + + + + + + + + +); + +export const SkeletonAccessPointsListFragment: React.FC = () => ( + + + + + +); diff --git a/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx index e1e05a4..c7379c6 100644 --- a/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx +++ b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx @@ -1,4 +1,5 @@ import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; +import { SkeletonAccessPointsListFragment } from './main/skeleton.ap-list'; export const IndexedNFASkeletonFragment: React.FC = () => ( @@ -7,10 +8,7 @@ export const IndexedNFASkeletonFragment: React.FC = () => ( - - - - + ); diff --git a/ui/src/views/indexed-nfa/indexed-nfa.context.tsx b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx index fc99dac..ef044b9 100644 --- a/ui/src/views/indexed-nfa/indexed-nfa.context.tsx +++ b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx @@ -1,4 +1,6 @@ -import { Owner, Token } from '@/graphclient'; +import { useState } from 'react'; + +import { OrderDirection, Owner, Token } from '@/graphclient'; import { createContext } from '@/utils'; const [Provider, useContext] = createContext({ @@ -10,7 +12,21 @@ const [Provider, useContext] = createContext({ export const IndexedNFA = { useContext, Provider: ({ children, nfa }: IndexedNFA.ProviderProps): JSX.Element => { - return {children}; + const [orderDirection, setOrderDirection] = + useState('desc'); + const [pageNumber, setPageNumber] = useState(0); + const [endReached, setEndReached] = useState(false); + + const context = { + nfa, + orderDirection, + pageNumber, + endReached, + setOrderDirection, + setPageNumber, + setEndReached, + }; + return {children}; }, }; @@ -19,6 +35,12 @@ export namespace IndexedNFA { nfa: Omit & { owner: Pick; }; + orderDirection: OrderDirection; + pageNumber: number; + endReached: boolean; + setOrderDirection: (orderDirection: OrderDirection) => void; + setPageNumber: (pageNumber: number) => void; + setEndReached: (isEndReaced: boolean) => void; }; export type ProviderProps = {