Squashed commit of the following:

commit b908db03ad9a982e0cf4e9f964ad03ba06a17294
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:02:56 2025 +0200

    Add SearchPage and enhance Navbar with Search component: create a new SearchPage for displaying search results, update Navbar to include a Search component with improved UI and functionality for movie searching.

commit ac1e2e67ec9a68d022123a7f0e3fa4a6e3fbe5ea
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:02:50 2025 +0200

    Enhance MovieCard and Search components: implement a new layout for MovieCard, add interactive buttons for managing movie state, and improve Search functionality with pagination support and a refined input design.

commit ce604baf702a418252c3575a0d575221a5492372
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:02:27 2025 +0200

    Add Button and Pagination components: create reusable Button component with support for links and a Pagination component for navigating between pages, enhancing UI functionality and user experience.

commit b60a43e7e15a92f02ac6fb7eb8d5304885453e5f
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:02:13 2025 +0200

    Add logo SVG and update global styles: introduce a new logo.svg file, enhance color palette in globals.css with additional shades for primary, secondary, and accent colors, and adjust layout components to incorporate Navbar in global data layout.

commit b2bfc87bd391df65277ad78a617800f51bc8f069
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:01:15 2025 +0200

    Add custom hooks: implement useKeyListener for keyboard event handling, useLocalStorage for managing local storage state, and useOutsideClick for detecting clicks outside a specified element.

commit fdf2a72eb1f6d93163476b04c735cc4a05bf6208
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:01:08 2025 +0200

    Refactor TMDB API integration: update fetch function to accept path parameters, enhance search functionality with additional options, and include total results in SearchResult type for improved data handling.

commit 62ab1698b6372940f12ff01f819cbdf662b7ad8a
Author: Norbert Maciaszek <norbert@ambiscale.com>
Date:   Sat Aug 9 20:00:57 2025 +0200

    Update package dependencies: add dotenv, drizzle-orm, and react-icons; update drizzle-kit and tsx versions; enhance package-lock.json with new modules and dependencies for improved functionality and compatibility.
This commit is contained in:
Norbert Maciaszek
2025-08-09 20:05:48 +02:00
parent 8b1bd6e174
commit 5c3423c353
22 changed files with 2179 additions and 53 deletions

View File

@@ -0,0 +1,101 @@
"use client";
import { IoClose, IoSearch } from "react-icons/io5";
import { useState } from "react";
import { SearchResult } from "@/lib/tmdb/types";
import { TMDB } from "@/lib/tmdb";
import { SearchInput } from "@/components/atoms/SearchInput";
import { MovieCard } from "@/components/atoms/MovieCard";
import { useKeyListener } from "@/hooks/useKeyListener";
import { useRef } from "react";
import { useOutsideClick } from "@/hooks/useOutsideClick";
import { Button } from "@/components/atoms/Button";
export const Search = () => {
const ref = useRef<HTMLDivElement>(null);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [response, setResponse] = useState<SearchResult | null>(null);
const [query, setQuery] = useState("");
const { results, total_pages, total_results = 0 } = response ?? {};
const handleSearch = async (query: string) => {
setQuery(query);
if (query.length < 3) {
setResponse(null);
return;
}
const data = await TMDB.search({
query,
});
setResponse(data);
};
const handleClose = () => {
setIsSearchOpen(false);
setResponse(null);
};
useKeyListener("Escape", handleClose);
useOutsideClick(ref, handleClose);
return (
<>
<button
className="text-text hover:text-primary-900 cursor-pointer"
onClick={() => setIsSearchOpen(!isSearchOpen)}
>
<IoSearch size={25} fill="currentColor" />
</button>
{isSearchOpen && (
<div className="fixed inset-0 bg-black/50 z-20 backdrop-blur-sm overflow-y-auto flex items-center">
<button
className="absolute top-4 right-4 text-white cursor-pointer"
onClick={handleClose}
>
<IoClose size={25} fill="currentColor" />
</button>
<div className="container" ref={ref}>
<div className="text-center">
<SearchInput
className="scale-200"
onChange={handleSearch}
placeholder="Search for a movie"
autoFocus={true}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mt-24">
{results && (
<div className="col-span-full">
<p className="text-white">{total_results} movies found</p>
</div>
)}
{results?.slice(0, 5).map((result) => (
<MovieCard
layout="zeus"
key={result.id}
id={result.id}
title={result.title}
releaseDate={result.release_date}
popularity={result.popularity}
overview={result.overview}
imagePath={result.poster_path}
seen={false}
favorite={false}
/>
))}
{total_results > 5 && (
<div className="col-span-full text-center">
<Button href={`/search?s=${query}`} onClick={handleClose}>
Show more
</Button>
</div>
)}
</div>
</div>
</div>
)}
</>
);
};

View File

@@ -1,13 +1,74 @@
import Link from "next/link";
import { Search } from "./components/Search";
const links = [
{
label: "My wall",
href: "/",
},
{
label: "Discover",
href: "/discover",
},
];
export const Navbar = () => {
return (
<header className="py-4">
<header className="bg-white shadow-sm">
<div className="container">
<img
className="mx-auto"
src="/logo.png"
alt="Movie Box"
style={{ maxWidth: 200 }}
/>
<div className="flex items-center gap-8 py-4">
<Link className="block text-teal-600" href="/">
<span className="sr-only">Home</span>
<img src="/logo.svg" alt="Logo" width={40} />
</Link>
<div className="flex flex-1 items-center justify-end md:justify-between">
<nav aria-label="Global" className="hidden md:block">
<ul className="flex items-center gap-6 text-sm">
{links.map((link) => (
<li key={link.href}>
<Link
className="text-text/70 font-semibold hover:text-text"
href={link.href}
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="flex items-center gap-4">
<div className="flex gap-4 sm:gap-6">
<Search />
<a
className="block rounded-md bg-primary px-5 py-2.5 text-sm font-medium text-white transition hover:bg-primary-900"
href="#"
>
Random Movie
</a>
</div>
<button className="block rounded-sm bg-gray-100 p-2.5 text-gray-600 transition hover:text- md:hidden">
<span className="sr-only">Toggle menu</span>
<svg
xmlns="http://www.w3.org/2000/svg"
className="size-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</header>
);