Skip to content

Commit

Permalink
fix: profile proposals pagination (#1384)
Browse files Browse the repository at this point in the history
* fix: profile proposals pagination

* fix: empty state

* feat: add isFetching check in view more button
  • Loading branch information
andyesp authored Nov 1, 2023
1 parent 9b3a1a9 commit c3e8589
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 187 deletions.
4 changes: 4 additions & 0 deletions src/components/Common/FullWidthButton.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ a.ui.fluid.primary.button.FullWidthButton {
align-items: center;
justify-content: center;
}

.FullWidthButton__Loader {
position: relative;
}
20 changes: 15 additions & 5 deletions src/components/Common/FullWidthButton.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import classNames from 'classnames'
import { Button } from 'decentraland-ui/dist/components/Button/Button'
import { Loader } from 'decentraland-ui/dist/components/Loader/Loader'

import './FullWidthButton.css'

interface Props {
onClick?: (param?: unknown) => void
onClick?: () => void
children: React.ReactNode
className?: string
link?: string
href?: string
newWindow?: boolean
loading?: boolean
}

const FullWidthButton = ({ onClick, children, className, link, newWindow = false }: Props) => {
// TODO: FullWidthButton should render Link when href is provided
const FullWidthButton = ({ onClick, children, className, href, newWindow = false, loading }: Props) => {
return (
<Button
primary
fluid
disabled={loading}
className={classNames('FullWidthButton', className)}
onClick={onClick}
target={newWindow ? '_blank' : ''}
rel={newWindow ? 'noopener noreferrer' : ''}
href={link}
href={href}
>
{children}
{loading ? (
<div className="FullWidthButton__Loader">
<Loader size="tiny" active />
</div>
) : (
children
)}
</Button>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Home/ActiveCommunityGrants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const ActiveCommunityGrants = () => {
))}
</div>
)}
<FullWidthButton link={locations.projects()}>
<FullWidthButton href={locations.projects()}>
{t('page.home.active_community_grants.view_all_grants')}
</FullWidthButton>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Home/OpenProposals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const OpenProposals = ({ endingSoonProposals, isLoadingProposals }: Props) => {
</>
)}
</BoxTabsContainer>
<FullWidthButton className="OpenProposals__ViewAllButton" link={locations.proposals()}>
<FullWidthButton className="OpenProposals__ViewAllButton" href={locations.proposals()}>
{t('page.home.open_proposals.view_all_proposals')}
</FullWidthButton>
</div>
Expand Down
57 changes: 15 additions & 42 deletions src/components/Profile/CoAuthoringTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@ import useAuthContext from 'decentraland-gatsby/dist/context/Auth/useAuthContext
import { CoauthorAttributes } from '../../entities/Coauthor/types'
import { isSameAddress } from '../../entities/Snapshot/utils'
import useFormatMessage from '../../hooks/useFormatMessage'
import usePaginatedProposals from '../../hooks/usePaginatedProposals'
import Empty from '../Common/Empty'
import FullWidthButton from '../Common/FullWidthButton'
import SkeletonBars from '../Common/SkeletonBars'
import Watermelon from '../Icon/Watermelon'

import ProposalCreatedItem from './ProposalCreatedItem'
import { ProposalCreatedList } from './ProposalCreatedList'

