ChandimaPrabath commited on
Commit
b89b138
·
1 Parent(s): d93535b
frontend/app/layout.tsx CHANGED
@@ -6,10 +6,10 @@ const inter = Inter({ subsets: ['latin'] });
6
 
7
  export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
8
  return (
9
- <html lang="en" data-oid="cok9i83">
10
- <body className={inter.className} data-oid="qfx_p2:">
11
- <div className="min-h-screen bg-gray-900 text-white" data-oid="yw_y-lk">
12
- <Navbar data-oid="lf8htjb" />
13
  {children}
14
  </div>
15
  </body>
 
6
 
7
  export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
8
  return (
9
+ <html lang="en" data-oid="i1r5a3t">
10
+ <body className={inter.className} data-oid="2zv74y9">
11
+ <div className="min-h-screen bg-gray-900 text-white" data-oid="roe.i3y">
12
+ <Navbar data-oid="8m1ce24" />
13
  {children}
14
  </div>
15
  </body>
frontend/app/movie/[title]/LoadingSkeleton.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function LoadingSkeleton() {
2
+ return (
3
+ <div className="w-full min-h-screen bg-gray-900 animate-pulse" data-oid="bxlim:7">
4
+ {/* Hero Section Skeleton */}
5
+ <div className="relative w-full h-[60vh] bg-gray-800" data-oid="15s-275">
6
+ <div
7
+ className="absolute inset-0 flex items-center justify-center"
8
+ data-oid="-7z9u4-"
9
+ >
10
+ <div className="w-full max-w-7xl px-6 space-y-4" data-oid="qjq7:4p">
11
+ <div
12
+ className="h-12 bg-gray-700 rounded-lg w-3/4 max-w-2xl"
13
+ data-oid=".crlged"
14
+ ></div>
15
+ <div
16
+ className="h-6 bg-gray-700 rounded-lg w-1/4 max-w-xs"
17
+ data-oid="m2f8k02"
18
+ ></div>
19
+ <div className="flex gap-4" data-oid="99ud:0g">
20
+ <div
21
+ className="h-12 bg-gray-700 rounded-3xl w-32"
22
+ data-oid="djnshw1"
23
+ ></div>
24
+ <div
25
+ className="h-12 bg-gray-700 rounded-3xl w-32"
26
+ data-oid="3cmt5:k"
27
+ ></div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ {/* Details Section Skeleton */}
34
+ <div className="max-w-7xl mx-auto px-6 py-12 space-y-8" data-oid="g-n.rse">
35
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8" data-oid="q1ayxmh">
36
+ <div className="md:col-span-2 space-y-4" data-oid="lq:eldw">
37
+ <div className="h-8 bg-gray-800 rounded-lg w-1/2" data-oid="ufp7lyx"></div>
38
+ <div
39
+ className="h-32 bg-gray-800 rounded-lg w-full"
40
+ data-oid="tgmoxx:"
41
+ ></div>
42
+ <div className="h-8 bg-gray-800 rounded-lg w-1/3" data-oid="-t-uslw"></div>
43
+ <div
44
+ className="h-24 bg-gray-800 rounded-lg w-full"
45
+ data-oid="wkup7cw"
46
+ ></div>
47
+ </div>
48
+ <div className="space-y-4" data-oid="760f:2.">
49
+ <div className="h-8 bg-gray-800 rounded-lg w-full" data-oid="zk.9vl4"></div>
50
+ <div
51
+ className="h-40 bg-gray-800 rounded-lg w-full"
52
+ data-oid="p5io6zx"
53
+ ></div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
frontend/app/movie/[title]/page.js CHANGED
@@ -1,8 +1,11 @@
1
  'use client';
2
 
3
- import { useEffect, useState } from "react";
4
- import { useParams } from "next/navigation";
5
- import { getMovieMetadata } from "@/lib/lb";
 
 
 
6
 
7
  export default function MovieTitlePage() {
8
  const { title } = useParams();
@@ -15,30 +18,58 @@ export default function MovieTitlePage() {
15
  const data = await getMovieMetadata(decodedTitle);
16
  setMovie(data.data);
17
  } catch (error) {
18
- console.error("Failed to fetch movie details", error);
19
  }
20
  }
21
  fetchMovie();
22
  }, [decodedTitle]);
23
 
24
  if (!movie) {
25
- return <div className="text-white text-center py-10">Loading...</div>;
26
  }
27
 
28
  return (
29
- <div
30
- className="relative w-full h-screen flex items-center justify-center text-white"
31
- style={{ backgroundImage: `url(${movie.image})`, backgroundSize: "cover", backgroundPosition: "center" }}
 
 
 
 
32
  >
33
- <div className="absolute inset-0 bg-black bg-opacity-50"></div>
34
- <div className="relative z-10 text-center max-w-2xl px-6">
35
- <h1 className="text-5xl font-bold mb-4">{movie.name} ({movie.year})</h1>
36
- <p className="text-lg mb-6">Runtime: {movie.runtime} mins</p>
37
- <div className="flex justify-center gap-4">
38
- <button className="bg-purple-600 px-6 py-3 rounded-lg text-lg font-bold hover:bg-purple-700">▶ Play Now</button>
39
- <button className="bg-gray-800 px-6 py-3 rounded-lg text-lg font-bold hover:bg-gray-700">More Info</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  </div>
41
  </div>
42
  </div>
43
  );
44
- }
 
1
  'use client';
2
 
3
+ import { useEffect, useState } from 'react';
4
+ import { useParams } from 'next/navigation';
5
+ import { getMovieMetadata } from '@/lib/lb';
6
+ import { LoadingSkeleton } from './LoadingSkeleton';
7
+ import { convertMinutesToHM } from '@lib/utils';
8
+ import Link from 'next/link';
9
 
10
  export default function MovieTitlePage() {
11
  const { title } = useParams();
 
18
  const data = await getMovieMetadata(decodedTitle);
19
  setMovie(data.data);
20
  } catch (error) {
21
+ console.error('Failed to fetch movie details', error);
22
  }
23
  }
24
  fetchMovie();
25
  }, [decodedTitle]);
26
 
27
  if (!movie) {
28
+ return <LoadingSkeleton />;
29
  }
30
 
