11import React , { useState , useMemo } from 'react' ;
2- import { PlayIcon } from '@heroicons/react/24/outline' ;
2+ import { PlayIcon , ChevronLeftIcon , ChevronRightIcon } from '@heroicons/react/24/outline' ;
33import { useSavedVideos , useDiscardVideo , useBulkDiscardVideos , useSavedVideoChannels } from '../hooks/useVideos' ;
44import { VideoList } from '../components/video/VideoList' ;
55import { RecentlyDeletedModal } from '../components/video/RecentlyDeletedModal' ;
@@ -28,6 +28,7 @@ export function Saved() {
2828 const [ viewMode , setViewMode ] = useLocalStorage < ViewMode > ( 'saved-view-mode' , 'large' ) ;
2929 const [ isSelectionMode , setIsSelectionMode ] = useState ( false ) ;
3030 const [ selectedIds , setSelectedIds ] = useState < Set < string > > ( new Set ( ) ) ;
31+ const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
3132
3233 const params : SavedVideosParams = useMemo ( ( ) => {
3334 const p : SavedVideosParams = { } ;
@@ -40,10 +41,17 @@ export function Saved() {
4041 if ( order ) {
4142 p . order = order ;
4243 }
44+ p . limit = 100 ;
45+ p . offset = ( currentPage - 1 ) * 100 ;
4346 return p ;
44- } , [ channelYoutubeId , sortBy , order ] ) ;
47+ } , [ channelYoutubeId , sortBy , order , currentPage ] ) ;
4548
46- const { data : videos , isLoading, error, refetch } = useSavedVideos ( params ) ;
49+ // Reset to page 1 when filters change
50+ const handleFilterChange = ( ) => {
51+ setCurrentPage ( 1 ) ;
52+ } ;
53+
54+ const { data, isLoading, error, refetch } = useSavedVideos ( params ) ;
4755 const { data : channelOptions } = useSavedVideoChannels ( ) ;
4856 const discardVideo = useDiscardVideo ( ) ;
4957 const bulkDiscard = useBulkDiscardVideos ( ) ;
@@ -107,9 +115,19 @@ export function Saved() {
107115 if ( selected ) {
108116 setSortBy ( selected . value ) ;
109117 setOrder ( selected . order ) ;
118+ handleFilterChange ( ) ;
110119 }
111120 } ;
112121
122+ const handleChannelChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
123+ setChannelYoutubeId ( e . target . value ) ;
124+ handleFilterChange ( ) ;
125+ } ;
126+
127+ const videos = data ?. videos ?? [ ] ;
128+ const totalCount = data ?. total ?? 0 ;
129+ const totalPages = Math . ceil ( totalCount / 100 ) ;
130+
113131 if ( isLoading ) {
114132 return (
115133 < div className = "flex items-center justify-center min-h-[400px]" >
@@ -140,9 +158,9 @@ export function Saved() {
140158 < div className = "flex flex-col sm:flex-row sm:items-start sm:justify-between mb-6 gap-4" >
141159 < div >
142160 < h1 className = "text-2xl font-bold text-white" > Saved Videos</ h1 >
143- { videos && videos . length > 0 && (
161+ { totalCount > 0 && (
144162 < p className = "text-gray-400 text-sm mt-1" >
145- { videos . length } video{ videos . length !== 1 ? 's' : '' } saved
163+ { totalCount } video{ totalCount !== 1 ? 's' : '' } saved
146164 </ p >
147165 ) }
148166 </ div >
@@ -173,7 +191,7 @@ export function Saved() {
173191 < select
174192 id = "channel-filter"
175193 value = { channelYoutubeId }
176- onChange = { ( e ) => setChannelYoutubeId ( e . target . value ) }
194+ onChange = { handleChannelChange }
177195 className = "w-full bg-gray-700 border border-gray-600 text-white rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
178196 >
179197 < option value = "" > All Channels</ option >
@@ -249,7 +267,7 @@ export function Saved() {
249267 ) }
250268
251269 < VideoList
252- videos = { videos || [ ] }
270+ videos = { videos }
253271 onDiscard = { handleDiscard }
254272 showSaveButton = { false }
255273 showDiscardButton = { false }
@@ -260,6 +278,63 @@ export function Saved() {
260278 onToggleSelect = { handleToggleSelect }
261279 />
262280
281+ { /* Pagination Controls */ }
282+ { totalPages > 1 && (
283+ < div className = "mt-6 flex items-center justify-center gap-2" >
284+ < Button
285+ variant = "secondary"
286+ onClick = { ( ) => setCurrentPage ( p => Math . max ( 1 , p - 1 ) ) }
287+ disabled = { currentPage === 1 }
288+ className = "px-3 py-2"
289+ >
290+ < ChevronLeftIcon className = "w-5 h-5" />
291+ </ Button >
292+
293+ < div className = "flex items-center gap-1" >
294+ { Array . from ( { length : Math . min ( totalPages , 10 ) } , ( _ , i ) => {
295+ // Show first page, last page, current page, and pages around current
296+ let pageNum : number ;
297+ if ( totalPages <= 10 ) {
298+ pageNum = i + 1 ;
299+ } else if ( currentPage <= 5 ) {
300+ pageNum = i < 7 ? i + 1 : ( i === 7 ? - 1 : totalPages ) ;
301+ } else if ( currentPage >= totalPages - 4 ) {
302+ pageNum = i < 2 ? i + 1 : ( i === 2 ? - 1 : totalPages - 6 + i ) ;
303+ } else {
304+ pageNum = i < 2 ? i + 1 : ( i === 2 ? - 1 : ( i === 7 ? - 1 : currentPage - 3 + i ) ) ;
305+ }
306+
307+ if ( pageNum === - 1 ) {
308+ return < span key = { i } className = "px-2 text-gray-500" > ...</ span > ;
309+ }
310+
311+ return (
312+ < button
313+ key = { i }
314+ onClick = { ( ) => setCurrentPage ( pageNum ) }
315+ className = { `px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
316+ currentPage === pageNum
317+ ? 'bg-blue-600 text-white'
318+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
319+ } `}
320+ >
321+ { pageNum }
322+ </ button >
323+ ) ;
324+ } ) }
325+ </ div >
326+
327+ < Button
328+ variant = "secondary"
329+ onClick = { ( ) => setCurrentPage ( p => Math . min ( totalPages , p + 1 ) ) }
330+ disabled = { currentPage === totalPages }
331+ className = "px-3 py-2"
332+ >
333+ < ChevronRightIcon className = "w-5 h-5" />
334+ </ Button >
335+ </ div >
336+ ) }
337+
263338 < RecentlyDeletedModal
264339 isOpen = { showRecentlyDeleted }
265340 onClose = { ( ) => setShowRecentlyDeleted ( false ) }
0 commit comments