ChandimaPrabath commited on
Commit
7428ad9
1 Parent(s): f672bdc

0.0.0.4 Alpha

Browse files
frontend/next.config.mjs CHANGED
@@ -1,7 +1,7 @@
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
  images: {
4
- domains: ['artworks.thetvdb.com'],
5
  },
6
  };
7
 
 
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
  images: {
4
+ domains: ['artworks.thetvdb.com','via.placeholder.com'],
5
  },
6
  };
7
 
frontend/src/app/genres/[genre]/genres.css CHANGED
@@ -10,17 +10,10 @@ body {
10
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
11
  }
12
 
13
- /* Title styling */
14
- .genre-title {
15
- font-size: 2rem;
16
- text-align: center;
17
- color: #ffffff; /* Light color for the title */
18
- }
19
-
20
- .genre-title-section{
21
- display: flex;
22
- justify-content: center;
23
- align-items: center;
24
  }
25
 
26
  /* Section styling */
@@ -29,10 +22,10 @@ body {
29
  }
30
 
31
  @media (orientation: landscape) {
32
- .section {
33
- margin-left: 25px;
34
- margin-right: 25px;
35
- }
36
  }
37
 
38
  .section h2 {
@@ -63,12 +56,12 @@ body {
63
 
64
  /* Card for each item */
65
  .item-card {
66
- background-color: #202232; /* Dark card background */
67
  border-radius: 8px;
68
  overflow: hidden;
69
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
70
  transition: transform 0.3s, box-shadow 0.3s;
71
- flex: 0 0 300px; /* Fixed width for horizontal scrolling */
72
  }
73
 
74
  .item-card:hover {
@@ -85,18 +78,18 @@ body {
85
 
86
  .item-image {
87
  width: 100%;
88
- height: 120px; /* Fixed height for consistency */
89
  object-fit: cover; /* Maintain aspect ratio and fill container */
90
  display: block;
91
  }
92
 
93
  /* Item information */
94
  .item-info {
95
- padding: 10px;
96
  }
97
 
98
  .item-title {
99
- font-size: 1.2rem;
100
  font-weight: bold;
101
  margin: 0;
102
  color: #ffffff; /* Light color for item titles */
@@ -133,7 +126,7 @@ body {
133
  /* Load More Button container */
134
  .load-more-container {
135
  text-align: center;
136
- margin: 20px 0;
137
  }
138
 
139
  /* Load More Button styling */
@@ -169,3 +162,55 @@ body {
169
  margin-left: 10px;
170
  margin-right: 10px;
171
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
11
  }
12
 
13
+ .genre-title-section {
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: center;
 
 
 
 
 
 
 
17
  }
18
 
19
  /* Section styling */
 
22
  }
23
 
24
  @media (orientation: landscape) {
25
+ .section {
26
+ margin-left: 25px;
27
+ margin-right: 25px;
28
+ }
29
  }
30
 
31
  .section h2 {
 
56
 
57
  /* Card for each item */
58
  .item-card {
59
+ background-color: #1d1f2d; /* Dark card background */
60
  border-radius: 8px;
61
  overflow: hidden;
62
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
63
  transition: transform 0.3s, box-shadow 0.3s;
64
+ flex: 0 0 200px; /* Fixed width for horizontal scrolling */
65
  }
66
 
67
  .item-card:hover {
 
78
 
79
  .item-image {
80
  width: 100%;
81
+ height: 100px; /* Fixed height for consistency */
82
  object-fit: cover; /* Maintain aspect ratio and fill container */
83
  display: block;
84
  }
85
 
86
  /* Item information */
87
  .item-info {
88
+ padding: 5px;
89
  }
90
 
91
  .item-title {
92
+ font-size: 1rem;
93
  font-weight: bold;
94
  margin: 0;
95
  color: #ffffff; /* Light color for item titles */
 
126
  /* Load More Button container */
127
  .load-more-container {
128
  text-align: center;
129
+ margin: 10px 0;
130
  }
131
 
132
  /* Load More Button styling */
 
162
  margin-left: 10px;
163
  margin-right: 10px;
164
  }
165
+
166
+ .filter-button {
167
+ margin-right: 10px;
168
+ }
169
+ .bubbles-scroll-section{
170
+ display: flex;
171
+ overflow-x: auto;
172
+ width: 100%;
173
+ }
174
+
175
+ .bubbles-scroll-section::-webkit-scrollbar {
176
+ height: 8px;
177
+ }
178
+
179
+ .bubbles-scroll-section::-webkit-scrollbar-thumb {
180
+ background-color: #3c23cf;
181
+ border-radius: 4px;
182
+ }
183
+
184
+ .bubbles-scroll-section::-webkit-scrollbar-track {
185
+ background-color: #2c2c2c;
186
+ }
187
+
188
+ .genre-bubbles {
189
+ text-align: center;
190
+ color: #ffffff; /* Light color for the title */
191
+ width: 92dvw;
192
+ display: flex;
193
+ }
194
+
195
+ .bubbles {
196
+ display: inline-block;
197
+ margin: 0 5px;
198
+ padding: 5px 10px;
199
+ border-radius: 20px;
200
+ background-color: #1d233e;
201
+ font-size: 14px;
202
+ color: white;
203
+ white-space: nowrap;
204
+ border: 1px solid #4339ff;
205
+ }
206
+
207
+ .bubble-close-button {
208
+ font-size: 15px;
209
+ cursor: pointer;
210
+ padding: 2px;
211
+ color: #716ffc;
212
+ }
213
+
214
+ .bubble-close-button:hover {
215
+ color: red; /* Optional: Change color on hover */
216
+ }
frontend/src/app/genres/[genre]/page.js CHANGED
@@ -3,15 +3,26 @@ import apiClient from "@/api/apiClient";
3
  import { useEffect, useState, useRef } from "react";
4
  import Link from "next/link";
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
- import { faCaretLeft, faCaretRight } from "@fortawesome/free-solid-svg-icons";
7
- import "./genres.css";
 
 
 
 
 
8
  import Image from "next/image";
 
 
9
 
10
  export default function GenrePage({ params }) {
11
  const [genreItems, setGenreItems] = useState(null);
12
  const [loading, setLoading] = useState(true);
13
  const [error, setError] = useState(null);
14
- const [itemLimit, setItemLimit] = useState(5);
 
 
 
 
15
 
16
  const moviesRef = useRef(null);
17
  const seriesRef = useRef(null);
@@ -19,20 +30,29 @@ export default function GenrePage({ params }) {
19
  useEffect(() => {
20
  async function fetchData() {
21
  try {
22
- const data = await apiClient.getGenreItems(params.genre, null, itemLimit);
 
 
 
 
 
 
 
23
  if (data) {
24
  setGenreItems(data);
 
25
  } else {
26
  setError("Genre data not found.");
27
  }
28
  } catch (err) {
29
- setError("An error occurred while fetching genre data.");
 
30
  } finally {
31
  setLoading(false);
32
  }
33
  }
34
  fetchData();
35
- }, [params.genre, itemLimit]);
36
 
37
  const loadMore = () => {
38
  setItemLimit((prevLimit) => prevLimit + 5);
@@ -45,35 +65,73 @@ export default function GenrePage({ params }) {
45
  }
46
  };
47
 
 
 
 
 
 
 
 
 
 
 
 
48
  if (loading) {
49
  return <div className="loading">Loading...</div>;
50
  }
51
 
52
- if (error) {
53
- return <div className="error">Error: {error}</div>;
54
- }
55
-
56
  return (
57
  <div className="genre-page">
58
- <div className="genre-title-section">
59
- <h1 className="genre-title">{params.genre}</h1>
60
- <div className="load-more-container">
61
- <button className="load-more-button" onClick={loadMore}>
62
- Load More
63
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  </div>
65
  </div>
 
 
 
 
 
 
 
66
  {genreItems && (
67
  <>
68
  {genreItems.movies && genreItems.movies.length > 0 && (
69
  <section className="section movies">
70
  <div className="section-controls">
71
  <h2>Movies</h2>
 
 
 
 
 
72
  <div className="scroll-controls">
73
- <button onClick={() => scroll(moviesRef, "left")} className="scroll-button">
 
 
 
74
  <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
75
  </button>
76
- <button onClick={() => scroll(moviesRef, "right")} className="scroll-button">
 
 
 
77
  <FontAwesomeIcon icon={faCaretRight} size="2xl" />
78
  </button>
79
  </div>
@@ -84,7 +142,12 @@ export default function GenrePage({ params }) {
84
  <Link href={`/movie/${item[0]}`} passHref>
85
  <div className="item-link">
86
  <Image
87
- src={item[3] || `https://via.placeholder.com/400x100/1a1c3f/FFF?text=${encodeURIComponent(item[0])}`}
 
 
 
 
 
88
  alt={item[0]}
89
  className="item-image"
90
  height={120}
@@ -107,10 +170,16 @@ export default function GenrePage({ params }) {
107
  <div className="section-controls">
108
  <h2>Series</h2>
109
  <div className="scroll-controls">
110
- <button onClick={() => scroll(seriesRef, "left")} className="scroll-button">
 
 
 
111
  <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
112
  </button>
113
- <button onClick={() => scroll(seriesRef, "right")} className="scroll-button">
 
 
 
114
  <FontAwesomeIcon icon={faCaretRight} size="2xl" />
115
  </button>
116
  </div>
@@ -121,7 +190,12 @@ export default function GenrePage({ params }) {
121
  <Link href={`/series/${item[0]}`} passHref>
122
  <div className="item-link">
123
  <Image
124
- src={item[3] || `https://via.placeholder.com/150?text=${encodeURIComponent(item[0])}`}
 
 
 
 
 
125
  alt={item[0]}
126
  className="item-image"
127
  width={250}
@@ -141,10 +215,14 @@ export default function GenrePage({ params }) {
141
  </>
142
  )}
143
 
144
- {!genreItems ||
145
- (!genreItems.movies && !genreItems.series && (
146
- <p className="no-items">No items available for this genre.</p>
147
- ))}
 
 
 
 
148
  </div>
149
  );
150
  }
 
3
  import { useEffect, useState, useRef } from "react";
4
  import Link from "next/link";
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
+ import {
7
+ faCaretLeft,
8
+ faCaretRight,
9
+ faFilter,
10
+ faTimes,
11
+ faXmark, // Import the close icon
12
+ } from "@fortawesome/free-solid-svg-icons";
13
  import Image from "next/image";
14
+ import "./genres.css";
15
+ import GenreFilterModal from "@/modals/GenreFilterModal"; // Import the Modal
16
 
17
  export default function GenrePage({ params }) {
18
  const [genreItems, setGenreItems] = useState(null);
19
  const [loading, setLoading] = useState(true);
20
  const [error, setError] = useState(null);
21
+ const [itemLimit, setItemLimit] = useState(10);
22
+ const [selectedGenres, setSelectedGenres] = useState(
23
+ new Set(params.genre ? decodeURIComponent(params.genre).split(",") : [])
24
+ );
25
+ const [isModalOpen, setIsModalOpen] = useState(false); // State for modal visibility
26
 
27
  const moviesRef = useRef(null);
28
  const seriesRef = useRef(null);
 
30
  useEffect(() => {
31
  async function fetchData() {
32
  try {
33
+ if (selectedGenres.size === 0) {
34
+ setGenreItems({ movies: [], series: [] });
35
+ setLoading(false);
36
+ return;
37
+ }
38
+
39
+ const genreArray = Array.from(selectedGenres);
40
+ const data = await apiClient.getGenreItems(genreArray, null, itemLimit);
41
  if (data) {
42
  setGenreItems(data);
43
+ console.log(data);
44
  } else {
45
  setError("Genre data not found.");
46
  }
47
  } catch (err) {
48
+ setError(err.message || "An error occurred while fetching genre data.");
49
+ console.log(err);
50
  } finally {
51
  setLoading(false);
52
  }
53
  }
54
  fetchData();
55
+ }, [selectedGenres, itemLimit]);
56
 
57
  const loadMore = () => {
58
  setItemLimit((prevLimit) => prevLimit + 5);
 
65
  }
66
  };
67
 
68
+ const openModal = () => setIsModalOpen(true);
69
+ const closeModal = () => setIsModalOpen(false);
70
+
71
+ const removeGenre = (genreToRemove) => {
72
+ setSelectedGenres((prevGenres) => {
73
+ const updatedGenres = new Set(prevGenres);
74
+ updatedGenres.delete(genreToRemove);
75
+ return updatedGenres;
76
+ });
77
+ };
78
+
79
  if (loading) {
80
  return <div className="loading">Loading...</div>;
81
  }
82
 
 
 
 
 
83
  return (
84
  <div className="genre-page">
85
+ <div className="genre-title-section"></div>
86
+ <div className="genre-bubbles">
87
+ <button className="filter-button" onClick={openModal}>
88
+ <FontAwesomeIcon icon={faFilter} size="sm" />
89
+ </button>
90
+ <div className="bubbles-scroll-section">
91
+ {selectedGenres.size > 0
92
+ ? Array.from(selectedGenres).map((genre, index) => (
93
+ <div key={index} className="bubbles">
94
+ {genre}
95
+ <button
96
+ className="bubble-close-button"
97
+ onClick={() => removeGenre(genre)}
98
+ >
99
+ <FontAwesomeIcon icon={faXmark} size="sm" />
100
+ </button>
101
+ </div>
102
+ ))
103
+ : "Select Genres"}
104
  </div>
105
  </div>
106
+ <GenreFilterModal
107
+ isOpen={isModalOpen}
108
+ onClose={closeModal}
109
+ selectedGenres={selectedGenres}
110
+ onGenreChange={setSelectedGenres}
111
+ />
112
+
113
  {genreItems && (
114
  <>
115
  {genreItems.movies && genreItems.movies.length > 0 && (
116
  <section className="section movies">
117
  <div className="section-controls">
118
  <h2>Movies</h2>
119
+ <div className="load-more-container">
120
+ <button className="load-more-button" onClick={loadMore}>
121
+ Load More
122
+ </button>
123
+ </div>
124
  <div className="scroll-controls">
125
+ <button
126
+ onClick={() => scroll(moviesRef, "left")}
127
+ className="scroll-button"
128
+ >
129
  <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
130
  </button>
131
+ <button
132
+ onClick={() => scroll(moviesRef, "right")}
133
+ className="scroll-button"
134
+ >
135
  <FontAwesomeIcon icon={faCaretRight} size="2xl" />
136
  </button>
137
  </div>
 
142
  <Link href={`/movie/${item[0]}`} passHref>
143
  <div className="item-link">
144
  <Image
145
+ src={
146
+ item[3] ||
147
+ `https://via.placeholder.com/400x100/1a1c3f/FFF?text=${encodeURIComponent(
148
+ item[0]
149
+ )}`
150
+ }
151
  alt={item[0]}
152
  className="item-image"
153
  height={120}
 
170
  <div className="section-controls">
171
  <h2>Series</h2>
172
  <div className="scroll-controls">
173
+ <button
174
+ onClick={() => scroll(seriesRef, "left")}
175
+ className="scroll-button"
176
+ >
177
  <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
178
  </button>
179
+ <button
180
+ onClick={() => scroll(seriesRef, "right")}
181
+ className="scroll-button"
182
+ >
183
  <FontAwesomeIcon icon={faCaretRight} size="2xl" />
184
  </button>
185
  </div>
 
190
  <Link href={`/series/${item[0]}`} passHref>
191
  <div className="item-link">
192
  <Image
193
+ src={
194
+ item[3] ||
195
+ `https://via.placeholder.com/150?text=${encodeURIComponent(
196
+ item[0]
197
+ )}`
198
+ }
199
  alt={item[0]}
200
  className="item-image"
201
  width={250}
 
215
  </>
216
  )}
217
 
218
+ {(!genreItems ||
219
+ (genreItems.movies.length === 0 && genreItems.series.length === 0)) && (
220
+ <p className="no-items">
221
+ {selectedGenres.size === 0
222
+ ? "Please select a genre to see results."
223
+ : "No items available for the selected genres."}
224
+ </p>
225
+ )}
226
  </div>
227
  );
228
  }
frontend/src/lib/LoadBalancer.js CHANGED
@@ -73,14 +73,25 @@ class LoadBalancerAPI {
73
  return this._getRequest('/api/get/recent');
74
  }
75
 
76
- async getGenreItems(genre, mediaType = null, limit = 5) {
77
- const queryParams = new URLSearchParams({ genre, limit });
 
 
 
 
 
 
 
 
 
78
  if (mediaType) {
79
- queryParams.append('media_type', mediaType);
80
  }
 
 
81
  return this._getRequest(`/api/get/genre?${queryParams.toString()}`);
82
- }
83
-
84
  async getDownloadProgress(url) {
85
  return this._getRequestNoBase(url);
86
  }
 
73
  return this._getRequest('/api/get/recent');
74
  }
75
 
76
+ async getGenreItems(genres, mediaType = null, limit = 5) {
77
+ // Create a new URLSearchParams object
78
+ const queryParams = new URLSearchParams();
79
+
80
+ // Append each genre to the query parameters
81
+ genres.forEach(genre => queryParams.append('genre', genre));
82
+
83
+ // Append the limit parameter
84
+ queryParams.append('limit', limit);
85
+
86
+ // Conditionally append the media_type parameter
87
  if (mediaType) {
88
+ queryParams.append('media_type', mediaType);
89
  }
90
+
91
+ // Make the GET request with the query parameters
92
  return this._getRequest(`/api/get/genre?${queryParams.toString()}`);
93
+ }
94
+
95
  async getDownloadProgress(url) {
96
  return this._getRequestNoBase(url);
97
  }
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.3 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.4 Alpha",
5
  };
6
 
7
  export default config;
frontend/src/modals/GenreFilterModal.css ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* GenreFilterModal.css */
2
+ .modal-overlay {
3
+ position: fixed;
4
+ top: 0;
5
+ left: 0;
6
+ width: 100%;
7
+ height: 100%;
8
+ background: rgba(0, 0, 0, 0.814); /* Slightly darker overlay */
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ }
13
+
14
+ .modal-content {
15
+ background: #11111e; /* Slightly lighter dark background */
16
+ color: #e0e0e0; /* Light grey text color */
17
+ padding: 20px;
18
+ border-radius: 10px; /* More rounded corners */
19
+ width: 90%;
20
+ max-width: 600px;
21
+ position: relative;
22
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5); /* Enhanced shadow effect */
23
+ }
24
+
25
+ .modal-close {
26
+ position: absolute;
27
+ top: 15px;
28
+ right: 15px;
29
+ border: none;
30
+ background: transparent;
31
+ font-size: 24px; /* Larger icon size */
32
+ color: #e0e0e0;
33
+ cursor: pointer;
34
+ transition: color 0.3s ease;
35
+ }
36
+
37
+ .modal-close:hover {
38
+ color: #ff6f61; /* Highlight color on hover */
39
+ }
40
+
41
+ .genre-options {
42
+ margin-top: 20px;
43
+ display: flex;
44
+ flex-wrap: wrap; /* Allow genres to wrap onto multiple lines */
45
+ gap: 15px; /* Increased space between options */
46
+ overflow-y: auto; /* Scroll if content overflows vertically */
47
+ max-height: 50vh; /* Fixed maximum height */
48
+ background-color: #17162c;
49
+ padding: 5px;
50
+ }
51
+
52
+ .genre-options::-webkit-scrollbar {
53
+ width: 8px;
54
+ }
55
+
56
+ .genre-options::-webkit-scrollbar-thumb {
57
+ background-color: #3c23cf;
58
+ border-radius: 4px;
59
+ }
60
+
61
+ .genre-options::-webkit-scrollbar-track {
62
+ background-color: #2c2c2c;
63
+ }
64
+
65
+ .genre-option {
66
+ display: flex;
67
+ align-items: center;
68
+ margin-bottom: 12px; /* Slightly increased space between rows */
69
+ cursor: pointer;
70
+ }
71
+
72
+ .genre-option input {
73
+ margin-right: 10px; /* Increased space between checkbox and label */
74
+ accent-color: #616bff; /* Custom checkbox color */
75
+ }
76
+
77
+ .genre-label {
78
+ font-size: 16px; /* Adjusted font size for better readability */
79
+ }
80
+
81
+ @media (orientation: portrait) {
82
+ .modal-content {
83
+ width: 95%;
84
+ max-height: 60vh; /* Adjusted height for portrait orientation */
85
+ }
86
+ }
frontend/src/modals/GenreFilterModal.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import "./GenreFilterModal.css";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import { faXmark } from "@fortawesome/free-solid-svg-icons";
5
+
6
+ const allGenres = [
7
+ "Action",
8
+ "Adventure",
9
+ "Animation",
10
+ "Biography",
11
+ "Comedy",
12
+ "Crime",
13
+ "Documentary",
14
+ "Drama",
15
+ "Family",
16
+ "Fantasy",
17
+ "Film-Noir",
18
+ "History",
19
+ "Horror",
20
+ "Music",
21
+ "Musical",
22
+ "Mystery",
23
+ "Romance",
24
+ "Sci-Fi",
25
+ "Sport",
26
+ "Thriller",
27
+ "War",
28
+ "Western",
29
+ "Action & Adventure",
30
+ "Adult Animation",
31
+ "Anthology",
32
+ "Bollywood",
33
+ "Chick Flick",
34
+ "Cult",
35
+ "Dance",
36
+ "Game Show",
37
+ "Gothic",
38
+ "Historical Fiction",
39
+ "Romantic Comedy",
40
+ "Superhero",
41
+ "Teen",
42
+ "True Crime",
43
+ "Urban",
44
+ "Political",
45
+ "Experimental",
46
+ "Supernatural",
47
+ "Sci-Fi & Fantasy",
48
+ "Psychological",
49
+ "Dystopian",
50
+ "Medical",
51
+ "Legal",
52
+ "Military",
53
+ "Romantic Drama",
54
+ "Stand-Up Comedy",
55
+ "Docudrama",
56
+ "Family Drama",
57
+ "Social",
58
+ "Science",
59
+ "Historical Drama",
60
+ "Travel",
61
+ "Biography & Memoir",
62
+ "Kids",
63
+ ];
64
+
65
+ export default function GenreFilterModal({
66
+ isOpen,
67
+ onClose,
68
+ selectedGenres,
69
+ onGenreChange,
70
+ }) {
71
+ if (!isOpen) return null;
72
+
73
+ const handleGenreToggle = (genre) => {
74
+ onGenreChange((prevSelected) => {
75
+ const newSelected = new Set(prevSelected);
76
+ if (newSelected.has(genre)) {
77
+ newSelected.delete(genre);
78
+ } else {
79
+ newSelected.add(genre);
80
+ }
81
+ return newSelected;
82
+ });
83
+ };
84
+
85
+ return (
86
+ <div className="modal-overlay">
87
+ <div className="modal-content">
88
+ <button className="modal-close" onClick={onClose}>
89
+ <FontAwesomeIcon icon={faXmark} size="l" />
90
+ </button>
91
+ <h2>Filter by Genre</h2>
92
+ <div className="genre-options">
93
+ {allGenres.map((genre) => (
94
+ <label key={genre} className="genre-option">
95
+ <input
96
+ type="checkbox"
97
+ checked={selectedGenres.has(genre)}
98
+ onChange={() => handleGenreToggle(genre)}
99
+ className="genre-checkbox"
100
+ />
101
+ <span className="genre-label">{genre}</span>
102
+ </label>
103
+ ))}
104
+ </div>
105
+ </div>
106
+ </div>
107
+ );
108
+ }