interface Props {
address?: string
Expand All @@ -23,43 +18,21 @@ const CoAuthoringTab = ({ address, pendingCoauthorRequests }: Props) => {
const isLoggedUserProfile = isSameAddress(account, address || '')
const user = isLoggedUserProfile ? account : address

const { proposals, hasMoreProposals, loadMore, isLoadingProposals } = usePaginatedProposals({
load: !!user,
...(!!user && { user: user?.toLowerCase() }),
coauthor: true,
})

return (
<>
{isLoadingProposals && <SkeletonBars amount={proposals.length || 5} height={89} />}
{!isLoadingProposals && (
<>
{proposals.length > 0 ? (
proposals.map((proposal) => (
<ProposalCreatedItem
key={proposal.id}
proposal={proposal}
showCoauthoring
hasCoauthorRequests={!!pendingCoauthorRequests?.find((req) => req.proposal_id === proposal.id)}
/>
))
) : (
<Empty
className="ActivityBox__Empty"
icon={<Watermelon />}
description={
isLoggedUserProfile
? t('page.profile.activity.coauthoring.empty_logged_user')
: t('page.profile.activity.coauthoring.empty')
}
/>
)}
</>
)}
{!isLoadingProposals && hasMoreProposals && (
<FullWidthButton onClick={loadMore}>{t('page.profile.activity.button')}</FullWidthButton>
)}
</>
<ProposalCreatedList
proposalsFilter={{
load: !!user,
...(!!user && { user: user?.toLowerCase() }),
coauthor: true,
}}
emptyDescriptionText={
isLoggedUserProfile
? t('page.profile.activity.coauthoring.empty_logged_user')
: t('page.profile.activity.coauthoring.empty')
}
showCoauthoring
pendingCoauthorRequests={pendingCoauthorRequests}
/>
)
}

Expand Down
58 changes: 58 additions & 0 deletions src/components/Profile/ProposalCreatedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { CoauthorAttributes } from '../../entities/Coauthor/types'
import useFormatMessage from '../../hooks/useFormatMessage'
import useInfiniteProposals from '../../hooks/useInfiniteProposals'
import { UseProposalsFilter } from '../../hooks/useProposals'
import Empty from '../Common/Empty'
import FullWidthButton from '../Common/FullWidthButton'
import SkeletonBars from '../Common/SkeletonBars'
import Watermelon from '../Icon/Watermelon'

import ProposalCreatedItem from './ProposalCreatedItem'

interface Props {
proposalsFilter: Partial<UseProposalsFilter>
pendingCoauthorRequests?: CoauthorAttributes[]
emptyDescriptionText: string
showCoauthoring?: boolean
}

export function ProposalCreatedList({
proposalsFilter,
pendingCoauthorRequests,
emptyDescriptionText,
showCoauthoring = false,
}: Props) {
const t = useFormatMessage()
const { proposals, isLoadingProposals, isFetchingNextPage, isFetchingProposals, hasMoreProposals, loadMore } =
useInfiniteProposals(proposalsFilter)
const hasProposals = proposals && proposals?.[0]?.total > 0

return (
<>
{isLoadingProposals && <SkeletonBars amount={3} height={89} />}
{!isLoadingProposals && (
<>
{hasProposals ? (
proposals.map((page) =>
page.data.map((proposal) => (
<ProposalCreatedItem
key={proposal.id}
proposal={proposal}
showCoauthoring={showCoauthoring}
hasCoauthorRequests={!!pendingCoauthorRequests?.find((req) => req.proposal_id === proposal.id)}
/>
))
)
) : (
<Empty className="ActivityBox__Empty" icon={<Watermelon />} description={emptyDescriptionText} />
)}
{hasMoreProposals && (
<FullWidthButton loading={isFetchingNextPage || isFetchingProposals} onClick={loadMore}>
{t('page.profile.activity.button')}
</FullWidthButton>
)}
</>
)}
</>
)
}
36 changes: 8 additions & 28 deletions src/components/Profile/ProposalsCreatedTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ import useAuthContext from 'decentraland-gatsby/dist/context/Auth/useAuthContext

import { isSameAddress } from '../../entities/Snapshot/utils'
import useFormatMessage from '../../hooks/useFormatMessage'
import usePaginatedProposals from '../../hooks/usePaginatedProposals'
import Empty from '../Common/Empty'
import FullWidthButton from '../Common/FullWidthButton'
import SkeletonBars from '../Common/SkeletonBars'
import Watermelon from '../Icon/Watermelon'

import ProposalCreatedItem from './ProposalCreatedItem'
import { ProposalCreatedList } from './ProposalCreatedList'

interface Props {
address?: string
Expand All @@ -21,33 +16,18 @@ const ProposalsCreatedTab = ({ address }: Props) => {
const isLoggedUserAddress = isSameAddress(account, address || '')
const user = isLoggedUserAddress ? account : address

const { proposals, hasMoreProposals, loadMore, isLoadingProposals } = usePaginatedProposals({
load: !!user,
...(!!user && { user: user?.toLowerCase() }),
})

const emptyDescriptionKey = isLoggedUserAddress
? 'page.profile.activity.my_proposals.empty'
: 'page.profile.created_proposals.empty'

return (
<>
{isLoadingProposals && <SkeletonBars amount={proposals.length || 5} height={89} />}
{!isLoadingProposals && (
<>
{proposals.length > 0 ? (
proposals.map((proposal) => (
<ProposalCreatedItem key={`${proposal.id}#${Math.random()}`} proposal={proposal} />
))
) : (
<Empty className="ActivityBox__Empty" icon={<Watermelon />} description={t(emptyDescriptionKey)} />
)}
</>
)}
{!isLoadingProposals && hasMoreProposals && (
<FullWidthButton onClick={loadMore}>{t('page.profile.activity.button')}</FullWidthButton>
)}
</>
<ProposalCreatedList
proposalsFilter={{
load: !!user,
...(!!user && { user: user?.toLowerCase() }),
}}
emptyDescriptionText={t(emptyDescriptionKey)}
/>
)
}

Expand Down
38 changes: 8 additions & 30 deletions src/components/Profile/WatchlistTab.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,21 @@
import useAuthContext from 'decentraland-gatsby/dist/context/Auth/useAuthContext'

import useFormatMessage from '../../hooks/useFormatMessage'
import usePaginatedProposals from '../../hooks/usePaginatedProposals'
import Empty from '../Common/Empty'
import FullWidthButton from '../Common/FullWidthButton'
import SkeletonBars from '../Common/SkeletonBars'
import Watermelon from '../Icon/Watermelon'

import ProposalCreatedItem from './ProposalCreatedItem'
import { ProposalCreatedList } from './ProposalCreatedList'

const WatchlistTab = () => {
const [account] = useAuthContext()
const t = useFormatMessage()

const { proposals, hasMoreProposals, loadMore, isLoadingProposals } = usePaginatedProposals({
load: !!account,
...(!!account && { subscribed: account }),
})

return (
<>
{isLoadingProposals && <SkeletonBars amount={proposals.length || 5} height={89} />}
{!isLoadingProposals && (
<>
{proposals.length > 0 ? (
proposals.map((proposal) => <ProposalCreatedItem key={proposal.id} proposal={proposal} />)
) : (
<Empty
className="ActivityBox__Empty"
icon={<Watermelon />}
description={t('page.profile.activity.watchlist.empty')}
/>
)}
</>
)}
{!isLoadingProposals && hasMoreProposals && (
<FullWidthButton onClick={loadMore}>{t('page.profile.activity.button')}</FullWidthButton>
)}
</>
<ProposalCreatedList
proposalsFilter={{
load: !!account,
...(!!account && { subscribed: account }),
}}
emptyDescriptionText={t('page.profile.activity.watchlist.empty')}
/>
)
}

Expand Down
34 changes: 34 additions & 0 deletions src/hooks/useInfiniteProposals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useInfiniteQuery } from '@tanstack/react-query'

import { DEFAULT_QUERY_STALE_TIME } from './constants'
import { UseProposalsFilter, getProposalsQueryFn } from './useProposals'

const DEFAULT_ITEMS_PER_PAGE = 5

export default function useInfiniteProposals(filter: Partial<UseProposalsFilter> = {}) {
const {
data: proposals,
isLoading: isLoadingProposals,
isFetching: isFetchingProposals,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: [`infinite-proposals#${JSON.stringify(filter)}`],
queryFn: getProposalsQueryFn(filter, DEFAULT_ITEMS_PER_PAGE),
staleTime: DEFAULT_QUERY_STALE_TIME,
getNextPageParam: (lastPage, pages) => {
const fetchedTotal = pages.reduce((acc, currentValue) => acc + currentValue.data.length, 0)
return lastPage.total > fetchedTotal ? pages.length : undefined
},
})

return {
proposals: proposals?.pages,
isLoadingProposals,
isFetchingProposals,
isFetchingNextPage,
hasMoreProposals: !!hasNextPage,
loadMore: fetchNextPage,
}
}
30 changes: 0 additions & 30 deletions src/hooks/usePaginatedProposals.ts

This file was deleted.

Loading

0 comments on commit c3e8589

Please sign in to comment.