Implement loading spinner and Polish translations: add Spinner component for loading states in Search and SearchList, update UI text to Polish for improved localization, and enhance Pagination styles for better visibility.
This commit is contained in:
parent
3a7669e26d
commit
7373d64123
|
|
@ -11,7 +11,7 @@ export default async function SearchPage({
|
||||||
<>
|
<>
|
||||||
<section className="container">
|
<section className="container">
|
||||||
<h1 className="text-2xl">
|
<h1 className="text-2xl">
|
||||||
Search for: <strong>{s}</strong>
|
Wyszukiwanie: <strong>{s}</strong>
|
||||||
</h1>
|
</h1>
|
||||||
</section>
|
</section>
|
||||||
<SearchList query={s} />
|
<SearchList query={s} />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import Link from "next/link";
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -13,11 +12,11 @@ export const Pagination: FC<Props> = ({
|
||||||
onPageChange,
|
onPageChange,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ul className="flex justify-center gap-3 text-gray-900 my-10">
|
<ul className="flex justify-center gap-3 my-10">
|
||||||
{currentPage > 1 && (
|
{currentPage > 1 && (
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="grid size-8 place-content-center rounded border border-primary transition-colors hover:bg-primary hover:text-white cursor-pointer"
|
className="grid size-8 place-content-center rounded border bg-white text-black border-primary transition-colors hover:bg-primary hover:text-white cursor-pointer"
|
||||||
aria-label="Previous page"
|
aria-label="Previous page"
|
||||||
onClick={() => onPageChange(currentPage - 1)}
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
>
|
>
|
||||||
|
|
@ -44,7 +43,7 @@ export const Pagination: FC<Props> = ({
|
||||||
{currentPage < totalPages && (
|
{currentPage < totalPages && (
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="grid size-8 place-content-center rounded border border-primary transition-colors hover:bg-primary hover:text-white cursor-pointer"
|
className="grid size-8 place-content-center rounded border bg-white text-black border-primary transition-colors hover:bg-primary hover:text-white cursor-pointer"
|
||||||
aria-label="Next page"
|
aria-label="Next page"
|
||||||
onClick={() => onPageChange(currentPage + 1)}
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
|
export const Spinner = () => {
|
||||||
|
return <span className={styles.loader} />;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
.loader {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: rotation 1s linear infinite;
|
||||||
|
}
|
||||||
|
.loader::after {
|
||||||
|
content: "";
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
left: 6px;
|
||||||
|
top: 10px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
color: #ff3d00;
|
||||||
|
background: currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 25px 2px, 10px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotation {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { TMDB } from "@/lib/tmdb";
|
import { TMDB } from "@/lib/tmdb";
|
||||||
import { MovieCard } from "@/components/atoms/MovieCard";
|
|
||||||
import { SearchResult } from "@/lib/tmdb/types";
|
import { SearchResult } from "@/lib/tmdb/types";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Pagination } from "@/components/atoms/Pagination";
|
import { Pagination } from "@/components/atoms/Pagination";
|
||||||
import { MovieList } from "../MovieList";
|
import { MovieList } from "../MovieList";
|
||||||
|
import { Spinner } from "@/components/atoms/Spinner";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
query: string;
|
query: string;
|
||||||
|
|
@ -21,12 +21,17 @@ export const SearchList: FC<Props> = ({ query }) => {
|
||||||
page = 1,
|
page = 1,
|
||||||
} = response ?? {};
|
} = response ?? {};
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleSearch = async (query: string, page: number) => {
|
const handleSearch = async (query: string, page: number) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
const data = await TMDB.search({
|
const data = await TMDB.search({
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
});
|
});
|
||||||
setResponse(data);
|
setResponse(data);
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
|
|
@ -43,18 +48,26 @@ export const SearchList: FC<Props> = ({ query }) => {
|
||||||
<section className="mb-4 md:mb-10">
|
<section className="mb-4 md:mb-10">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{total_results} movies found for your search
|
{total_results} filmów znaleziono dla Twojego wyszukiwania
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<MovieList
|
<div className="relative">
|
||||||
overrideMovies={results?.map((m) => ({
|
{isLoading && (
|
||||||
...m,
|
<div className="absolute -inset-10 flex pt-60 justify-center backdrop-blur-xs z-10">
|
||||||
favorite: false,
|
<Spinner />
|
||||||
seen: false,
|
</div>
|
||||||
genre_ids: JSON.stringify(m.genre_ids),
|
)}
|
||||||
}))}
|
<MovieList
|
||||||
fluid
|
overrideMovies={results?.map((m) => ({
|
||||||
/>
|
...m,
|
||||||
|
favorite: false,
|
||||||
|
seen: false,
|
||||||
|
genre_ids: JSON.stringify(m.genre_ids),
|
||||||
|
}))}
|
||||||
|
fluid
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Pagination
|
<Pagination
|
||||||
totalPages={total_pages}
|
totalPages={total_pages}
|
||||||
currentPage={page}
|
currentPage={page}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,20 @@ import { useRef } from "react";
|
||||||
import { useOutsideClick } from "@/hooks/useOutsideClick";
|
import { useOutsideClick } from "@/hooks/useOutsideClick";
|
||||||
import { Button } from "@/components/atoms/Button";
|
import { Button } from "@/components/atoms/Button";
|
||||||
import { MovieList } from "@/components/molecules/MovieList";
|
import { MovieList } from "@/components/molecules/MovieList";
|
||||||
|
import { Spinner } from "@/components/atoms/Spinner";
|
||||||
|
|
||||||
export const Search = () => {
|
export const Search = () => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
const [response, setResponse] = useState<SearchResult | null>(null);
|
const [response, setResponse] = useState<SearchResult | null>(null);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const isLoading = query.length > 2 && !response;
|
||||||
const { results, total_pages, total_results = 0 } = response ?? {};
|
const { results, total_pages, total_results = 0 } = response ?? {};
|
||||||
|
|
||||||
const handleSearch = async (query: string) => {
|
const handleSearch = async (query: string) => {
|
||||||
setQuery(query);
|
setQuery(query);
|
||||||
|
setResponse(null);
|
||||||
if (query.length < 3) {
|
if (query.length < 3) {
|
||||||
setResponse(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,6 +67,11 @@ export const Search = () => {
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="col-span-full mt-2 lg:mt-30 text-center ">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{results && (
|
{results && (
|
||||||
<div className="col-span-full mt-2 lg:mt-10 text-center ">
|
<div className="col-span-full mt-2 lg:mt-10 text-center ">
|
||||||
<p className="text-white">{total_results} movies found</p>
|
<p className="text-white">{total_results} movies found</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue