Skip to content
Merged

Feat #27

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useAuthLoader from "./hooks/useAuthLoader";
import MoviePage from "./pages/MoviePage";
import Navbar from "./components/Navbar";
import TmdbMovie from "./pages/MoviesPage";
import SearchResultPage from "./pages/SearchResultPage";

function isTokenExpired(token) {
if (!token) return true;
Expand Down Expand Up @@ -37,7 +38,7 @@ export default function App() {

return (
<BrowserRouter>
<Navbar />
<Navbar />

<Routes>

Expand Down Expand Up @@ -98,6 +99,15 @@ export default function App() {
}
/>

<Route
path="/search"
element={
token && !isExpired
? <SearchResultPage />
: <Navigate to="/login" />
}
/>

</Routes>
</BrowserRouter>
);
Expand Down
5 changes: 5 additions & 0 deletions client/src/api/tmdb.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ import axiosInstance from "./axiosInstance";
export const getTrendingMovies = async () => {
const res = await axiosInstance.get('/movies/trending');
return res;
}

export const getTopRatedMovies = async () => {
const res = await axiosInstance.get('/movies/top-rated');
return res;
}
5 changes: 5 additions & 0 deletions client/src/api/user.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ export const setProfileCover = async (backdrop) => {
cover: backdrop
});

return res.data;
}

export const searchUsers = async (query) => {
const res = await axiosInstance.get(`/user/search?q=${query}`);
return res.data;
}
18 changes: 12 additions & 6 deletions client/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link, useLocation } from "react-router-dom";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useState } from "react";
import useUserStore from "../store/userStore";
import Modal from "./ui/Modal";
Expand All @@ -21,12 +21,18 @@ export default function Navbar() {
return (userData.firstName[0] + (userData.lastName?.[0] || "")).toUpperCase();
};

const navigate = useNavigate();

const handleSearch = (query) => {
navigate(`/search?q=${query}`);
};

const isActive = (path) => location.pathname === path;

