add client side pagination
This commit is contained in:
@ -319,8 +319,8 @@ const appendSectionsToHome = async (
|
||||
const ttl = 86_400_000
|
||||
pipeline.zremrangebyscore(key, '-inf', Date.now() - ttl)
|
||||
|
||||
// keep only the top MAX_FEED_ITEMS items
|
||||
pipeline.zremrangebyrank(key, 0, -(MAX_FEED_ITEMS + 1))
|
||||
// keep only the new sections and remove the oldest ones
|
||||
pipeline.zremrangebyrank(key, 0, -(sections.length + 1))
|
||||
|
||||
logger.info('Adding home sections to redis')
|
||||
await pipeline.exec()
|
||||
|
||||
48
packages/web/components/elements/Pagination.tsx
Normal file
48
packages/web/components/elements/Pagination.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from './Button'
|
||||
import { HStack, VStack } from './LayoutPrimitives'
|
||||
|
||||
type PaginationProps<T> = {
|
||||
items: T[]
|
||||
itemsPerPage: number
|
||||
render: (item: T) => React.ReactNode
|
||||
}
|
||||
|
||||
const Pagination = <T,>({
|
||||
items,
|
||||
itemsPerPage,
|
||||
render,
|
||||
}: PaginationProps<T>) => {
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const maxPage = Math.ceil(items.length / itemsPerPage)
|
||||
|
||||
function createChangePageHandler(page: number) {
|
||||
return function handlePageChange() {
|
||||
setCurrentPage(page)
|
||||
}
|
||||
}
|
||||
|
||||
const itemsToShow = items.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
currentPage * itemsPerPage
|
||||
)
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
{itemsToShow.map(render)}
|
||||
<HStack>
|
||||
{Array.from({ length: maxPage }, (_, i) => i + 1).map((pageNum) => (
|
||||
<Button
|
||||
key={pageNum}
|
||||
onClick={createChangePageHandler(pageNum)}
|
||||
disabled={pageNum === currentPage}
|
||||
>
|
||||
{pageNum}
|
||||
</Button>
|
||||
))}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
@ -1,18 +1,17 @@
|
||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
||||
import { styled } from '@stitches/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMemo } from 'react'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import { AddToLibraryActionIcon } from '../../components/elements/icons/home/AddToLibraryActionIcon'
|
||||
import { ArchiveActionIcon } from '../../components/elements/icons/home/ArchiveActionIcon'
|
||||
import { CommentActionIcon } from '../../components/elements/icons/home/CommentActionIcon'
|
||||
import { RemoveActionIcon } from '../../components/elements/icons/home/RemoveActionIcon'
|
||||
import { ShareActionIcon } from '../../components/elements/icons/home/ShareActionIcon'
|
||||
import Pagination from '../../components/elements/Pagination'
|
||||
import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
|
||||
import { theme } from '../../components/tokens/stitches.config'
|
||||
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
|
||||
import {
|
||||
HStack,
|
||||
SpanBox,
|
||||
VStack,
|
||||
} from './../../components/elements/LayoutPrimitives'
|
||||
|
||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
||||
import { Button } from '../../components/elements/Button'
|
||||
import {
|
||||
HomeItem,
|
||||
HomeItemSource,
|
||||
@ -20,14 +19,15 @@ import {
|
||||
HomeSection,
|
||||
useGetHomeItems,
|
||||
} from '../../lib/networking/queries/useGetHome'
|
||||
import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
|
||||
import { theme } from '../../components/tokens/stitches.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
SubscriptionType,
|
||||
useGetSubscriptionsQuery,
|
||||
} from '../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
HStack,
|
||||
SpanBox,
|
||||
VStack,
|
||||
} from './../../components/elements/LayoutPrimitives'
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const homeData = useGetHomeItems()
|
||||
@ -59,6 +59,12 @@ export default function Home(): JSX.Element {
|
||||
{homeData.sections?.map((homeSection, idx) => {
|
||||
switch (homeSection.layout) {
|
||||
case 'just added':
|
||||
return (
|
||||
<JustReadHomeSection
|
||||
key={`section-${idx}`}
|
||||
homeSection={homeSection}
|
||||
/>
|
||||
)
|
||||
case 'long':
|
||||
return (
|
||||
<LongHomeSection
|
||||
@ -66,6 +72,13 @@ export default function Home(): JSX.Element {
|
||||
homeSection={homeSection}
|
||||
/>
|
||||
)
|
||||
case 'quick links':
|
||||
return (
|
||||
<QuickLinksHomeSection
|
||||
key={`section-${idx}`}
|
||||
homeSection={homeSection}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</VStack>
|
||||
@ -77,13 +90,92 @@ type HomeSectionProps = {
|
||||
homeSection: HomeSection
|
||||
}
|
||||
|
||||
const JustReadHomeSection = (props: HomeSectionProps): JSX.Element => {
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
css={{
|
||||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
</SpanBox>
|
||||
|
||||
{props.homeSection.items.map((homeItem) => {
|
||||
return <JustReadItemView key={homeItem.id} homeItem={homeItem} />
|
||||
})}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
const LongHomeSection = (props: HomeSectionProps): JSX.Element => {
|
||||
return (
|
||||
<SpanBox css={{ width: '100%' }}>
|
||||
{props.homeSection.items.map((homeItem) => {
|
||||
return <HomeItemView key={homeItem.id} homeItem={homeItem} />
|
||||
})}
|
||||
</SpanBox>
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
css={{
|
||||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
</SpanBox>
|
||||
|
||||
<Pagination
|
||||
items={props.homeSection.items}
|
||||
itemsPerPage={10}
|
||||
render={(homeItem) => (
|
||||
<LongHomeItemView key={homeItem.id} homeItem={homeItem} />
|
||||
)}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
const QuickLinksHomeSection = (props: HomeSectionProps): JSX.Element => {
|
||||
return (
|
||||
<VStack
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
css={{
|
||||
fontFamily: '$inter',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '$readerText',
|
||||
}}
|
||||
>
|
||||
{props.homeSection.title}
|
||||
</SpanBox>
|
||||
|
||||
<Pagination
|
||||
items={props.homeSection.items}
|
||||
itemsPerPage={15}
|
||||
render={(homeItem) => (
|
||||
<QuickLinkHomeItemView key={homeItem.id} homeItem={homeItem} />
|
||||
)}
|
||||
/>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
@ -130,7 +222,59 @@ const Title = (props: HomeItemViewProps): JSX.Element => {
|
||||
)
|
||||
}
|
||||
|
||||
const HomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
||||
const JustReadItemView = (props: HomeItemViewProps): JSX.Element => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
width: '100%',
|
||||
padding: '20px',
|
||||
borderRadius: '5px',
|
||||
'&:hover': {
|
||||
bg: '$thBackground',
|
||||
borderRadius: '0px',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(props.homeItem.url, '_blank')
|
||||
} else {
|
||||
router.push(props.homeItem.url)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HStack css={{ width: '100%', gap: '5px' }}>
|
||||
<VStack css={{ gap: '15px' }}>
|
||||
<HStack
|
||||
distribution="start"
|
||||
alignment="center"
|
||||
css={{ gap: '5px', lineHeight: '1' }}
|
||||
>
|
||||
<SourceInfo homeItem={props.homeItem} />
|
||||
<TimeAgo homeItem={props.homeItem} />
|
||||
</HStack>
|
||||
<Title homeItem={props.homeItem} />
|
||||
</VStack>
|
||||
<SpanBox css={{ ml: 'auto' }}>
|
||||
{props.homeItem.thumbnail && (
|
||||
<CoverImage
|
||||
css={{
|
||||
mt: '6px',
|
||||
width: '120px',
|
||||
height: '70px',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
src={props.homeItem.thumbnail}
|
||||
></CoverImage>
|
||||
)}
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
const LongHomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
@ -204,6 +348,39 @@ const HomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
||||
)
|
||||
}
|
||||
|
||||
const QuickLinkHomeItemView = (props: HomeItemViewProps): JSX.Element => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
'&:hover': {
|
||||
bg: '$thBackground',
|
||||
borderRadius: '0px',
|
||||
},
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(props.homeItem.url, '_blank')
|
||||
} else {
|
||||
router.push(props.homeItem.url)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TimeAgo homeItem={props.homeItem} />
|
||||
<Title homeItem={props.homeItem} />
|
||||
<SpanBox
|
||||
css={{ fontFamily: '$inter', fontSize: '13px', lineHeight: '23px' }}
|
||||
>
|
||||
{props.homeItem.previewContent}
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
const SiteIconSmall = styled('img', {
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
|
||||
Reference in New Issue
Block a user