31
  return (
32
+ <div
33
+ className="relative w-full h-screen flex items-start landscape:pt-36 pt-40 text-white"
34
+ style={{
35
+ backgroundImage: `url(${movie.image})`,
36
+ backgroundSize: 'cover',
37
+ backgroundPosition: 'center',
38
+ }}
39
  >
40
+ <div className="absolute inset-0 bg-gray-900 bg-opacity-40">
41
+ <div className=" h-1/6 "></div>
42
+ <div className=" h-5/6 bg-gradient-to-t from-gray-900 to-transparent"></div>
43
+ </div>
44
+ <div className="relative z-10 max-w-1/3 landscape:mx-40 mx-6">
45
+ <div className="relative z-10 max-w-2/6 w-5/6 flex-col items-start justify-start">
46
+ <h1 className="landscape:text-3xl text-2xl font-bold mb-2 text-gray-200">
47
+ {movie.name} ({movie.year})
48
+ </h1>
49
+ <p className="text-lg text-yellow-400 mb-2">Runtime: {convertMinutesToHM(movie.runtime)}</p>
50
+ <div className="flex justify-start landscape:gap-4">
51
+ <Link
52
+ href={'#play'}
53
+ className="bg-purple-500 hover:bg-purple-600 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-s-2xl flex items-center transition-colors text-sm md:text-base"
54
+ >
55
+ <svg
56
+ className="w-4 h-4 md:w-5 md:h-5 mr-2"
57
+ fill="currentColor"
58
+ viewBox="0 0 20 20"
59
+ >
60
+ <path d="M4 4l12 6-12 6V4z" />
61
+ </svg>
62
+ Play Now
63
+ </Link>
64
+ <Link
65
+ href={'#'}
66
+ className="bg-gray-800/80 hover:bg-gray-700/80 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-e-2xl transition-colors text-sm md:text-base"
67
+ >
68
+ Add to My List
69
+ </Link>
70
+ </div>
71
  </div>
72
  </div>
73
  </div>
74
  );
75
+ }
frontend/app/page.js CHANGED
@@ -1,125 +1,122 @@
1
  'use client';
2
 
3
  import { useState, useEffect } from 'react';
4
- import { TrendingSection } from '@components/sections/TrendingSection';
5
  import { getRecentItems } from '@lib/lb';
6
  import Link from 'next/link';
7
 
8
  export default function Page() {
9
- const [slides, setSlides] = useState([]);
10
- const [currentSlide, setCurrentSlide] = useState(0);
 
 
11
 
12
- // Fetch slides once on mount
13
- useEffect(() => {
14
- async function fetchSlides() {
15
- try {
16
- const slidesData = await getRecentItems();
17
- setSlides(slidesData);
18
- } catch (error) {
19
- console.error('Error fetching slides:', error);
20
- }
21
- }
22
- fetchSlides();
23
- }, []);
 
 
24
 
25
- // Set up interval for slide rotation (only after slides are loaded)
26
- useEffect(() => {
27
- if (slides.length === 0) return;
28
 
29
- const timer = setInterval(() => {
30
- setCurrentSlide((prev) => (prev + 1) % slides.length);
31
- }, 5000);
32
 
33
- return () => clearInterval(timer);
34
- }, [slides]);
35
 
36
- return (
37
- <>
38
- {/* Hero Slideshow */}
39
- <div
40
- className={`relative h-screen transition-opacity duration-1000 ${
41
- slides.length ? 'opacity-100' : 'opacity-0'
42
- }`}
43
- data-oid="j81usgt"
44
- >
45
- {slides.map((slide, index) => (
46
- <div
47
- key={index}
48
- className={`absolute inset-0 transition-opacity duration-1000 ${
49
- index === currentSlide ? 'opacity-100' : 'opacity-0'
50
- }`}
51
- data-oid="ch5cilb"
52
- >
53
- {/* Overlay */}
54
- <div className="absolute inset-0 bg-black/50 z-10" data-oid="bysxjzu" />
55
-
56
- {/* Background Image with Ken Burns effect */}
57
- <div
58
- className="w-full h-full bg-center pan-animation transition-transform duration-1000 transform-gpu"
59
- style={{ backgroundImage: `url(${slide.image})` }}
60
- data-oid="g0qbl4_"
61
- ></div>
62
-
63
- {/* Slide Content */}
64
- <div
65
- className="absolute bottom-0 left-0 right-0 z-20 p-10 md:p-20 bg-gradient-to-t from-gray-900"
66
- data-oid="t35kxk_"
67
- >
68
- <div className="container mx-auto" data-oid="k6wlkb.">
69
- <span
70
- className="text-purple-400 text-sm font-semibold"
71
- data-oid="oui44e1"
72
- >
73
- {Array.isArray(slide.genre)
74
- ? slide.genre.join(', ')
75
- : slide.genre}
76
- </span>
77
- <h1
78
- className="text-4xl md:text-5xl font-bold mt-2 mb-4"
79
- data-oid="9g2c-q9"
80
- >
81
- {slide.title}
82
- </h1>
83
- <p
84
- className="text-gray-300 text-lg max-w-xl overflow-hidden line-clamp-5"
85
- data-oid="sp2z9q3"
86
  >
87
- {slide.description}
88
- </p>
89
- {/* Responsive Buttons */}
 
 
 
 
 
90
  <div
91
- className="mt-8 flex flex-col md:flex-row md:space-x-4 space-y-4 md:space-y-0"
92
- data-oid="7c583ne"
 
93
  >
94
- <Link
95
- href={'#play'}
96
- className="bg-purple-500 hover:bg-purple-600 px-4 py-2 md:px-8 md:py-3 rounded-full flex items-center transition-colors text-sm md:text-base"
97
- data-oid="3bbf80p"
98
- >
99
- <svg
100
- className="w-4 h-4 md:w-5 md:h-5 mr-2"
101
- fill="currentColor"
102
- viewBox="0 0 20 20"
103
- data-oid="t3e4jf_"
104
- >
105
- <path d="M4 4l12 6-12 6V4z" data-oid="89v0-rn" />
106
- </svg>
107
- Play Now
108
- </Link>
109
- <Link
110
- href={slide.type === "movie" ? `/movie/${slide.title}`: `/tvshow/${slide.title}`}
111
- className="bg-gray-800/80 hover:bg-gray-700/80 px-4 py-2 md:px-8 md:py-3 rounded-full transition-colors text-sm md:text-base"
112
- data-oid="wizdlzr"
113
- >
114
- More Info
115
- </Link>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  </div>
117
- </div>
118
  </div>
119
- </div>
120
- ))}
121
- </div>
122
- <TrendingSection data-oid="ccjez3g" />
123
- </>
124
- );
125
  }
 
1
  'use client';
2
 
3
  import { useState, useEffect } from 'react';
4
+ import NewContentSection from '@components/sections/NewContentSection';
5
  import { getRecentItems } from '@lib/lb';
6
  import Link from 'next/link';
7
 