return (
<nav className="fixed top-0 left-0 right-0 z-50 flex justify-center p-4 pointer-events-none">
{/* GLASS CONTAINER */}
<div className="w-full max-w-6xl bg-white/80 backdrop-blur-xl border border-white/40 shadow-[0_8px_32px_0_rgba(0,0,0,0.1)] rounded-3xl px-4 md:px-8 py-2 flex justify-between items-center pointer-events-auto transition-all duration-500">
<div className="w-full max-w-6xl bg-white/40 backdrop-blur-xl border border-white/80 shadow-[0_8px_32px_0_rgba(0,0,0,0.1)] rounded-3xl px-4 md:px-8 py-2 flex justify-between items-center pointer-events-auto transition-all duration-500">

{/* LOGO */}
<Link to="/" className="flex items-center gap-2.5 group shrink-0">
Expand All @@ -46,7 +52,7 @@ export default function Navbar() {
</div>

<div className="flex-1">
<SearchBar />
<SearchBar onSearch={handleSearch} />
</div>
</div>

Expand All @@ -71,7 +77,7 @@ export default function Navbar() {
</div>
)}
</div>
<span className="hidden xl:block text-xs font-black uppercase tracking-widest text-stone-600">
<span className="hidden xl:block text-xs font-black uppercase tracking-widest text-stone-800">
{user?.user?.firstName || user?.firstName}
</span>
</Link>
Expand Down Expand Up @@ -138,8 +144,8 @@ function NavLink({ to, active, icon, label }) {
<Link
to={to}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-black uppercase tracking-widest transition-all ${active
? "bg-white text-amber-600 shadow-sm"
: "text-stone-400 hover:text-stone-900"
? "bg-white text-amber-600 shadow-sm"
: "text-stone-400 hover:text-stone-900"
}`}
>
{icon}
Expand Down
17 changes: 15 additions & 2 deletions client/src/components/ui/SearchBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { useState, useEffect, useRef } from "react";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { AnimatePresence, motion } from "framer-motion";

export default function SearchBar({ placeholder = "Search..." }) {
export default function SearchBar({
placeholder = "Search...",
onSearch
}) {
const [query, setQuery] = useState("");
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef(null);
Expand Down Expand Up @@ -46,6 +49,13 @@ export default function SearchBar({ placeholder = "Search..." }) {
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
const trimmed = query.trim();
if (!trimmed) return;
onSearch?.(trimmed);
}
}}
className="w-full bg-transparent border-none px-4 py-4 text-stone-800 placeholder-stone-400 focus:ring-0 text-sm md:text-base font-medium outline-none"
/>

Expand Down Expand Up @@ -73,7 +83,10 @@ export default function SearchBar({ placeholder = "Search..." }) {
<motion.button
initial={{ opacity: 0, x: 10 }}
animate={{ opacity: 1, x: 0 }}
onClick={() => setQuery("")}
onClick={() => {
setQuery("");
onSearch?.("");
}}
className="pr-4 text-xs font-bold text-amber-600 hover:text-amber-700 transition-colors"
>
Clear
Expand Down
57 changes: 44 additions & 13 deletions client/src/pages/MoviesPage.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import { useEffect, useState, useRef } from "react";
import { motion } from "framer-motion";
import { getTrendingMovies } from "../api/tmdb.api";
import { getTopRatedMovies, getTrendingMovies } from "../api/tmdb.api";
import MovieCard from "../components/ui/MovieCard";
import Loader from "../components/ui/Loader";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";

export default function MoviesPage() {
const [loading, setLoading] = useState(true);
const [trendingMovies, setTrendingMovies] = useState([]);

const [trendingMovies, setTrendingMovies] = useState(() => {
const cached = sessionStorage.getItem("trendingMovies");
return cached ? JSON.parse(cached) : [];
});

const [topRatedMovies, setTopRatedMovies] = useState(() => {
const cached = sessionStorage.getItem("topRatedMovies");
return cached ? JSON.parse(cached) : [];
});

const [featuredMovie, setFeaturedMovie] = useState(null);

useEffect(() => {
const fetchTrending = async () => {
const fetchData = async () => {
try {
const res = await getTrendingMovies();
const movies = res.data || [];
setTrendingMovies(movies);
if (trendingMovies.length === 0) {
const res = await getTrendingMovies();
const movies = res.data || [];
setTrendingMovies(movies);
sessionStorage.setItem("trendingMovies", JSON.stringify(movies));
}

if (topRatedMovies.length === 0) {
const res = await getTopRatedMovies();
const movies = res.data || [];
setTopRatedMovies(movies);
sessionStorage.setItem("topRatedMovies", JSON.stringify(movies));
}

const movies = trendingMovies;

if (movies.length > 0) {
setFeaturedMovie(null);
const randomIndex = Math.floor(Math.random() * movies.length);
setFeaturedMovie(movies[randomIndex]);
}
Expand All @@ -28,10 +51,10 @@ export default function MoviesPage() {
}
};

fetchTrending();
fetchData();
}, []);

if (loading) return <Loader />;
if (loading || !featuredMovie) return <Loader />;

return (
<div className="min-h-screen bg-stone-50 font-sans text-stone-900 selection:bg-amber-200">
Expand All @@ -58,9 +81,11 @@ export default function MoviesPage() {
<h3 className="text-[10px] font-black uppercase tracking-[0.4em] text-amber-500 mb-4">
Featured Spotlight
</h3>
<h1 className="text-4xl md:text-7xl font-black text-stone-900 tracking-tighter mb-4 leading-none">
{featuredMovie?.title || featuredMovie?.name}
</h1>
<a href={`/movies/${featuredMovie?._id}`}>
<h1 className="text-4xl md:text-7xl font-black text-stone-900 tracking-tighter mb-4 leading-none">
{featuredMovie?.title}
</h1>
</a>
<p className="text-stone-600 text-sm md:text-lg font-serif italic line-clamp-3 max-w-xl">
{featuredMovie?.overview}
</p>
Expand All @@ -75,6 +100,12 @@ export default function MoviesPage() {
subtitle="Global Cinema"
movies={trendingMovies}
/>

<MovieSlider
title="Top Rated"
subtitle="Critically Acclaimed"
movies={topRatedMovies}
/>
</main>
</div>
);
Expand All @@ -96,7 +127,7 @@ function MovieSlider({ title, subtitle, movies }) {

return (
<section className="relative">
<div className="flex items-end justify-between mb-8 px-2">
<div className="flex items-end justify-between mb-8 px-2 mt-8">
<div>
<div className="flex items-center gap-3 mb-2">
<div className="h-0.5 w-6 bg-amber-500/40" />
Expand Down Expand Up @@ -132,7 +163,7 @@ function MovieSlider({ title, subtitle, movies }) {
>
{movies.map((movie) => (
<div
key={movie.id}
key={movie._id}
className="min-w-40 sm:min-w-50 md:min-w-55 snap-start"
>
<MovieCard
Expand Down
Loading
Loading