add tmdb search
This commit is contained in:
parent
65cccb9620
commit
afd9170d6d
9 changed files with 224 additions and 1 deletions
53
src/components/DebouceInput.tsx
Normal file
53
src/components/DebouceInput.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
27
src/components/TmdbSearch.tsx
Normal file
27
src/components/TmdbSearch.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
34
src/components/TmdbSearchInput.tsx
Normal file
34
src/components/TmdbSearchInput.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
18
src/components/lists/movies/GroupMoviesList.tsx
Normal file
18
src/components/lists/movies/GroupMoviesList.tsx
Normal 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>;
|
||||||
|
}
|
43
src/components/lists/movies/GroupMoviesListItem.tsx
Normal file
43
src/components/lists/movies/GroupMoviesListItem.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
20
src/pages/api/tmdb/movies/search.ts
Normal file
20
src/pages/api/tmdb/movies/search.ts
Normal 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 });
|
||||||
|
};
|
|
@ -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>
|
||||||
|
|
|
@ -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
7
src/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export interface Movie {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
overview: string;
|
||||||
|
poster_path: string;
|
||||||
|
release_date: string;
|
||||||
|
}
|
Loading…
Reference in a new issue