ChandimaPrabath commited on
Commit
3b85fd7
1 Parent(s): 2e4a89e

0.0.0.2 Alpha

Browse files
frontend/src/components/HeroSection.css CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  .hero-container {
2
  position: relative;
3
  width: 100%;
@@ -13,6 +23,7 @@
13
  padding: 1rem;
14
  padding-bottom: 2.5rem;
15
  justify-content: flex-end;
 
16
  }
17
 
18
  .hero-image {
@@ -24,7 +35,7 @@
24
  background-size: cover;
25
  background-position: center;
26
  background-repeat: no-repeat;
27
- transition: opacity .1s ease-in-out;
28
  opacity: 0;
29
  }
30
 
@@ -54,6 +65,11 @@
54
  font-size: 1rem;
55
  font-weight: normal;
56
  line-height: 1.4;
 
 
 
 
 
57
  transition: font-size 0.3s ease-in-out;
58
  }
59
 
 
1
+ /* Fade-in animation for the hero section */
2
+ @keyframes fadeIn {
3
+ 0% {
4
+ opacity: 0;
5
+ }
6
+ 100% {
7
+ opacity: 1;
8
+ }
9
+ }
10
+
11
  .hero-container {
12
  position: relative;
13
  width: 100%;
 
23
  padding: 1rem;
24
  padding-bottom: 2.5rem;
25
  justify-content: flex-end;
26
+ animation: fadeIn 1s ease-in-out; /* Add fade-in animation */
27
  }
28
 
29
  .hero-image {
 
35
  background-size: cover;
36
  background-position: center;
37
  background-repeat: no-repeat;
38
+ transition: opacity 1s ease-in-out; /* Smooth transition */
39
  opacity: 0;
40
  }
41
 
 
65
  font-size: 1rem;
66
  font-weight: normal;
67
  line-height: 1.4;
68
+ overflow: hidden; /* Hide text overflow */
69
+ display: -webkit-box; /* Use flexbox layout */
70
+ -webkit-line-clamp: 3; /* Limit text to 3 lines */
71
+ -webkit-box-orient: vertical; /* Set box orientation to vertical */
72
+ text-overflow: ellipsis; /* Add ellipsis (...) */
73
  transition: font-size 0.3s ease-in-out;
74
  }
75
 
frontend/src/components/HeroSection.js CHANGED
@@ -1,113 +1,84 @@
1
  'use client';
2
  import { useState, useEffect, useRef } from 'react';
3
  import './HeroSection.css';
4
-
5
- const dummyItems = [
6
- {
7
- title: "My Spy (2020)",
8
- description: "An exciting new release!",
9
- imageUrl: "https://i.ytimg.com/vi/pfAhQSz-j_o/maxresdefault.jpg"
10
- },
11
- {
12
- title: "My Spy 2 (2024)",
13
- description: "Don't miss this one!",
14
- imageUrl: "https://m.media-amazon.com/images/M/MV5BNDA5MTlhMDMtN2QwOS00N2MwLTgyY2ItOTU3YjBkNjM4OTMwXkEyXkFqcGc@._V1_.jpg"
15
- },
16
- {
17
- title: "The Matrix (1999)",
18
- description: "A revolutionary sci-fi film.",
19
- imageUrl: "https://www.visitindianacountypa.org/wp-content/uploads/2024/05/The-Matrix-1999-Movie-Poster.jpg"
20
- },
21
- {
22
- title: "Inception (2010)",
23
- description: "A mind-bending thriller.",
24
- imageUrl: "https://i.ytimg.com/vi/T03XUKBn8UA/maxresdefault.jpg"
25
- },
26
- {
27
- title: "Interstellar (2014)",
28
- description: "A journey through space and time.",
29
- imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZgarY79EPQu_BBe86NdqmVxRhgH0N6AgLEA&s"
30
- },
31
- {
32
- title: "Parasite (2019)",
33
- description: "A gripping social thriller.",
34
- imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQxR9Ata15qwHHpAnmpQrxHjHc19Y2UwBaTYw&s"
35
- },
36
- {
37
- title: "Spider-Man: Into the Spider-Verse (2018)",
38
- description: "A fresh take on the Spider-Man story.",
39
- imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_Xg7zSuKysUusyQFXFSB1oy82y9wjHPx_qQ&s"
40
- },
41
- {
42
- title: "Knives Out (2019)",
43
- description: "A clever whodunit mystery.",
44
- imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThiumkG9e13lc94b71SstKXbSiIrzcgZgBIA&s"
45
- },
46
- {
47
- title: "Dune (2021)",
48
- description: "An epic adaptation of the sci-fi classic.",
49
- imageUrl: "https://spaceandsorcery.wordpress.com/wp-content/uploads/2021/10/dune-2021.jpg"
50
- },
51
- {
52
- title: "Joker (2019)",
53
- description: "A dark take on the iconic character.",
54
- imageUrl: "https://miro.medium.com/v2/resize:fit:1400/1*WVZSodH2x8YwwjtotNbVxw.jpeg"
55
- },
56
- {
57
- title: "Blade Runner 2049 (2017)",
58
- description: "A visually stunning sci-fi sequel.",
59
- imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSXIGK_OIyih2iwse36yeywRq4nYLocWtLjcQ&s"
60
- }
61
- ];
62
-
63
 
