From 0f99bb392991850793a01f11f5f192cc03ea4036 Mon Sep 17 00:00:00 2001 From: Ronja Fagerdahl Date: Mon, 2 Jun 2025 18:25:40 +0200 Subject: [PATCH 1/5] (Feat) Backend search Logic for SearchField.jsx --- src/app/api/movies/route.js | 23 +++++++++++++++++----- src/components/SearchField.jsx | 35 +++++++++++++++++++++++++++++++++- src/lib/db/movieDbService.js | 13 +++++++++++++ src/styles/MovieCard.scss | 33 ++++++++++++++++++++++++++++++++ src/styles/moviePage.scss | 16 ++++++++++++++++ 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/app/api/movies/route.js b/src/app/api/movies/route.js index 2796fab..13ebe50 100644 --- a/src/app/api/movies/route.js +++ b/src/app/api/movies/route.js @@ -1,13 +1,26 @@ import { NextResponse } from 'next/server' import connectDB from '@/lib/db/connectDB' -import { getAllMovies, createMovieFromOmdbTitle, findMovieByTitle } from '@/lib/db/movieDbService' +import { getAllMovies, createMovieFromOmdbTitle, findMovieByTitle, findMoviesByTitle } from '@/lib/db/movieDbService' -export async function GET() { +export async function GET(req) { await connectDB() - const movies = await getAllMovies() - return NextResponse.json(movies) -} + //Backend search logic + const { searchParams } = new URL(req.url) + const query = searchParams.get('q') + + try { + if (query) { + const result = await findMoviesByTitle(query) + return NextResponse.json(result) + } + + const movies = await getAllMovies() + return NextResponse.json(movies) + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }) + } +} export async function POST(req) { if (process.env.NODE_ENV !== 'development') { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) diff --git a/src/components/SearchField.jsx b/src/components/SearchField.jsx index 5819776..acdc786 100644 --- a/src/components/SearchField.jsx +++ b/src/components/SearchField.jsx @@ -1,11 +1,44 @@ 'use client' +import { useState } from 'react' + +//Updating the SearchField to handle a backend search logic export default function SearchField() { + const [query, setQuery] = useState('') + const [results, setResults] = useState([]) + + const handleSearch = async () => { + if (!query) return + try { + const res = await fetch(`/api/movies?q=${encodeURIComponent(query)}`) + if (!res.ok) throw new Error('Något gick fel vid sökning') + const data = await res.json() + setResults(data) + } catch (error) { + console.error('Sökfel:', error) + } + } + return ( <>

Sök efter film:

- + setQuery(e.target.value)}> + +
+ {results.map((movie) => ( + +
+ {movie.posterUrl && ( + {`${movie.title} + )} +

{movie.title}

+
+
+ ))} +
) diff --git a/src/lib/db/movieDbService.js b/src/lib/db/movieDbService.js index 87e818d..3e4d6fd 100644 --- a/src/lib/db/movieDbService.js +++ b/src/lib/db/movieDbService.js @@ -47,3 +47,16 @@ export async function createMovieFromOmdbTitle(title) { return await movie.save() } + +//MongoDB searches all titles that contains the query, no matter if upper or lower case +function escapeRegex(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +export async function findMoviesByTitle(query) { + await connectDB() + const safeQuery = escapeRegex(query) + return await Movie.find({ + title: { $regex: safeQuery, $options: 'i' }, + }).lean() //Lean improves performance, returns JS objects instead of mongoose documents. +} diff --git a/src/styles/MovieCard.scss b/src/styles/MovieCard.scss index 4c55491..ff658fe 100644 --- a/src/styles/MovieCard.scss +++ b/src/styles/MovieCard.scss @@ -98,3 +98,36 @@ text-align: center; } } + +.moviecard__list-search { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1rem; + margin-top: 1rem; +} + +.moviecard__container-search { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + background: #f2f2f2; + padding: 1rem; + border-radius: 8px; + transition: transform 0.2s ease; +} + +.moviecard__container-search:hover { + transform: scale(1.05); +} + +.moviecard__poster-search { + width: 100%; + height: auto; + border-radius: 4px; +} + +.moviecard__title-search { + margin-top: 0.5rem; + font-weight: bold; +} diff --git a/src/styles/moviePage.scss b/src/styles/moviePage.scss index e1c81b8..6b1194e 100644 --- a/src/styles/moviePage.scss +++ b/src/styles/moviePage.scss @@ -36,6 +36,22 @@ } } +.search-button { + background-color: #03658c; + color: white; + border: none; + padding: 0.5rem 1rem; + margin-left: 0.2rem; + border-radius: 6px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.search-button:hover { + background-color: #02496a; +} + .moviecard__list { list-style: none; From 0aeed72e6bf89928e47e45f8083ed88b1f2b507f Mon Sep 17 00:00:00 2001 From: Ronja Fagerdahl Date: Mon, 2 Jun 2025 18:49:26 +0200 Subject: [PATCH 2/5] (Feat) UX improvements for searchfield --- src/components/SearchField.jsx | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/SearchField.jsx b/src/components/SearchField.jsx index acdc786..b500bd4 100644 --- a/src/components/SearchField.jsx +++ b/src/components/SearchField.jsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' //Updating the SearchField to handle a backend search logic export default function SearchField() { @@ -8,7 +8,7 @@ export default function SearchField() { const [results, setResults] = useState([]) const handleSearch = async () => { - if (!query) return + if (query.trim() === '') return try { const res = await fetch(`/api/movies?q=${encodeURIComponent(query)}`) if (!res.ok) throw new Error('Något gick fel vid sökning') @@ -16,18 +16,46 @@ export default function SearchField() { setResults(data) } catch (error) { console.error('Sökfel:', error) + setResults([]) } } + //Debounce for UX improvements + useEffect(() => { + const timeoutId = setTimeout(() => { + handleSearch() + }, 500) + + return () => clearTimeout(timeoutId) + }, [query]) + return ( <>

Sök efter film:

- setQuery(e.target.value)}> + setQuery(e.target.value)} + aria-label="Sök efter film" + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleSearch() + } + }} + > +
+ {results.length === 0 && query !== '' && ( +

+ Inga resultat hittades för "{query}" +

+ )} + {results.map((movie) => (
From d2f9f152753e8b7cbeac95f589c5fd346a266bff Mon Sep 17 00:00:00 2001 From: Ronja Fagerdahl Date: Mon, 2 Jun 2025 19:12:03 +0200 Subject: [PATCH 3/5] (Test failed) I added our exports to avoid "does not provide an export" in our CI --- src/lib/db/movieDbService.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/db/movieDbService.js b/src/lib/db/movieDbService.js index 3e4d6fd..ee2f6fb 100644 --- a/src/lib/db/movieDbService.js +++ b/src/lib/db/movieDbService.js @@ -60,3 +60,14 @@ export async function findMoviesByTitle(query) { title: { $regex: safeQuery, $options: 'i' }, }).lean() //Lean improves performance, returns JS objects instead of mongoose documents. } + +//Export of our functions +export { + getAllMovies, + getTopRatedMovies, + findMovieByTitle, + deleteMovieAndScreenings, + findMovieById, + createMovieFromOmdbTitle, + findMoviesByTitle, +} From 950df167efd95d9ce665e81682fb47742234c5b0 Mon Sep 17 00:00:00 2001 From: Ronja Fagerdahl Date: Tue, 3 Jun 2025 19:01:35 +0200 Subject: [PATCH 4/5] (fix) Suggested changes in PR review --- src/lib/db/movieDbService.js | 11 ----------- src/tests/api/movies/movies.test.js | 4 ++-- src/tests/api/rooms/rooms.test.js | 1 - src/tests/api/screenings/screenings.test.js | 1 - 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/lib/db/movieDbService.js b/src/lib/db/movieDbService.js index ee2f6fb..3e4d6fd 100644 --- a/src/lib/db/movieDbService.js +++ b/src/lib/db/movieDbService.js @@ -60,14 +60,3 @@ export async function findMoviesByTitle(query) { title: { $regex: safeQuery, $options: 'i' }, }).lean() //Lean improves performance, returns JS objects instead of mongoose documents. } - -//Export of our functions -export { - getAllMovies, - getTopRatedMovies, - findMovieByTitle, - deleteMovieAndScreenings, - findMovieById, - createMovieFromOmdbTitle, - findMoviesByTitle, -} diff --git a/src/tests/api/movies/movies.test.js b/src/tests/api/movies/movies.test.js index bbddf0f..630f40b 100644 --- a/src/tests/api/movies/movies.test.js +++ b/src/tests/api/movies/movies.test.js @@ -1,7 +1,6 @@ // src/tests/admin/admin.test.js import { FormData } from 'formdata-node' import { expect, jest, test, describe, beforeEach, beforeAll } from '@jest/globals' -import { findMovieById } from '@/lib/db/movieDbService' // Mock database and services jest.unstable_mockModule('@/lib/db/connectDB', () => ({ @@ -93,7 +92,8 @@ describe('GET /api/movies (mocked)', () => { const mockMovies = [{ _id: '1', title: 'Interstellar' }] movieService.getAllMovies.mockResolvedValue(mockMovies) - const res = await GET() + const req = { url: 'http://localhost/api/movies' } + const res = await GET(req) const data = await res.json() expect(res.status).toBe(200) diff --git a/src/tests/api/rooms/rooms.test.js b/src/tests/api/rooms/rooms.test.js index 39ef390..f22ca43 100644 --- a/src/tests/api/rooms/rooms.test.js +++ b/src/tests/api/rooms/rooms.test.js @@ -1,4 +1,3 @@ -import { getRoomById } from '@/lib/db/roomDbService' import { expect, jest, test, describe, beforeAll, beforeEach } from '@jest/globals' jest.unstable_mockModule('@/lib/db/connectDB', () => ({ diff --git a/src/tests/api/screenings/screenings.test.js b/src/tests/api/screenings/screenings.test.js index 9b6e3bd..83983e1 100644 --- a/src/tests/api/screenings/screenings.test.js +++ b/src/tests/api/screenings/screenings.test.js @@ -1,7 +1,6 @@ // src/tests/screenings/screenings.test.js import { FormData } from 'formdata-node' import { expect, jest, test, describe, beforeEach, beforeAll } from '@jest/globals' -import { getScreeningById } from '@/lib/db/screeningDbService' // Mock database and services jest.unstable_mockModule('@/lib/db/connectDB', () => ({ From 80bfb4704ec85d9aa2b25760473961aebe9b5083 Mon Sep 17 00:00:00 2001 From: Ronja Fagerdahl Date: Tue, 3 Jun 2025 19:12:03 +0200 Subject: [PATCH 5/5] (Fix) Added missing findMoviesByTitle mock to tests --- src/tests/api/movies/movies.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/api/movies/movies.test.js b/src/tests/api/movies/movies.test.js index 630f40b..4d20559 100644 --- a/src/tests/api/movies/movies.test.js +++ b/src/tests/api/movies/movies.test.js @@ -1,6 +1,7 @@ // src/tests/admin/admin.test.js import { FormData } from 'formdata-node' import { expect, jest, test, describe, beforeEach, beforeAll } from '@jest/globals' +import { findMoviesByTitle } from '@/lib/db/movieDbService' // Mock database and services jest.unstable_mockModule('@/lib/db/connectDB', () => ({ @@ -15,6 +16,7 @@ jest.unstable_mockModule('@/lib/db/movieDbService', () => ({ getAllMovies: jest.fn(), deleteMovieAndScreenings: jest.fn(), findMovieById: jest.fn(), + findMoviesByTitle: jest.fn(), })) let POST, GET, DELETE