diff options
| author | real-zephex <[email protected]> | 2024-05-07 03:38:39 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-05-07 03:38:39 +0530 |
| commit | ec22f9e3fa3e64a2d79331325c753bbaf05b2ebd (patch) | |
| tree | deb3da965856da224b89ddf0441175256eddb39e | |
| parent | Merge pull request #16 from zephex-alt/master (diff) | |
| parent | last minute fixes (diff) | |
| download | dramalama-ec22f9e3fa3e64a2d79331325c753bbaf05b2ebd.tar.xz dramalama-ec22f9e3fa3e64a2d79331325c753bbaf05b2ebd.zip | |
Merge pull request #17 from zephex-alt/master
Added MOVIES support
| -rw-r--r-- | next.config.mjs | 10 | ||||
| -rw-r--r-- | src/app/components/header/header.jsx | 3 | ||||
| -rw-r--r-- | src/app/movies/[id]/page.jsx | 117 | ||||
| -rw-r--r-- | src/app/movies/components/cacher.js | 17 | ||||
| -rw-r--r-- | src/app/movies/components/popular.jsx | 58 | ||||
| -rw-r--r-- | src/app/movies/components/search.jsx | 38 | ||||
| -rw-r--r-- | src/app/movies/components/search_2.jsx | 57 | ||||
| -rw-r--r-- | src/app/movies/components/trending.jsx | 57 | ||||
| -rw-r--r-- | src/app/movies/components/video_player.jsx | 60 | ||||
| -rw-r--r-- | src/app/movies/page.jsx | 18 | ||||
| -rw-r--r-- | src/app/movies/styles/info.module.css | 120 | ||||
| -rw-r--r-- | src/app/movies/styles/page.module.css | 3 | ||||
| -rw-r--r-- | src/app/movies/styles/pop_trend.module.css | 82 | ||||
| -rw-r--r-- | src/app/movies/styles/search.module.css | 70 | ||||
| -rw-r--r-- | src/app/movies/styles/video_player.module.css | 25 | ||||
| -rw-r--r-- | src/app/page.jsx | 11 | ||||
| -rw-r--r-- | src/app/page.module.css | 3 | ||||
| -rw-r--r-- | utils/movie_urls.js | 8 |
18 files changed, 751 insertions, 6 deletions
diff --git a/next.config.mjs b/next.config.mjs index a4fb287..e38fbe2 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -31,6 +31,10 @@ const nextConfig = { protocol: "https",
hostname: "sup-proxy.zephex0-f6c.workers.dev",
},
+ {
+ protocol: "https",
+ hostname: "image.tmdb.org",
+ },
],
},
logging: {
@@ -40,9 +44,9 @@ const nextConfig = { },
experimental: {
serverActions: {
- allowedOrigins: ["localhost:3000"]
- }
- }
+ allowedOrigins: ["localhost:3000"],
+ },
+ },
};
export default nextConfig;
diff --git a/src/app/components/header/header.jsx b/src/app/components/header/header.jsx index 9be6de2..c1fbc2d 100644 --- a/src/app/components/header/header.jsx +++ b/src/app/components/header/header.jsx @@ -1,7 +1,7 @@ import Link from "next/link";
import styles from "../../page.module.css";
-export default function Header() {
+export default async function Header() {
return (
<main className={styles.main}>
<div className={styles.header}>
@@ -17,6 +17,7 @@ export default function Header() { <Link href="/anime">Anime</Link>
<Link href="/kdrama">Kdrama</Link>
<Link href="/manga">Manga</Link>
+ <Link href="/movies">Movies</Link>
</div>
</div>
</main>
diff --git a/src/app/movies/[id]/page.jsx b/src/app/movies/[id]/page.jsx new file mode 100644 index 0000000..1baec1f --- /dev/null +++ b/src/app/movies/[id]/page.jsx @@ -0,0 +1,117 @@ +import styles from "../styles/info.module.css"; +import { getInfoURL } from "../../../../utils/movie_urls"; +import Image from "next/image"; +import { PiThumbsUpFill } from "react-icons/pi"; +import { FaRegCheckCircle } from "react-icons/fa"; +import { RxDividerVertical } from "react-icons/rx"; +import { FaDollarSign } from "react-icons/fa"; +import { FaSackDollar } from "react-icons/fa6"; +import VIDEO_PLAYER from "../components/video_player"; + +export default async function MOVIE_INFO({ params }) { + const id = params.id; + const data = await get_movie_info(id); + + return ( + <main + style={{ + backgroundImage: `url(https://image.tmdb.org/t/p/original${data.backdrop_path})`, + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + }} + className={styles.Main} + > + <section className={styles.MovieInfoSection}> + <section className={styles.MovieInfo}> + <div className={styles.HeroSection}> + <Image + src={`https://image.tmdb.org/t/p/original${data.poster_path}`} + width={200} + height={300} + alt="Movie Poster" + priority + ></Image> + <div className={styles.HeroTitle}> + <h2>{data.title || "Not found"}</h2> + <p className={styles.tagline}> + <span>{data.tagline || "Not found"}</span> + </p> + <p className={styles.MovieDescription}> + {data.overview || "Not found"} + </p> + </div> + </div> + <div className={styles.OtherInfo}> + <section className={styles.Ratings}> + <span> + <PiThumbsUpFill size={16} /> + <p>{data.vote_average || "Not found"}</p> + </span> + <span className={styles.divider}> + <RxDividerVertical size={22} /> + </span> + <span> + <FaRegCheckCircle size={16} /> + <p>{data.vote_count || "Not found"}</p> + </span> + </section> + <section className={styles.Money}> + <span title="revenue"> + <FaSackDollar /> + <p> + $ + {data.revenue.toLocaleString() || + "Not found"} + </p> + </span> + <span className={styles.divider}> + <RxDividerVertical size={22} /> + </span> + <span title="budget"> + <FaDollarSign /> + <p> + $ + {data.budget.toLocaleString() || + "Not found"} + </p> + </span> + </section> + <section className={styles.Money}> + <span title="Released on"> + <p>Release Date: {data.release_date}</p> + </span> + </section> + <section className={styles.Genre}> + {data.genres.map((item) => ( + <p key={item.id}>{item.name || "Not found"}</p> + ))} + </section> + </div> + <section className={styles.VideoPlayer}> + <VIDEO_PLAYER id={id} /> + <p + style={{ + textAlign: "center", + margin: 0, + fontSize: 14, + }} + > + NOTE: Please wait for some time for the video to + load. If it buffers for a long time, then try + chanding the server. If the issue persists, please + check your internet connection. + </p> + </section> + <br /> + <br /> + </section> + </section> + </main> + ); +} + +const get_movie_info = async (id) => { + const res = await fetch(getInfoURL(id), { next: { revalidate: 21620 } }); + const data = await res.json(); + return data; +}; diff --git a/src/app/movies/components/cacher.js b/src/app/movies/components/cacher.js new file mode 100644 index 0000000..169508a --- /dev/null +++ b/src/app/movies/components/cacher.js @@ -0,0 +1,17 @@ +import { getInfoURL } from "../../../../utils/movie_urls"; + +const PreFetchMovieInfo = async (data) => { + try { + const fetchPromises = data.results.map(async (element) => { + const link = `${getInfoURL(element.id)}`; + await fetch(link, { next: { revalidate: 21600 } }); + }); + + await Promise.all(fetchPromises); + console.log("Movie info pre-fetched successfully!"); + } catch (error) { + console.error("Error occurred while pre-fetching video links:", error); + } +}; + +export default PreFetchMovieInfo; diff --git a/src/app/movies/components/popular.jsx b/src/app/movies/components/popular.jsx new file mode 100644 index 0000000..04d9cf8 --- /dev/null +++ b/src/app/movies/components/popular.jsx @@ -0,0 +1,58 @@ +import { POPULAR } from "../../../../utils/movie_urls"; +import PreFetchMovieInfo from "./cacher"; +import styles from "../styles/pop_trend.module.css"; +import Image from "next/image"; +import Link from "next/link"; + +export default async function POPULAR_MOVIES() { + const data = await get_popular(); + PreFetchMovieInfo(data); + + return ( + <main className={styles.Main}> + <h1>Popular Movies</h1> + <section className={styles.MovieContainer}> + {data && + data.results && + data.results.slice(0, 16).map((item, index) => ( + <Link + href={`/movies/${item.id}`} + style={{ + textDecoration: "none", + color: "white", + }} + key={index} + > + <div + style={{ + borderRadius: "0.5rem", + overflow: "hidden", + backgroundImage: `url(https://image.tmdb.org/t/p/original${item.backdrop_path})`, + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + }} + className={styles.MovieEntryPrev} + > + <div className={styles.MovieEntry}> + <Image + src={`https://image.tmdb.org/t/p/original${item.poster_path}`} + width={200} + height={300} + alt="Movie Poster" + priority + ></Image> + <p>{item.title}</p> + </div> + </div> + </Link> + ))} + </section> + </main> + ); +} + +const get_popular = async () => { + const res = await fetch(POPULAR, { next: { revalidate: 21600 } }); + const data = await res.json(); + return data; +}; diff --git a/src/app/movies/components/search.jsx b/src/app/movies/components/search.jsx new file mode 100644 index 0000000..6514b76 --- /dev/null +++ b/src/app/movies/components/search.jsx @@ -0,0 +1,38 @@ +"use client"; + +import { useState } from "react"; +import styles from "../styles/search.module.css"; +import { FaSearch } from "react-icons/fa"; +import SearchResults from "./search_2"; + +export default function SEARCH_COMPONENT() { + const [title, setTitle] = useState(""); + const [result, setResults] = useState(null); + + const fetch_results = async (title) => { + setResults(await SearchResults(title)); + }; + + return ( + <main className={styles.Main}> + <section className={styles.InputContainer}> + <FaSearch + color="white" + className={styles.SearchIcon} + size={17} + /> + <input + placeholder="Enter movie title here" + onChange={(event) => setTitle(event.target.value)} + onKeyDown={async (e) => { + if ((e.key === "Enter" || e.code === 13) && title) { + await fetch_results(e.target.value); + } + }} + ></input> + </section> + + <section className={styles.SearchResults}>{result}</section> + </main> + ); +} diff --git a/src/app/movies/components/search_2.jsx b/src/app/movies/components/search_2.jsx new file mode 100644 index 0000000..0eb66fb --- /dev/null +++ b/src/app/movies/components/search_2.jsx @@ -0,0 +1,57 @@ +"use server"; + +import { SEARCH } from "../../../../utils/movie_urls"; +import PreFetchMovieInfo from "./cacher"; +import Image from "next/image"; +import Link from "next/link"; +import styles from "../styles/search.module.css"; + +const SearchResults = async (title) => { + const data = await get_search_results(title); + PreFetchMovieInfo(data); + return ( + <div className={styles.MovieSearchResultsContainer}> + {data && + data.results && + data.results.map((item, index) => { + if (item.poster_path) { + return ( + <Link + href={`/movies/${item.id}`} + key={index} + style={{ + backgroundImage: `url(https://image.tmdb.org/t/p/original${item.backdrop_path})`, + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + textDecoration: "none", + color: "white", + borderRadius: "0.5rem", + overflow: "hidden", + }} + className={styles.MovieResultsPrev} + > + <section className={styles.MovieEntry}> + <p>{item.title || item.original_title}</p> + <Image + src={`https://image.tmdb.org/t/p/original${item.poster_path}`} + width={130} + height={230} + alt="Movie Poster" + priority + /> + </section> + </Link> + ); + } + })} + </div> + ); +}; + +const get_search_results = async (title) => { + const res = await fetch(SEARCH + title, { next: { revalidate: 21600 } }); + const data = await res.json(); + return data; +}; + +export default SearchResults; diff --git a/src/app/movies/components/trending.jsx b/src/app/movies/components/trending.jsx new file mode 100644 index 0000000..be2a2e1 --- /dev/null +++ b/src/app/movies/components/trending.jsx @@ -0,0 +1,57 @@ +import { TRENDING } from "../../../../utils/movie_urls"; +import PreFetchMovieInfo from "./cacher"; +import styles from "../styles/pop_trend.module.css"; +import Link from "next/link"; +import Image from "next/image"; + +export default async function TREDNING_MOVIES() { + const data = await get_popular(); + PreFetchMovieInfo(data); + + return ( + <main className={styles.Main}> + <h1>Trending Movies</h1> + <section className={styles.MovieContainer}> + {data && + data.results && + data.results.slice(0, 16).map((item, index) => ( + <Link + href={`/movies/${item.id}`} + style={{ + textDecoration: "none", + color: "white", + }} + key={index} + > + <div + style={{ + borderRadius: "0.5rem", + overflow: "hidden", + backgroundImage: `url(https://image.tmdb.org/t/p/original${item.backdrop_path})`, + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + }} + className={styles.MovieEntryPrev} + > + <div className={styles.MovieEntry}> + <Image + src={`https://image.tmdb.org/t/p/original${item.poster_path}`} + width={200} + height={300} + alt="Movie Poster" + ></Image> + <p>{item.title}</p> + </div> + </div> + </Link> + ))} + </section> + </main> + ); +} + +const get_popular = async () => { + const res = await fetch(TRENDING, { next: { revalidate: 21620 } }); + const data = await res.json(); + return data; +}; diff --git a/src/app/movies/components/video_player.jsx b/src/app/movies/components/video_player.jsx new file mode 100644 index 0000000..15db6a8 --- /dev/null +++ b/src/app/movies/components/video_player.jsx @@ -0,0 +1,60 @@ +"use client"; + +import styles from "../styles/video_player.module.css"; +import { useState, useEffect } from "react"; + +export default function VIDEO_PLAYER({ id: id }) { + const [frame, setFrame] = useState(null); + + useEffect(() => { + make_player(`https://vidsrc.pro/embed/movie/${id}`); + }, []); + + function make_player(url) { + setFrame( + <iframe + src={url} + referrerPolicy="origin" + allowFullScreen + height={500} + className={styles.VideoPlayer} + ></iframe> + ); + } + + return ( + <section className={styles.VideoContainer}> + <div> + <button + onClick={() => + make_player(`https://vidsrc.pro/embed/movie/${id}`) + } + > + Vidsrc.pro + </button> + <button + onClick={() => + make_player(`https://blackvid.space/embed?tmdb=${id}`) + } + > + Blackvid + </button> + <button + onClick={() => + make_player(`https://vidsrc.to/embed/movie/${id}`) + } + > + Vidsrc.to + </button> + <button + onClick={() => + make_player(`https://vidsrc.icu/embed/movie/${id}`) + } + > + Vidsrc.icu + </button> + </div> + {frame} + </section> + ); +} diff --git a/src/app/movies/page.jsx b/src/app/movies/page.jsx new file mode 100644 index 0000000..24c2ed9 --- /dev/null +++ b/src/app/movies/page.jsx @@ -0,0 +1,18 @@ +import POPULAR_MOVIES from "./components/popular"; +import TREDNING_MOVIES from "./components/trending"; +import SEARCH_COMPONENT from "./components/search"; +import styles from "./styles/page.module.css"; + +export default async function MOVIE_HOME() { + return ( + <main className={styles.MovieMain}> + <SEARCH_COMPONENT /> + <POPULAR_MOVIES /> + <br /> + <TREDNING_MOVIES /> + <br /> + <br /> + <br /> + </main> + ); +} diff --git a/src/app/movies/styles/info.module.css b/src/app/movies/styles/info.module.css new file mode 100644 index 0000000..23b3322 --- /dev/null +++ b/src/app/movies/styles/info.module.css @@ -0,0 +1,120 @@ +.Main { + margin-top: 60px; + color: white; +} + +.MovieInfoSection { + background-color: #1f1f1fcc; + backdrop-filter: blur(10px); +} + +.MovieInfo { + max-width: 60%; + margin: 0 auto; +} + +.HeroSection { + display: flex; + align-items: center; +} + +.HeroSection img { + border-radius: 1rem; + padding: 0.5rem; +} + +.HeroTitle { + display: flex; + flex-direction: column; + margin-left: 1rem; + padding: 0.5rem; +} + +.HeroTitle h2 { + font-size: 28px; + margin: 0; +} + +.tagline { + margin: 0.2rem 0 0 0; + font-size: 12px; +} + +.tagline span { + background-color: #12121286; + padding: 0.2rem; + border-radius: 0.5rem; +} + +.MovieDescription { + font-size: 18px; + margin: 0.2rem 0 0 0; +} + +.OtherInfo { + display: flex; + flex-direction: column; + align-items: center; + margin: 1rem 0 0 0; + background-color: #12121286; + padding: 1rem; + border-radius: 0.5rem; +} + +.Ratings { + display: flex; +} + +.divider { + margin: 0 0.5rem 0 0.5rem; +} + +.Ratings span { + display: flex; + align-items: center; + justify-content: center; +} + +.Ratings p { + margin: 0 0 0 0.4rem; +} + +.Money { + margin: 0.5rem 0 0 0; + display: flex; +} + +.Money p { + margin: 0 0 0 0.4rem; +} + +.Money span { + display: flex; + align-items: center; +} + +.Genre { + display: flex; +} + +.Genre p { + background-color: #12121257; + margin: 0.5rem 0.2rem 0 0.2rem; + padding: 0.3rem; + border-radius: 0.5rem; +} + +@media screen and (max-width: 768px) { + .MovieInfo { + max-width: 100%; + } + + .VideoPlayer { + width: 98%; + margin: 1rem auto; + } + + .VideoPlayer iframe { + height: 250px; + } +} diff --git a/src/app/movies/styles/page.module.css b/src/app/movies/styles/page.module.css new file mode 100644 index 0000000..5cba0a1 --- /dev/null +++ b/src/app/movies/styles/page.module.css @@ -0,0 +1,3 @@ +.MovieMain { + margin-top: 65px; +} diff --git a/src/app/movies/styles/pop_trend.module.css b/src/app/movies/styles/pop_trend.module.css new file mode 100644 index 0000000..9b94078 --- /dev/null +++ b/src/app/movies/styles/pop_trend.module.css @@ -0,0 +1,82 @@ +.Main { + color: white; + margin-left: 0.2rem; + margin-right: 0.2rem; +} + +.Main h1 { + margin: 0 0 0.5rem 0; + text-align: center; + color: transparent; + background: linear-gradient( + 90deg, + var(--neon-green) 40%, + var(--light-green) 60%, + var(--neon-yellow) 80%, + var(--soft-purple) 100% + ); + background-size: 60% 50%; + background-clip: text; +} + +.MovieContainer { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); + grid-gap: 0.5rem; + align-items: center; +} + +.MovieContainer::-webkit-scrollbar { + height: 0; +} + +.MovieContainer:hover .MovieEntryPrev { + opacity: 0.5; +} + +.MovieContainer:hover .MovieEntryPrev:hover { + opacity: 1; + scale: 1.02; +} + +.MovieEntryPrev { + transition: opacity 200ms ease, scale 200ms ease; +} + +.MovieEntry { + background-color: #1f1f1fc2; + padding: 0.5rem; + /* border-radius: 0.5rem; */ + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + cursor: pointer; + backdrop-filter: blur(5px); +} + +.MovieEntry img { + border-radius: 0.5rem; +} + +.MovieEntry p { + width: 190px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-align: center; + margin: 0.3rem 0 0 0; +} + +@media screen and (max-width: 768px) { + .Main h1 { + font-size: 24px; + text-align: start; + } + + .MovieContainer { + display: flex; + overflow-x: auto; + overflow-y: hidden; + } +} diff --git a/src/app/movies/styles/search.module.css b/src/app/movies/styles/search.module.css new file mode 100644 index 0000000..55d4591 --- /dev/null +++ b/src/app/movies/styles/search.module.css @@ -0,0 +1,70 @@ +.Main { + margin: 0 0.2rem 0 0.2rem; +} + +.InputContainer { + display: flex; + align-items: center; + background-color: #121212; + padding: 0.2rem; + width: 40vw; + border-radius: 0.5rem; +} + +.SearchIcon { + margin-left: 0.4rem; +} + +.InputContainer input { + background-color: transparent; + outline: none; + border: none; + padding: 0.4rem; + font-family: "Lexend Deca", serif; + margin-left: 0.2rem; + font-size: large; + color: white; + width: 100%; +} + +/* Search Results */ + +.MovieSearchResultsContainer { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + grid-gap: 0.5rem; + align-items: center; + margin: 0.2rem 0.2rem 0 0.2rem; +} + +.MovieSearchResultsContainer:hover .MovieResultsPrev { + opacity: 0.5; +} + +.MovieSearchResultsContainer:hover .MovieResultsPrev:hover { + opacity: 1; + scale: 1.02; +} + +.MovieResultsPrev { + transition: opacity 200ms ease, scale 200ms ease; +} + +.MovieEntry { + display: flex; + align-items: center; + justify-content: space-between; + color: white; + backdrop-filter: blur(10px); + padding: 0.4rem; + background-color: #1212129f; +} + +.MovieEntry img { + border-radius: 0.5rem; +} + +.MovieEntry p { + margin: 0 0.2rem 0 0; + font-size: 18px; +} diff --git a/src/app/movies/styles/video_player.module.css b/src/app/movies/styles/video_player.module.css new file mode 100644 index 0000000..aa65b07 --- /dev/null +++ b/src/app/movies/styles/video_player.module.css @@ -0,0 +1,25 @@ +.VideoContainer { + margin: 0.5rem 0 0 0; +} + +.VideoContainer button { + font-family: "Lexend Deca", serif; + outline: none; + border: none; + background-color: #121212be; + + color: white; + margin-right: 0.2rem; + padding: 0.5rem; + border-radius: 0.5rem; + cursor: pointer; +} + +.VideoPlayer { + width: 100%; + aspect-ratio: "16/9"; + border: none; + outline: none; + border-radius: 0.5rem; + margin: 0.5rem 0 0 0; +} diff --git a/src/app/page.jsx b/src/app/page.jsx index d154ccd..6efc2f3 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -1,7 +1,7 @@ import styles from "./page.module.css"; import Link from "next/link"; -export default function Home() { +export default async function Home() { return ( <main className={styles.newbg}> <div className={styles.content}> @@ -33,6 +33,15 @@ export default function Home() { <p>Your one stop for all your kdrama needs</p> </div> </Link> + <Link + href={"/movies"} + title="Click here to get redirected to the kdrama webpage" + > + <div className={styles.movies}> + <h2>Movies</h2> + <p>Your one stop for all your kdrama needs</p> + </div> + </Link> </div> </div> </main> diff --git a/src/app/page.module.css b/src/app/page.module.css index 80a2d67..131bf0c 100644 --- a/src/app/page.module.css +++ b/src/app/page.module.css @@ -105,7 +105,8 @@ .manga, .anime, -.kdrama { +.kdrama, +.movies { background-color: #121212e0; color: white; border-radius: 0.5rem; diff --git a/utils/movie_urls.js b/utils/movie_urls.js new file mode 100644 index 0000000..20cbfef --- /dev/null +++ b/utils/movie_urls.js @@ -0,0 +1,8 @@ +const API_KEY = "171fe27dbfecc58e2a18fbced644cda9"; + +// MOVIES +export const TRENDING = `https://api.themoviedb.org/3/trending/movie/day?api_key=${API_KEY}`; +export const SEARCH = `https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=`; +export const POPULAR = `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`; +export const getInfoURL = (movieId) => + `https://api.themoviedb.org/3/movie/${movieId}?api_key=${API_KEY}`; |