Skip to content
Merged

Feat #34

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
10 changes: 10 additions & 0 deletions client/src/api/movie.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,14 @@ export const getMovieById = async (id) => {
console.error("Error fetching movie: ", error);
throw error;
}
}

export const searchMovies = async (title) => {
try {
const response = await axiosInstance.get(`/movies/search?q=${title}`);
return response;
} catch (error) {
console.error("Error searching movie: ", error);
throw error;
}
}
Binary file added client/src/assets/popcorn-cup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions client/src/components/ui/MovieCard.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { motion } from "framer-motion";

import popcornCup from "../../assets/popcorn-cup.png"

export default function MovieCard({
id,
title,
Expand All @@ -19,7 +21,7 @@ export default function MovieCard({
{/* Poster Container */}
<div className="relative aspect-2/3 overflow-hidden rounded-4xl bg-stone-200 shadow-md group-hover:shadow-2xl transition-all duration-500 border border-stone-100">
<img
src={`${import.meta.env.VITE_TMDB_POSTER_BASE_URL}${poster}`}
src={poster ? `${import.meta.env.VITE_TMDB_POSTER_BASE_URL}${poster}` : popcornCup}
alt={`${title} Poster`}
className="w-full h-full object-cover transition-transform duration-700"
draggable="false"
Expand All @@ -46,7 +48,7 @@ export default function MovieCard({
{title}
</h2>
<p className="text-stone-400 text-[10px] font-bold uppercase tracking-widest mt-1">
{releaseYear}
{releaseYear?.toString()}
</p>
</div>
</a>
Expand Down
35 changes: 21 additions & 14 deletions client/src/pages/EditProfile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,31 @@ export default function EditProfile() {
e.preventDefault();
setLoading(true);
try {
const formData = new FormData();
formData.append("file", selectedPfpFile);
formData.append("upload_preset", import.meta.env.VITE_CLOUDINARY_PRESET);
formData.append("folder", import.meta.env.VITE_CLOUDINARY_ASSET_FOLDER);
setLoading(true);
let imageUrl = user.user.pfp;

const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD;
if (selectedPfpFile) {
const formData = new FormData();
formData.append("file", selectedPfpFile);
formData.append("upload_preset", import.meta.env.VITE_CLOUDINARY_PRESET);
formData.append("folder", import.meta.env.VITE_CLOUDINARY_ASSET_FOLDER);

const cloudinaryRes = await axios.post(
`https://api.cloudinary.com/v1_1/${cloudName}/image/upload`,
formData
);
const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD;

const cloudinaryRes = await axios.post(
`https://api.cloudinary.com/v1_1/${cloudName}/image/upload`,
formData
);

imageUrl = cloudinaryRes.data.secure_url;
}

const res = await editProfileData(imageUrl, about);

const res = await editProfileData(cloudinaryRes.data.secure_url, about);
if (res.success) {
setUser({ ...user, user: res.user });
setLoading(false);
}
setLoading(false);
} catch (err) {
console.error(err);
setLoading(false);
Expand All @@ -44,7 +52,7 @@ export default function EditProfile() {
return (
<main className="min-h-screen bg-stone-50 py-12 px-6 pt-36">
<div className="max-w-3xl mx-auto">
{/* Header */}

<header className="mb-12">
<div className="flex items-center gap-2 mb-2">
<div className="h-1 w-6 bg-amber-500 rounded-full" />
Expand Down Expand Up @@ -100,7 +108,7 @@ export default function EditProfile() {
</div>
</section>

{/* Bio / About Section */}
{/* Bio Section */}
<section className="bg-white p-8 rounded-[2.5rem] border border-stone-200 shadow-sm">
<h3 className="text-xs font-black uppercase tracking-widest text-stone-500 mb-6">Director's Statement</h3>
<div className="relative">
Expand All @@ -117,7 +125,6 @@ export default function EditProfile() {
</div>
</section>

{/* Actions */}
<div className="flex items-center justify-end gap-4 pt-4">
<button
type="button"
Expand Down
77 changes: 48 additions & 29 deletions client/src/pages/SearchResultPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { MagnifyingGlassIcon, UserGroupIcon, FilmIcon } from "@heroicons/react/2
import MovieCard from "../components/ui/MovieCard";
import { searchUsers } from "../api/user.api";
import Loader from "../components/ui/Loader";
// import { searchMovies } from "../api/movie.api";
import { searchMovies } from "../api/movie.api";

export default function SearchResultPage() {
const [movies, setMovies] = useState([]);
Expand All @@ -19,33 +19,53 @@ export default function SearchResultPage() {

useEffect(() => {
const fetchResults = async () => {
if (!searchQuery) return;
setLoading(true);
try {
const [usersRes] = await Promise.all([
searchUsers(searchQuery),
// searchMovies(searchQuery),
]);

setUsers(usersRes.users || []);
setMovies([]);
} catch (error) {
console.error("Search error:", error);
} finally {
setLoading(false);
}
};
if (!searchQuery) {
setLoading(false);
return;
}

setLoading(true);

try {
const results = await Promise.allSettled([
searchUsers(searchQuery),
searchMovies(searchQuery),
]);

const usersRes = results[0];
const movieRes = results[1];

if (usersRes.status === "fulfilled") {
setUsers(usersRes.value.users || []);
} else {
console.error("Users failed:", usersRes.reason);
setUsers([]);
}

if (movieRes.status === "fulfilled") {
setMovies(movieRes.value?.data.movies || []);
} else {
console.error("Movies failed:", movieRes.reason);
setMovies([]);
}

} catch (error) {
console.error("Search error:", error);
} finally {
setLoading(false);
}
};
fetchResults();
}, [searchQuery]);

if (loading) return <Loader
// text={`Searching the archives for "${searchQuery}"`}
/>;
/>;

return (
<div className="min-h-screen bg-stone-50 pt-28 pb-20">
<div className="max-w-7xl mx-auto px-6 md:px-12">

{/* 1. SEARCH HEADER */}
<header className="mb-12">
<div className="flex items-center gap-3 mb-4">
Expand All @@ -62,11 +82,10 @@ export default function SearchResultPage() {
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-6 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${
activeTab === tab
? "bg-stone-900 text-white shadow-lg"
: "bg-white border border-stone-200 text-stone-400 hover:text-stone-900"
}`}
className={`px-6 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${activeTab === tab
? "bg-stone-900 text-white shadow-lg"
: "bg-white border border-stone-200 text-stone-400 hover:text-stone-900"
}`}
>
{tab}
</button>
Expand All @@ -82,7 +101,7 @@ export default function SearchResultPage() {
{movies.length > 0 ? (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-8">
{movies.map((m) => (
<MovieCard key={m.id} title={m.title} poster={m.posterPath} rating={m.voteAverage} releaseDate={m.releaseDate} />
<MovieCard key={m.id} title={m.title} poster={m.poster_path} rating={m.voteAverage} releaseDate={m.release_date} />
))}
</div>
) : (
Expand Down Expand Up @@ -130,13 +149,13 @@ function SectionHeader({ title, count, icon }) {
function UserResultCard({ user }) {
return (
<Link to={`/user/${user._id}`}>
<motion.div
<motion.div
whileHover={{ y: -4 }}
className="flex items-center gap-4 p-4 bg-white rounded-2xl border border-stone-100 shadow-sm hover:shadow-md transition-all group"
>
<img
src={user.pfp}
className="w-14 h-14 rounded-xl object-cover grayscale group-hover:grayscale-0 transition-all duration-500"
<img
src={user.pfp}
className="w-14 h-14 rounded-xl object-cover grayscale group-hover:grayscale-0 transition-all duration-500"
alt={user.firstName}
/>
<div className="flex-1">
Expand Down
44 changes: 0 additions & 44 deletions server/controllers/history.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,50 +219,6 @@ export const removeWatchedMovie = async (req, res) => {
}
};

























// /**
// * Remove a watched movie
// */
// export const removeWatchedMovie = async (req, res) => {
// try {
// const { movieId } = req.params;
// const userId = req.user.userId;

// const movie = await History.findOneAndDelete({ movieId, userId });
// if (!movie) {
// return res.status(404).json({ success: false, message: 'Movie not found in watched list' });
// }

// res.status(200).json({ success: true, message: `${movie.title} removed from watched list`, data: movie });
// } catch (error) {
// console.error(error);
// res.status(500).json({ success: false, error: 'Server error' });
// }
// };

/**
* Get another user's watched movies (public)
*/
Expand Down
27 changes: 26 additions & 1 deletion server/controllers/movie.controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios from "axios";
import Movie from "../models/movie.model.js";
import { TMDB_BASE_URL, TMDB_KEY } from "../config/env.js";

export const getMovieById = async (req, res) => {
const movieId = req.params.id;
Expand All @@ -16,4 +18,27 @@ export const getMovieById = async (req, res) => {
console.error("Error fetching movie: ", error);
res.status(500).json({ message: "Failed to fetch movie" });
}
};
};

export const searchMovie = async (req, res) => {
try {
const query = req.query.q;

if (!query || query.trim() === "") {
return res.status(400).json({ success: false, error: "Query parameter 'q' is required" });
}

const movies = await axios.get(`${TMDB_BASE_URL}/search/movie`, {
params: {
api_key: TMDB_KEY,
query: query
}
});

res.status(200).json({ success: true, movies: movies.data.results });

} catch (error) {
console.error("Error searching movie: ", error);
res.status(500).json({ message: "Failed to search movie" });
}
}
7 changes: 3 additions & 4 deletions server/database/mongodb.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import mongoose from "mongoose";

import * as mongoose from "mongoose";
import { DB_URI, NODE_ENV } from "../config/env.js";

if (!DB_URI) {
Expand All @@ -9,9 +8,9 @@ if (!DB_URI) {
const connectToDatabase = async () => {
try {
await mongoose.connect(DB_URI);
console.log(`Connected to MongoDB in ${NODE_ENV} mode`);
console.log("Database connected successfully in " + NODE_ENV + " environment");
} catch (error) {
console.error("Error connecting to MongoDB: ", Error);
console.log("DB connection error:", error);
process.exit(1);
}
}
Expand Down
4 changes: 3 additions & 1 deletion server/routes/movie.route.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Router } from "express";
import authorize from "../middlewares/auth.middleware.js";
import { getMovieById } from "../controllers/movie.controller.js";
import { getMovieById, searchMovie } from "../controllers/movie.controller.js";

const movieRouter = Router();

movieRouter.get('/search', authorize, searchMovie);

movieRouter.get('/:id', authorize, getMovieById);

export default movieRouter;
Loading