64
  const HeroSection = () => {
65
  const [currentIndex, setCurrentIndex] = useState(0);
66
- const intervalRef = useRef(null);
67
  const [fadeOut, setFadeOut] = useState(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  const startAutoSwitch = () => {
70
  if (intervalRef.current) clearInterval(intervalRef.current);
71
  intervalRef.current = setInterval(() => {
72
- setFadeOut(true); // Trigger fade-out
73
  setTimeout(() => {
74
- setCurrentIndex(prevIndex => (prevIndex + 1) % dummyItems.length);
75
- setFadeOut(false); // Trigger fade-in
76
- }, 200); // Match this timeout with the animation duration
77
  }, 5000);
78
  };
79
 
80
  useEffect(() => {
81
- startAutoSwitch(); // Start auto-switching when component mounts
 
 
82
 
83
  return () => {
84
- clearInterval(intervalRef.current); // Clean up interval on component unmount
85
  };
86
- }, []);
87
 
88
  const handleIndicatorClick = (index) => {
89
- setFadeOut(true); // Trigger fade-out
90
  setTimeout(() => {
91
  setCurrentIndex(index);
92
- setFadeOut(false); // Trigger fade-in
93
  }, 100);
94
  startAutoSwitch();
95
  };
96
 
97
- useEffect(() => {
98
- startAutoSwitch();
99
-
100
- return () => {
101
- clearInterval(intervalRef.current);
102
- };
103
- }, [currentIndex]);
104
 
105
- const { title, description, imageUrl } = dummyItems[currentIndex];
106
 
107
  return (
108
  <div className="hero-container">
109
  <div className="hero-section">
110
- {dummyItems.map((item, index) => (
111
  <div
112
  key={index}
113
  className={`hero-image ${index === currentIndex ? 'active' : ''} ${fadeOut ? 'fade-out' : ''}`}
@@ -116,12 +87,12 @@ const HeroSection = () => {
116
  ))}
117
  <div className="hero-text">
118
  <h1 className="hero-title">{title}</h1>
119
- <h2 className="hero-description">{description}</h2>
120
  </div>
121
  </div>
122
 
123
  <div className="hero-indicators">
124
- {dummyItems.map((_, index) => (
125
  <div
126
  key={index}
127
  className={`indicator ${index === currentIndex ? 'active' : ''}`}
 
1
  'use client';
2
  import { useState, useEffect, useRef } from 'react';
3
  import './HeroSection.css';
4
+ import apiClient from '@/api/apiClient';
5
+ import SkeletonLoader from '@/skeletons/HeroSection';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  const HeroSection = () => {
8
  const [currentIndex, setCurrentIndex] = useState(0);
9
+ const intervalRef = useRef(null);
10
  const [fadeOut, setFadeOut] = useState(false);
11
+ const [items, setItems] = useState([]);
12
+ const [loading, setLoading] = useState(true);
13
+
14
+ useEffect(() => {
15
+ const fetchRecentItems = async () => {
16
+ try {
17
+ const response = await apiClient.getRecent();
18
+ const films = response.films.map(film => ({
19
+ title: film[0],
20
+ description: film[2],
21
+ imageUrl: film[3],
22
+ type: 'Film'
23
+ }));
24
+ const series = response.series.map(serie => ({
25
+ title: serie[0],
26
+ description: serie[2],
27
+ imageUrl: serie[3],
28
+ type: 'Series'
29
+ }));
30
+ setItems([...films, ...series]);
31
+ } catch (error) {
32
+ console.error('Error fetching recent items:', error);
33
+ // Handle error (e.g., set a default list or show an error message)
34
+ } finally {
35
+ setLoading(false);
36
+ }
37
+ };
38
+
39
+ fetchRecentItems();
40
+ }, []);
41
 
42
  const startAutoSwitch = () => {
43
  if (intervalRef.current) clearInterval(intervalRef.current);
44
  intervalRef.current = setInterval(() => {
45
+ setFadeOut(true);
46
  setTimeout(() => {
47
+ setCurrentIndex(prevIndex => (prevIndex + 1) % items.length);
48
+ setFadeOut(false);
49
+ }, 200);
50
  }, 5000);
51
  };
52
 
53
  useEffect(() => {
54
+ if (items.length > 0) {
55
+ startAutoSwitch();
56
+ }
57
 
58
  return () => {
59
+ clearInterval(intervalRef.current);
60
  };
61
+ }, [items]);
62
 
63
  const handleIndicatorClick = (index) => {
64
+ setFadeOut(true);
65
  setTimeout(() => {
66
  setCurrentIndex(index);
67
+ setFadeOut(false);
68
  }, 100);
69
  startAutoSwitch();
70
  };
71
 
72
+ if (loading) {
73
+ return <SkeletonLoader />;
74
+ }
 
 
 
 
75
 
76
+ const { title, description, imageUrl } = items[currentIndex];
77
 
78
  return (
79
  <div className="hero-container">
80
  <div className="hero-section">
81
+ {items.map((item, index) => (
82
  <div
83
  key={index}
84
  className={`hero-image ${index === currentIndex ? 'active' : ''} ${fadeOut ? 'fade-out' : ''}`}
 
87
  ))}
88
  <div className="hero-text">
89
  <h1 className="hero-title">{title}</h1>
90
+ <p className="hero-description">{description}</p>
91
  </div>
92
  </div>
93
 
94
  <div className="hero-indicators">
95
+ {items.map((_, index) => (
96
  <div
97
  key={index}
98
  className={`indicator ${index === currentIndex ? 'active' : ''}`}
frontend/src/components/Sidebar.css CHANGED
@@ -87,11 +87,9 @@
87
  @media only screen and (orientation: portrait) {
88
  .sidebar-footer {
89
  padding: 16px;
90
- margin-bottom: 100px;
91
  }
92
  }
93
 
94
-
95
  .sidebar-item {
96
  display: flex;
97
  align-items: center;
 
87
  @media only screen and (orientation: portrait) {
88
  .sidebar-footer {
89
  padding: 16px;
 
90
  }
91
  }
92
 
 
93
  .sidebar-item {
94
  display: flex;
95
  align-items: center;
frontend/src/components/Sidebar.js CHANGED
@@ -12,7 +12,10 @@ import {
12
  faTv,
13
  faFilm,
14
  faBookBookmark,
 
 
15
  } from "@fortawesome/free-solid-svg-icons";
 
16
 
17
  const Sidebar = () => {
18
  const [isOpen, setIsOpen] = useState(false);
@@ -83,9 +86,7 @@ const Sidebar = () => {
83
  </Link>
84
  <Link
85
  href="/new"
86
- className={`sidebar-link ${
87
- pathname === "/new" ? "active" : ""
88
- }`}
89
  onMouseEnter={handleMouseEnter}
90
  onMouseLeave={handleMouseLeave}
91
  >
@@ -105,7 +106,28 @@ const Sidebar = () => {
105
  )}
106
  {isOpen && (
107
  <div className="sidebar-footer">
108
- <SidebarItem icon={faCogs} text="Settings" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  </div>
110
  )}
111
  </div>
 
12
  faTv,
13
  faFilm,
14
  faBookBookmark,
15
+ faCodeCommit,
16
+ faBook,
17
  } from "@fortawesome/free-solid-svg-icons";
18
+ import config from "@/lib/config";
19
 
20
  const Sidebar = () => {
21
  const [isOpen, setIsOpen] = useState(false);
 
86
  </Link>
87
  <Link
88
  href="/new"
89
+ className={`sidebar-link ${pathname === "/new" ? "active" : ""}`}
 
 
90
  onMouseEnter={handleMouseEnter}
91
  onMouseLeave={handleMouseLeave}
92
  >
 
106
  )}
107
  {isOpen && (
108
  <div className="sidebar-footer">
109
+ <Link
110
+ href="/dashboard"
111
+ className={`sidebar-link ${
112
+ pathname === "/dashboard" ? "active" : ""
113
+ }`}
114
+ onMouseEnter={handleMouseEnter}
115
+ onMouseLeave={handleMouseLeave}
116
+ >
117
+ <SidebarItem icon={faBook} text="Dashboard" />
118
+ </Link>
119
+ <Link
120
+ href="/settings"
121
+ className={`sidebar-link ${
122
+ pathname === "/settings" ? "active" : ""
123
+ }`}
124
+ onMouseEnter={handleMouseEnter}
125
+ onMouseLeave={handleMouseLeave}
126
+ >
127
+ <SidebarItem icon={faCogs} text="Settings" />
128
+ </Link>
129
+
130
+ <SidebarItem icon={faCodeCommit} text={config.version} />
131
  </div>
132
  )}
133
  </div>
frontend/src/lib/LoadBalancer.js CHANGED
@@ -60,7 +60,11 @@ class LoadBalancerAPI {
60
  async getAllTVShows() {
61
  return this._getRequest('/api/get/tv/all');
62
  }
63
-
 
 
 
 
64
  async getDownloadProgress(url) {
65
  return this._getRequestNoBase(url);
66
  }
 
60
  async getAllTVShows() {
61
  return this._getRequest('/api/get/tv/all');
62
  }
63
+
64
+ async getRecent() {
65
+ return this._getRequest('/api/get/recent');
66
+ }
67
+
68
  async getDownloadProgress(url) {
69
  return this._getRequestNoBase(url);
70
  }
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.1 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.2 Alpha",
5
  };
6
 
7
  export default config;
frontend/src/skeletons/HeroSection.css ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* SkeletonLoader.css */
2
+
3
+ .skeleton-container {
4
+ position: relative;
5
+ width: 100%;
6
+ height: 480px; /* Set height to auto for responsiveness */
7
+ display: flex;
8
+ flex-direction: column;
9
+ justify-content: flex-end;
10
+ background-color: #1b1d2b; /* Dark purple background */
11
+ padding: 1rem;
12
+ padding-bottom: 2rem;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .skeleton-image {
17
+ width: 100%;
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, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
22
+ background-size: 200% 100%;
23
+ animation: shimmer 1.5s infinite;
24
+ }
25
+
26
+ .skeleton-text {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 0.4rem;
30
+ position: relative;
31
+ z-index: 1;
32
+ padding-top: 10px;
33
+ }
34
+
35
+ .skeleton-title, .skeleton-description {
36
+ background: linear-gradient(90deg, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
37
+ background-size: 200% 100%;
38
+ border-radius: 4px;
39
+ animation: shimmer 1.5s infinite;
40
+ }
41
+
42
+ .skeleton-title {
43
+ width: 50%;
44
+ height: 2rem;
45
+ }
46
+
47
+ .skeleton-description {
48
+ width: 70%;
49
+ height: 1.2rem;
50
+ }
51
+
52
+ /* Responsive Styles */
53
+ @media (max-width: 1200px) {
54
+ .skeleton-title {
55
+ width: 60%;
56
+ }
57
+ .skeleton-description {
58
+ width: 80%;
59
+ }
60
+ }
61
+
62
+ @media (max-width: 992px) {
63
+ .skeleton-container {
64
+ height: 400px;
65
+ }
66
+ .skeleton-title {
67
+ width: 70%;
68
+ height: 1.8rem;
69
+ }
70
+ .skeleton-description {
71
+ width: 85%;
72
+ height: 1.1rem;
73
+ }
74
+ }
75
+
76
+ @media (max-width: 768px) {
77
+ .skeleton-container {
78
+ height: 300px;
79
+ }
80
+ .skeleton-title {
81
+ width: 80%;
82
+ height: 1.6rem;
83
+ }
84
+ .skeleton-description {
85
+ width: 90%;
86
+ height: 1rem;
87
+ }
88
+ }
89
+
90
+ @media (max-width: 576px) {
91
+ .skeleton-container {
92
+ height: 200px;
93
+ }
94
+ .skeleton-title {
95
+ width: 90%;
96
+ height: 1.4rem;
97
+ }
98
+ .skeleton-description {
99
+ width: 95%;
100
+ height: 0.9rem;
101
+ }
102
+ }
103
+
104
+ @keyframes shimmer {
105
+ 0% {
106
+ background-position: -200% 0;
107
+ }
108
+ 100% {
109
+ background-position: 200% 0;
110
+ }
111
+ }
frontend/src/skeletons/HeroSection.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SkeletonLoader.js
2
+ import './HeroSection.css';
3
+
4
+ const SkeletonLoader = () => {
5
+ return (
6
+ <div className="skeleton-container">
7
+ <div className="skeleton-image"></div>
8
+ <div className="skeleton-text">
9
+ <div className="skeleton-title"></div>
10
+ <div className="skeleton-description"></div>
11
+ </div>
12
+ </div>
13
+ );
14
+ };
15
+
16
+ export default SkeletonLoader;