diff --git a/src/app/api/movies/route.js b/src/app/api/movies/route.js index 8da3a51..e96f428 100644 --- a/src/app/api/movies/route.js +++ b/src/app/api/movies/route.js @@ -1,14 +1,28 @@ 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' import { requireAdminAccess } from '@/lib/auth/requireAdminAccess' -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 (requireAdminAccess()) { return NextResponse.json({ error: 'Endast tillgängligt för administratörer' }, { status: 403 }) diff --git a/src/components/SearchField.jsx b/src/components/SearchField.jsx index 5819776..b500bd4 100644 --- a/src/components/SearchField.jsx +++ b/src/components/SearchField.jsx @@ -1,11 +1,72 @@ 'use client' +import { useState, useEffect } 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.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') + const data = await res.json() + 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)} + 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) => ( + +
+ {movie.posterUrl && ( + {`${movie.title} + )} +

{movie.title}

+
+
+ ))} +
) diff --git a/src/lib/db/movieDbService.js b/src/lib/db/movieDbService.js index e000bad..482de4e 100644 --- a/src/lib/db/movieDbService.js +++ b/src/lib/db/movieDbService.js @@ -56,3 +56,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; diff --git a/src/tests/api/movies/movies.test.js b/src/tests/api/movies/movies.test.js index 9eac272..2d3b2ea 100644 --- a/src/tests/api/movies/movies.test.js +++ b/src/tests/api/movies/movies.test.js @@ -1,7 +1,7 @@ // 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' +import { findMoviesByTitle } from '@/lib/db/movieDbService' // Mock database and services jest.unstable_mockModule('@/lib/db/connectDB', () => ({ @@ -16,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 @@ -93,7 +94,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 2d6fe6d..84451d7 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 80cf33a..bbd644e 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', () => ({