ChandimaPrabath commited on
Commit
e17b178
·
1 Parent(s): fe54350

0.0.0.8 Alpha

Browse files
frontend/src/app/genres/[genre]/page.js CHANGED
@@ -141,7 +141,7 @@ export default function GenrePage({ params }) {
141
  <ScrollSection title="TV Shows" link={"/tvshows"}>
142
  {genreItems.series.map((item, index) => (
143
  <div key={index} className="genre-item-card">
144
- <Link href={`/series/${item[0]}`} passHref>
145
  <div className="genre-item-link">
146
  <Image
147
  src={
 
141
  <ScrollSection title="TV Shows" link={"/tvshows"}>
142
  {genreItems.series.map((item, index) => (
143
  <div key={index} className="genre-item-card">
144
+ <Link href={`/tvshow/${item[0]}`} passHref>
145
  <div className="genre-item-link">
146
  <Image
147
  src={
frontend/src/app/globals.css CHANGED
@@ -9,6 +9,7 @@
9
  --bg-secondary: #0c0c16;
10
  --primary-special-color:#4343ff;
11
  --gray-text-color:#d8d8d8;
 
12
  }
13
 
14
  body {
 
9
  --bg-secondary: #0c0c16;
10
  --primary-special-color:#4343ff;
11
  --gray-text-color:#d8d8d8;
12
+ --card-color: #1b1b3b;
13
  }
14
 
15
  body {
frontend/src/app/index.css CHANGED
@@ -2,9 +2,17 @@
2
  max-width: 100dvw;
3
  }
4
 
 
 
 
 
 
 
 
5
  @media (orientation: landscape) {
6
  .index-page-content {
7
- padding: 50px;
 
8
  }
9
  }
10
 
@@ -14,9 +22,8 @@
14
  flex: 1;
15
  }
16
 
17
- .index-page-content{
18
  width: 100dvw;
19
- padding: 10px;
20
  z-index: 1;
21
  }
22
 
@@ -33,4 +40,4 @@
33
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
34
  gap: 16px;
35
  padding: 0 16px;
36
- }
 
2
  max-width: 100dvw;
3
  }
4
 
5
+ @media (orientation: portrait) {
6
+ .index-page-content {
7
+ padding-left: 10px;
8
+ padding-right: 10px;
9
+ }
10
+ }
11
+
12
  @media (orientation: landscape) {
13
  .index-page-content {
14
+ padding-left: 50px;
15
+ padding-right: 50px;
16
  }
17
  }
18
 
 
22
  flex: 1;
23
  }
24
 
25
+ .index-page-content {
26
  width: 100dvw;
 
27
  z-index: 1;
28
  }
29
 
 
40
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
41
  gap: 16px;
42
  padding: 0 16px;
43
+ }
frontend/src/app/movie/[title]/movie.css CHANGED
@@ -222,6 +222,7 @@
222
  text-align: center;
223
  justify-content: center;
224
  align-items: center;
 
225
  }
226
 
227
  .movie-genre-item {
 
222
  text-align: center;
223
  justify-content: center;
224
  align-items: center;
225
+ flex-wrap: wrap;
226
  }
227
 
228
  .movie-genre-item {
frontend/src/app/movie/[title]/page.js CHANGED
@@ -43,7 +43,9 @@ export async function generateMetadata({ params }) {
43
  const englishTitle =
44
  metadata?.translations?.nameTranslations?.find(
45
  (translation) => translation.language === "eng"
46
- )?.name || metadata?.name || "Title not available";
 
 
47
 
48
  return {
49
  title: englishTitle,
@@ -65,7 +67,9 @@ export default async function MovieDetailsPage({ params }) {
65
  const englishTitle =
66
  metadata?.translations?.nameTranslations?.find(
67
  (translation) => translation.language === "eng"
68
- )?.name || metadata?.name || "Title not available";
 
 
69
 
70
  const englishOverview =
71
  metadata?.translations?.overviewTranslations?.find(
@@ -81,16 +85,18 @@ export default async function MovieDetailsPage({ params }) {
81
 
82
  return (
83
  <div className="movie-details-page app-container">
84
- <div
85
- className="movie-details-backdrop"
86
- style={{
87
- backgroundImage: `
 
88
  linear-gradient(to right, rgb(17 18 31 / 80%) 50%, transparent 50%),
89
  linear-gradient(rgba(0, 0, 0, 0.1) 0%, #11121f 70%),
90
  url("${backdropImage}")
91
  `,
92
- }}
93
- ></div>
 
94
 
95
  <div className="movie-details-page-container">
96
  <div className="movie-details-info">
@@ -114,7 +120,6 @@ export default async function MovieDetailsPage({ params }) {
114
  </h1>
115
  </div>
116
  <label className="movie-geners-section">
117
- <strong>Genre:</strong>{" "}
118
  {genres.length > 0 ? (
119
  <ul className="movie-genre-list">
120
  {genres.map((genre) => (
@@ -128,7 +133,7 @@ export default async function MovieDetailsPage({ params }) {
128
  ))}
129
  </ul>
130
  ) : (
131
- "Genres not available"
132
  )}
133
  </label>
134
  <p>
@@ -148,23 +153,9 @@ export default async function MovieDetailsPage({ params }) {
148
  <FontAwesomeIcon icon={faBookmarkRegular} size="lg" /> MyList
149
  </button>
150
  </div>
151
- <div className="movie-details-metadata">
152
- <p>
153
- <strong>Director / Writer:</strong> Jon Watts, Steve Ditko, Stan
154
- Lee, Chris McKenna, Erik Sommers
155
- </p>
156
- <p>
157
- <strong>Stars:</strong> Tom Holland, Angourie Rice, Samuel L.
158
- Jackson, Zendaya, Jon Favreau, Jake Gyllenhaal, Marisa Tomei
159
- </p>
160
- </div>
161
  <div className="movie-details-overview">
162
  <h2>Storyline</h2>
163
  <p>{englishOverview}</p>
164
- <p>
165
- <strong>Content Rating:</strong>{" "}
166
- {metadata?.contentRatings?.[0]?.fullname || "Not Rated"}
167
- </p>
168
  </div>
169
  <div className="movie-details-metadata">
170
  <CastSection cast={cast} />
 
43
  const englishTitle =
44
  metadata?.translations?.nameTranslations?.find(
45
  (translation) => translation.language === "eng"
46
+ )?.name ||
47
+ metadata?.name ||
48
+ "Title not available";
49
 
50
  return {
51
  title: englishTitle,
 
67
  const englishTitle =
68
  metadata?.translations?.nameTranslations?.find(
69
  (translation) => translation.language === "eng"
70
+ )?.name ||
71
+ metadata?.name ||
72
+ "Title not available";
73
 
74
  const englishOverview =
75
  metadata?.translations?.overviewTranslations?.find(
 
85
 
86
  return (
87
  <div className="movie-details-page app-container">
88
+ {backdropImage && (
89
+ <div
90
+ className="movie-details-backdrop"
91
+ style={{
92
+ backgroundImage: `
93
  linear-gradient(to right, rgb(17 18 31 / 80%) 50%, transparent 50%),
94
  linear-gradient(rgba(0, 0, 0, 0.1) 0%, #11121f 70%),
95
  url("${backdropImage}")
96
  `,
97
+ }}
98
+ ></div>
99
+ )}
100
 
101
  <div className="movie-details-page-container">
102
  <div className="movie-details-info">
 
120
  </h1>
121
  </div>
122
  <label className="movie-geners-section">
 
123
  {genres.length > 0 ? (
124
  <ul className="movie-genre-list">
125
  {genres.map((genre) => (
 
133
  ))}
134
  </ul>
135
  ) : (
136
+ ""
137
  )}
138
  </label>
139
  <p>
 
153
  <FontAwesomeIcon icon={faBookmarkRegular} size="lg" /> MyList
154
  </button>
155
  </div>
 
 
 
 
 
 
 
 
 
 
156
  <div className="movie-details-overview">
157
  <h2>Storyline</h2>
158
  <p>{englishOverview}</p>
 
 
 
 
159
  </div>
160
  <div className="movie-details-metadata">
161
  <CastSection cast={cast} />
frontend/src/app/player/tvshow/[title]/[season]/[episode]/TvShowPlayerPage.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .film-player-container {
2
+ width: 100dvw;
3
+ height: 100dvh;
4
+ padding: 0;
5
+ margin: 0;
6
+ background-color: transparent;
7
+ }
8
+
9
+ .loading {
10
+ position: fixed;
11
+ top: 50%;
12
+ left: 50%;
13
+ transform: translate(-50%, -50%);
14
+ justify-content: center;
15
+ align-items: center;
16
+ z-index: 100;
17
+ }
18
+ .text-white {
19
+ color: black;
20
+ }
21
+
22
+ .download-status {
23
+ display: flex;
24
+ justify-content: center;
25
+ align-items: center;
26
+ height: 100%;
27
+ }
28
+
29
+ .subtitle-inputs {
30
+ width: 30%;
31
+ height: 10%;
32
+ background-color: green;
33
+ position: absolute;
34
+ top: 0;
35
+ right: 0;
36
+ z-index: 1000;
37
+ }
frontend/src/app/player/tvshow/[title]/[season]/[episode]/page.js CHANGED
@@ -1,16 +1,148 @@
1
  "use client";
 
 
 
 
 
 
 
 
 
2
 
3
- import { useParams } from "next/navigation";
4
 
5
- export default function TVShowPlayer() {
6
- const { title, season, episode } = useParams();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  return (
9
- <>
10
- <div>
11
- <h1 className="text-white">TV Show Player - {title + season + episode}</h1>
12
- {/* Add TV show player component here */}
13
- </div>
14
- </>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  );
16
  }
 
1
  "use client";
2
+ import apiClient from "@/api/apiClient";
3
+ import { useEffect, useState } from "react";
4
+ import MoviePlayer from "@/components/Player/Movie/MoviePlayer";
5
+ import "./TvShowPlayerPage.css";
6
+ import { Spinner } from "@/components/shared/Spinner/Spinner";
7
+ import { Alert } from "react-bootstrap";
8
+ import "bootstrap/dist/css/bootstrap.min.css";
9
+ import NotFound from "@/app/not-found";
10
+ import ErrorPage from "@/app/_error";
11
 
12
+ const DEBUG = false; // Set to true to enable debug mode
13
 
14
+ export default function FilmPlayer({ params }) {
15
+ const title = decodeURIComponent(params.title);
16
+ const season = decodeURIComponent(params.season);
17
+ const episode = decodeURIComponent(params.episode);
18
+ const [videoUrl, setVideoUrl] = useState(null);
19
+ const [loading, setLoading] = useState(true);
20
+ const [error, setError] = useState(null);
21
+ const [downloadProgress, setDownloadProgress] = useState(null);
22
+ const [downloadProgressUrl, setDownloadProgressUrl] = useState(null);
23
+ const [metadata, setMetadata] = useState(null);
24
+
25
+ useEffect(() => {
26
+ if (DEBUG) {
27
+ setMetadata({ title: "Debug Tv Show", year: "2024" });
28
+ } else {
29
+ const fetchSeriesCard = async () => {
30
+ try {
31
+ const data = await apiClient.getSeriesCard(title);
32
+ if (data.title) {
33
+ setMetadata(data);
34
+ } else {
35
+ setError("Movie card not found");
36
+ setLoading(false);
37
+ }
38
+ } catch (err) {
39
+ setError("Failed to fetch movie card");
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ fetchSeriesCard();
45
+ }
46
+ }, [title]);
47
+
48
+ useEffect(() => {
49
+ if (!metadata) return;
50
+
51
+ if (DEBUG) {
52
+ setTimeout(() => {
53
+ setDownloadProgressUrl("dummy_progress_url");
54
+ setLoading(false);
55
+ }, 1000);
56
+ } else {
57
+ const fetchVideoData = async () => {
58
+ try {
59
+ const data = await apiClient.getSeriesEpisode(title, season, episode);
60
+ if (data.url) {
61
+ setVideoUrl(data.url);
62
+ setLoading(false);
63
+ } else if (data.status === "Download started") {
64
+ setDownloadProgressUrl(data.progress_url);
65
+ setLoading(false);
66
+ }
67
+ } catch (err) {
68
+ setError("Failed to fetch video data");
69
+ setLoading(false);
70
+ }
71
+ };
72
+
73
+ fetchVideoData();
74
+ }
75
+ }, [title, season, episode, metadata]);
76
+
77
+ useEffect(() => {
78
+ if (!downloadProgressUrl) return;
79
+
80
+ if (DEBUG) {
81
+ const dummyProgressData = {
82
+ progress: {
83
+ status: "Downloading",
84
+ progress: 45.5,
85
+ eta: 120,
86
+ },
87
+ };
88
+ setDownloadProgress(dummyProgressData.progress);
89
+ } else {
90
+ const intervalId = setInterval(async () => {
91
+ try {
92
+ const response = await fetch(downloadProgressUrl);
93
+ const progressData = await response.json();
94
+ if (progressData.progress.status === "Completed") {
95
+ clearInterval(intervalId);
96
+ setDownloadProgressUrl(null);
97
+ setDownloadProgress(null);
98
+ setLoading(true);
99
+ setTimeout(async () => {
100
+ const data = await apiClient.getSeriesEpisode(title, season, episode);
101
+ if (data.url) {
102
+ setVideoUrl(data.url);
103
+ setLoading(false);
104
+ }
105
+ }, 5000); // Wait 5 seconds before fetching the video URL again
106
+ } else {
107
+ setDownloadProgress(progressData.progress);
108
+ }
109
+ } catch (err) {
110
+ setError("Failed to fetch download progress");
111
+ }
112
+ }, 5000);
113
+
114
+ return () => clearInterval(intervalId);
115
+ }
116
+ }, [downloadProgressUrl, title, season, episode]);
117
 
118
  return (
119
+ <div className="film-player-container">
120
+ {loading && (
121
+ <div className="loading">
122
+ <Spinner />
123
+ </div>
124
+ )}
125
+ {error === "Failed to fetch movie card" ? (
126
+ <NotFound />
127
+ ) : error && (
128
+ <ErrorPage errorMessage={error} />
129
+ )}
130
+ {downloadProgressUrl && downloadProgress ? (
131
+ <div className="download-status">
132
+ <Alert variant="info">
133
+ <strong>{downloadProgress.status}</strong> -{" "}
134
+ {downloadProgress.progress.toFixed(2)}%{" "}
135
+ <p>Wait for {downloadProgress.eta.toFixed(1)} Seconds</p>
136
+ </Alert>
137
+ </div>
138
+ ) : (
139
+ videoUrl && (
140
+ <MoviePlayer
141
+ videoUrl={videoUrl}
142
+ title={`${metadata.title} (${metadata.year})`}
143
+ />
144
+ )
145
+ )}
146
+ </div>
147
  );
148
  }
frontend/src/app/tvshow/[title]/SeasonSection.css ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .seasons-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ margin: 20px;
6
+ }
7
+
8
+ select {
9
+ padding: 10px;
10
+ margin-bottom: 20px;
11
+ font-size: 1rem;
12
+ border: 1px solid var(--primary-special-color);
13
+ border-radius: 8px;
14
+ background-color: var(--card-color);
15
+ color: var(--primary-text-color);
16
+ outline: none;
17
+ cursor: pointer;
18
+ transition: background-color 0.3s ease;
19
+ }
20
+
21
+ select:hover {
22
+ background-color: var(--primary-special-color);
23
+ color: var(--secondary-text-color);
24
+ }
25
+
26
+ .episode-list {
27
+ display: flex;
28
+ flex-direction: column;
29
+ flex-wrap: wrap;
30
+ justify-content: center;
31
+ gap: 16px;
32
+ }
33
+
34
+ .episode-link {
35
+ text-decoration: none;
36
+ color: inherit;
37
+ }
38
+
39
+ .episode-card {
40
+ border: 1px solid var(--primary-special-color);
41
+ background-color: var(--card-color);
42
+ border-radius: 8px;
43
+ padding: 16px;
44
+ margin: 8px;
45
+ width: 100%;
46
+ max-width: 600px;
47
+ display: flex;
48
+ flex-direction: column;
49
+ align-items: center;
50
+ overflow: hidden;
51
+ position: relative;
52
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
53
+ }
54
+
55
+ .episode-card:hover {
56
+ transform: translateY(-5px);
57
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
58
+ }
59
+
60
+ .episode-header {
61
+ display: flex;
62
+ justify-content: space-between;
63
+ width: 100%;
64
+ margin-bottom: 10px;
65
+ }
66
+
67
+ .episode-number {
68
+ font-size: 1rem;
69
+ color: var(--secondary-text-color);
70
+ }
71
+
72
+ .episode-image {
73
+ width: 100%;
74
+ height: 200px;
75
+ object-fit: cover;
76
+ border-radius: 4px;
77
+ margin-bottom: 10px;
78
+ }
79
+
80
+ .episode-name {
81
+ font-size: 1.2rem;
82
+ font-weight: 600;
83
+ color: var(--primary-text-color);
84
+ text-align: center;
85
+ }
86
+
87
+ .episode-details {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ width: 100%;
91
+ font-size: 0.9rem;
92
+ color: var(--secondary-text-color);
93
+ }
94
+
95
+ .episode-overview-overlay {
96
+ position: absolute;
97
+ bottom: 0;
98
+ left: 0;
99
+ right: 0;
100
+ background: rgba(0, 0, 0, 0.8);
101
+ color: white;
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ opacity: 0;
106
+ transition: opacity 0.3s ease;
107
+ padding: 20px;
108
+ box-sizing: border-box;
109
+ height: 100%;
110
+ flex-direction: column;
111
+ justify-content: center;
112
+ }
113
+
114
+ .episode-card:hover .episode-overview-overlay,
115
+ .show-overlay .episode-overview-overlay {
116
+ opacity: 1;
117
+ }
118
+
119
+ .episode-overview {
120
+ font-size: 0.9rem;
121
+ color: var(--secondary-text-color);
122
+ text-align: center;
123
+ margin-top: 10px;
124
+ padding: 0 20px;
125
+ }
126
+
127
+ /* Responsive design */
128
+ @media (max-width: 768px) {
129
+ .episode-card {
130
+ width: 90%;
131
+ }
132
+
133
+ .episode-header {
134
+ flex-direction: column;
135
+ align-items: center;
136
+ }
137
+
138
+ .episode-name {
139
+ font-size: 1rem;
140
+ }
141
+
142
+ .episode-image {
143
+ height: 150px;
144
+ }
145
+
146
+ .episode-details {
147
+ flex-direction: column;
148
+ align-items: center;
149
+ }
150
+
151
+ .episode-overview {
152
+ font-size: 0.8rem;
153
+ }
154
+ }
frontend/src/app/tvshow/[title]/SeasonsSection.js ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import "./SeasonSection.css";
5
+ import apiClient from "@/api/apiClient";
6
+ import Image from "next/image";
7
+ import Link from "next/link";
8
+
9
+ const EpisodeSection = ({ episodes }) => (
10
+ <div className="episode-list">
11
+ {episodes.map((episode, index) => {
12
+ // Split the path and remove the first part
13
+ const pathParts = episode.path.split('/');
14
+ const newPath = pathParts.slice(1).join('/'); // Join remaining parts
15
+
16
+ return (
17
+ <Link href={`/player/tvshow/${newPath}`} key={episode.path}>
18
+ <div className="episode-card">
19
+ <div className="episode-header">
20
+ <p className="episode-number">Episode {index + 1}</p>
21
+ <p className="episode-name">{episode.name}</p>
22
+ </div>
23
+ <Image
24
+ src={`https://artworks.thetvdb.com${episode.image}`}
25
+ alt={episode.name}
26
+ width={200}
27
+ height={150}
28
+ className="episode-image"
29
+ />
30
+ <div className="episode-details">
31
+ <p className="episode-aired">Aired: {episode.aired}</p>
32
+ <p className="episode-runtime">Runtime: {episode.runtime} mins</p>
33
+ </div>
34
+ <div className="episode-overview-overlay">
35
+ <p className="episode-overview">{episode.overview}</p>
36
+ </div>
37
+ </div>
38
+ </Link>
39
+ );
40
+ })}
41
+ </div>
42
+ );
43
+
44
+ const fetchSeasonMetadata = async (seriesId, seasonName) => {
45
+ try {
46
+ const metadata = await apiClient.getSeasonMetadataBySeriesId(
47
+ seriesId,
48
+ seasonName
49
+ );
50
+ return metadata;
51
+ } catch (error) {
52
+ console.error("Failed to fetch season metadata:", error);
53
+ return null; // Handle error appropriately
54
+ }
55
+ };
56
+
57
+ const SeasonsSection = ({ fileStructure, seriesId, selectedSeason }) => {
58
+ const [episodesWithMetadata, setEpisodesWithMetadata] = useState([]);
59
+ const [seasonToFetch, setSeasonToFetch] = useState(
60
+ selectedSeason ||
61
+ (fileStructure.contents.length > 0
62
+ ? fileStructure.contents[0].path.split("/").pop()
63
+ : "")
64
+ );
65
+
66
+ useEffect(() => {
67
+ const loadSeasonMetadata = async () => {
68
+ const seasons = fileStructure.contents.filter(
69
+ (item) => item.type === "directory"
70
+ );
71
+ const metadata = await fetchSeasonMetadata(seriesId, seasonToFetch);
72
+
73
+ const selectedSeasonData = seasons.find(
74
+ (season) => season.path.split("/").pop() === seasonToFetch
75
+ );
76
+
77
+ const episodes = selectedSeasonData
78
+ ? selectedSeasonData.contents.map((episode) => {
79
+ const episodeNumber = parseInt(episode.path.match(/S\d+E(\d+)/)[1]); // Extract episode number from path
80
+ const matchingMetadata = metadata
81
+ ? metadata.find((meta) => meta.number === episodeNumber)
82
+ : null;
83
+
84
+ return {
85
+ path: episode.path,
86
+ name: matchingMetadata
87
+ ? matchingMetadata.name
88
+ : episode.path.split("/").pop(),
89
+ overview: matchingMetadata
90
+ ? matchingMetadata.overview
91
+ : "No overview available.",
92
+ aired: matchingMetadata
93
+ ? matchingMetadata.aired
94
+ : "Unknown air date",
95
+ runtime: matchingMetadata ? matchingMetadata.runtime : 0,
96
+ image: matchingMetadata ? matchingMetadata.image : "", // Fallback to no image
97
+ };
98
+ })
99
+ : [];
100
+
101
+ setEpisodesWithMetadata(episodes);
102
+ };
103
+
104
+ loadSeasonMetadata();
105
+ }, [fileStructure, seriesId, seasonToFetch]);
106
+
107
+ const handleSeasonChange = (event) => {
108
+ setSeasonToFetch(event.target.value);
109
+ };
110
+
111
+ const seasons = fileStructure.contents.filter(
112
+ (item) => item.type === "directory"
113
+ );
114
+
115
+ return (
116
+ <div className="seasons-container">
117
+ <select defaultValue={seasonToFetch} onChange={handleSeasonChange}>
118
+ {seasons.map((season) => (
119
+ <option key={season.path} value={season.path.split("/").pop()}>
120
+ {season.path.split("/").pop()}
121
+ </option>
122
+ ))}
123
+ </select>
124
+
125
+ {episodesWithMetadata.length > 0 ? (
126
+ <EpisodeSection episodes={episodesWithMetadata} />
127
+ ) : (
128
+ <p>No episodes available for this season.</p>
129
+ )}
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export default SeasonsSection;
frontend/src/app/tvshow/[title]/page.js ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server";
2
+
3
+ import Link from "next/link";
4
+ import Image from "next/image";
5
+ import "./tvshow.css";
6
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7
+ import {
8
+ faPlay,
9
+ faBookmark as faBookmarkSolid,
10
+ } from "@fortawesome/free-solid-svg-icons";
11
+ import { faBookmark as faBookmarkRegular } from "@fortawesome/free-regular-svg-icons";
12
+ import apiClient from "@/api/apiClient";
13
+ import CastSection from "@/components/shared/Sections/CastSection";
14
+ import NotFound from "@/app/not-found";
15
+ import SeasonsSection from "./SeasonsSection";
16
+
17
+ function formatTime(minutes) {
18
+ const hours = Math.floor(minutes / 60);
19
+ const remainingMinutes = minutes % 60;
20
+ return `${hours}h ${remainingMinutes}m`;
21
+ }
22
+
23
+ // Fetch tvshow metadata once and return it
24
+ async function fetchTvshowMetadata(title) {
25
+ try {
26
+ const decodedTitle = decodeURIComponent(title);
27
+ const metadata = await apiClient.getSeriesMetadataByTitle(decodedTitle);
28
+ return metadata?.data ? metadata : null;
29
+ } catch (err) {
30
+ console.error(err);
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export async function generateMetadata({ params }) {
36
+ const { title } = params;
37
+ const data = await fetchTvshowMetadata(title);
38
+ const metadata = data?.data;
39
+ if (!metadata) {
40
+ return {
41
+ title: "Tv Show Not Found",
42
+ description: "The Tv Show you are looking for does not exist.",
43
+ };
44
+ }
45
+
46
+ const englishTitle =
47
+ metadata?.translations?.nameTranslations?.find(
48
+ (translation) => translation.language === "eng"
49
+ )?.name ||
50
+ metadata?.name ||
51
+ "Title not available";
52
+
53
+ return {
54
+ title: englishTitle,
55
+ description: `Details and information about the Tv Show ${englishTitle}`,
56
+ openGraph: {
57
+ images: [metadata?.image || "/default-movie-image.jpg"],
58
+ },
59
+ };
60
+ }
61
+
62
+ export default async function TvShowDetailsPage({ params }) {
63
+ const { title } = params;
64
+ const data = await fetchTvshowMetadata(title);
65
+ const metadata = data?.data;
66
+ const fileStructure = data?.file_structure || [];
67
+
68
+ if (!metadata) {
69
+ return <NotFound />;
70
+ }
71
+
72
+ const seriesId = metadata?.id || null;
73
+
74
+ const englishTitle =
75
+ metadata?.translations?.nameTranslations?.find(
76
+ (translation) => translation.language === "eng"
77
+ )?.name ||
78
+ metadata?.name ||
79
+ "Title not available";
80
+
81
+ const englishOverview =
82
+ metadata?.translations?.overviewTranslations?.find(
83
+ (translation) => translation.language === "eng"
84
+ )?.overview || "Overview not available";
85
+
86
+ const backdropImage = metadata?.artworks?.find(
87
+ (artwork) => artwork.type === 3
88
+ )?.image;
89
+
90
+ const genres = metadata?.genres || [];
91
+ const cast = metadata?.characters || [];
92
+
93
+ return (
94
+ <div className="movie-details-page app-container">
95
+ {backdropImage && (
96
+ <div
97
+ className="movie-details-backdrop"
98
+ style={{
99
+ backgroundImage: `
100
+ linear-gradient(to right, rgb(17 18 31 / 80%) 50%, transparent 50%),
101
+ linear-gradient(rgba(0, 0, 0, 0.1) 0%, #11121f 70%),
102
+ url("${backdropImage}")
103
+ `,
104
+ }}
105
+ ></div>
106
+ )}
107
+
108
+ <div className="movie-details-page-container">
109
+ <div className="movie-details-info">
110
+ <div className="movie-details-head-section">
111
+ <div className="movie-details-poster-layout">
112
+ <div className="movie-details-poster">
113
+ <Image
114
+ src={
115
+ metadata?.image ||
116
+ "https://via.placeholder.com/800x450?text=No+Image+Available"
117
+ }
118
+ alt={`${englishTitle} Poster`}
119
+ height={300}
120
+ width={250}
121
+ />
122
+ </div>
123
+ <div className="movie-details-metadata-right">
124
+ <div className="movie-details-header">
125
+ <h1>
126
+ {englishTitle} {"(" + metadata?.year + ")" || ""}
127
+ </h1>
128
+ </div>
129
+ <label className="movie-geners-section">
130
+ {genres.length > 0 ? (
131
+ <ul className="movie-genre-list">
132
+ {genres.map((genre) => (
133
+ <li key={genre.id} className="movie-genre-item">
134
+ <Link href={`/genres/${genre.name}`} passHref>
135
+ <label className="movie-genre-link">
136
+ {genre.name}
137
+ </label>
138
+ </Link>
139
+ </li>
140
+ ))}
141
+ </ul>
142
+ ) : (
143
+ "Genres not available"
144
+ )}
145
+ </label>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ <div className="movie-details-overview">
150
+ <h2>Storyline</h2>
151
+ <p>{englishOverview}</p>
152
+ </div>
153
+ <div className="movie-details-metadata">
154
+ <CastSection cast={cast} />
155
+ </div>
156
+ <SeasonsSection fileStructure={fileStructure} seriesId={seriesId} />
157
+ </div>
158
+ </div>
159
+ </div>
160
+ );
161
+ }
frontend/src/app/tvshow/[title]/tvshow.css ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url("https://fonts.googleapis.com/css2?family=Anton&display=swap");
2
+ .movie-details-page {
3
+ position: relative;
4
+ overflow: hidden;
5
+ scroll-behavior: smooth;
6
+ color: #fff;
7
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
8
+ }
9
+
10
+ /* Backdrop Styling */
11
+ .movie-details-backdrop {
12
+ position: fixed;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ background-size: 250% ; /* Adjusted size to create a zooming effect */
18
+ background-position: center;
19
+ background-repeat: no-repeat;
20
+ z-index: -1;
21
+ animation: backdrop-anim-portrait 20s ease-in-out infinite;
22
+ filter: blur(10px);
23
+ }
24
+
25
+ @keyframes backdrop-anim-portrait {
26
+ 0% {
27
+ background-position: center;
28
+ background-size: 300% ;
29
+ }
30
+ 25% {
31
+ background-position: top left;
32
+ background-size: 250% ;
33
+ }
34
+ 50% {
35
+ background-position: bottom right;
36
+ background-size: 300% ;
37
+ }
38
+ 75% {
39
+ background-position: top right;
40
+ background-size: 2500% ;
41
+ }
42
+ 100% {
43
+ background-position: center;
44
+ background-size: 300% ;
45
+ }
46
+ }
47
+
48
+ @media (orientation: landscape) {
49
+ .movie-details-backdrop {
50
+ background-size: 250%; /* Adjusted size to create a zooming effect */
51
+ animation: backdrop-anim-landscape 20s ease-in-out infinite;
52
+ }
53
+ }
54
+
55
+ @keyframes backdrop-anim-landscape {
56
+ 0% {
57
+ background-position: center;
58
+ background-size: 150% ;
59
+ }
60
+ 25% {
61
+ background-position: top left;
62
+ background-size: 120% ;
63
+ }
64
+ 50% {
65
+ background-position: bottom right;
66
+ background-size: 150% ;
67
+ }
68
+ 75% {
69
+ background-position: top right;
70
+ background-size: 120% ;
71
+ }
72
+ 100% {
73
+ background-position: center;
74
+ background-size: 150% ;
75
+ }
76
+ }
77
+ /* Container Styling */
78
+ .movie-details-page-container {
79
+ position: relative;
80
+ max-width: 1200px;
81
+ margin: 0 auto;
82
+ padding: 20px;
83
+ }
84
+
85
+ /* Header Styling */
86
+ .movie-details-header {
87
+ display: flex;
88
+ justify-content: space-between;
89
+ align-items: center;
90
+ margin-bottom: 20px;
91
+ }
92
+
93
+ .movie-details-header h1 {
94
+ font-size: 4.5rem;
95
+ font-family: "Anton", sans-serif;
96
+ font-weight: 400;
97
+ font-style: normal;
98
+ margin: 0;
99
+ text-overflow: ellipsis;
100
+ }
101
+
102
+ /* Action Buttons */
103
+ .movie-details-actions {
104
+ display: flex;
105
+ gap: 15px;
106
+ }
107
+
108
+ .play-button {
109
+ border: 1px solid #4339ff;
110
+ background-color: #17174a; /* Red button color */
111
+ }
112
+
113
+ .add-list-button {
114
+ background-color: #333;
115
+ }
116
+
117
+ .play-button,
118
+ .add-list-button {
119
+ color: #fff;
120
+ padding: 12px 24px;
121
+ border-radius: 5px;
122
+ cursor: pointer;
123
+ font-size: 1rem;
124
+ transition: background-color 0.3s ease, transform 0.3s ease;
125
+ }
126
+
127
+ .play-button:hover {
128
+ background-color: #27277c;
129
+ }
130
+
131
+ .add-list-button:hover {
132
+ background-color: #444;
133
+ }
134
+
135
+ .play-button:hover,
136
+ .add-list-button:hover {
137
+ transform: scale(1.05);
138
+ }
139
+
140
+ /* Poster Styling */
141
+ .movie-details-poster img {
142
+ width: 250px;
143
+ height: auto;
144
+ border-radius: 8px;
145
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
146
+ margin-bottom: 20px;
147
+ }
148
+
149
+ /* Info Section Styling */
150
+ .movie-details-info {
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 20px;
154
+ }
155
+
156
+ .movie-details-metadata,
157
+ .movie-details-overview {
158
+ padding: 20px;
159
+ border-radius: 8px;
160
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
161
+ }
162
+
163
+ .movie-details-metadata p,
164
+ .movie-details-overview p {
165
+ margin: 8px 0;
166
+ line-height: 1.6;
167
+ font-weight: 300;
168
+ }
169
+
170
+ .movie-details-metadata-right{
171
+ padding: 20px;
172
+ display: flex;
173
+ flex-direction: column;
174
+ justify-content: center;
175
+ max-width: 100%;
176
+ flex-wrap: wrap;
177
+ overflow-wrap: break-word;
178
+ }
179
+
180
+ .movie-details-overview h2 {
181
+ margin-bottom: 15px;
182
+ font-size: 1.5rem;
183
+ border-bottom: 2px solid #4339ff;
184
+ padding-bottom: 5px;
185
+ font-weight: 600;
186
+ }
187
+
188
+ .movie-details-head-section{
189
+ display: flex;
190
+ max-width: 100%;
191
+ overflow-wrap: break-word;
192
+ }
193
+
194
+ .movie-details-poster-layout{
195
+ display: flex;
196
+ max-width: 100%;
197
+ overflow-wrap: break-word;
198
+ }
199
+
200
+ /* Responsive Design */
201
+ @media (max-width: 768px) {
202
+ .movie-details-header h1 {
203
+ font-size: 1.5rem;
204
+ }
205
+
206
+ .movie-details-poster img {
207
+ width: 100%;
208
+ max-width: 400px;
209
+ min-width: 150px;
210
+ }
211
+
212
+ .movie-details-info {
213
+ flex-direction: column;
214
+ }
215
+ }
216
+
217
+ .movie-geners-section {
218
+ display: flex;
219
+ flex-wrap: wrap;
220
+ text-align: center;
221
+ align-items: center;
222
+ }
223
+
224
+ .movie-genre-list {
225
+ list-style-type: none;
226
+ padding: 0;
227
+ margin: 0;
228
+ display: flex;
229
+ text-align: center;
230
+ justify-content: center;
231
+ align-items: center;
232
+ flex-wrap: wrap;
233
+ }
234
+
235
+ .movie-genre-item {
236
+ margin: 5px;
237
+ }
238
+
239
+ .movie-genre-link {
240
+ text-decoration: none;
241
+ color: #0082f3; /* Next.js blue color */
242
+ font-weight: bold;
243
+ }
244
+
245
+ .movie-genre-link:hover {
246
+ text-decoration: underline;
247
+ }
248
+
249
+ .loading {
250
+ position: fixed;
251
+ top: 50%;
252
+ left: 50%;
253
+ transform: translate(-50%, -50%);
254
+ justify-content: center;
255
+ align-items: center;
256
+ z-index: 100;
257
+ }
frontend/src/app/tvshows/TvShows.css CHANGED
@@ -1,4 +1,97 @@
1
- .page-container{
2
- width: 100dvw;
 
 
 
 
 
 
3
  height: 100dvh;
4
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Films Page Container */
2
+ .films-page-container {
3
+ display: flex;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ padding: 20px;
7
+ max-width: 1200px;
8
+ margin: 0 auto;
9
  height: 100dvh;
10
+ }
11
+
12
+ /* Films Grid */
13
+ .films-page {
14
+ display: grid;
15
+ grid-template-columns: repeat(auto-fill, minmax(140px, 0fr));
16
+ gap: 0;
17
+ width: 100%;
18
+ height: auto;
19
+ justify-content: center;
20
+ }
21
+
22
+ /* Media Queries for Responsiveness */
23
+ @media (max-width: 768px) {
24
+ .films-page {
25
+ grid-template-columns: repeat(auto-fill, minmax(120px, 0rem));
26
+ gap: 0;
27
+ }
28
+ }
29
+
30
+ @media (max-width: 480px) {
31
+ .films-page {
32
+ grid-template-columns: repeat(auto-fill, minmax(120px, 0rem));
33
+ }
34
+ }
35
+
36
+ /* Pagination Controls */
37
+ .pagination-controls {
38
+ display: flex;
39
+ align-items: center;
40
+ position: absolute;
41
+ top: 93dvh;
42
+ transition: top .2s ease;
43
+ }
44
+
45
+ /* Pagination Button */
46
+ .pagination-button {
47
+ background-color: #1c2354;
48
+ border: 1px solid #4339ff;
49
+ color: #ffffff;
50
+ border-radius: 50%;
51
+ padding: 10px;
52
+ width: 40px;
53
+ height: 40px;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ cursor: pointer;
58
+ transition: background-color 1s, border .5s;
59
+ }
60
+
61
+ .pagination-button.enabled:hover {
62
+ background-color: #273281;
63
+ }
64
+
65
+ .pagination-button.disabled {
66
+ background-color: #333;
67
+ cursor: not-allowed;
68
+ border: none;
69
+ }
70
+
71
+ /* Page Info */
72
+ .page-info {
73
+ font-size: 1.2em;
74
+ margin: 0 15px;
75
+ color: #e0e0e0;
76
+ }
77
+
78
+ /* Handle Animations on Page Load */
79
+ @keyframes pageLoad {
80
+ from {
81
+ opacity: 0;
82
+ }
83
+ to {
84
+ opacity: 1;
85
+ }
86
+ }
87
+
88
+ .films-page-container {
89
+ animation: pageLoad 1s ease;
90
+ }
91
+
92
+ .page-h1{
93
+ font-size: 2rem;
94
+ color: white;
95
+ font-weight: 600;
96
+ }
97
+
frontend/src/app/tvshows/page.js CHANGED
@@ -1,13 +1,81 @@
1
- import StillInDevelopment from '../_still-in-development';
2
- import './TvShows.css';
 
 
 
 
 
 
 
 
3
 
4
  export const metadata = {
5
  title: 'Tv Shows',
6
- description: 'This is Tv Shows Page. here you can Discover Tv Shows.',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
8
 
9
- export default function TVShows() {
 
 
 
 
 
 
10
  return (
11
- <StillInDevelopment/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  );
13
- }
 
 
 
1
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2
+ import apiClient from "@/api/apiClient";
3
+ import TvShowCard from "@/components/shared/Card/TvShowCard";
4
+ import "./TvShows.css";
5
+
6
+ import {
7
+ faChevronLeft,
8
+ faChevronRight,
9
+ } from "@fortawesome/free-solid-svg-icons";
10
+ import Link from "next/link";
11
 
12
  export const metadata = {
13
  title: 'Tv Shows',
14
+ description: 'This is Tv Shows Page. Here you can discover TV shows.',
15
+ }
16
+
17
+ const SHOWS_PER_PAGE = 10;
18
+
19
+ async function fetchShows(page) {
20
+ let shows = [];
21
+
22
+ try {
23
+ const response = await apiClient.getAllSeriesShows();
24
+ shows = Object.keys(response);
25
+ } catch (error) {
26
+ console.error("Failed to get all shows:", error);
27
+ }
28
+
29
+ const totalPages = Math.ceil(shows.length / SHOWS_PER_PAGE);
30
+ const currentPage = Math.max(1, Math.min(page, totalPages));
31
+ const startIndex = (currentPage - 1) * SHOWS_PER_PAGE;
32
+ const currentShows = shows.slice(startIndex, startIndex + SHOWS_PER_PAGE);
33
+
34
+ return { currentShows, currentPage, totalPages };
35
  }
36
 
37
+ const TvShowsPage = async ({ searchParams }) => {
38
+ const page = parseInt(searchParams.page) || 1;
39
+ const { currentShows, currentPage, totalPages } = await fetchShows(page);
40
+
41
+ const isPrevButtonEnabled = currentPage > 1;
42
+ const isNextButtonEnabled = currentPage < totalPages;
43
+
44
  return (
45
+ <div className="films-page-container app-container">
46
+ <h1 className="page-h1">TV Shows</h1>
47
+ <div className="films-page">
48
+ {currentShows.map((title, index) => (
49
+ <TvShowCard key={index} title={title} />
50
+ ))}
51
+ </div>
52
+ <div className="pagination-controls">
53
+ <Link href={`?page=${currentPage - 1}`} passHref>
54
+ <button
55
+ disabled={!isPrevButtonEnabled}
56
+ className={`pagination-button ${
57
+ isPrevButtonEnabled ? "enabled" : "disabled"
58
+ }`}
59
+ >
60
+ <FontAwesomeIcon icon={faChevronLeft} size="xl" />
61
+ </button>
62
+ </Link>
63
+ <span className="page-info">
64
+ {currentPage} of {totalPages}
65
+ </span>
66
+ <Link href={`?page=${currentPage + 1}`} passHref>
67
+ <button
68
+ disabled={!isNextButtonEnabled}
69
+ className={`pagination-button ${
70
+ isNextButtonEnabled ? "enabled" : "disabled"
71
+ }`}
72
+ >
73
+ <FontAwesomeIcon icon={faChevronRight} size="xl" />
74
+ </button>
75
+ </Link>
76
+ </div>
77
+ </div>
78
  );
79
+ };
80
+
81
+ export default TvShowsPage;
frontend/src/components/shared/Card/Card.css CHANGED
@@ -4,10 +4,10 @@
4
 
5
  .movie-card {
6
  position: relative;
7
- width: 115px;
8
- height: 225px;
9
- min-width: 115px;
10
- min-height: 225px;
11
  margin: 10px;
12
  border-radius: 8px;
13
  overflow: hidden;
 
4
 
5
  .movie-card {
6
  position: relative;
7
+ width: 130px;
8
+ height: 250px;
9
+ min-width: 130px;
10
+ min-height: 250px;
11
  margin: 10px;
12
  border-radius: 8px;
13
  overflow: hidden;
frontend/src/components/shared/Card/TvShowCard.js CHANGED
@@ -3,6 +3,7 @@
3
  import Image from 'next/image';
4
  import apiClient from '@/api/apiClient';
5
  import SkeletonLoader from '@/skeletons/Card/movieCard';
 
6
  import './Card.css';
7
 
8
  const TvShowCard = async ({ title }) => {
@@ -27,6 +28,7 @@ const TvShowCard = async ({ title }) => {
27
  }
28
 
29
  return (
 
30
  <div className="movie-card">
31
  <div className="movie-card-image-container">
32
  <Image
@@ -43,6 +45,7 @@ const TvShowCard = async ({ title }) => {
43
  <p className="movie-card-year">{movieData.year}</p>
44
  </div>
45
  </div>
 
46
  );
47
  };
48
 
 
3
  import Image from 'next/image';
4
  import apiClient from '@/api/apiClient';
5
  import SkeletonLoader from '@/skeletons/Card/movieCard';
6
+ import Link from 'next/link';
7
  import './Card.css';
8
 
9
  const TvShowCard = async ({ title }) => {
 
28
  }
29
 
30
  return (
31
+ <Link href={`/tvshow/${title}`}>
32
  <div className="movie-card">
33
  <div className="movie-card-image-container">
34
  <Image
 
45
  <p className="movie-card-year">{movieData.year}</p>
46
  </div>
47
  </div>
48
+ </Link>
49
  );
50
  };
51
 
frontend/src/lib/config.js CHANGED
@@ -1,7 +1,7 @@
1
  const config = {
2
  apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
3
  searchUrl: 'https://unicone-studio-search.hf.space/api/search',
4
- version: "0.0.0.6 Alpha",
5
  };
6
 
7
  export default config;
 
1
  const config = {
2
  apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
3
  searchUrl: 'https://unicone-studio-search.hf.space/api/search',
4
+ version: "0.0.0.8 Alpha",
5
  };
6
 
7
  export default config;