11'use client' ;
22
3- import { useState , useEffect , useRef } from 'react' ;
3+ import { useState , useEffect , useRef , useMemo , useCallback } from 'react' ;
44import { Search } from 'lucide-react' ;
55import { VideoCard } from './video-card' ;
66import { EmptyState } from './empty-state' ;
7+ import { VideoFeed } from '@/components/feed/video-feed' ;
78import { Skeleton } from '@/components/ui/skeleton' ;
89import { Button } from '@/components/ui/button' ;
10+ import {
11+ Dialog ,
12+ DialogPortal ,
13+ DialogOverlay ,
14+ DialogClose ,
15+ } from '@/components/ui/dialog' ;
16+ import * as DialogPrimitive from '@radix-ui/react-dialog' ;
17+ import { X } from 'lucide-react' ;
918import { useVideos } from '@/hooks/use-videos' ;
19+ import { getVideos } from '@/lib/api/videos' ;
20+ import type { Video } from '@/lib/types/video' ;
1021
1122const STATUS_OPTIONS = [
1223 { label : 'All' , value : '' } ,
@@ -21,6 +32,7 @@ export function VideoGrid() {
2132 const [ searchInput , setSearchInput ] = useState ( '' ) ;
2233 const [ debouncedSearch , setDebouncedSearch ] = useState ( '' ) ;
2334 const [ statusFilter , setStatusFilter ] = useState ( '' ) ;
35+ const [ watchVideoId , setWatchVideoId ] = useState < string | null > ( null ) ;
2436 const debounceRef = useRef < ReturnType < typeof setTimeout > > ( undefined ) ;
2537
2638 useEffect ( ( ) => {
@@ -42,6 +54,29 @@ export function VideoGrid() {
4254 const total = data ?. total ?? 0 ;
4355 const totalPages = Math . ceil ( total / 12 ) ;
4456
57+ const feedVideos = useMemo (
58+ ( ) => videos . filter ( ( v ) => v . status === 'READY' ) ,
59+ [ videos ] ,
60+ ) ;
61+
62+ const feedPageRef = useRef ( page ) ;
63+ feedPageRef . current = page ;
64+
65+ const handleLoadMore = useCallback ( async ( ) : Promise < Video [ ] > => {
66+ feedPageRef . current += 1 ;
67+ try {
68+ const result = await getVideos (
69+ feedPageRef . current ,
70+ 12 ,
71+ debouncedSearch || undefined ,
72+ statusFilter || undefined ,
73+ ) ;
74+ return result . data . filter ( ( v ) => v . status === 'READY' ) ;
75+ } catch {
76+ return [ ] ;
77+ }
78+ } , [ debouncedSearch , statusFilter ] ) ;
79+
4580 return (
4681 < div className = "space-y-6" >
4782 < div className = "flex flex-col gap-4 sm:flex-row" >
@@ -105,7 +140,7 @@ export function VideoGrid() {
105140 key = { video . id }
106141 style = { { animationDelay : `${ index * 50 } ms` } }
107142 >
108- < VideoCard video = { video } />
143+ < VideoCard video = { video } onWatch = { setWatchVideoId } />
109144 </ div >
110145 ) ) }
111146 </ div >
@@ -135,6 +170,36 @@ export function VideoGrid() {
135170 ) }
136171 </ >
137172 ) }
173+
174+ < Dialog
175+ open = { watchVideoId !== null }
176+ onOpenChange = { ( open ) => { if ( ! open ) setWatchVideoId ( null ) ; } }
177+ >
178+ < DialogPortal >
179+ < DialogOverlay className = "bg-black/20 backdrop-blur-[24px] saturate-[180%]" />
180+ < DialogPrimitive . Content
181+ className = "fixed inset-0 z-50 flex items-center justify-center p-4 focus:outline-none"
182+ aria-describedby = { undefined }
183+ >
184+ < DialogPrimitive . Title className = "sr-only" >
185+ Video Feed
186+ </ DialogPrimitive . Title >
187+ < div className = "relative h-full max-h-[85vh] w-full max-w-2xl overflow-hidden rounded-[0.625rem]" >
188+ { watchVideoId && (
189+ < VideoFeed
190+ initialVideoId = { watchVideoId }
191+ videos = { feedVideos }
192+ onLoadMore = { handleLoadMore }
193+ />
194+ ) }
195+ </ div >
196+ < DialogClose className = "absolute right-6 top-6 z-50 flex h-10 w-10 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70" >
197+ < X className = "h-5 w-5" />
198+ < span className = "sr-only" > Close</ span >
199+ </ DialogClose >
200+ </ DialogPrimitive . Content >
201+ </ DialogPortal >
202+ </ Dialog >
138203 </ div >
139204 ) ;
140205}
0 commit comments