8
  export default function Page() {
9
+ const [slides, setSlides] = useState([]);
10
+ const [currentSlide, setCurrentSlide] = useState(0);
11
+ const [loading, setLoading] = useState(true);
12
+ const [loaded, setLoaded] = useState(false);
13
 
14
+ useEffect(() => {
15
+ async function fetchSlides() {
16
+ try {
17
+ const slidesData = await getRecentItems();
18
+ setSlides(slidesData);
19
+ } catch (error) {
20
+ console.error('Error fetching slides:', error);
21
+ } finally {
22
+ setLoading(false);
23
+ setTimeout(() => setLoaded(true), 100); // Delay to ensure smooth transition
24
+ }
25
+ }
26
+ fetchSlides();
27
+ }, []);
28
 
29
+ useEffect(() => {
30
+ if (slides.length === 0) return;
 
31
 
32
+ const timer = setInterval(() => {
33
+ setCurrentSlide((prev) => (prev + 1) % slides.length);
34
+ }, 5000);
35
 
36
+ return () => clearInterval(timer);
37
+ }, [slides]);
38
 
39
+ return (
40
+ <>
41
+ {/* Hero Slideshow */}
42
+ <div className="relative h-screen">
43
+ {/* Loading Skeleton */}
44
+ <div
45
+ className={`absolute inset-0 flex items-center justify-center bg-gray-900 transition-opacity duration-1000 ${
46
+ loading ? 'opacity-100' : 'opacity-0 pointer-events-none'
47
+ }`}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  >
49
+ <div className="w-full h-full flex flex-col justify-end bg-gray-800 rounded-lg animate-pulse">
50
+ <div className="w-2/4 h-5 landscape:w-1/4 ml-10 landscape:ml-20 mb-4 bg-gray-700 rounded-lg"></div>
51
+ <div className="w-3/4 h-14 landscape:w-2/4 ml-10 landscape:ml-20 mb-4 bg-gray-700 rounded-lg"></div>
52
+ <div className="w-3/4 h-1/4 landscape:w-2/4 ml-10 landscape:ml-20 mb-20 bg-gray-700 rounded-lg"></div>
53
+ </div>
54
+ </div>
55
+
56
+ {/* Actual Content with Fade-in Effect */}
57
  <div
58
+ className={`absolute inset-0 transition-opacity duration-1000 ${
59
+ loaded ? 'opacity-100' : 'opacity-0'
60
+ }`}
61
  >
62
+ {slides.map((slide, index) => (
63
+ <div
64
+ key={index}
65
+ className={`absolute inset-0 transition-opacity duration-1000 ${
66
+ index === currentSlide ? 'opacity-100' : 'opacity-0'
67
+ }`}
68
+ >
69
+ <div className="absolute inset-0 bg-black/50 z-10" />
70
+ <div
71
+ className="w-full h-full bg-center pan-animation transition-transform duration-1000 transform-gpu"
72
+ style={{ backgroundImage: `url(${slide.image})` }}
73
+ ></div>
74
+ <div className="absolute bottom-0 left-0 right-0 z-20 p-10 md:p-20 bg-gradient-to-t from-gray-900">
75
+ <div className="container mx-auto">
76
+ <span className="text-purple-400 text-base font-semibold">
77
+ {Array.isArray(slide.genre)
78
+ ? slide.genre.join(', ')
79
+ : slide.genre}
80
+ </span>
81
+ <h1 className="text-4xl md:text-5xl font-bold mt-2 mb-4">
82
+ {slide.title}
83
+ </h1>
84
+ <p className="text-gray-300 text-lg max-w-xl overflow-hidden line-clamp-5">
85
+ {slide.description}
86
+ </p>
87
+ <div className="flex justify-start landscape:gap-4 mt-8">
88
+ <Link
89
+ href={'#play'}
90
+ className="bg-purple-500 hover:bg-purple-600 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-s-2xl flex items-center transition-colors text-sm md:text-base"
91
+ >
92
+ <svg
93
+ className="w-4 h-4 md:w-5 md:h-5 mr-2"
94
+ fill="currentColor"
95
+ viewBox="0 0 20 20"
96
+ >
97
+ <path d="M4 4l12 6-12 6V4z" />
98
+ </svg>
99
+ Play Now
100
+ </Link>
101
+ <Link
102
+ href={
103
+ slide.type === 'movie'
104
+ ? `/movie/${slide.title}`
105
+ : `/tvshow/${slide.title}`
106
+ }
107
+ className="bg-gray-800/80 hover:bg-gray-700/80 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-e-2xl transition-colors text-sm md:text-base"
108
+ >
109
+ More Info
110
+ </Link>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ ))}
116
  </div>
 
117
  </div>
118
+
119
+ <NewContentSection />
120
+ </>
121
+ );
 
 
122
  }
