Skip to content

Commit d3eace9

Browse files
committed
Update loading effect on paging
1 parent cc65c01 commit d3eace9

File tree

2 files changed

+105
-26
lines changed

2 files changed

+105
-26
lines changed

app/(main)/client-page.tsx

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,23 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
431431
const [voteStates, setVoteStates] = useState<Record<string, VoteState>>({})
432432
const [votingItems, setVotingItems] = useState<Set<string>>(new Set())
433433
const [currentPage, setCurrentPage] = useState(1)
434+
const [isLoading, setIsLoading] = useState(false)
435+
const [scrollPosition, setScrollPosition] = useState(0)
434436
const { user, isAuthenticated } = useAuth()
435437

436438
const POSTS_PER_PAGE = 25
437439
const MAX_PAGES = 10
438440

441+
// Save scroll position before page change
442+
useEffect(() => {
443+
const handleScroll = () => {
444+
setScrollPosition(window.scrollY)
445+
}
446+
447+
window.addEventListener('scroll', handleScroll)
448+
return () => window.removeEventListener('scroll', handleScroll)
449+
}, [])
450+
439451
// Update newsItems when initialPosts changes
440452
useEffect(() => {
441453
setNewsItems(initialPosts)
@@ -446,6 +458,8 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
446458
const fetchPostsForPage = async (page: number) => {
447459
if (page < 1 || page > MAX_PAGES) return
448460

461+
setIsLoading(true)
462+
449463
try {
450464
const offset = (page - 1) * POSTS_PER_PAGE
451465

@@ -471,13 +485,15 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
471485
}
472486
} catch (error) {
473487
console.error('Error fetching posts for page:', error)
488+
} finally {
489+
setIsLoading(false)
474490
}
475491
}
476492

477-
// Handle page change
478-
const handlePageChange = (page: number) => {
493+
// Handle page change - now returns a Promise
494+
const handlePageChange = async (page: number) => {
479495
if (page < 1 || page > MAX_PAGES) return
480-
fetchPostsForPage(page)
496+
await fetchPostsForPage(page)
481497
}
482498

483499
// Calculate pagination state
@@ -575,9 +591,33 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
575591
}
576592

577593
return (
578-
<div className="flex-1 flex flex-col sm:flex-row gap-4 lg:gap-6 min-w-0">
594+
<div className={`flex-1 flex flex-col sm:flex-row gap-4 lg:gap-6 min-w-0 transition-all duration-300 ease-out ${
595+
isLoading ? 'opacity-95' : 'opacity-100'
596+
}`}>
597+
{/* Progress Bar */}
598+
{isLoading && (
599+
<div className="fixed top-0 left-0 w-full h-0.5 bg-gray-100 z-50">
600+
<div className="h-full bg-[#4e1cb3] transition-all duration-300 ease-out"
601+
style={{ width: '100%' }}>
602+
<div className="h-full bg-gradient-to-r from-[#4e1cb3] to-[#7c3aed] animate-pulse"></div>
603+
</div>
604+
</div>
605+
)}
606+
579607
{/* Main Content */}
580-
<main className="flex-1 space-y-6 min-w-0">
608+
<main className={`flex-1 space-y-6 min-w-0 transition-opacity duration-200 ${
609+
isLoading ? 'opacity-90' : 'opacity-100'
610+
}`}>
611+
{/* Content Loading Indicator */}
612+
{isLoading && (
613+
<div className="flex items-center justify-center py-6">
614+
<div className="flex items-center gap-2 text-gray-400">
615+
<div className="w-4 h-4 border-2 border-gray-200 border-t-[#4e1cb3] rounded-full animate-spin"></div>
616+
<span className="text-xs font-medium">Updating content...</span>
617+
</div>
618+
</div>
619+
)}
620+
581621
{/* Error Display */}
582622
{error && (
583623
<div className="bg-red-500 rounded-lg p-4 mb-6">
@@ -609,7 +649,10 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
609649
{/* <SearchAndFilter /> */}
610650

611651
{/* News Items List - All posts rendered at once */}
612-
<div className="space-y-4 min-h-screen">
652+
<div className={`space-y-4 min-h-screen transition-all duration-300 ease-out ${
653+
isLoading ? 'scale-98 opacity-90' : 'scale-100 opacity-100'
654+
}`}>
655+
{/* Actual Posts */}
613656
{newsItems.map((item, index) => {
614657
// Calculate the position in the list
615658
const position = index + 1
@@ -625,7 +668,13 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
625668

626669
return (
627670
<div
628-
key={item.id}
671+
key={`${item.id}-${currentPage}`}
672+
className={`transition-all duration-300 ease-out ${
673+
isLoading ? 'opacity-0 translate-y-2' : 'opacity-100 translate-y-0'
674+
}`}
675+
style={{
676+
transitionDelay: `${index * 30}ms`
677+
}}
629678
>
630679
<PostCard
631680
item={item}
@@ -651,6 +700,7 @@ export function ClientPage({ initialPosts, error, sortType = 'score', userId }:
651700
hasNextPage={hasNextPage}
652701
hasPrevPage={hasPrevPage}
653702
onPageChange={handlePageChange}
703+
isLoading={isLoading}
654704
/>
655705
</main>
656706

components/pagination.tsx

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,80 @@
11
"use client"
22

3-
import { ChevronLeft, ChevronRight } from "lucide-react"
3+
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react"
44
import { Button } from "@/components/ui/button"
5+
import { useState } from "react"
56

67
interface PaginationProps {
78
currentPage: number
89
hasNextPage: boolean
910
hasPrevPage: boolean
10-
onPageChange: (page: number) => void
11+
onPageChange: (page: number) => Promise<void>
12+
isLoading?: boolean
1113
}
1214

13-
export function Pagination({ currentPage, hasNextPage, hasPrevPage, onPageChange }: PaginationProps) {
14-
const handlePageChange = (page: number) => {
15-
// Call the page change handler first
16-
onPageChange(page)
17-
// Then immediately jump to top
18-
window.scrollTo(0, 0)
15+
export function Pagination({
16+
currentPage,
17+
hasNextPage,
18+
hasPrevPage,
19+
onPageChange,
20+
isLoading = false
21+
}: PaginationProps) {
22+
const [isNavigating, setIsNavigating] = useState(false)
23+
const [navigatingDirection, setNavigatingDirection] = useState<'prev' | 'next' | null>(null)
24+
25+
const handlePageChange = async (page: number, direction: 'prev' | 'next') => {
26+
if (isNavigating || isLoading) return
27+
28+
setIsNavigating(true)
29+
setNavigatingDirection(direction)
30+
31+
try {
32+
// Call the page change handler and wait for it to complete
33+
await onPageChange(page)
34+
35+
// Always scroll to top after new content loads
36+
window.scrollTo({
37+
top: 0,
38+
behavior: 'smooth'
39+
})
40+
} catch (error) {
41+
console.error('Error changing page:', error)
42+
} finally {
43+
setIsNavigating(false)
44+
setNavigatingDirection(null)
45+
}
1946
}
2047

48+
const isDisabled = isNavigating || isLoading
49+
2150
return (
22-
<div className="flex justify-center items-center gap-2 mt-8 mb-12">
51+
<div className="flex justify-center items-center gap-2 mt-8 mb-12 animate-in fade-in-0 slide-in-from-bottom-1 duration-300">
2352
<Button
2453
variant="ghost"
2554
size="sm"
26-
onClick={() => handlePageChange(currentPage - 1)}
27-
disabled={!hasPrevPage}
55+
onClick={() => handlePageChange(currentPage - 1, 'prev')}
56+
disabled={!hasPrevPage || isDisabled}
2857
aria-label="Go to previous page"
2958
className={`w-6 h-6 p-0 rounded-full transition-all duration-200 ${
30-
hasPrevPage
31-
? 'bg-[#4e1cb3] hover:bg-[#4e1cb3]/80 text-white'
59+
hasPrevPage && !isDisabled
60+
? 'bg-[#4e1cb3] hover:bg-black hover:text-white text-white'
3261
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
33-
}`}
62+
} ${isDisabled ? 'opacity-50' : ''}`}
3463
>
3564
<ChevronLeft className="h-3 w-3" />
3665
</Button>
3766

3867
<Button
3968
variant="ghost"
4069
size="sm"
41-
onClick={() => handlePageChange(currentPage + 1)}
42-
disabled={!hasNextPage}
70+
onClick={() => handlePageChange(currentPage + 1, 'next')}
71+
disabled={!hasNextPage || isDisabled}
4372
aria-label="Go to next page"
4473
className={`w-6 h-6 p-0 rounded-full transition-all duration-200 ${
45-
hasNextPage
46-
? 'bg-[#4e1cb3] hover:bg-[#4e1cb3]/80 text-white'
74+
hasNextPage && !isDisabled
75+
? 'bg-[#4e1cb3] hover:bg-black hover:text-white text-white'
4776
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
48-
}`}
77+
} ${isDisabled ? 'opacity-50' : ''}`}
4978
>
5079
<ChevronRight className="h-3 w-3" />
5180
</Button>

0 commit comments

Comments
 (0)