add tmdb search

This commit is contained in:
qpismont 2024-07-01 23:33:36 +02:00
parent 65cccb9620
commit afd9170d6d
9 changed files with 224 additions and 1 deletions

View file

@ -0,0 +1,53 @@
import type React from "react";
import { useEffect, useRef, useState } from "react";
interface DebounceProps {
debouceDelay: number;
onDebounce: (value: string) => void;
}
type DebouceInputProps = DebounceProps &
React.InputHTMLAttributes<HTMLInputElement>;
export default function DebounceInput(props: DebouceInputProps) {
const [value, setValue] = useState<string>("");
const debounceTimeout = useRef<number | null>(null);
useEffect(() => {
if (typeof props.value === "string") {
setValue(props.value);
} else {
setValue("");
}
}, [props.value]);
function handleOnChange(e: React.ChangeEvent<HTMLInputElement>) {
if (debounceTimeout.current) {
clearTimeout(debounceTimeout.current);
}
setValue(e.target.value);
debounceTimeout.current = setTimeout(
() => props.onDebounce(e.target.value),
props.debouceDelay,
);
}
const inputAttr = {
...props,
debouceDelay: undefined,
onDebounce: undefined,
};
return (
<input
className={props.className}
placeholder={props.placeholder}
type="text"
value={value}
onChange={handleOnChange}
/>
);
}

View file

@ -0,0 +1,27 @@
import { useState } from "react";
import type { Movie } from "../types";
import TmdbSearchInput from "./TmdbSearchInput";
import GroupMoviesList from "./lists/movies/GroupMoviesList";
export default function TmdbSearch() {
const [movies, setMovies] = useState<Movie[]>([]);
function handleOnSearch(movies: Movie[]) {
setMovies(movies);
}
return (
<>
<div className="row">
<div className="col">
<TmdbSearchInput onSearch={handleOnSearch} />
</div>
</div>
<div style={{ marginTop: "8px" }} className="row">
<div className="col">
<GroupMoviesList movies={movies} />
</div>
</div>
</>
);
}

View file

@ -0,0 +1,34 @@
import type { Movie } from "../types";
import DebounceInput from "./DebouceInput";
interface TmdbSearchInputProps {
onSearch: (movies: Movie[]) => void;
}
export default function TmdbSearchInput({ onSearch }: TmdbSearchInputProps) {
function handleOnSearchInputDebounce(value: string) {
fetch(`/api/tmdb/movies/search?query=${value}`, {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then((json) => {
onSearch(json.movies);
})
.catch((err) => console.log(err));
onSearch([]);
}
return (
<DebounceInput
className="form-control"
placeholder="Search here"
onDebounce={handleOnSearchInputDebounce}
debouceDelay={1000}
/>
);
}

View file

@ -0,0 +1,18 @@
import type { Movie } from "../../../types";
import GroupMoviesListItem from "./GroupMoviesListItem";
interface GroupMoviesListProps {
movies: Movie[];
onClick?: (movie: Movie) => void;
}
export default function GroupMoviesList({
movies,
onClick,
}: GroupMoviesListProps) {
const items = movies.map((elt) => (
<GroupMoviesListItem key={elt.id} item={elt} onClick={onClick} />
));
return <div className="list-group">{items}</div>;
}

View file

@ -0,0 +1,43 @@
import type { Movie } from "../../../types";
interface GroupMoviesListItemProps {
item: Movie;
onClick?: (movie: Movie) => void;
}
export default function GroupMoviesListItem({
item,
onClick,
}: GroupMoviesListItemProps) {
function handleOnClick(e: React.MouseEvent<HTMLElement>) {
e.stopPropagation();
e.preventDefault();
if (onClick) onClick(item);
}
return (
<button
onClick={handleOnClick}
type="submit"
className="list-group-item list-group-item-action flex-column align-items-start"
>
<div className="d-flex w-100 justify-content-between">
<h5 className="mb-1">{item.title}</h5>
<small>{item.release_date}</small>
</div>
<div className="row">
<div className="col-2">
<img
className="img-thumbnail"
src={`https://image.tmdb.org/t/p/w500${item.poster_path}`}
alt="lol"
/>
</div>
<div className="col-10">
<p className="mb-1">{item.overview}</p>
</div>
</div>
</button>
);
}

View file

@ -0,0 +1,20 @@
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ request, cookies }) => {
const jwt = cookies.get("jwt")?.value as string;
const url = new URL(request.url);
const res = await fetch(
`${import.meta.env.API_URL}/tmdb/movies/search?query=${url.searchParams.get("query")}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwt}`,
},
},
);
const resBody = await res.text();
return new Response(resBody, { status: res.status });
};

View file

@ -26,7 +26,7 @@ import HomeLayout from "../../../layouts/HomeLayout.astro";
<a <a
href="/home/settings/movies" href="/home/settings/movies"
class="list-group-item list-group-item-action" class="list-group-item list-group-item-action"
><MdOutlineLocalMovies /> Movies</a ><MdOutlineLocalMovies /> Add movie</a
> >
</div> </div>
</div> </div>

View file

@ -0,0 +1,21 @@
---
import TmdbSearch from "../../../components/TmdbSearch";
import HomeLayout from "../../../layouts/HomeLayout.astro";
---
<HomeLayout title="Add movie">
<div class="row justify-content-center">
<div class="col">
<div class="row">
<div class="col"><h1>Add movie</h1></div>
</div>
<div class="row">
<div class="col">
<h3>Search on TMDB Database</h3>
<TmdbSearch client:load />
</div>
<div class="col"></div>
</div>
</div>
</div>
</HomeLayout>

7
src/types.ts Normal file
View file

@ -0,0 +1,7 @@
export interface Movie {
id: number;
title: string;
overview: string;
poster_path: string;
release_date: string;
}