ChandimaPrabath
commited on
Commit
β’
c9869b0
1
Parent(s):
fcb32f7
0.0.0.6 Alpha
Browse files- .gitignore +3 -0
- frontend/public/deep-sea-abyss.jpg +0 -0
- frontend/src/app/_error.js +12 -7
- frontend/src/app/_still-in-development.js +68 -0
- frontend/src/app/genres/[genre]/genres.css +13 -57
- frontend/src/app/genres/[genre]/page.js +60 -111
- frontend/src/app/globals.css +10 -11
- frontend/src/app/index.css +4 -0
- frontend/src/app/layout.js +6 -8
- frontend/src/app/movie/[title]/movie.css +13 -12
- frontend/src/app/movie/[title]/page.js +3 -3
- frontend/src/app/movies/filmsPage.css +7 -10
- frontend/src/app/movies/page.js +35 -35
- frontend/src/app/not-found.js +9 -8
- frontend/src/app/page.js +8 -5
- frontend/src/app/player/movie/[title]/MoviePlayerPage.css +10 -0
- frontend/src/app/player/movie/[title]/page.js +1 -1
- frontend/src/app/search/page.js +15 -70
- frontend/src/app/search/searchPage.css +2 -73
- frontend/src/app/tvshows/page.js +2 -2
- frontend/src/components/MovieCard.css +0 -83
- frontend/src/components/{MoviePlayer.css β Player/Movie/MoviePlayer.css} +7 -0
- frontend/src/components/{MoviePlayer.js β Player/Movie/MoviePlayer.js} +3 -2
- frontend/src/components/shared/Card/Card.css +113 -0
- frontend/src/components/{MovieCard.js β shared/Card/MovieCard.js} +23 -20
- frontend/src/components/{TvShowCard.js β shared/Card/TvShowCard.js} +2 -2
- frontend/src/components/{Header.css β shared/Header/Header.css} +0 -0
- frontend/src/components/{Header.js β shared/Header/Header.js} +1 -12
- frontend/src/components/{SeekableProgressBar.css β shared/ProgressBar/SeekableProgressBar.css} +0 -0
- frontend/src/components/{SeekableProgressBar.js β shared/ProgressBar/SeekableProgressBar.js} +0 -0
- frontend/src/components/{CastSection.css β shared/Sections/CastSection.css} +0 -0
- frontend/src/components/{CastSection.js β shared/Sections/CastSection.js} +0 -0
- frontend/src/components/{HeroSection.css β shared/Sections/HeroSection.css} +14 -10
- frontend/src/components/{HeroSection.js β shared/Sections/HeroSection.js} +5 -1
- frontend/src/components/shared/Sections/ScrollSection.css +87 -0
- frontend/src/components/shared/Sections/ScrollSection.js +79 -0
- frontend/src/components/{Sidebar.css β shared/Sidebar/Sidebar.css} +0 -0
- frontend/src/components/{Sidebar.js β shared/Sidebar/Sidebar.js} +0 -0
- frontend/src/components/{Spinner.css β shared/Spinner/Spinner.css} +0 -0
- frontend/src/components/{Spinner.js β shared/Spinner/Spinner.js} +0 -0
- frontend/src/lib/config.js +1 -1
- frontend/src/modals/{GenreFilterModal.css β Genre/GenreFilterModal.css} +0 -0
- frontend/src/modals/{GenreFilterModal.js β Genre/GenreFilterModal.js} +0 -0
- frontend/src/skeletons/{movieCard.css β Card/movieCard.css} +4 -5
- frontend/src/skeletons/{movieCard.js β Card/movieCard.js} +1 -1
- frontend/src/skeletons/{HeroSection.css β Sections/HeroSection.css} +6 -5
- frontend/src/skeletons/{HeroSection.js β Sections/HeroSection.js} +0 -1
.gitignore
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
frontend/public/sub.srt
|
2 |
+
frontend/public/sub.vtt
|
3 |
+
frontend/public/My.Spy.The.Eternal.City.(2024).WEB.vtt
|
frontend/public/deep-sea-abyss.jpg
ADDED
frontend/src/app/_error.js
CHANGED
@@ -4,7 +4,8 @@ import Link from "next/link";
|
|
4 |
export default function ErrorPage({ errorMessage }) {
|
5 |
return (
|
6 |
<div style={styles.container}>
|
7 |
-
<
|
|
|
8 |
<p style={styles.message}>{errorMessage}</p>
|
9 |
<Link href="/" style={styles.link}>
|
10 |
Go Home
|
@@ -21,8 +22,8 @@ const styles = {
|
|
21 |
alignItems: 'center',
|
22 |
justifyContent: 'center',
|
23 |
height: '100dvh',
|
24 |
-
width:'
|
25 |
-
top:'0',
|
26 |
textAlign: 'center',
|
27 |
color: 'white',
|
28 |
padding: '20px',
|
@@ -34,23 +35,27 @@ const styles = {
|
|
34 |
left: 0,
|
35 |
right: 0,
|
36 |
bottom: 0,
|
37 |
-
background: 'url("/
|
38 |
filter: "blur(5px)",
|
39 |
-
zIndex: -1,
|
40 |
},
|
41 |
title: {
|
|
|
42 |
fontSize: "3rem",
|
43 |
fontWeight: "bold",
|
44 |
-
|
|
|
45 |
},
|
46 |
message: {
|
47 |
fontSize: "1.5rem",
|
48 |
margin: "20px 0",
|
|
|
|
|
49 |
},
|
50 |
link: {
|
51 |
marginTop: "20px",
|
52 |
padding: "12px 20px",
|
53 |
-
backgroundColor: "var(--bg-primary)",
|
54 |
color: "white",
|
55 |
textDecoration: "none",
|
56 |
borderRadius: "5px",
|
|
|
4 |
export default function ErrorPage({ errorMessage }) {
|
5 |
return (
|
6 |
<div style={styles.container}>
|
7 |
+
<div style={styles.overlay}></div>
|
8 |
+
<h1 style={styles.title}>Uh-oh! π¨</h1>
|
9 |
<p style={styles.message}>{errorMessage}</p>
|
10 |
<Link href="/" style={styles.link}>
|
11 |
Go Home
|
|
|
22 |
alignItems: 'center',
|
23 |
justifyContent: 'center',
|
24 |
height: '100dvh',
|
25 |
+
width: '100vw',
|
26 |
+
top: '0',
|
27 |
textAlign: 'center',
|
28 |
color: 'white',
|
29 |
padding: '20px',
|
|
|
35 |
left: 0,
|
36 |
right: 0,
|
37 |
bottom: 0,
|
38 |
+
background: 'url("/glich_bg.jpg") no-repeat center center/cover',
|
39 |
filter: "blur(5px)",
|
40 |
+
zIndex: -1,
|
41 |
},
|
42 |
title: {
|
43 |
+
color: 'var(--primary-special-color)',
|
44 |
fontSize: "3rem",
|
45 |
fontWeight: "bold",
|
46 |
+
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
|
47 |
+
marginBottom: "20px",
|
48 |
},
|
49 |
message: {
|
50 |
fontSize: "1.5rem",
|
51 |
margin: "20px 0",
|
52 |
+
fontWeight: "300",
|
53 |
+
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
|
54 |
},
|
55 |
link: {
|
56 |
marginTop: "20px",
|
57 |
padding: "12px 20px",
|
58 |
+
backgroundColor: "var(--bg-primary)",
|
59 |
color: "white",
|
60 |
textDecoration: "none",
|
61 |
borderRadius: "5px",
|
frontend/src/app/_still-in-development.js
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Link from "next/link";
|
2 |
+
|
3 |
+
export default function StillInDevelopment() {
|
4 |
+
return (
|
5 |
+
<div style={styles.container}>
|
6 |
+
<div style={styles.overlay}></div>
|
7 |
+
<h2 style={styles.title}>Lost in Dev Space π€¨</h2>
|
8 |
+
<p style={styles.message}>
|
9 |
+
Oops! This page is still under construction. <br />
|
10 |
+
Our devs are probably chasingπͺ²bugs or having a coffee break β. <br />
|
11 |
+
In the meantime, click below to escape!ππͺ
|
12 |
+
</p>
|
13 |
+
<Link href="/" style={styles.link}>
|
14 |
+
Take Me ποΈHome
|
15 |
+
</Link>
|
16 |
+
</div>
|
17 |
+
);
|
18 |
+
}
|
19 |
+
|
20 |
+
const styles = {
|
21 |
+
container: {
|
22 |
+
position: "absolute",
|
23 |
+
display: "flex",
|
24 |
+
flexDirection: "column",
|
25 |
+
alignItems: "center",
|
26 |
+
justifyContent: "center",
|
27 |
+
height: "100dvh",
|
28 |
+
width: "100dvw",
|
29 |
+
top: "0",
|
30 |
+
textAlign: "center",
|
31 |
+
color: "white",
|
32 |
+
padding: "20px",
|
33 |
+
overflow: "hidden",
|
34 |
+
},
|
35 |
+
overlay: {
|
36 |
+
position: "absolute",
|
37 |
+
top: 0,
|
38 |
+
left: 0,
|
39 |
+
right: 0,
|
40 |
+
bottom: 0,
|
41 |
+
background: 'url("/404_bg3.jpg") no-repeat center center/cover',
|
42 |
+
filter: "blur(10px)",
|
43 |
+
zIndex: -1,
|
44 |
+
},
|
45 |
+
title: {
|
46 |
+
fontSize: "2.5rem",
|
47 |
+
fontWeight: "900",
|
48 |
+
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
|
49 |
+
marginBottom: "20px",
|
50 |
+
color: "var(--primary-special-color)",
|
51 |
+
},
|
52 |
+
message: {
|
53 |
+
fontSize: "1.2rem",
|
54 |
+
marginBottom: "20px",
|
55 |
+
fontWeight: "400",
|
56 |
+
},
|
57 |
+
link: {
|
58 |
+
padding: "10px 20px",
|
59 |
+
backgroundColor: "var(--bg-primary)",
|
60 |
+
color: "white",
|
61 |
+
textDecoration: "none",
|
62 |
+
borderRadius: "5px",
|
63 |
+
border: "2px solid var(--primary-special-color)",
|
64 |
+
transition: "background-color 0.3s ease",
|
65 |
+
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.5)",
|
66 |
+
fontWeight: "200",
|
67 |
+
},
|
68 |
+
};
|
frontend/src/app/genres/[genre]/genres.css
CHANGED
@@ -3,57 +3,12 @@ body {
|
|
3 |
}
|
4 |
/* Container for the genre page */
|
5 |
.genre-page {
|
6 |
-
|
7 |
margin: 0 auto;
|
8 |
padding: 20px;
|
9 |
-
background-color: #0a0b19; /* Slightly lighter dark background */
|
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 */
|
20 |
-
.section {
|
21 |
-
margin-bottom: 40px;
|
22 |
-
}
|
23 |
-
|
24 |
-
@media (orientation: landscape) {
|
25 |
-
.section {
|
26 |
-
margin-left: 25px;
|
27 |
-
margin-right: 25px;
|
28 |
-
}
|
29 |
-
}
|
30 |
-
|
31 |
-
.section h2 {
|
32 |
-
font-size: 1.8rem;
|
33 |
-
color: #cccccc; /* Lighter gray for section titles */
|
34 |
-
}
|
35 |
-
|
36 |
-
/* Horizontal scrolling container */
|
37 |
-
.genre-items-grid {
|
38 |
-
display: flex;
|
39 |
-
overflow-x: auto;
|
40 |
-
padding: 10px 0;
|
41 |
-
gap: 20px;
|
42 |
-
}
|
43 |
-
|
44 |
-
.genre-items-grid::-webkit-scrollbar {
|
45 |
-
height: 8px;
|
46 |
-
}
|
47 |
-
|
48 |
-
.genre-items-grid::-webkit-scrollbar-thumb {
|
49 |
-
background-color: #3c23cf;
|
50 |
-
border-radius: 4px;
|
51 |
-
}
|
52 |
-
|
53 |
-
.genre-items-grid::-webkit-scrollbar-track {
|
54 |
-
background-color: #2c2c2c;
|
55 |
-
}
|
56 |
-
|
57 |
/* Card for each item */
|
58 |
.genre-item-card {
|
59 |
background-color: #1d1f2d; /* Dark card background */
|
@@ -64,6 +19,10 @@ body {
|
|
64 |
flex: 0 0 200px; /* Fixed width for horizontal scrolling */
|
65 |
}
|
66 |
|
|
|
|
|
|
|
|
|
67 |
.genre-item-card:hover {
|
68 |
transform: scale(1.03);
|
69 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.7);
|
@@ -160,21 +119,18 @@ body {
|
|
160 |
outline: none;
|
161 |
}
|
162 |
|
163 |
-
.genre-
|
164 |
-
display: flex;
|
165 |
-
align-items: center;
|
166 |
-
text-align: center;
|
167 |
-
justify-content: space-between;
|
168 |
-
}
|
169 |
-
|
170 |
-
.genre-scroll-controls button {
|
171 |
-
margin-left: 10px;
|
172 |
margin-right: 10px;
|
|
|
|
|
|
|
|
|
173 |
}
|
174 |
|
175 |
-
.genre-filter-button {
|
176 |
-
|
177 |
}
|
|
|
178 |
.genre-bubbles-scroll-section{
|
179 |
display: flex;
|
180 |
overflow-x: auto;
|
|
|
3 |
}
|
4 |
/* Container for the genre page */
|
5 |
.genre-page {
|
6 |
+
width: 100dvw;
|
7 |
margin: 0 auto;
|
8 |
padding: 20px;
|
|
|
9 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
10 |
}
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
/* Card for each item */
|
13 |
.genre-item-card {
|
14 |
background-color: #1d1f2d; /* Dark card background */
|
|
|
19 |
flex: 0 0 200px; /* Fixed width for horizontal scrolling */
|
20 |
}
|
21 |
|
22 |
+
.genre-item-card a{
|
23 |
+
text-decoration: none;
|
24 |
+
}
|
25 |
+
|
26 |
.genre-item-card:hover {
|
27 |
transform: scale(1.03);
|
28 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.7);
|
|
|
119 |
outline: none;
|
120 |
}
|
121 |
|
122 |
+
.genre-filter-button {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
margin-right: 10px;
|
124 |
+
padding: 10px;
|
125 |
+
min-width: 45px;
|
126 |
+
border-radius: 50%;
|
127 |
+
transition: background-color .3s ease;
|
128 |
}
|
129 |
|
130 |
+
.genre-filter-button:hover {
|
131 |
+
background-color: var(--primary-special-color);
|
132 |
}
|
133 |
+
|
134 |
.genre-bubbles-scroll-section{
|
135 |
display: flex;
|
136 |
overflow-x: auto;
|
frontend/src/app/genres/[genre]/page.js
CHANGED
@@ -1,19 +1,14 @@
|
|
1 |
"use client";
|
2 |
import apiClient from "@/api/apiClient";
|
3 |
-
import { useEffect, useState
|
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";
|
16 |
-
import { Spinner } from "@/components/Spinner";
|
|
|
17 |
|
18 |
export default function GenrePage({ params }) {
|
19 |
const [genreItems, setGenreItems] = useState(null);
|
@@ -23,10 +18,7 @@ export default function GenrePage({ params }) {
|
|
23 |
const [selectedGenres, setSelectedGenres] = useState(
|
24 |
new Set(params.genre ? decodeURIComponent(params.genre).split(",") : [])
|
25 |
);
|
26 |
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
27 |
-
|
28 |
-
const moviesRef = useRef(null);
|
29 |
-
const seriesRef = useRef(null);
|
30 |
|
31 |
useEffect(() => {
|
32 |
async function fetchData() {
|
@@ -59,13 +51,6 @@ export default function GenrePage({ params }) {
|
|
59 |
setItemLimit((prevLimit) => prevLimit + 5);
|
60 |
};
|
61 |
|
62 |
-
const scroll = (ref, direction) => {
|
63 |
-
if (ref.current) {
|
64 |
-
const scrollAmount = direction === "left" ? -360 : 360;
|
65 |
-
ref.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
|
66 |
-
}
|
67 |
-
};
|
68 |
-
|
69 |
const openModal = () => setIsModalOpen(true);
|
70 |
const closeModal = () => setIsModalOpen(false);
|
71 |
|
@@ -86,62 +71,45 @@ export default function GenrePage({ params }) {
|
|
86 |
}
|
87 |
|
88 |
return (
|
89 |
-
<div className="
|
90 |
-
<div className="genre-
|
91 |
-
|
92 |
-
<
|
93 |
-
<FontAwesomeIcon icon={faFilter} size="sm" />
|
94 |
-
</button>
|
95 |
-
<div className="genre-bubbles-scroll-section">
|
96 |
-
{selectedGenres.size > 0
|
97 |
-
? Array.from(selectedGenres).map((genre, index) => (
|
98 |
-
<div key={index} className="bubbles">
|
99 |
-
{genre}
|
100 |
-
<button
|
101 |
-
className="genre-bubble-close-button"
|
102 |
-
onClick={() => removeGenre(genre)}
|
103 |
-
>
|
104 |
-
<FontAwesomeIcon icon={faXmark} size="sm" />
|
105 |
-
</button>
|
106 |
-
</div>
|
107 |
-
))
|
108 |
-
: "Select Genres"}
|
109 |
-
</div>
|
110 |
-
</div>
|
111 |
-
<GenreFilterModal
|
112 |
-
isOpen={isModalOpen}
|
113 |
-
onClose={closeModal}
|
114 |
-
selectedGenres={selectedGenres}
|
115 |
-
onGenreChange={setSelectedGenres}
|
116 |
-
/>
|
117 |
-
|
118 |
-
{genreItems && (
|
119 |
-
<>
|
120 |
-
{genreItems.movies && genreItems.movies.length > 0 && (
|
121 |
-
<section className="genre-section movies">
|
122 |
-
<div className="genre-section-controls">
|
123 |
-
<h2>Movies</h2>
|
124 |
-
<div className="load-more-container">
|
125 |
<button className="load-more-button" onClick={loadMore}>
|
126 |
Load More
|
127 |
</button>
|
128 |
</div>
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
<
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
{genreItems.movies.map((item, index) => (
|
146 |
<div key={index} className="genre-item-card">
|
147 |
<Link href={`/movie/${item[0]}`} passHref>
|
@@ -149,14 +117,14 @@ export default function GenrePage({ params }) {
|
|
149 |
<Image
|
150 |
src={
|
151 |
item[3] ||
|
152 |
-
`https://via.placeholder.com/300x80
|
153 |
item[0]
|
154 |
)}`
|
155 |
}
|
156 |
alt={item[0]}
|
157 |
className="genre-item-image"
|
158 |
-
height={80}
|
159 |
width={300}
|
|
|
160 |
/>
|
161 |
<div className="genre-item-info">
|
162 |
<h3 className="genre-item-title">{item[0]}</h3>
|
@@ -166,30 +134,11 @@ export default function GenrePage({ params }) {
|
|
166 |
</Link>
|
167 |
</div>
|
168 |
))}
|
169 |
-
</
|
170 |
-
|
171 |
-
)}
|
172 |
|
173 |
-
|
174 |
-
|
175 |
-
<div className="genre-section-controls">
|
176 |
-
<h2>TV Shows</h2>
|
177 |
-
<div className="genre-scroll-controls">
|
178 |
-
<button
|
179 |
-
onClick={() => scroll(seriesRef, "left")}
|
180 |
-
className="genre-scroll-button"
|
181 |
-
>
|
182 |
-
<FontAwesomeIcon icon={faCaretLeft} size="2xl" />
|
183 |
-
</button>
|
184 |
-
<button
|
185 |
-
onClick={() => scroll(seriesRef, "right")}
|
186 |
-
className="genre-scroll-button"
|
187 |
-
>
|
188 |
-
<FontAwesomeIcon icon={faCaretRight} size="2xl" />
|
189 |
-
</button>
|
190 |
-
</div>
|
191 |
-
</div>
|
192 |
-
<div className="genre-items-grid" ref={seriesRef}>
|
193 |
{genreItems.series.map((item, index) => (
|
194 |
<div key={index} className="genre-item-card">
|
195 |
<Link href={`/series/${item[0]}`} passHref>
|
@@ -214,20 +163,20 @@ export default function GenrePage({ params }) {
|
|
214 |
</Link>
|
215 |
</div>
|
216 |
))}
|
217 |
-
</
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
)}
|
222 |
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
|
|
231 |
</div>
|
232 |
);
|
233 |
}
|
|
|
1 |
"use client";
|
2 |
import apiClient from "@/api/apiClient";
|
3 |
+
import { useEffect, useState } from "react";
|
4 |
import Link from "next/link";
|
5 |
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
6 |
+
import { faFilter, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
import Image from "next/image";
|
8 |
import "./genres.css";
|
9 |
+
import GenreFilterModal from "@/modals/Genre/GenreFilterModal";
|
10 |
+
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
11 |
+
import ScrollSection from "@/components/shared/Sections/ScrollSection";
|
12 |
|
13 |
export default function GenrePage({ params }) {
|
14 |
const [genreItems, setGenreItems] = useState(null);
|
|
|
18 |
const [selectedGenres, setSelectedGenres] = useState(
|
19 |
new Set(params.genre ? decodeURIComponent(params.genre).split(",") : [])
|
20 |
);
|
21 |
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
|
|
|
22 |
|
23 |
useEffect(() => {
|
24 |
async function fetchData() {
|
|
|
51 |
setItemLimit((prevLimit) => prevLimit + 5);
|
52 |
};
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
const openModal = () => setIsModalOpen(true);
|
55 |
const closeModal = () => setIsModalOpen(false);
|
56 |
|
|
|
71 |
}
|
72 |
|
73 |
return (
|
74 |
+
<div className="app-container">
|
75 |
+
<div className="genre-page">
|
76 |
+
<div className="genre-title-section"></div>
|
77 |
+
<div className="load-more-container">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
<button className="load-more-button" onClick={loadMore}>
|
79 |
Load More
|
80 |
</button>
|
81 |
</div>
|
82 |
+
<div className="genre-bubbles">
|
83 |
+
<button className="genre-filter-button" onClick={openModal}>
|
84 |
+
<FontAwesomeIcon icon={faFilter} size="sm" />
|
85 |
+
</button>
|
86 |
+
<div className="genre-bubbles-scroll-section">
|
87 |
+
{selectedGenres.size > 0
|
88 |
+
? Array.from(selectedGenres).map((genre, index) => (
|
89 |
+
<div key={index} className="bubbles">
|
90 |
+
{genre}
|
91 |
+
<button
|
92 |
+
className="genre-bubble-close-button"
|
93 |
+
onClick={() => removeGenre(genre)}
|
94 |
+
>
|
95 |
+
<FontAwesomeIcon icon={faXmark} size="sm" />
|
96 |
+
</button>
|
97 |
+
</div>
|
98 |
+
))
|
99 |
+
: "Select Genres"}
|
100 |
+
</div>
|
101 |
+
</div>
|
102 |
+
<GenreFilterModal
|
103 |
+
isOpen={isModalOpen}
|
104 |
+
onClose={closeModal}
|
105 |
+
selectedGenres={selectedGenres}
|
106 |
+
onGenreChange={setSelectedGenres}
|
107 |
+
/>
|
108 |
+
|
109 |
+
{genreItems && (
|
110 |
+
<>
|
111 |
+
{genreItems.movies && genreItems.movies.length > 0 && (
|
112 |
+
<ScrollSection title="Movies">
|
113 |
{genreItems.movies.map((item, index) => (
|
114 |
<div key={index} className="genre-item-card">
|
115 |
<Link href={`/movie/${item[0]}`} passHref>
|
|
|
117 |
<Image
|
118 |
src={
|
119 |
item[3] ||
|
120 |
+
`https://via.placeholder.com/300x80?text=${encodeURIComponent(
|
121 |
item[0]
|
122 |
)}`
|
123 |
}
|
124 |
alt={item[0]}
|
125 |
className="genre-item-image"
|
|
|
126 |
width={300}
|
127 |
+
height={80}
|
128 |
/>
|
129 |
<div className="genre-item-info">
|
130 |
<h3 className="genre-item-title">{item[0]}</h3>
|
|
|
134 |
</Link>
|
135 |
</div>
|
136 |
))}
|
137 |
+
</ScrollSection>
|
138 |
+
)}
|
|
|
139 |
|
140 |
+
{genreItems.series && genreItems.series.length > 0 && (
|
141 |
+
<ScrollSection title="TV Shows">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
{genreItems.series.map((item, index) => (
|
143 |
<div key={index} className="genre-item-card">
|
144 |
<Link href={`/series/${item[0]}`} passHref>
|
|
|
163 |
</Link>
|
164 |
</div>
|
165 |
))}
|
166 |
+
</ScrollSection>
|
167 |
+
)}
|
168 |
+
</>
|
169 |
+
)}
|
|
|
170 |
|
171 |
+
{(!genreItems ||
|
172 |
+
(genreItems.movies.length === 0 && genreItems.series.length === 0)) && (
|
173 |
+
<p className="genre-no-items">
|
174 |
+
{selectedGenres.size === 0
|
175 |
+
? "Please select a genre to see results."
|
176 |
+
: "No items available for the selected genres."}
|
177 |
+
</p>
|
178 |
+
)}
|
179 |
+
</div>
|
180 |
</div>
|
181 |
);
|
182 |
}
|
frontend/src/app/globals.css
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
--bg-primary: #11121f;
|
9 |
--bg-secondary: #0c0c16;
|
10 |
--primary-special-color:#4343ff;
|
|
|
11 |
}
|
12 |
|
13 |
body {
|
@@ -19,18 +20,16 @@ body {
|
|
19 |
}
|
20 |
|
21 |
.app-container {
|
22 |
-
-ms-overflow-style: none; /* IE and Edge */
|
23 |
-
scrollbar-width: none; /* Firefox */
|
24 |
-
scroll-behavior: smooth;
|
25 |
-
overflow: scroll;
|
26 |
-
height: 100dvh;
|
27 |
-
padding-top: 80px;
|
28 |
-
|
29 |
-
|
30 |
-
.no-padding {
|
31 |
-
padding-top: 0;
|
32 |
}
|
33 |
|
34 |
.app-container::-webkit-scrollbar {
|
35 |
-
display: none;
|
36 |
}
|
|
|
8 |
--bg-primary: #11121f;
|
9 |
--bg-secondary: #0c0c16;
|
10 |
--primary-special-color:#4343ff;
|
11 |
+
--gray-text-color:#d8d8d8;
|
12 |
}
|
13 |
|
14 |
body {
|
|
|
20 |
}
|
21 |
|
22 |
.app-container {
|
23 |
+
-ms-overflow-style: none !important; /* IE and Edge */
|
24 |
+
scrollbar-width: none !important; /* Firefox */
|
25 |
+
scroll-behavior: smooth !important;
|
26 |
+
overflow: scroll !important;
|
27 |
+
height: 100dvh !important;
|
28 |
+
padding-top: 80px !important;
|
29 |
+
padding-left: 0 !important;
|
30 |
+
padding-right: 0 !important;
|
|
|
|
|
31 |
}
|
32 |
|
33 |
.app-container::-webkit-scrollbar {
|
34 |
+
display: none !important;
|
35 |
}
|
frontend/src/app/index.css
CHANGED
@@ -14,6 +14,10 @@
|
|
14 |
flex: 1;
|
15 |
}
|
16 |
|
|
|
|
|
|
|
|
|
17 |
.section-title {
|
18 |
color: #ffffff;
|
19 |
font-size: 1.4rem;
|
|
|
14 |
flex: 1;
|
15 |
}
|
16 |
|
17 |
+
.index-page-content{
|
18 |
+
width: 100dvw;
|
19 |
+
}
|
20 |
+
|
21 |
.section-title {
|
22 |
color: #ffffff;
|
23 |
font-size: 1.4rem;
|
frontend/src/app/layout.js
CHANGED
@@ -3,10 +3,10 @@ import "./globals.css";
|
|
3 |
import { FilmProvider } from "@/context/FilmContext";
|
4 |
import { config } from "@fortawesome/fontawesome-svg-core";
|
5 |
import "@fortawesome/fontawesome-svg-core/styles.css";
|
6 |
-
import Header from "@/components/Header";
|
7 |
import { TvShowsProvider } from "@/context/TvshowContext";
|
8 |
|
9 |
-
config.autoAddCss =
|
10 |
const inter = Inter({ subsets: ["latin"] });
|
11 |
|
12 |
export default function RootLayout({ children }) {
|
@@ -14,14 +14,12 @@ export default function RootLayout({ children }) {
|
|
14 |
<html lang="en">
|
15 |
<body className={inter.className}>
|
16 |
<header>
|
17 |
-
<link rel="manifest" href="webmanifest.json"/>
|
18 |
<Header />
|
19 |
</header>
|
20 |
-
<
|
21 |
-
<
|
22 |
-
|
23 |
-
</TvShowsProvider>
|
24 |
-
</div>
|
25 |
</body>
|
26 |
</html>
|
27 |
);
|
|
|
3 |
import { FilmProvider } from "@/context/FilmContext";
|
4 |
import { config } from "@fortawesome/fontawesome-svg-core";
|
5 |
import "@fortawesome/fontawesome-svg-core/styles.css";
|
6 |
+
import Header from "@/components/shared/Header/Header";
|
7 |
import { TvShowsProvider } from "@/context/TvshowContext";
|
8 |
|
9 |
+
config.autoAddCss = false;
|
10 |
const inter = Inter({ subsets: ["latin"] });
|
11 |
|
12 |
export default function RootLayout({ children }) {
|
|
|
14 |
<html lang="en">
|
15 |
<body className={inter.className}>
|
16 |
<header>
|
17 |
+
<link rel="manifest" href="webmanifest.json" />
|
18 |
<Header />
|
19 |
</header>
|
20 |
+
<TvShowsProvider>
|
21 |
+
<FilmProvider>{children}</FilmProvider>
|
22 |
+
</TvShowsProvider>
|
|
|
|
|
23 |
</body>
|
24 |
</html>
|
25 |
);
|
frontend/src/app/movie/[title]/movie.css
CHANGED
@@ -14,8 +14,9 @@
|
|
14 |
left: 0;
|
15 |
width: 100%;
|
16 |
height: 100%;
|
17 |
-
background-size: 250%
|
18 |
background-position: center;
|
|
|
19 |
z-index: -1;
|
20 |
animation: backdrop-anim-portrait 20s ease-in-out infinite;
|
21 |
filter: blur(10px);
|
@@ -24,29 +25,29 @@
|
|
24 |
@keyframes backdrop-anim-portrait {
|
25 |
0% {
|
26 |
background-position: center;
|
27 |
-
background-size:
|
28 |
}
|
29 |
25% {
|
30 |
background-position: top left;
|
31 |
-
background-size:
|
32 |
}
|
33 |
50% {
|
34 |
background-position: bottom right;
|
35 |
-
background-size:
|
36 |
}
|
37 |
75% {
|
38 |
background-position: top right;
|
39 |
-
background-size:
|
40 |
}
|
41 |
100% {
|
42 |
background-position: center;
|
43 |
-
background-size:
|
44 |
}
|
45 |
}
|
46 |
|
47 |
@media (orientation: landscape) {
|
48 |
.movie-details-backdrop {
|
49 |
-
background-size: 250
|
50 |
animation: backdrop-anim-landscape 20s ease-in-out infinite;
|
51 |
}
|
52 |
}
|
@@ -54,23 +55,23 @@
|
|
54 |
@keyframes backdrop-anim-landscape {
|
55 |
0% {
|
56 |
background-position: center;
|
57 |
-
background-size: 150%
|
58 |
}
|
59 |
25% {
|
60 |
background-position: top left;
|
61 |
-
background-size: 120%
|
62 |
}
|
63 |
50% {
|
64 |
background-position: bottom right;
|
65 |
-
background-size: 150%
|
66 |
}
|
67 |
75% {
|
68 |
background-position: top right;
|
69 |
-
background-size: 120%
|
70 |
}
|
71 |
100% {
|
72 |
background-position: center;
|
73 |
-
background-size: 150%
|
74 |
}
|
75 |
}
|
76 |
/* Container Styling */
|
|
|
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);
|
|
|
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 |
}
|
|
|
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 */
|
frontend/src/app/movie/[title]/page.js
CHANGED
@@ -10,8 +10,8 @@ import {
|
|
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 { Spinner } from "@/components/Spinner";
|
14 |
-
import CastSection from "@/components/CastSection";
|
15 |
import NotFound from "@/app/not-found";
|
16 |
|
17 |
export default function MovieDetailsPage({ params }) {
|
@@ -69,7 +69,7 @@ export default function MovieDetailsPage({ params }) {
|
|
69 |
const genres = metadata?.data?.genres || [];
|
70 |
const cast = metadata?.data?.characters || [];
|
71 |
return (
|
72 |
-
<div className="movie-details-page">
|
73 |
<div
|
74 |
className="movie-details-backdrop"
|
75 |
style={{
|
|
|
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 { Spinner } from "@/components/shared/Spinner/Spinner";
|
14 |
+
import CastSection from "@/components/shared/Sections/CastSection";
|
15 |
import NotFound from "@/app/not-found";
|
16 |
|
17 |
export default function MovieDetailsPage({ params }) {
|
|
|
69 |
const genres = metadata?.data?.genres || [];
|
70 |
const cast = metadata?.data?.characters || [];
|
71 |
return (
|
72 |
+
<div className="movie-details-page app-container">
|
73 |
<div
|
74 |
className="movie-details-backdrop"
|
75 |
style={{
|
frontend/src/app/movies/filmsPage.css
CHANGED
@@ -6,11 +6,7 @@
|
|
6 |
padding: 20px;
|
7 |
max-width: 1200px;
|
8 |
margin: 0 auto;
|
9 |
-
height:
|
10 |
-
overflow-y: scroll;
|
11 |
-
-ms-overflow-style: none; /* IE and Edge */
|
12 |
-
scrollbar-width: none; /* Firefox */
|
13 |
-
scroll-behavior: smooth;
|
14 |
}
|
15 |
|
16 |
/* Films Grid */
|
@@ -19,30 +15,31 @@
|
|
19 |
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
20 |
gap: 30px;
|
21 |
width: 100%;
|
|
|
22 |
justify-content: center;
|
23 |
}
|
24 |
|
25 |
/* Media Queries for Responsiveness */
|
26 |
@media (max-width: 768px) {
|
27 |
.films-page {
|
28 |
-
grid-template-columns: repeat(auto-fill, minmax(
|
29 |
-
gap:
|
30 |
}
|
31 |
}
|
32 |
|
33 |
@media (max-width: 480px) {
|
34 |
.films-page {
|
35 |
-
grid-template-columns: repeat(auto-fill, minmax(120px,
|
36 |
}
|
37 |
}
|
38 |
|
39 |
/* Pagination Controls */
|
40 |
.pagination-controls {
|
41 |
-
margin-top: 20px;
|
42 |
display: flex;
|
43 |
align-items: center;
|
44 |
position: absolute;
|
45 |
-
|
|
|
46 |
}
|
47 |
|
48 |
/* Pagination Button */
|
|
|
6 |
padding: 20px;
|
7 |
max-width: 1200px;
|
8 |
margin: 0 auto;
|
9 |
+
height: 100dvh;
|
|
|
|
|
|
|
|
|
10 |
}
|
11 |
|
12 |
/* Films Grid */
|
|
|
15 |
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
16 |
gap: 30px;
|
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, 10rem));
|
26 |
+
gap: 5px;
|
27 |
}
|
28 |
}
|
29 |
|
30 |
@media (max-width: 480px) {
|
31 |
.films-page {
|
32 |
+
grid-template-columns: repeat(auto-fill, minmax(120px, 10rem));
|
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 */
|
frontend/src/app/movies/page.js
CHANGED
@@ -1,15 +1,18 @@
|
|
1 |
-
|
2 |
-
import { useEffect, useState } from
|
3 |
-
import { FontAwesomeIcon } from
|
4 |
-
import apiClient from
|
5 |
-
import MovieCard from
|
6 |
-
import { useFilmContext } from
|
7 |
-
import
|
8 |
|
9 |
-
import {
|
10 |
-
|
|
|
|
|
|
|
11 |
|
12 |
-
const FILMS_PER_PAGE =
|
13 |
|
14 |
export default function FilmsPage() {
|
15 |
const { films, setFilms } = useFilmContext();
|
@@ -17,12 +20,13 @@ export default function FilmsPage() {
|
|
17 |
|
18 |
useEffect(() => {
|
19 |
if (films.length === 0) {
|
20 |
-
apiClient
|
21 |
-
.
|
22 |
-
|
|
|
23 |
})
|
24 |
-
.catch(error => {
|
25 |
-
console.error(
|
26 |
});
|
27 |
}
|
28 |
}, [films, setFilms]);
|
@@ -31,11 +35,11 @@ export default function FilmsPage() {
|
|
31 |
const currentFilms = films.slice(startIndex, startIndex + FILMS_PER_PAGE);
|
32 |
|
33 |
const handleNextPage = () => {
|
34 |
-
setCurrentPage(prevPage => prevPage + 1);
|
35 |
};
|
36 |
|
37 |
const handlePrevPage = () => {
|
38 |
-
setCurrentPage(prevPage => Math.max(prevPage - 1, 1));
|
39 |
};
|
40 |
|
41 |
const totalPages = Math.ceil(films.length / FILMS_PER_PAGE);
|
@@ -43,37 +47,33 @@ export default function FilmsPage() {
|
|
43 |
const isNextButtonEnabled = currentPage < totalPages;
|
44 |
|
45 |
return (
|
46 |
-
<div className="films-page-container">
|
47 |
<div className="films-page">
|
48 |
{currentFilms.map((title, index) => (
|
49 |
-
|
50 |
-
<MovieCard title={title} />
|
51 |
-
</Link>
|
52 |
))}
|
53 |
</div>
|
54 |
<div className="pagination-controls">
|
55 |
-
<button
|
56 |
-
onClick={handlePrevPage}
|
57 |
disabled={!isPrevButtonEnabled}
|
58 |
-
className={`pagination-button ${
|
|
|
|
|
59 |
>
|
60 |
-
<FontAwesomeIcon
|
61 |
-
icon={faChevronLeft}
|
62 |
-
size="xl"
|
63 |
-
/>
|
64 |
</button>
|
65 |
<span className="page-info">
|
66 |
{currentPage} of {totalPages}
|
67 |
</span>
|
68 |
-
<button
|
69 |
-
onClick={handleNextPage}
|
70 |
disabled={!isNextButtonEnabled}
|
71 |
-
className={`pagination-button ${
|
|
|
|
|
72 |
>
|
73 |
-
<FontAwesomeIcon
|
74 |
-
icon={faChevronRight}
|
75 |
-
size="xl"
|
76 |
-
/>
|
77 |
</button>
|
78 |
</div>
|
79 |
</div>
|
|
|
1 |
+
"use client";
|
2 |
+
import { useEffect, useState } from "react";
|
3 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
4 |
+
import apiClient from "@/api/apiClient";
|
5 |
+
import MovieCard from "@/components/shared/Card/MovieCard";
|
6 |
+
import { useFilmContext } from "@/context/FilmContext";
|
7 |
+
import "./filmsPage.css";
|
8 |
|
9 |
+
import {
|
10 |
+
faChevronLeft,
|
11 |
+
faChevronRight,
|
12 |
+
} from "@fortawesome/free-solid-svg-icons";
|
13 |
+
import Link from "next/link";
|
14 |
|
15 |
+
const FILMS_PER_PAGE = 10;
|
16 |
|
17 |
export default function FilmsPage() {
|
18 |
const { films, setFilms } = useFilmContext();
|
|
|
20 |
|
21 |
useEffect(() => {
|
22 |
if (films.length === 0) {
|
23 |
+
apiClient
|
24 |
+
.getAllMovies()
|
25 |
+
.then((response) => {
|
26 |
+
setFilms(response.map((film) => film.replace("films/", "")));
|
27 |
})
|
28 |
+
.catch((error) => {
|
29 |
+
console.error("Failed to get all films:", error);
|
30 |
});
|
31 |
}
|
32 |
}, [films, setFilms]);
|
|
|
35 |
const currentFilms = films.slice(startIndex, startIndex + FILMS_PER_PAGE);
|
36 |
|
37 |
const handleNextPage = () => {
|
38 |
+
setCurrentPage((prevPage) => prevPage + 1);
|
39 |
};
|
40 |
|
41 |
const handlePrevPage = () => {
|
42 |
+
setCurrentPage((prevPage) => Math.max(prevPage - 1, 1));
|
43 |
};
|
44 |
|
45 |
const totalPages = Math.ceil(films.length / FILMS_PER_PAGE);
|
|
|
47 |
const isNextButtonEnabled = currentPage < totalPages;
|
48 |
|
49 |
return (
|
50 |
+
<div className="films-page-container app-container">
|
51 |
<div className="films-page">
|
52 |
{currentFilms.map((title, index) => (
|
53 |
+
<MovieCard key={index} title={title} />
|
|
|
|
|
54 |
))}
|
55 |
</div>
|
56 |
<div className="pagination-controls">
|
57 |
+
<button
|
58 |
+
onClick={handlePrevPage}
|
59 |
disabled={!isPrevButtonEnabled}
|
60 |
+
className={`pagination-button ${
|
61 |
+
isPrevButtonEnabled ? "enabled" : "disabled"
|
62 |
+
}`}
|
63 |
>
|
64 |
+
<FontAwesomeIcon icon={faChevronLeft} size="xl" />
|
|
|
|
|
|
|
65 |
</button>
|
66 |
<span className="page-info">
|
67 |
{currentPage} of {totalPages}
|
68 |
</span>
|
69 |
+
<button
|
70 |
+
onClick={handleNextPage}
|
71 |
disabled={!isNextButtonEnabled}
|
72 |
+
className={`pagination-button ${
|
73 |
+
isNextButtonEnabled ? "enabled" : "disabled"
|
74 |
+
}`}
|
75 |
>
|
76 |
+
<FontAwesomeIcon icon={faChevronRight} size="xl" />
|
|
|
|
|
|
|
77 |
</button>
|
78 |
</div>
|
79 |
</div>
|
frontend/src/app/not-found.js
CHANGED
@@ -4,12 +4,13 @@ export default function NotFound() {
|
|
4 |
return (
|
5 |
<div style={styles.container}>
|
6 |
<div style={styles.overlay}></div>
|
7 |
-
<h2 style={styles.title}>Whoops
|
8 |
<p style={styles.message}>
|
9 |
-
|
|
|
10 |
</p>
|
11 |
<Link href="/" style={styles.link}>
|
12 |
-
Go Home
|
13 |
</Link>
|
14 |
</div>
|
15 |
);
|
@@ -36,24 +37,24 @@ const styles = {
|
|
36 |
left: 0,
|
37 |
right: 0,
|
38 |
bottom: 0,
|
39 |
-
background: 'url("/
|
40 |
filter: "blur(5px)",
|
41 |
zIndex: -1, // Sends the overlay behind the text
|
42 |
},
|
43 |
title: {
|
44 |
-
color:'var(--primary-special-color)',
|
45 |
fontSize: "3rem",
|
46 |
fontWeight: "bold",
|
47 |
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
|
|
|
48 |
},
|
49 |
message: {
|
50 |
fontSize: "1.5rem",
|
51 |
-
fontWeight: "
|
52 |
-
|
53 |
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
|
54 |
},
|
55 |
link: {
|
56 |
-
marginTop: "20px",
|
57 |
padding: "12px 20px",
|
58 |
backgroundColor: "var(--bg-primary)", // Semi-transparent red
|
59 |
color: "white",
|
|
|
4 |
return (
|
5 |
<div style={styles.container}>
|
6 |
<div style={styles.overlay}></div>
|
7 |
+
<h2 style={styles.title}>Whoops! π§</h2>
|
8 |
<p style={styles.message}>
|
9 |
+
It looks like this page wandered off into the internet abyss. <br />
|
10 |
+
Let's get you back on track!
|
11 |
</p>
|
12 |
<Link href="/" style={styles.link}>
|
13 |
+
Go ποΈHome
|
14 |
</Link>
|
15 |
</div>
|
16 |
);
|
|
|
37 |
left: 0,
|
38 |
right: 0,
|
39 |
bottom: 0,
|
40 |
+
background: 'url("/deep-sea-abyss.jpg") no-repeat center center/cover',
|
41 |
filter: "blur(5px)",
|
42 |
zIndex: -1, // Sends the overlay behind the text
|
43 |
},
|
44 |
title: {
|
45 |
+
color: 'var(--primary-special-color)',
|
46 |
fontSize: "3rem",
|
47 |
fontWeight: "bold",
|
48 |
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
|
49 |
+
marginBottom: "20px",
|
50 |
},
|
51 |
message: {
|
52 |
fontSize: "1.5rem",
|
53 |
+
fontWeight: "300",
|
54 |
+
marginBottom: "20px",
|
55 |
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
|
56 |
},
|
57 |
link: {
|
|
|
58 |
padding: "12px 20px",
|
59 |
backgroundColor: "var(--bg-primary)", // Semi-transparent red
|
60 |
color: "white",
|
frontend/src/app/page.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
import HeroSection from "@/components/HeroSection";
|
2 |
import "./index.css";
|
|
|
3 |
|
4 |
export const metadata = {
|
5 |
title: "Home",
|
@@ -10,15 +11,17 @@ export const metadata = {
|
|
10 |
|
11 |
const HomePage = () => {
|
12 |
return (
|
13 |
-
|
14 |
<div className="index-page">
|
15 |
<div className="index-page-container">
|
16 |
<HeroSection />
|
17 |
-
<
|
18 |
-
|
|
|
|
|
19 |
</div>
|
20 |
</div>
|
21 |
-
|
22 |
);
|
23 |
};
|
24 |
|
|
|
1 |
+
import HeroSection from "@/components/shared/Sections/HeroSection";
|
2 |
import "./index.css";
|
3 |
+
import ScrollSection from "@/components/shared/Sections/ScrollSection";
|
4 |
|
5 |
export const metadata = {
|
6 |
title: "Home",
|
|
|
11 |
|
12 |
const HomePage = () => {
|
13 |
return (
|
14 |
+
<div className="app-container">
|
15 |
<div className="index-page">
|
16 |
<div className="index-page-container">
|
17 |
<HeroSection />
|
18 |
+
<div className="index-page-content">
|
19 |
+
<ScrollSection title="Popular TV Shows"></ScrollSection>
|
20 |
+
<ScrollSection title="Trending Movies"></ScrollSection>
|
21 |
+
</div>
|
22 |
</div>
|
23 |
</div>
|
24 |
+
</div>
|
25 |
);
|
26 |
};
|
27 |
|
frontend/src/app/player/movie/[title]/MoviePlayerPage.css
CHANGED
@@ -25,3 +25,13 @@
|
|
25 |
align-items: center;
|
26 |
height: 100%;
|
27 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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/movie/[title]/page.js
CHANGED
@@ -3,7 +3,7 @@ import apiClient from "@/api/apiClient";
|
|
3 |
import { useEffect, useState } from "react";
|
4 |
import MoviePlayer from "@/components/MoviePlayer";
|
5 |
import "./MoviePlayerPage.css";
|
6 |
-
import { Spinner } from "@/components/Spinner";
|
7 |
import { ProgressBar, Container, Row, Col, Alert } from "react-bootstrap";
|
8 |
import "bootstrap/dist/css/bootstrap.min.css";
|
9 |
import NotFound from "@/app/not-found";
|
|
|
3 |
import { useEffect, useState } from "react";
|
4 |
import MoviePlayer from "@/components/MoviePlayer";
|
5 |
import "./MoviePlayerPage.css";
|
6 |
+
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
7 |
import { ProgressBar, Container, Row, Col, Alert } from "react-bootstrap";
|
8 |
import "bootstrap/dist/css/bootstrap.min.css";
|
9 |
import NotFound from "@/app/not-found";
|
frontend/src/app/search/page.js
CHANGED
@@ -9,8 +9,9 @@ import {
|
|
9 |
} from "@fortawesome/free-solid-svg-icons";
|
10 |
import { search } from "@/api/searchApi";
|
11 |
import "./searchPage.css";
|
12 |
-
import MovieCard from "@/components/MovieCard";
|
13 |
-
import TvShowCard from "@/components/TvShowCard";
|
|
|
14 |
|
15 |
const SearchPage = () => {
|
16 |
const [query, setQuery] = useState("");
|
@@ -22,8 +23,6 @@ const SearchPage = () => {
|
|
22 |
|
23 |
const router = useRouter();
|
24 |
const timeoutRef = useRef(null);
|
25 |
-
const moviesRef = useRef(null);
|
26 |
-
const seriesRef = useRef(null);
|
27 |
|
28 |
useEffect(() => {
|
29 |
if (timeoutRef.current) {
|
@@ -77,7 +76,7 @@ const SearchPage = () => {
|
|
77 |
};
|
78 |
|
79 |
return (
|
80 |
-
<div className="search-page">
|
81 |
<div className="search-container">
|
82 |
<input
|
83 |
type="text"
|
@@ -87,7 +86,7 @@ const SearchPage = () => {
|
|
87 |
className="search-input"
|
88 |
/>
|
89 |
{loading && (
|
90 |
-
<div className="
|
91 |
<div className="search-icon">
|
92 |
<FontAwesomeIcon icon={faSearch} size="xl" />
|
93 |
</div>
|
@@ -98,72 +97,18 @@ const SearchPage = () => {
|
|
98 |
{error && <div className="error-message">{error}</div>}
|
99 |
<div className="results-container">
|
100 |
{results.films.length > 0 && (
|
101 |
-
<
|
102 |
-
|
103 |
-
<
|
104 |
-
|
105 |
-
|
106 |
-
onClick={() => scroll(moviesRef, "left")}
|
107 |
-
className="scroll-button"
|
108 |
-
>
|
109 |
-
<FontAwesomeIcon icon={faCaretLeft} size="2xl" />
|
110 |
-
</button>
|
111 |
-
<button
|
112 |
-
onClick={() => scroll(moviesRef, "right")}
|
113 |
-
className="scroll-button"
|
114 |
-
>
|
115 |
-
<FontAwesomeIcon icon={faCaretRight} size="2xl" />
|
116 |
-
</button>
|
117 |
-
</div>
|
118 |
-
</div>
|
119 |
-
<ul className="results-list" ref={moviesRef}>
|
120 |
-
{results.films.map((film, index) => (
|
121 |
-
<li
|
122 |
-
key={index}
|
123 |
-
className="result-item"
|
124 |
-
onClick={() =>
|
125 |
-
handleItemClick(`/movie/${encodeURIComponent(film)}`)
|
126 |
-
}
|
127 |
-
>
|
128 |
-
<MovieCard title={film} />
|
129 |
-
</li>
|
130 |
-
))}
|
131 |
-
</ul>
|
132 |
-
</div>
|
133 |
)}
|
134 |
{results.tv_series.length > 0 && (
|
135 |
-
<
|
136 |
-
|
137 |
-
<
|
138 |
-
|
139 |
-
|
140 |
-
onClick={() => scroll(seriesRef, "left")}
|
141 |
-
className="scroll-button"
|
142 |
-
>
|
143 |
-
<FontAwesomeIcon icon={faCaretLeft} size="2xl" />
|
144 |
-
</button>
|
145 |
-
<button
|
146 |
-
onClick={() => scroll(seriesRef, "right")}
|
147 |
-
className="scroll-button"
|
148 |
-
>
|
149 |
-
<FontAwesomeIcon icon={faCaretRight} size="2xl" />
|
150 |
-
</button>
|
151 |
-
</div>
|
152 |
-
</div>
|
153 |
-
<ul className="results-list" ref={seriesRef}>
|
154 |
-
{results.tv_series.map((series, index) => (
|
155 |
-
<li
|
156 |
-
key={index}
|
157 |
-
className="result-item"
|
158 |
-
onClick={() =>
|
159 |
-
handleItemClick(`/tvshow/${encodeURIComponent(series)}`)
|
160 |
-
}
|
161 |
-
>
|
162 |
-
<TvShowCard title={series} />
|
163 |
-
</li>
|
164 |
-
))}
|
165 |
-
</ul>
|
166 |
-
</div>
|
167 |
)}
|
168 |
</div>
|
169 |
</div>
|
|
|
9 |
} from "@fortawesome/free-solid-svg-icons";
|
10 |
import { search } from "@/api/searchApi";
|
11 |
import "./searchPage.css";
|
12 |
+
import MovieCard from "@/components/shared/Card/MovieCard";
|
13 |
+
import TvShowCard from "@/components/shared/Card/TvShowCard";
|
14 |
+
import ScrollSection from "@/components/shared/Sections/ScrollSection";
|
15 |
|
16 |
const SearchPage = () => {
|
17 |
const [query, setQuery] = useState("");
|
|
|
23 |
|
24 |
const router = useRouter();
|
25 |
const timeoutRef = useRef(null);
|
|
|
|
|
26 |
|
27 |
useEffect(() => {
|
28 |
if (timeoutRef.current) {
|
|
|
76 |
};
|
77 |
|
78 |
return (
|
79 |
+
<div className="search-page app-container">
|
80 |
<div className="search-container">
|
81 |
<input
|
82 |
type="text"
|
|
|
86 |
className="search-input"
|
87 |
/>
|
88 |
{loading && (
|
89 |
+
<div className="search-indicator">
|
90 |
<div className="search-icon">
|
91 |
<FontAwesomeIcon icon={faSearch} size="xl" />
|
92 |
</div>
|
|
|
97 |
{error && <div className="error-message">{error}</div>}
|
98 |
<div className="results-container">
|
99 |
{results.films.length > 0 && (
|
100 |
+
<ScrollSection title={"Movies"}>
|
101 |
+
{results.films.map((film, index) => (
|
102 |
+
<MovieCard key={index} title={film} />
|
103 |
+
))}
|
104 |
+
</ScrollSection>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
)}
|
106 |
{results.tv_series.length > 0 && (
|
107 |
+
<ScrollSection title={"TV Series"}>
|
108 |
+
{results.tv_series.map((series, index) => (
|
109 |
+
<TvShowCard key={index} title={series} />
|
110 |
+
))}
|
111 |
+
</ScrollSection>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
)}
|
113 |
</div>
|
114 |
</div>
|
frontend/src/app/search/searchPage.css
CHANGED
@@ -1,9 +1,7 @@
|
|
1 |
.search-page {
|
2 |
-
background-color: #121212;
|
3 |
color: #ffffff;
|
4 |
height: 100%;
|
5 |
min-height: 89dvh;
|
6 |
-
transition: background-color 0.3s ease;
|
7 |
font-family: "Signika", sans-serif;
|
8 |
font-optical-sizing: auto;
|
9 |
font-style: normal;
|
@@ -83,17 +81,11 @@
|
|
83 |
.results-container {
|
84 |
padding-left: 20px;
|
85 |
padding-right: 20px;
|
86 |
-
display: flex;
|
87 |
-
flex-direction: column;
|
88 |
-
align-items: center;
|
89 |
}
|
90 |
|
91 |
.results-section {
|
92 |
width: 100%;
|
93 |
max-width: 100%;
|
94 |
-
display: flex;
|
95 |
-
flex-direction: column;
|
96 |
-
white-space: nowrap;
|
97 |
}
|
98 |
|
99 |
.results-section h2 {
|
@@ -102,50 +94,6 @@
|
|
102 |
animation: fadeIn 1s ease;
|
103 |
}
|
104 |
|
105 |
-
.result-scroll {
|
106 |
-
display: flex;
|
107 |
-
justify-content: space-between;
|
108 |
-
margin-bottom: 15px;
|
109 |
-
border-bottom: 1px solid #4a41ef;
|
110 |
-
padding-bottom: 5px;
|
111 |
-
}
|
112 |
-
.scroll-controls button {
|
113 |
-
margin-left: 10px;
|
114 |
-
margin-right: 10px;
|
115 |
-
color: #9b9a9f;
|
116 |
-
}
|
117 |
-
|
118 |
-
.results-list {
|
119 |
-
list-style-type: none;
|
120 |
-
padding: 0;
|
121 |
-
display: flex;
|
122 |
-
flex-direction: row;
|
123 |
-
margin: 0;
|
124 |
-
scroll-behavior: smooth;
|
125 |
-
overflow-x: scroll;
|
126 |
-
overflow-y: hidden;
|
127 |
-
}
|
128 |
-
|
129 |
-
.results-list::-webkit-scrollbar {
|
130 |
-
height: 8px;
|
131 |
-
}
|
132 |
-
|
133 |
-
.results-list::-webkit-scrollbar-thumb {
|
134 |
-
background-color: #3c23cf;
|
135 |
-
border-radius: 4px;
|
136 |
-
}
|
137 |
-
|
138 |
-
.result-item {
|
139 |
-
background-color: #1e1e1e;
|
140 |
-
border: 1px solid #585858;
|
141 |
-
border-radius: 5px;
|
142 |
-
margin-right: 10px;
|
143 |
-
transition: background-color 0.3s ease, transform 0.3s ease;
|
144 |
-
animation: slideIn 0.5s ease;
|
145 |
-
display: inline-block;
|
146 |
-
flex: 0 0 auto;
|
147 |
-
}
|
148 |
-
|
149 |
@keyframes slideIn {
|
150 |
from {
|
151 |
transform: translateY(20px);
|
@@ -156,27 +104,8 @@
|
|
156 |
opacity: 1;
|
157 |
}
|
158 |
}
|
159 |
-
|
160 |
-
.result-item:hover {
|
161 |
-
background-color: #333;
|
162 |
-
transform: scale(1.02);
|
163 |
-
}
|
164 |
-
|
165 |
-
/* Link styling */
|
166 |
-
.result-link {
|
167 |
-
text-decoration: none;
|
168 |
-
color: #ffffff;
|
169 |
-
font-size: 18px;
|
170 |
-
transition: color 0.3s ease;
|
171 |
-
}
|
172 |
-
|
173 |
-
.result-link:hover {
|
174 |
-
text-decoration: underline;
|
175 |
-
color: #ff6f00;
|
176 |
-
}
|
177 |
-
|
178 |
/* Loading indicator styles */
|
179 |
-
.
|
180 |
display: flex;
|
181 |
align-items: center;
|
182 |
margin-top: 20px;
|
@@ -257,7 +186,7 @@
|
|
257 |
}
|
258 |
}
|
259 |
|
260 |
-
.
|
261 |
width: 40px;
|
262 |
height: 40px;
|
263 |
background-image: linear-gradient(#146c98, #7139ff);
|
|
|
1 |
.search-page {
|
|
|
2 |
color: #ffffff;
|
3 |
height: 100%;
|
4 |
min-height: 89dvh;
|
|
|
5 |
font-family: "Signika", sans-serif;
|
6 |
font-optical-sizing: auto;
|
7 |
font-style: normal;
|
|
|
81 |
.results-container {
|
82 |
padding-left: 20px;
|
83 |
padding-right: 20px;
|
|
|
|
|
|
|
84 |
}
|
85 |
|
86 |
.results-section {
|
87 |
width: 100%;
|
88 |
max-width: 100%;
|
|
|
|
|
|
|
89 |
}
|
90 |
|
91 |
.results-section h2 {
|
|
|
94 |
animation: fadeIn 1s ease;
|
95 |
}
|
96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
@keyframes slideIn {
|
98 |
from {
|
99 |
transform: translateY(20px);
|
|
|
104 |
opacity: 1;
|
105 |
}
|
106 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
/* Loading indicator styles */
|
108 |
+
.search-indicator {
|
109 |
display: flex;
|
110 |
align-items: center;
|
111 |
margin-top: 20px;
|
|
|
186 |
}
|
187 |
}
|
188 |
|
189 |
+
.search-indicator .spinner {
|
190 |
width: 40px;
|
191 |
height: 40px;
|
192 |
background-image: linear-gradient(#146c98, #7139ff);
|
frontend/src/app/tvshows/page.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
-
import
|
2 |
import './TvShows.css';
|
3 |
|
4 |
export default function TVShows() {
|
5 |
return (
|
6 |
-
<
|
7 |
);
|
8 |
}
|
|
|
1 |
+
import StillInDevelopment from '../_still-in-development';
|
2 |
import './TvShows.css';
|
3 |
|
4 |
export default function TVShows() {
|
5 |
return (
|
6 |
+
<StillInDevelopment/>
|
7 |
);
|
8 |
}
|
frontend/src/components/MovieCard.css
DELETED
@@ -1,83 +0,0 @@
|
|
1 |
-
/* styles/MovieCard.css */
|
2 |
-
|
3 |
-
.movie-card {
|
4 |
-
position: relative;
|
5 |
-
width: 150px;
|
6 |
-
height: 250px;
|
7 |
-
margin: 10px;
|
8 |
-
border-radius: 8px;
|
9 |
-
overflow: hidden;
|
10 |
-
background-color: #202232;
|
11 |
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
12 |
-
cursor: pointer;
|
13 |
-
transition: transform 0.3s ease;
|
14 |
-
display: flex;
|
15 |
-
flex-direction: column;
|
16 |
-
opacity: 0;
|
17 |
-
animation: fadeIn 0.5s forwards;
|
18 |
-
}
|
19 |
-
|
20 |
-
.movie-card-image-container {
|
21 |
-
position: relative;
|
22 |
-
width: 100%;
|
23 |
-
height: 80%;
|
24 |
-
}
|
25 |
-
|
26 |
-
.movie-card-poster {
|
27 |
-
object-fit: cover;
|
28 |
-
object-position: top;
|
29 |
-
border-radius: 8px 8px 0 0;
|
30 |
-
animation: fade-in 2s forwards;
|
31 |
-
opacity: 0;
|
32 |
-
}
|
33 |
-
|
34 |
-
@keyframes fade-in{
|
35 |
-
0%{opacity: 0;}
|
36 |
-
100%{opacity: 1;}
|
37 |
-
}
|
38 |
-
|
39 |
-
.movie-card-info {
|
40 |
-
position: relative;
|
41 |
-
width: 100%;
|
42 |
-
height: 28%;
|
43 |
-
padding: 10px;
|
44 |
-
background: #1b1c24;
|
45 |
-
color: #fff;
|
46 |
-
text-align: center;
|
47 |
-
box-sizing: border-box;
|
48 |
-
display: flex;
|
49 |
-
flex-direction: column;
|
50 |
-
justify-content: center;
|
51 |
-
text-decoration: none !important;
|
52 |
-
}
|
53 |
-
|
54 |
-
.movie-card-title {
|
55 |
-
margin: 0;
|
56 |
-
font-size: 16px;
|
57 |
-
font-weight: bold;
|
58 |
-
overflow: hidden;
|
59 |
-
text-overflow: ellipsis;
|
60 |
-
white-space: nowrap;
|
61 |
-
}
|
62 |
-
|
63 |
-
.movie-card-year {
|
64 |
-
margin: 5px 0 0;
|
65 |
-
font-size: 14px;
|
66 |
-
overflow: hidden;
|
67 |
-
text-overflow: ellipsis;
|
68 |
-
white-space: nowrap;
|
69 |
-
}
|
70 |
-
|
71 |
-
.error {
|
72 |
-
color: #e74c3c;
|
73 |
-
}
|
74 |
-
|
75 |
-
@keyframes fadeIn {
|
76 |
-
from {
|
77 |
-
opacity: 0;
|
78 |
-
}
|
79 |
-
to {
|
80 |
-
opacity: 1;
|
81 |
-
}
|
82 |
-
}
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/components/{MoviePlayer.css β Player/Movie/MoviePlayer.css}
RENAMED
@@ -229,3 +229,10 @@ input[type="range"]::-moz-range-thumb {
|
|
229 |
width: 0.8rem;
|
230 |
border-radius: 10px;
|
231 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
width: 0.8rem;
|
230 |
border-radius: 10px;
|
231 |
}
|
232 |
+
|
233 |
+
::cue {
|
234 |
+
color: rgb(199, 199, 199);
|
235 |
+
background-color: rgba(0, 0, 0, 0.7);
|
236 |
+
font-size: 1.3em;
|
237 |
+
line-height: 1.2; /* Adjust line height for spacing */
|
238 |
+
}
|
frontend/src/components/{MoviePlayer.js β Player/Movie/MoviePlayer.js}
RENAMED
@@ -15,7 +15,7 @@ import {
|
|
15 |
faAngleDoubleLeft,
|
16 |
faCompress,
|
17 |
} from "@fortawesome/free-solid-svg-icons";
|
18 |
-
import { Spinner } from "@/components/Spinner";
|
19 |
import SeekableProgressBar from "./SeekableProgressBar";
|
20 |
|
21 |
export default function MoviePlayer({ videoUrl, title }) {
|
@@ -277,7 +277,8 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
277 |
kind="subtitles"
|
278 |
label="English"
|
279 |
srcLang="en"
|
280 |
-
src="
|
|
|
281 |
/>
|
282 |
Your browser does not support the video tag.
|
283 |
</video>
|
|
|
15 |
faAngleDoubleLeft,
|
16 |
faCompress,
|
17 |
} from "@fortawesome/free-solid-svg-icons";
|
18 |
+
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
19 |
import SeekableProgressBar from "./SeekableProgressBar";
|
20 |
|
21 |
export default function MoviePlayer({ videoUrl, title }) {
|
|
|
277 |
kind="subtitles"
|
278 |
label="English"
|
279 |
srcLang="en"
|
280 |
+
src="/My.Spy.The.Eternal.City.(2024).WEB.vtt"
|
281 |
+
default
|
282 |
/>
|
283 |
Your browser does not support the video tag.
|
284 |
</video>
|
frontend/src/components/shared/Card/Card.css
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.movie-card:hover .movie-card-poster{
|
2 |
+
transform: scale(1.1);
|
3 |
+
}
|
4 |
+
|
5 |
+
.movie-card {
|
6 |
+
position: relative;
|
7 |
+
width: 150px;
|
8 |
+
height: 250px;
|
9 |
+
min-width: 150px;
|
10 |
+
min-height: 250px;
|
11 |
+
margin: 10px;
|
12 |
+
border-radius: 8px;
|
13 |
+
overflow: hidden;
|
14 |
+
background-color: #202232;
|
15 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
16 |
+
cursor: pointer;
|
17 |
+
transition: transform 0.3s ease;
|
18 |
+
display: flex;
|
19 |
+
flex-direction: column;
|
20 |
+
opacity: 0;
|
21 |
+
animation: fadeIn 0.5s forwards;
|
22 |
+
}
|
23 |
+
|
24 |
+
.movie-card-image-container {
|
25 |
+
position: relative;
|
26 |
+
width: 100%;
|
27 |
+
height: inherit;
|
28 |
+
}
|
29 |
+
|
30 |
+
.movie-card-poster {
|
31 |
+
object-fit: cover;
|
32 |
+
object-position: top;
|
33 |
+
border-radius: 8px 8px 0 0;
|
34 |
+
animation: fade-in 2s forwards;
|
35 |
+
opacity: 0;
|
36 |
+
transition: transform .1s ease;
|
37 |
+
}
|
38 |
+
|
39 |
+
@keyframes fade-in {
|
40 |
+
0% {
|
41 |
+
opacity: 0;
|
42 |
+
}
|
43 |
+
100% {
|
44 |
+
opacity: 1;
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
.movie-card-info {
|
49 |
+
position: relative;
|
50 |
+
width: 100%;
|
51 |
+
height: 28%;
|
52 |
+
padding: 10px;
|
53 |
+
background: #1b1c24;
|
54 |
+
color: #fff;
|
55 |
+
text-align: center;
|
56 |
+
box-sizing: border-box;
|
57 |
+
display: flex;
|
58 |
+
flex-direction: column;
|
59 |
+
justify-content: center;
|
60 |
+
text-decoration: none !important;
|
61 |
+
}
|
62 |
+
|
63 |
+
.movie-card-title {
|
64 |
+
margin: 0;
|
65 |
+
font-size: 16px;
|
66 |
+
font-weight: 500;
|
67 |
+
overflow: hidden;
|
68 |
+
text-overflow: ellipsis;
|
69 |
+
white-space: nowrap;
|
70 |
+
}
|
71 |
+
|
72 |
+
.movie-card-year {
|
73 |
+
margin: 5px 0 0;
|
74 |
+
font-size: 1rem;
|
75 |
+
font-weight: 200;
|
76 |
+
overflow: hidden;
|
77 |
+
text-overflow: ellipsis;
|
78 |
+
white-space: nowrap;
|
79 |
+
}
|
80 |
+
|
81 |
+
.error {
|
82 |
+
color: #e74c3c;
|
83 |
+
}
|
84 |
+
|
85 |
+
@keyframes fadeIn {
|
86 |
+
from {
|
87 |
+
opacity: 0;
|
88 |
+
}
|
89 |
+
to {
|
90 |
+
opacity: 1;
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
@media (orientation: portrait) {
|
95 |
+
.movie-card {
|
96 |
+
width: 130px;
|
97 |
+
height: 220px;
|
98 |
+
margin: 5px;
|
99 |
+
}
|
100 |
+
.movie-card-info {
|
101 |
+
height: 30%;
|
102 |
+
padding: 5px;
|
103 |
+
}
|
104 |
+
.movie-card-title {
|
105 |
+
margin: 0;
|
106 |
+
font-size: 1rem;
|
107 |
+
}
|
108 |
+
|
109 |
+
.movie-card-year {
|
110 |
+
margin: 5px 0 0;
|
111 |
+
font-size: 0.8rem;
|
112 |
+
}
|
113 |
+
}
|
frontend/src/components/{MovieCard.js β shared/Card/MovieCard.js}
RENAMED
@@ -1,8 +1,9 @@
|
|
1 |
-
import { useEffect, useState } from
|
2 |
-
import Image from
|
3 |
-
import apiClient from
|
4 |
-
import SkeletonLoader from
|
5 |
-
import
|
|
|
6 |
|
7 |
const MovieCard = ({ title }) => {
|
8 |
const [movieData, setMovieData] = useState(null);
|
@@ -33,22 +34,24 @@ const MovieCard = ({ title }) => {
|
|
33 |
}
|
34 |
|
35 |
return (
|
36 |
-
<
|
37 |
-
<div className="movie-card
|
38 |
-
<
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
</div>
|
47 |
-
|
48 |
-
<h3 className="movie-card-title">{movieData.title}</h3>
|
49 |
-
<p className="movie-card-year">{movieData.year}</p>
|
50 |
-
</div>
|
51 |
-
</div>
|
52 |
);
|
53 |
};
|
54 |
|
|
|
1 |
+
import { useEffect, useState } from "react";
|
2 |
+
import Image from "next/image";
|
3 |
+
import apiClient from "@/api/apiClient";
|
4 |
+
import SkeletonLoader from "@/skeletons/Card/movieCard";
|
5 |
+
import "./Card.css";
|
6 |
+
import Link from "next/link";
|
7 |
|
8 |
const MovieCard = ({ title }) => {
|
9 |
const [movieData, setMovieData] = useState(null);
|
|
|
34 |
}
|
35 |
|
36 |
return (
|
37 |
+
<Link href={`/movie/${title}`}>
|
38 |
+
<div className="movie-card">
|
39 |
+
<div className="movie-card-image-container">
|
40 |
+
<Image
|
41 |
+
src={movieData.image}
|
42 |
+
alt={`${movieData.title} poster`}
|
43 |
+
fill
|
44 |
+
sizes="(max-width: 500px) 100vw, (max-width: 1200spx) 50vw, 33vw"
|
45 |
+
priority
|
46 |
+
className="movie-card-poster"
|
47 |
+
/>
|
48 |
+
</div>
|
49 |
+
<div className="movie-card-info">
|
50 |
+
<h3 className="movie-card-title">{movieData.title}</h3>
|
51 |
+
<p className="movie-card-year">{movieData.year}</p>
|
52 |
+
</div>
|
53 |
</div>
|
54 |
+
</Link>
|
|
|
|
|
|
|
|
|
55 |
);
|
56 |
};
|
57 |
|
frontend/src/components/{TvShowCard.js β shared/Card/TvShowCard.js}
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
import { useEffect, useState } from 'react';
|
2 |
import Image from 'next/image';
|
3 |
import apiClient from '@/api/apiClient';
|
4 |
-
import SkeletonLoader from '@/skeletons/movieCard';
|
5 |
-
import './
|
6 |
|
7 |
const TvShowCard = ({ title }) => {
|
8 |
const [movieData, setMovieData] = useState(null);
|
|
|
1 |
import { useEffect, useState } from 'react';
|
2 |
import Image from 'next/image';
|
3 |
import apiClient from '@/api/apiClient';
|
4 |
+
import SkeletonLoader from '@/skeletons/Card/movieCard';
|
5 |
+
import './Card.css';
|
6 |
|
7 |
const TvShowCard = ({ title }) => {
|
8 |
const [movieData, setMovieData] = useState(null);
|
frontend/src/components/{Header.css β shared/Header/Header.css}
RENAMED
File without changes
|
frontend/src/components/{Header.js β shared/Header/Header.js}
RENAMED
@@ -6,7 +6,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
|
6 |
import Image from "next/image";
|
7 |
import { useEffect } from "react";
|
8 |
import { usePathname } from "next/navigation";
|
9 |
-
import Sidebar from "@/components/Sidebar";
|
10 |
|
11 |
const Header = () => {
|
12 |
const pathname = usePathname();
|
@@ -14,17 +14,6 @@ const Header = () => {
|
|
14 |
pathname.startsWith("/player/movie") ||
|
15 |
pathname.startsWith("/player/tvshow");
|
16 |
|
17 |
-
useEffect(() => {
|
18 |
-
const appContainer = document.querySelector('.app-container');
|
19 |
-
if (appContainer) {
|
20 |
-
if (isPlayerPage) {
|
21 |
-
appContainer.classList.add('no-padding');
|
22 |
-
} else {
|
23 |
-
appContainer.classList.remove('no-padding');
|
24 |
-
}
|
25 |
-
}
|
26 |
-
}, [isPlayerPage]);
|
27 |
-
|
28 |
return (
|
29 |
<>
|
30 |
{!isPlayerPage && (
|
|
|
6 |
import Image from "next/image";
|
7 |
import { useEffect } from "react";
|
8 |
import { usePathname } from "next/navigation";
|
9 |
+
import Sidebar from "@/components/shared/Sidebar/Sidebar";
|
10 |
|
11 |
const Header = () => {
|
12 |
const pathname = usePathname();
|
|
|
14 |
pathname.startsWith("/player/movie") ||
|
15 |
pathname.startsWith("/player/tvshow");
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
return (
|
18 |
<>
|
19 |
{!isPlayerPage && (
|
frontend/src/components/{SeekableProgressBar.css β shared/ProgressBar/SeekableProgressBar.css}
RENAMED
File without changes
|
frontend/src/components/{SeekableProgressBar.js β shared/ProgressBar/SeekableProgressBar.js}
RENAMED
File without changes
|
frontend/src/components/{CastSection.css β shared/Sections/CastSection.css}
RENAMED
File without changes
|
frontend/src/components/{CastSection.js β shared/Sections/CastSection.js}
RENAMED
File without changes
|
frontend/src/components/{HeroSection.css β shared/Sections/HeroSection.css}
RENAMED
@@ -44,23 +44,23 @@
|
|
44 |
@keyframes hero-anim-portrait {
|
45 |
0% {
|
46 |
background-position: center;
|
47 |
-
background-size: 160%
|
48 |
}
|
49 |
25% {
|
50 |
background-position: top left;
|
51 |
-
background-size: 120%
|
52 |
}
|
53 |
50% {
|
54 |
background-position: bottom right;
|
55 |
-
background-size: 160%
|
56 |
}
|
57 |
75% {
|
58 |
background-position: top right;
|
59 |
-
background-size: 120%
|
60 |
}
|
61 |
100% {
|
62 |
background-position: center;
|
63 |
-
background-size: 160%
|
64 |
}
|
65 |
}
|
66 |
|
@@ -68,23 +68,23 @@
|
|
68 |
@keyframes hero-anim-landscape {
|
69 |
0% {
|
70 |
background-position: center;
|
71 |
-
background-size: 130%
|
72 |
}
|
73 |
25% {
|
74 |
background-position: top left;
|
75 |
-
background-size: 100%
|
76 |
}
|
77 |
50% {
|
78 |
background-position: bottom right;
|
79 |
-
background-size: 130%
|
80 |
}
|
81 |
75% {
|
82 |
background-position: top right;
|
83 |
-
background-size: 100%
|
84 |
}
|
85 |
100% {
|
86 |
background-position: center;
|
87 |
-
background-size: 130%
|
88 |
}
|
89 |
}
|
90 |
|
@@ -106,6 +106,10 @@
|
|
106 |
justify-content: space-around;
|
107 |
}
|
108 |
|
|
|
|
|
|
|
|
|
109 |
.hero-title {
|
110 |
color: white;
|
111 |
font-size: 7rem;
|
|
|
44 |
@keyframes hero-anim-portrait {
|
45 |
0% {
|
46 |
background-position: center;
|
47 |
+
background-size: 160% ;
|
48 |
}
|
49 |
25% {
|
50 |
background-position: top left;
|
51 |
+
background-size: 120% ;
|
52 |
}
|
53 |
50% {
|
54 |
background-position: bottom right;
|
55 |
+
background-size: 160% ;
|
56 |
}
|
57 |
75% {
|
58 |
background-position: top right;
|
59 |
+
background-size: 120% ;
|
60 |
}
|
61 |
100% {
|
62 |
background-position: center;
|
63 |
+
background-size: 160% ;
|
64 |
}
|
65 |
}
|
66 |
|
|
|
68 |
@keyframes hero-anim-landscape {
|
69 |
0% {
|
70 |
background-position: center;
|
71 |
+
background-size: 130% ;
|
72 |
}
|
73 |
25% {
|
74 |
background-position: top left;
|
75 |
+
background-size: 100% ;
|
76 |
}
|
77 |
50% {
|
78 |
background-position: bottom right;
|
79 |
+
background-size: 130% ;
|
80 |
}
|
81 |
75% {
|
82 |
background-position: top right;
|
83 |
+
background-size: 100% ;
|
84 |
}
|
85 |
100% {
|
86 |
background-position: center;
|
87 |
+
background-size: 130% ;
|
88 |
}
|
89 |
}
|
90 |
|
|
|
106 |
justify-content: space-around;
|
107 |
}
|
108 |
|
109 |
+
.hero-text a{
|
110 |
+
text-decoration: none;
|
111 |
+
}
|
112 |
+
|
113 |
.hero-title {
|
114 |
color: white;
|
115 |
font-size: 7rem;
|
frontend/src/components/{HeroSection.js β shared/Sections/HeroSection.js}
RENAMED
@@ -2,7 +2,7 @@
|
|
2 |
import { useState, useEffect, useRef } from "react";
|
3 |
import "./HeroSection.css";
|
4 |
import apiClient from "@/api/apiClient";
|
5 |
-
import SkeletonLoader from "@/skeletons/HeroSection";
|
6 |
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
7 |
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
8 |
import Link from "next/link";
|
@@ -121,6 +121,10 @@ const HeroSection = () => {
|
|
121 |
return <SkeletonLoader />;
|
122 |
}
|
123 |
|
|
|
|
|
|
|
|
|
124 |
const { title, description, imageUrl, type } = items[currentIndex];
|
125 |
const linkPath = `/${type.toLowerCase()}/${encodeURIComponent(title)}`;
|
126 |
|
|
|
2 |
import { useState, useEffect, useRef } from "react";
|
3 |
import "./HeroSection.css";
|
4 |
import apiClient from "@/api/apiClient";
|
5 |
+
import SkeletonLoader from "@/skeletons/Sections/HeroSection";
|
6 |
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
7 |
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
8 |
import Link from "next/link";
|
|
|
121 |
return <SkeletonLoader />;
|
122 |
}
|
123 |
|
124 |
+
if (items.length === 0) {
|
125 |
+
return <div>No items available</div>;
|
126 |
+
}
|
127 |
+
|
128 |
const { title, description, imageUrl, type } = items[currentIndex];
|
129 |
const linkPath = `/${type.toLowerCase()}/${encodeURIComponent(title)}`;
|
130 |
|
frontend/src/components/shared/Sections/ScrollSection.css
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Section styling */
|
2 |
+
.scroll-section {
|
3 |
+
margin-bottom: 40px;
|
4 |
+
}
|
5 |
+
|
6 |
+
@media (orientation: landscape) {
|
7 |
+
.scroll-section {
|
8 |
+
margin-left: 25px;
|
9 |
+
margin-right: 25px;
|
10 |
+
}
|
11 |
+
}
|
12 |
+
|
13 |
+
@media (max-width: 768px) {
|
14 |
+
.scroll-section {
|
15 |
+
margin-left: 10px;
|
16 |
+
margin-right: 10px;
|
17 |
+
margin-bottom: 20px;
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
.scroll-title-section {
|
22 |
+
display: flex;
|
23 |
+
justify-content: center;
|
24 |
+
align-items: center;
|
25 |
+
}
|
26 |
+
|
27 |
+
.scroll-section-controls {
|
28 |
+
display: flex;
|
29 |
+
align-items: center;
|
30 |
+
text-align: center;
|
31 |
+
justify-content: space-between;
|
32 |
+
flex-wrap: wrap;
|
33 |
+
}
|
34 |
+
|
35 |
+
.scroll-controls button {
|
36 |
+
color: var(--gray-text-color);
|
37 |
+
margin-left: 10px;
|
38 |
+
margin-right: 10px;
|
39 |
+
border-radius: 50%;
|
40 |
+
width: auto;
|
41 |
+
height: auto;
|
42 |
+
min-width: 50px;
|
43 |
+
padding: 9px;
|
44 |
+
border: 1px solid;
|
45 |
+
transition: background-color 0.3s ease, border 0.5s ease, color 1s ease;
|
46 |
+
}
|
47 |
+
|
48 |
+
.scroll-controls button:hover {
|
49 |
+
background-color: var(--bg-secondary);
|
50 |
+
border: 1px solid var(--primary-special-color);
|
51 |
+
}
|
52 |
+
|
53 |
+
.scroll-controls button:disabled {
|
54 |
+
color: #797979;
|
55 |
+
}
|
56 |
+
|
57 |
+
.scroll-section h2 {
|
58 |
+
font-size: 1.8rem;
|
59 |
+
color: var(--gray-text-color);
|
60 |
+
}
|
61 |
+
|
62 |
+
@media (max-width: 768px) {
|
63 |
+
.scroll-section h2 {
|
64 |
+
font-size: 1.5rem;
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
.items-grid {
|
69 |
+
display: flex;
|
70 |
+
overflow-x: auto;
|
71 |
+
padding: 10px 0;
|
72 |
+
gap: 10px;
|
73 |
+
flex-direction: row;
|
74 |
+
}
|
75 |
+
|
76 |
+
.items-grid::-webkit-scrollbar {
|
77 |
+
display: none;
|
78 |
+
}
|
79 |
+
|
80 |
+
.items-grid::-webkit-scrollbar-thumb {
|
81 |
+
background-color: var(--primary-special-color);
|
82 |
+
border-radius: 4px;
|
83 |
+
}
|
84 |
+
|
85 |
+
.items-grid::-webkit-scrollbar-track {
|
86 |
+
background-color: var(--bg-primary);
|
87 |
+
}
|
frontend/src/components/shared/Sections/ScrollSection.js
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
import { useRef, useState, useEffect } from "react";
|
3 |
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
4 |
+
import { faCaretLeft, faCaretRight } from "@fortawesome/free-solid-svg-icons";
|
5 |
+
import "./ScrollSection.css";
|
6 |
+
|
7 |
+
const ScrollSection = ({ title, children }) => {
|
8 |
+
const scrollRef = useRef(null);
|
9 |
+
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
10 |
+
const [canScrollRight, setCanScrollRight] = useState(true);
|
11 |
+
|
12 |
+
useEffect(() => {
|
13 |
+
const currentScrollRef = scrollRef.current;
|
14 |
+
const firstChild = currentScrollRef?.firstElementChild;
|
15 |
+
const lastChild = currentScrollRef?.lastElementChild;
|
16 |
+
|
17 |
+
if (!firstChild || !lastChild) return;
|
18 |
+
|
19 |
+
const observerOptions = {
|
20 |
+
root: currentScrollRef,
|
21 |
+
threshold: 1.0
|
22 |
+
};
|
23 |
+
|
24 |
+
const callback = (entries) => {
|
25 |
+
entries.forEach((entry) => {
|
26 |
+
if (entry.target === firstChild) {
|
27 |
+
setCanScrollLeft(!entry.isIntersecting);
|
28 |
+
} else if (entry.target === lastChild) {
|
29 |
+
setCanScrollRight(!entry.isIntersecting);
|
30 |
+
}
|
31 |
+
});
|
32 |
+
};
|
33 |
+
|
34 |
+
const observer = new IntersectionObserver(callback, observerOptions);
|
35 |
+
|
36 |
+
observer.observe(firstChild);
|
37 |
+
observer.observe(lastChild);
|
38 |
+
|
39 |
+
return () => {
|
40 |
+
observer.disconnect();
|
41 |
+
};
|
42 |
+
}, [children]);
|
43 |
+
|
44 |
+
const scroll = (direction) => {
|
45 |
+
if (scrollRef.current) {
|
46 |
+
const scrollAmount = direction === "left" ? -360 : 360;
|
47 |
+
scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
|
48 |
+
}
|
49 |
+
};
|
50 |
+
|
51 |
+
return (
|
52 |
+
<section className="scroll-section">
|
53 |
+
<div className="scroll-section-controls">
|
54 |
+
<h2>{title}</h2>
|
55 |
+
<div className="scroll-controls">
|
56 |
+
<button
|
57 |
+
onClick={() => scroll("left")}
|
58 |
+
className="scroll-button"
|
59 |
+
disabled={!canScrollLeft}
|
60 |
+
>
|
61 |
+
<FontAwesomeIcon icon={faCaretLeft} size="2xl" />
|
62 |
+
</button>
|
63 |
+
<button
|
64 |
+
onClick={() => scroll("right")}
|
65 |
+
className="scroll-button"
|
66 |
+
disabled={!canScrollRight}
|
67 |
+
>
|
68 |
+
<FontAwesomeIcon icon={faCaretRight} size="2xl" />
|
69 |
+
</button>
|
70 |
+
</div>
|
71 |
+
</div>
|
72 |
+
<div className="items-grid" ref={scrollRef}>
|
73 |
+
{children}
|
74 |
+
</div>
|
75 |
+
</section>
|
76 |
+
);
|
77 |
+
};
|
78 |
+
|
79 |
+
export default ScrollSection;
|
frontend/src/components/{Sidebar.css β shared/Sidebar/Sidebar.css}
RENAMED
File without changes
|
frontend/src/components/{Sidebar.js β shared/Sidebar/Sidebar.js}
RENAMED
File without changes
|
frontend/src/components/{Spinner.css β shared/Spinner/Spinner.css}
RENAMED
File without changes
|
frontend/src/components/{Spinner.js β shared/Spinner/Spinner.js}
RENAMED
File without changes
|
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.
|
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.6 Alpha",
|
5 |
};
|
6 |
|
7 |
export default config;
|
frontend/src/modals/{GenreFilterModal.css β Genre/GenreFilterModal.css}
RENAMED
File without changes
|
frontend/src/modals/{GenreFilterModal.js β Genre/GenreFilterModal.js}
RENAMED
File without changes
|
frontend/src/skeletons/{movieCard.css β Card/movieCard.css}
RENAMED
@@ -51,13 +51,12 @@
|
|
51 |
|
52 |
@keyframes pulse {
|
53 |
0% {
|
54 |
-
background-color:
|
55 |
}
|
56 |
50% {
|
57 |
-
background-color:
|
58 |
}
|
59 |
100% {
|
60 |
-
background-color:
|
61 |
}
|
62 |
-
}
|
63 |
-
|
|
|
51 |
|
52 |
@keyframes pulse {
|
53 |
0% {
|
54 |
+
background-color: var(--bg-primary);
|
55 |
}
|
56 |
50% {
|
57 |
+
background-color: var(--bg-secondary);
|
58 |
}
|
59 |
100% {
|
60 |
+
background-color: var(--bg-primary);
|
61 |
}
|
62 |
+
}
|
|
frontend/src/skeletons/{movieCard.js β Card/movieCard.js}
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
import React from 'react';
|
2 |
-
import '
|
3 |
|
4 |
const SkeletonLoader = () => {
|
5 |
return (
|
|
|
1 |
import React from 'react';
|
2 |
+
import './movieCard.css';
|
3 |
|
4 |
const SkeletonLoader = () => {
|
5 |
return (
|
frontend/src/skeletons/{HeroSection.css β Sections/HeroSection.css}
RENAMED
@@ -3,14 +3,15 @@
|
|
3 |
.skeleton-container {
|
4 |
position: relative;
|
5 |
width: 100dvw;
|
6 |
-
height: 480px;
|
7 |
display: flex;
|
8 |
flex-direction: column;
|
9 |
justify-content: flex-end;
|
10 |
-
background-color:
|
11 |
padding: 1rem;
|
12 |
padding-bottom: 2rem;
|
13 |
-
overflow:
|
|
|
14 |
}
|
15 |
|
16 |
.skeleton-image {
|
@@ -18,7 +19,7 @@
|
|
18 |
height: 100%; /* Adjust height to fit container */
|
19 |
max-height: 400px; /* Maximum height to maintain aspect ratio */
|
20 |
border-radius: 4px;
|
21 |
-
background: linear-gradient(90deg,
|
22 |
background-size: 200% 100%;
|
23 |
animation: shimmer 1.5s infinite;
|
24 |
}
|
@@ -33,7 +34,7 @@
|
|
33 |
}
|
34 |
|
35 |
.skeleton-title, .skeleton-description {
|
36 |
-
background: linear-gradient(90deg,
|
37 |
background-size: 200% 100%;
|
38 |
border-radius: 4px;
|
39 |
animation: shimmer 1.5s infinite;
|
|
|
3 |
.skeleton-container {
|
4 |
position: relative;
|
5 |
width: 100dvw;
|
6 |
+
height: 480px;
|
7 |
display: flex;
|
8 |
flex-direction: column;
|
9 |
justify-content: flex-end;
|
10 |
+
background-color: var(--bg-primary); /* Dark purple background */
|
11 |
padding: 1rem;
|
12 |
padding-bottom: 2rem;
|
13 |
+
overflow: visible;
|
14 |
+
z-index: -1;
|
15 |
}
|
16 |
|
17 |
.skeleton-image {
|
|
|
19 |
height: 100%; /* Adjust height to fit container */
|
20 |
max-height: 400px; /* Maximum height to maintain aspect ratio */
|
21 |
border-radius: 4px;
|
22 |
+
background: linear-gradient(90deg, var(--bg-primary) 25%, var(--bg-secondary) 50%, var(--bg-primary) 75%);
|
23 |
background-size: 200% 100%;
|
24 |
animation: shimmer 1.5s infinite;
|
25 |
}
|
|
|
34 |
}
|
35 |
|
36 |
.skeleton-title, .skeleton-description {
|
37 |
+
background: linear-gradient(90deg, var(--bg-primary) 25%, var(--bg-secondary) 50%, var(--bg-primary) 75%);
|
38 |
background-size: 200% 100%;
|
39 |
border-radius: 4px;
|
40 |
animation: shimmer 1.5s infinite;
|
frontend/src/skeletons/{HeroSection.js β Sections/HeroSection.js}
RENAMED
@@ -1,4 +1,3 @@
|
|
1 |
-
// SkeletonLoader.js
|
2 |
import './HeroSection.css';
|
3 |
|
4 |
const SkeletonLoader = () => {
|
|
|
|
|
1 |
import './HeroSection.css';
|
2 |
|
3 |
const SkeletonLoader = () => {
|