frontend/app/tvshow/[title]/LoadingSkeleton.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function LoadingSkeleton() {
2
+ return (
3
+ <div className="w-full min-h-screen bg-gray-900 animate-pulse" data-oid="bxlim:7">
4
+ {/* Hero Section Skeleton */}
5
+ <div className="relative w-full h-[60vh] bg-gray-800" data-oid="15s-275">
6
+ <div
7
+ className="absolute inset-0 flex items-center justify-center"
8
+ data-oid="-7z9u4-"
9
+ >
10
+ <div className="w-full max-w-7xl px-6 space-y-4" data-oid="qjq7:4p">
11
+ <div
12
+ className="h-12 bg-gray-700 rounded-lg w-3/4 max-w-2xl"
13
+ data-oid=".crlged"
14
+ ></div>
15
+ <div
16
+ className="h-6 bg-gray-700 rounded-lg w-1/4 max-w-xs"
17
+ data-oid="m2f8k02"
18
+ ></div>
19
+ <div className="flex gap-4" data-oid="99ud:0g">
20
+ <div
21
+ className="h-12 bg-gray-700 rounded-3xl w-32"
22
+ data-oid="djnshw1"
23
+ ></div>
24
+ <div
25
+ className="h-12 bg-gray-700 rounded-3xl w-32"
26
+ data-oid="3cmt5:k"
27
+ ></div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ {/* Details Section Skeleton */}
34
+ <div className="max-w-7xl mx-auto px-6 py-12 space-y-8" data-oid="g-n.rse">
35
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8" data-oid="q1ayxmh">
36
+ <div className="md:col-span-2 space-y-4" data-oid="lq:eldw">
37
+ <div className="h-8 bg-gray-800 rounded-lg w-1/2" data-oid="ufp7lyx"></div>
38
+ <div
39
+ className="h-32 bg-gray-800 rounded-lg w-full"
40
+ data-oid="tgmoxx:"
41
+ ></div>
42
+ <div className="h-8 bg-gray-800 rounded-lg w-1/3" data-oid="-t-uslw"></div>
43
+ <div
44
+ className="h-24 bg-gray-800 rounded-lg w-full"
45
+ data-oid="wkup7cw"
46
+ ></div>
47
+ </div>
48
+ <div className="space-y-4" data-oid="760f:2.">
49
+ <div className="h-8 bg-gray-800 rounded-lg w-full" data-oid="zk.9vl4"></div>
50
+ <div
51
+ className="h-40 bg-gray-800 rounded-lg w-full"
52
+ data-oid="p5io6zx"
53
+ ></div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
frontend/app/tvshow/[title]/page.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { useParams } from 'next/navigation';
5
+ import { getTvShowMetadata } from '@/lib/lb';
6
+ import { LoadingSkeleton } from './LoadingSkeleton';
7
+ import { convertMinutesToHM } from '@lib/utils';
8
+ import Link from 'next/link';
9
+
10
+ export default function TvShowTitlePage() {
11
+ const { title } = useParams();
12
+ const decodedTitle = decodeURIComponent(title);
13
+ const [tvshow, setTvShow] = useState(null);
14
+
15
+ useEffect(() => {
16
+ async function fetchMovie() {
17
+ try {
18
+ const data = await getTvShowMetadata(decodedTitle);
19
+ setTvShow(data.data);
20
+ } catch (error) {
21
+ console.error('Failed to fetch movie details', error);
22
+ }
23
+ }
24
+ fetchMovie();
25
+ }, [decodedTitle]);
26
+
27
+ if (!tvshow) {
28
+ return <LoadingSkeleton />;
29
+ }
30
+
31
+ return (
32
+ <div
33
+ className="relative w-full h-screen flex items-start landscape:pt-36 pt-40 text-white"
34
+ style={{
35
+ backgroundImage: `url(${tvshow.image})`,
36
+ backgroundSize: 'cover',
37
+ backgroundPosition: 'center',
38
+ }}
39
+ >
40
+ <div className="absolute inset-0 bg-gray-900 bg-opacity-40">
41
+ <div className=" h-1/6 "></div>
42
+ <div className=" h-5/6 bg-gradient-to-t from-gray-900 to-transparent"></div>
43
+ </div>
44
+ <div className="relative z-10 max-w-1/3 landscape:mx-40 mx-6">
45
+ <div className="relative z-10 max-w-1/3 w-5/6 flex-col items-start justify-start">
46
+ <h1 className="landscape:text-3xl text-2xl font-bold mb-2 text-gray-200">
47
+ {tvshow.name} ({tvshow.year})
48
+ </h1>
49
+ <p className="text-lg text-yellow-400 mb-2">Runtime: {convertMinutesToHM(tvshow.runtime)}</p>
50
+ <div className="flex justify-start landscape:gap-4">
51
+ <Link
52
+ href={'#play'}
53
+ className="bg-purple-500 hover:bg-purple-600 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-s-2xl flex items-center transition-colors text-sm md:text-base"
54
+ >
55
+ <svg
56
+ className="w-4 h-4 md:w-5 md:h-5 mr-2"
57
+ fill="currentColor"
58
+ viewBox="0 0 20 20"
59
+ >
60
+ <path d="M4 4l12 6-12 6V4z" />
61
+ </svg>
62
+ Play Now
63
+ </Link>
64
+ <Link
65
+ href={'#'}
66
+ className="bg-gray-800/80 hover:bg-gray-700/80 px-4 landscape:py-2 py-2 md:px-8 md:py-3 landscape:rounded-3xl rounded-e-2xl transition-colors text-sm md:text-base"
67
+ >
68
+ Add to My List
69
+ </Link>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ );
75
+ }
frontend/app/tvshows/page.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import NewContentSection from '@components/sections/NewContentSection';
2
+
3
+ export default function TvshowsPage() {
4
+ return <NewContentSection />;
5
+ }
frontend/app/tvshows/page.tsx DELETED
@@ -1,3 +0,0 @@
1
- export default function TvshowsPage() {
2
- return <></>;
3
- }
 
 
 
 
frontend/bun.lockb CHANGED
Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ
 
frontend/components/movie/MovieCard.js CHANGED
@@ -5,42 +5,58 @@ import { getMovieCard } from '@/lib/lb';
5
  import Link from 'next/link';
6
 
7
  export const MovieCard = ({ title }) => {
8
- const [card, setCard] = useState([]);
9
- const formattedTitle = title.split('/')
 
 
10
  useEffect(() => {
11
  async function fetchMovieCard() {
12
- console.log('formatted title: ',formattedTitle[1])
13
- const card = await getMovieCard(formattedTitle[1]);
14
- console.log(card)
15
- setCard(card);
 
 
16
  }
17
  fetchMovieCard();
18
  }, []);
19
- const rating = '0'
20
  return (
21
  <Link href={`/movie/${formattedTitle[1]}`} className="relative group" data-oid="_h1ze:e">
22
- <div className="aspect-[2/3] rounded-lg overflow-hidden" data-oid="tf3burv">
23
- <img
24
- src={card?.image}
25
- alt={card?.title}
26
- className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-300"
27
- data-oid="aqx14q7"
28
- />
 
 
 
 
 
 
 
 
 
 
 
29
 
 
30
  <div
31
  className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
32
  data-oid="c5z5p9j"
33
  >
34
  <div className="absolute bottom-0 p-4 w-full" data-oid="-3a50qd">
35
  <h3 className="text-lg font-semibold text-white" data-oid="vw8.q_r">
36
- {card?.title}
37
  </h3>
38
  <div className="flex items-center space-x-2 mt-1" data-oid="382afz5">
39
  <span className="text-yellow-400" data-oid="ol91ry.">
40
- ★ {rating}
41
  </span>
42
  <span className="text-gray-300" data-oid="s8mo0:q">
43
- • {card?.year}
44
  </span>
45
  </div>
46
  </div>
 
5
  import Link from 'next/link';
6
 
7
  export const MovieCard = ({ title }) => {
8
+ const [card, setCard] = useState(null);
9
+ const [imageLoaded, setImageLoaded] = useState(false);
10
+ const formattedTitle = title.split('/');
11
+
12
  useEffect(() => {
13
  async function fetchMovieCard() {
14
+ try {
15
+ const cardData = await getMovieCard(formattedTitle[1]);
16
+ setCard(cardData);
17
+ } catch (error) {
18
+ console.error('Error fetching movie card:', error);
19
+ }
20
  }
21
  fetchMovieCard();
22
  }, []);
23
+
24
  return (
25
  <Link href={`/movie/${formattedTitle[1]}`} className="relative group" data-oid="_h1ze:e">
26
+ <div className="aspect-[2/3] rounded-lg overflow-hidden relative" data-oid="tf3burv">
27
+ {/* Skeleton Loader */}
28
+ {!imageLoaded && (
29
+ <div className="absolute inset-0 bg-gray-700 animate-pulse rounded-lg" />
30
+ )}
31
+
32
+ {/* Image */}
33
+ {card?.image && (
34
+ <img
35
+ src={card.image}
36
+ alt={card.title}
37
+ className={`w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-300 opacity-0 transition-opacity ease-in-out duration-500 ${
38
+ imageLoaded ? 'opacity-100' : 'opacity-0'
39
+ }`}
40
+ onLoad={() => setImageLoaded(true)}
41
+ data-oid="aqx14q7"
42
+ />
43
+ )}
44
 
45
+ {/* Overlay */}
46
  <div
47
  className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
48
  data-oid="c5z5p9j"
49
  >
50
  <div className="absolute bottom-0 p-4 w-full" data-oid="-3a50qd">
51
  <h3 className="text-lg font-semibold text-white" data-oid="vw8.q_r">
52
+ {card?.title || 'Loading...'}
53
  </h3>
54
  <div className="flex items-center space-x-2 mt-1" data-oid="382afz5">
55
  <span className="text-yellow-400" data-oid="ol91ry.">
56
+ ★ {card?.rating || '0'}
57
  </span>
58
  <span className="text-gray-300" data-oid="s8mo0:q">
59
+ • {card?.year || '----'}
60
  </span>
61
  </div>
62
  </div>
frontend/components/movie/MovieCardTemp.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { getMovieCard } from '@/lib/lb';
5
+ import Link from 'next/link';
6
+
7
+ export const MovieCardTemp = ({ title }) => {
8
+ const [card, setCard] = useState(null);
9
+ const [imageLoaded, setImageLoaded] = useState(false);
10
+
11
+ useEffect(() => {
12
+ async function fetchMovieCard() {
13
+ try {
14
+ const cardData = await getMovieCard(title);
15
+ setCard(cardData);
16
+ } catch (error) {
17
+ console.error('Error fetching movie card:', error);
18
+ }
19
+ }
20
+ fetchMovieCard();
21
+ }, []);
22
+
23
+ return (
24
+ <Link href={`/movie/${title}`} className="relative group" data-oid="_h1ze:e">
25
+ <div className="aspect-[2/3] rounded-lg overflow-hidden relative" data-oid="tf3burv">
26
+ {/* Skeleton Loader */}
27
+ {!imageLoaded && (
28
+ <div className="absolute inset-0 bg-gray-700 animate-pulse rounded-lg" />
29
+ )}
30
+
31
+ {/* Image */}
32
+ {card?.image && (
33
+ <img
34
+ src={card.image}
35
+ alt={card.title}
36
+ className={`w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-300 opacity-0 transition-opacity ease-in-out duration-500 ${
37
+ imageLoaded ? 'opacity-100' : 'opacity-0'
38
+ }`}
39
+ onLoad={() => setImageLoaded(true)}
40
+ data-oid="aqx14q7"
41
+ />
42
+ )}
43
+
44
+ {/* Overlay */}
45
+ <div
46
+ className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
47
+ data-oid="c5z5p9j"
48
+ >
49
+ <div className="absolute bottom-0 p-4 w-full" data-oid="-3a50qd">
50
+ <h3 className="text-lg font-semibold text-white" data-oid="vw8.q_r">
51
+ {card?.title || 'Loading...'}
52
+ </h3>
53
+ <div className="flex items-center space-x-2 mt-1" data-oid="382afz5">
54
+ <span className="text-yellow-400" data-oid="ol91ry.">
55
+ ★ {card?.rating || '0'}
56
+ </span>
57
+ <span className="text-gray-300" data-oid="s8mo0:q">
58
+ • {card?.year || '----'}
59
+ </span>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </Link>
65
+ );
66
+ };
frontend/components/navigation/DesktopMenu.tsx CHANGED
@@ -2,25 +2,25 @@ import Link from 'next/link';
2
 
3
  export const DesktopMenu = () => {
4
  return (
5
- <div className="hidden md:flex space-x-8" data-oid="m3sc:7k">
6
- <Link href="/" className="hover:text-purple-400 transition-colors" data-oid="-a6t5y7">
7
  Home
8
  </Link>
9
  <Link
10
  href="/movies"
11
  className="hover:text-purple-400 transition-colors"
12
- data-oid="u0lgboq"
13
  >
14
  Movies
15
  </Link>
16
  <Link
17
  href="/tvshows"
18
  className="hover:text-purple-400 transition-colors"
19
- data-oid="hf_4ek:"
20
  >
21
  TV Shows
22
  </Link>
23
- <Link href="#" className="hover:text-purple-400 transition-colors" data-oid="83obfnh">
24
  My List
25
  </Link>
26
  </div>
 
2
 
3
  export const DesktopMenu = () => {
4
  return (
5
+ <div className="hidden md:flex space-x-8" data-oid="f74j-82">
6
+ <Link href="/" className="hover:text-purple-400 transition-colors" data-oid="8is.xkx">
7
  Home
8
  </Link>
9
  <Link
10
  href="/movies"
11
  className="hover:text-purple-400 transition-colors"
12
+ data-oid="c_8m5ym"
13
  >
14
  Movies
15
  </Link>
16
  <Link
17
  href="/tvshows"
18
  className="hover:text-purple-400 transition-colors"
19
+ data-oid="-45x9gu"
20
  >
21
  TV Shows
22
  </Link>
23
+ <Link href="#" className="hover:text-purple-400 transition-colors" data-oid="x9bnhx_">
24
  My List
25
  </Link>
26
  </div>
frontend/components/navigation/MobileMenu.tsx CHANGED
@@ -17,41 +17,41 @@ export const MobileMenu = ({ isOpen }: MobileMenuProps) => {
17
  leave="transition ease-in duration-200"
18
  leaveFrom="opacity-100 transform scale-100"
19
  leaveTo="opacity-0 transform scale-95"
20
- data-oid="_mftvkt"
21
  >
22
- <div className="md:hidden bg-gray-900 border-t border-gray-800" data-oid="e.dm1v0">
23
- <div className="px-6 py-4 space-y-4" data-oid="cd9e.i-">
24
  <Link
25
  href="/"
26
  className="block hover:text-purple-400 transition-colors"
27
- data-oid="fcj_e_5"
28
  >
29
  Home
30
  </Link>
31
  <Link
32
  href="/movies"
33
  className="block hover:text-purple-400 transition-colors"
34
- data-oid="mhb5lz2"
35
  >
36
  Movies
37
  </Link>
38
  <Link
39
  href="/tvshows"
40
  className="block hover:text-purple-400 transition-colors"
41
- data-oid="d9hidol"
42
  >
43
  TV Shows
44
  </Link>
45
  <Link
46
  href="#"
47
  className="block hover:text-purple-400 transition-colors"
48
- data-oid="l4u8nmx"
49
  >
50
  My List
51
  </Link>
52
  <button
53
  className="w-full bg-purple-500 hover:bg-purple-600 px-6 py-2 rounded-full transition-colors"
54
- data-oid="5d5i7xt"
55
  >
56
  Sign In
57
  </button>
 
17
  leave="transition ease-in duration-200"
18
  leaveFrom="opacity-100 transform scale-100"
19
  leaveTo="opacity-0 transform scale-95"
20
+ data-oid="r0xuznf"
21
  >
22
+ <div className="md:hidden bg-gray-900 border-t border-gray-800" data-oid="niku.c9">
23
+ <div className="px-6 py-4 space-y-4" data-oid="riw9-i3">
24
  <Link
25
  href="/"
26
  className="block hover:text-purple-400 transition-colors"
27
+ data-oid="ur.7z:6"
28
  >
29
  Home
30
  </Link>
31
  <Link
32
  href="/movies"
33
  className="block hover:text-purple-400 transition-colors"
34
+ data-oid="ci0e.io"
35
  >
36
  Movies
37
  </Link>
38
  <Link
39
  href="/tvshows"
40
  className="block hover:text-purple-400 transition-colors"
41
+ data-oid="mfc3-o4"
42
  >
43
  TV Shows
44
  </Link>
45
  <Link
46
  href="#"
47
  className="block hover:text-purple-400 transition-colors"
48
+ data-oid="-nzu18t"
49
  >
50
  My List
51
  </Link>
52
  <button
53
  className="w-full bg-purple-500 hover:bg-purple-600 px-6 py-2 rounded-full transition-colors"
54
+ data-oid="gtk.o:b"
55
  >
56
  Sign In
57
  </button>
frontend/components/navigation/Navbar.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState } from 'react';
4
  import { DesktopMenu } from './DesktopMenu';
5
  import { MobileMenu } from './MobileMenu';
 
6
 
7
  export const Navbar = () => {
8
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -10,21 +11,22 @@ export const Navbar = () => {
10
  return (
11
  <nav
12
  className="fixed w-full z-50 bg-gradient-to-b from-black to-transparent"
13
- data-oid="2d49xiu"
14
  >
15
- <div className="container mx-auto px-6 py-4" data-oid="oqg9w12">
16
- <div className="flex items-center justify-between" data-oid=".stx:bj">
17
- <div
 
18
  className="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent"
19
- data-oid="869culb"
20
  >
21
  NEXORA
22
- </div>
23
- <DesktopMenu data-oid="koenh9o" />
24
  <button
25
  className="md:hidden text-white"
26
  onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
27
- data-oid="4cablpp"
28
  >
29
  {isMobileMenuOpen ? (
30
  <svg
@@ -32,14 +34,14 @@ export const Navbar = () => {
32
  fill="none"
33
  stroke="currentColor"
34
  viewBox="0 0 24 24"
35
- data-oid="dmqqs_j"
36
  >
37
  <path
38
  strokeLinecap="round"
39
  strokeLinejoin="round"
40
  strokeWidth={2}
41
  d="M6 18L18 6M6 6l12 12"
42
- data-oid="o.2hmn0"
43
  />
44
  </svg>
45
  ) : (
@@ -48,27 +50,27 @@ export const Navbar = () => {
48
  fill="none"
49
  stroke="currentColor"
50
  viewBox="0 0 24 24"
51
- data-oid="92h7.kp"
52
  >
53
  <path
54
  strokeLinecap="round"
55
  strokeLinejoin="round"
56
  strokeWidth={2}
57
  d="M4 6h16M4 12h16M4 18h16"
58
- data-oid="8yh4i:i"
59
  />
60
  </svg>
61
  )}
62
  </button>
63
  <button
64
  className="hidden md:block bg-purple-500 hover:bg-purple-600 px-6 py-2 rounded-full transition-colors"
65
- data-oid="jilaptz"
66
  >
67
  Sign In
68
  </button>
69
  </div>
70
  </div>
71
- <MobileMenu isOpen={isMobileMenuOpen} data-oid="7mgh42-" />
72
  </nav>
73
  );
74
  };
 
3
  import { useState } from 'react';
4
  import { DesktopMenu } from './DesktopMenu';
5
  import { MobileMenu } from './MobileMenu';
6
+ import Link from 'next/link';
7
 
8
  export const Navbar = () => {
9
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
 
11
  return (
12
  <nav
13
  className="fixed w-full z-50 bg-gradient-to-b from-black to-transparent"
14
+ data-oid="aazy:e8"
15
  >
16
+ <div className="container mx-auto px-6 py-4" data-oid="5s-lctz">
17
+ <div className="flex items-center justify-between" data-oid="jdyyvox">
18
+ <Link
19
+ href={'/'}
20
  className="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent"
21
+ data-oid="43.njim"
22
  >
23
  NEXORA
24
+ </Link>
25
+ <DesktopMenu data-oid="yby6u0l" />
26
  <button
27
  className="md:hidden text-white"
28
  onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
29
+ data-oid="daoyxyh"
30
  >
31
  {isMobileMenuOpen ? (
32
  <svg
 
34
  fill="none"
35
  stroke="currentColor"
36
  viewBox="0 0 24 24"
37
+ data-oid="d706n0l"
38
  >
39
  <path
40
  strokeLinecap="round"
41
  strokeLinejoin="round"
42
  strokeWidth={2}
43
  d="M6 18L18 6M6 6l12 12"
44
+ data-oid="e4m0htn"
45
  />
46
  </svg>
47
  ) : (
 
50
  fill="none"
51
  stroke="currentColor"
52
  viewBox="0 0 24 24"
53
+ data-oid="bxfjqa7"
54
  >
55
  <path
56
  strokeLinecap="round"
57
  strokeLinejoin="round"
58
  strokeWidth={2}
59
  d="M4 6h16M4 12h16M4 18h16"
60
+ data-oid="8769og3"
61
  />
62
  </svg>
63
  )}
64
  </button>
65
  <button
66
  className="hidden md:block bg-purple-500 hover:bg-purple-600 px-6 py-2 rounded-full transition-colors"
67
+ data-oid="g612uv0"
68
  >
69
  Sign In
70
  </button>
71
  </div>
72
  </div>
73
+ <MobileMenu isOpen={isMobileMenuOpen} data-oid="5:7dc-f" />
74
  </nav>
75
  );
76
  };
frontend/components/sections/NewContentSection.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { getNewContents } from '@/lib/lb';
5
+ import { MovieCardTemp } from '@/components/movie/MovieCardTemp';
6
+ import { MovieCard } from '../movie/MovieCard';
7
+ import {TvShowCardTemp} from '@components/tvshow/TvShowCardTemp';
8
+
9
+ export default function NewContentSection() {
10
+ const [movies, setMovies] = useState([]);
11
+ const [tvshows, setTvShows] = useState([]);
12
+ useEffect(() => {
13
+ async function fetchMovies() {
14
+ try {
15
+ const { movies, tvshows } = await getNewContents(10); // Correctly destructure as an object
16
+ setMovies(movies);
17
+ setTvShows(tvshows);
18
+ } catch (error) {
19
+ console.error('Error fetching slides:', error);
20
+ }
21
+ }
22
+ fetchMovies();
23
+ }, []);
24
+
25
+ return (
26
+ <>
27
+ <section className="py-16" data-oid="yvz2_cl">
28
+ <div className="container mx-auto px-6" data-oid="_xku7wx">
29
+ <h2
30
+ className="text-2xl font-bold mb-8 bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent"
31
+ data-oid="62.dxoz"
32
+ >
33
+ Discover Movies
34
+ </h2>
35
+ <div
36
+ className="grid grid-cols-2 md:grid-cols-6 gap-4 md:gap-6"
37
+ data-oid="owphddg"
38
+ >
39
+ {movies.map((movie, index) => (
40
+ <MovieCardTemp title={movie.title} key={index} />
41
+ //<MovieCard title={movie?.title} key={index}/>
42
+ ))}
43
+ </div>
44
+ </div>
45
+ </section>
46
+ <section className="py-16" data-oid="yvz2_cl">
47
+ <div className="container mx-auto px-6" data-oid="_xku7wx">
48
+ <h2
49
+ className="text-2xl font-bold mb-8 bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent"
50
+ data-oid="62.dxoz"
51
+ >
52
+ Discover Tv Shows
53
+ </h2>
54
+ <div
55
+ className="grid grid-cols-2 md:grid-cols-6 gap-4 md:gap-6"
56
+ data-oid="owphddg"
57
+ >
58
+ {tvshows.map((tvshow, index) => (
59
+ <TvShowCardTemp title={tvshow.title} key={index} />
60
+ //<MovieCard title={tvshow?.title} key={index}/>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ </section>
65
+ </>
66
+ );
67
+ }
frontend/components/tvshow/TvShowCard.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { getTvShowCard } from '@/lib/lb';
5
+ import Link from 'next/link';
6
+
7
+ export const TvShowCard = ({ title }) => {
8
+ const [card, setCard] = useState(null);
9
+ const [imageLoaded, setImageLoaded] = useState(false);
10
+ const formattedTitle = title.split('/');
11
+
12
+ useEffect(() => {
13
+ async function fetchMovieCard() {
14
+ try {
15
+ const cardData = await getTvShowCard(formattedTitle[1]);
16
+ setCard(cardData);
17
+ } catch (error) {
18
+ console.error('Error fetching movie card:', error);
19
+ }
20
+ }
21
+ fetchMovieCard();
22
+ }, []);
23
+
24
+ return (
25
+ <Link href={`/tvshow/${formattedTitle[1]}`} className="relative group" data-oid="_h1ze:e">
26
+ <div className="aspect-[2/3] rounded-lg overflow-hidden relative" data-oid="tf3burv">
27
+ {/* Skeleton Loader */}
28
+ {!imageLoaded && (
29
+ <div className="absolute inset-0 bg-gray-700 animate-pulse rounded-lg" />
30
+ )}
31
+
32
+ {/* Image */}
33
+ {card?.image && (
34
+ <img
35
+ src={card.image}
36
+ alt={card.title}
37
+ className={`w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-300 opacity-0 transition-opacity ease-in-out duration-500 ${
38
+ imageLoaded ? 'opacity-100' : 'opacity-0'
39
+ }`}
40
+ onLoad={() => setImageLoaded(true)}
41
+ data-oid="aqx14q7"
42
+ />
43
+ )}
44
+
45
+ {/* Overlay */}
46
+ <div
47
+ className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
48
+ data-oid="c5z5p9j"
49
+ >
50
+ <div className="absolute bottom-0 p-4 w-full" data-oid="-3a50qd">
51
+ <h3 className="text-lg font-semibold text-white" data-oid="vw8.q_r">
52
+ {card?.title || 'Loading...'}
53
+ </h3>
54
+ <div className="flex items-center space-x-2 mt-1" data-oid="382afz5">
55
+ <span className="text-yellow-400" data-oid="ol91ry.">
56
+ ★ {card?.rating || '0'}
57
+ </span>
58
+ <span className="text-gray-300" data-oid="s8mo0:q">
59
+ • {card?.year || '----'}
60
+ </span>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </Link>
66
+ );
67
+ };
frontend/components/tvshow/TvShowCardTemp.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { getTvShowCard } from '@/lib/lb';
5
+ import Link from 'next/link';
6
+
7
+ export const TvShowCardTemp = ({ title }) => {
8
+ const [card, setCard] = useState(null);
9
+ const [imageLoaded, setImageLoaded] = useState(false);
10
+
11
+ useEffect(() => {
12
+ async function fetchMovieCard() {
13
+ try {
14
+ const cardData = await getTvShowCard(title);
15
+ setCard(cardData);
16
+ } catch (error) {
17
+ console.error('Error fetching movie card:', error);
18
+ }
19
+ }
20
+ fetchMovieCard();
21
+ }, []);
22
+
23
+ return (
24
+ <Link href={`/tvshow/${title}`} className="relative group" data-oid="_h1ze:e">
25
+ <div className="aspect-[2/3] rounded-lg overflow-hidden relative" data-oid="tf3burv">
26
+ {/* Skeleton Loader */}
27
+ {!imageLoaded && (
28
+ <div className="absolute inset-0 bg-gray-700 animate-pulse rounded-lg" />
29
+ )}
30
+
31
+ {/* Image */}
32
+ {card?.image && (
33
+ <img
34
+ src={card.image}
35
+ alt={card.title}
36
+ className={`w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-300 opacity-0 transition-opacity ease-in-out duration-500 ${
37
+ imageLoaded ? 'opacity-100' : 'opacity-0'
38
+ }`}
39
+ onLoad={() => setImageLoaded(true)}
40
+ data-oid="aqx14q7"
41
+ />
42
+ )}
43
+
44
+ {/* Overlay */}
45
+ <div
46
+ className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
47
+ data-oid="c5z5p9j"
48
+ >
49
+ <div className="absolute bottom-0 p-4 w-full" data-oid="-3a50qd">
50
+ <h3 className="text-lg font-semibold text-white" data-oid="vw8.q_r">
51
+ {card?.title || 'Loading...'}
52
+ </h3>
53
+ <div className="flex items-center space-x-2 mt-1" data-oid="382afz5">
54
+ <span className="text-yellow-400" data-oid="ol91ry.">
55
+ ★ {card?.rating || '0'}
56
+ </span>
57
+ <span className="text-gray-300" data-oid="s8mo0:q">
58
+ • {card?.year || '----'}
59
+ </span>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </Link>
65
+ );
66
+ };
frontend/lib/lb.js CHANGED
@@ -39,6 +39,43 @@ export async function getRecentItems(limit = 5) {
39
  return slides;
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  export async function getAllMovies(){
43
  const movies = await lb.getAllMovies();
44
  console.log(movies);
@@ -51,8 +88,20 @@ export async function getMovieCard(title){
51
  return movie
52
  }
53
 
 
 
 
 
 
 
54
  export async function getMovieMetadata(title){
55
  const movie = await lb.getMovieMetadataByTitle(title);
56
  console.log(movie);
57
  return movie
 
 
 
 
 
 
58
  }
 
39
  return slides;
40
  }
41
 
42
+
43
+ export async function getNewContents(limit = 5) {
44
+ const recentData = await lb.getRecent(limit);
45
+ console.log("Raw recent data:", recentData);
46
+
47
+ const movies = [];
48
+ const tvshows = [];
49
+
50
+ // Process movies
51
+ if (Array.isArray(recentData.movies)) {
52
+ recentData.movies.forEach(([title, year, description, image, genres]) => {
53
+ movies.push({
54
+ title,
55
+ genre: genres.map(g => g.name),
56
+ image,
57
+ description,
58
+ });
59
+ });
60
+ }
61
+
62
+ // Process TV shows
63
+ if (Array.isArray(recentData.series)) {
64
+ recentData.series.forEach(([title, year, description, image, genres]) => {
65
+ tvshows.push({
66
+ title,
67
+ genre: genres.map(g => g.name),
68
+ image,
69
+ description,
70
+ });
71
+ });
72
+ }
73
+
74
+ console.log({ movies, tvshows });
75
+ return { movies, tvshows };
76
+ }
77
+
78
+
79
  export async function getAllMovies(){
80
  const movies = await lb.getAllMovies();
81
  console.log(movies);
 
88
  return movie
89
  }
90
 
91
+ export async function getTvShowCard(title){
92
+ const tvshow = await lb.getSeriesCard(title);
93
+ console.log(tvshow);
94
+ return tvshow
95
+ }
96
+
97
  export async function getMovieMetadata(title){
98
  const movie = await lb.getMovieMetadataByTitle(title);
99
  console.log(movie);
100
  return movie
101
+ }
102
+
103
+ export async function getTvShowMetadata(title){
104
+ const tvshow = await lb.getSeriesMetadataByTitle(title);
105
+ console.log(tvshow);
106
+ return tvshow
107
  }
frontend/lib/utils.ts CHANGED
@@ -4,3 +4,9 @@ import { twMerge } from "tailwind-merge"
4
  export function cn(...inputs: ClassValue[]) {
5
  return twMerge(clsx(inputs))
6
  }
 
 
 
 
 
 
 
4
  export function cn(...inputs: ClassValue[]) {
5
  return twMerge(clsx(inputs))
6
  }
7
+
8
+ export function convertMinutesToHM(minutes:number) {
9
+ const hours = Math.floor(minutes / 60);
10
+ const mins = minutes % 60;
11
+ return `${hours}h${mins.toString().padStart(2, '0')}mins`;
12
+ }
frontend/package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "0.1.0",
10
  "dependencies": {
11
  "@headlessui/react": "^2.2.0",
 
12
  "@radix-ui/react-slot": "^1.1.0",
13
  "class-variance-authority": "^0.7.0",
14
  "clsx": "^2.1.1",
@@ -175,6 +176,15 @@
175
  "react-dom": "^18 || ^19 || ^19.0.0-rc"
176
  }
177
  },
 
 
 
 
 
 
 
 
 
178
  "node_modules/@humanwhocodes/config-array": {
179
  "version": "0.11.14",
180
  "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
 
9
  "version": "0.1.0",
10
  "dependencies": {
11
  "@headlessui/react": "^2.2.0",
12
+ "@heroicons/react": "^2.2.0",
13
  "@radix-ui/react-slot": "^1.1.0",
14
  "class-variance-authority": "^0.7.0",
15
  "clsx": "^2.1.1",
 
176
  "react-dom": "^18 || ^19 || ^19.0.0-rc"
177
  }
178
  },
179
+ "node_modules/@heroicons/react": {
180
+ "version": "2.2.0",
181
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
182
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
183
+ "license": "MIT",
184
+ "peerDependencies": {
185
+ "react": ">= 16 || ^19.0.0-rc"
186
+ }
187
+ },
188
  "node_modules/@humanwhocodes/config-array": {
189
  "version": "0.11.14",
190
  "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
frontend/package.json CHANGED
@@ -11,6 +11,7 @@
11
  },
12
  "dependencies": {
13
  "@headlessui/react": "^2.2.0",
 
14
  "@radix-ui/react-slot": "^1.1.0",
15
  "class-variance-authority": "^0.7.0",
16
  "clsx": "^2.1.1",
 
11
  },
12
  "dependencies": {
13
  "@headlessui/react": "^2.2.0",
14
+ "@heroicons/react": "^2.2.0",
15
  "@radix-ui/react-slot": "^1.1.0",
16
  "class-variance-authority": "^0.7.0",
17
  "clsx": "^2.1.1",
frontend/yarn.lock CHANGED
@@ -85,6 +85,11 @@
85
  "@react-aria/interactions" "^3.21.3"
86
  "@tanstack/react-virtual" "^3.8.1"
87
 
 
 
 
 
 
88
  "@humanwhocodes/config-array@^0.11.14":
89
  version "0.11.14"
90
  resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz"
@@ -2278,7 +2283,7 @@ react-is@^16.13.1:
2278
  resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
2279
  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
2280
 
2281
- "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", react@^18, "react@^18 || ^19 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8.0:
2282
  version "18.3.1"
2283
  resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
2284
  integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
 
85
  "@react-aria/interactions" "^3.21.3"
86
  "@tanstack/react-virtual" "^3.8.1"
87
 
88
+ "@heroicons/react@^2.2.0":
89
+ version "2.2.0"
90
+ resolved "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz"
91
+ integrity sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==
92
+
93
  "@humanwhocodes/config-array@^0.11.14":
94
  version "0.11.14"
95
  resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz"
 
2283
  resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
2284
  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
2285
 
2286
+ "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", react@^18, "react@^18 || ^19 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16 || ^19.0.0-rc", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8.0:
2287
  version "18.3.1"
2288
  resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
2289
  integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==