feat: add loading states to home page
This commit is contained in:
parent
ee47a05f9f
commit
db94d2480d
16
src/components/ui/spinner.tsx
Normal file
16
src/components/ui/spinner.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Loader2Icon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||
return (
|
||||
<Loader2Icon
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
className={cn("size-4 animate-spin", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Spinner }
|
||||
40
src/features/home/components/MangaLoadingState.tsx
Normal file
40
src/features/home/components/MangaLoadingState.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useRef } from "react";
|
||||
|
||||
const CHEEKY_MESSAGES = [
|
||||
"Sharpening katanas and downloading manga...",
|
||||
"Searching for the One Piece... and your titles.",
|
||||
"Summoning the Great Sage for faster loading...",
|
||||
"Powering up to Super Saiyan level... please wait.",
|
||||
"Collecting all seven Dragon Balls to fetch data...",
|
||||
"Even Saitama takes a second to load... sometimes.",
|
||||
"Naruto is training, wait for the results...",
|
||||
"Loading... because we don't have a Death Note for bugs.",
|
||||
"Entering the Hidden Leaf Village... of data.",
|
||||
"Waiting for the next chapter... and your results.",
|
||||
"Training in the Hyperbolic Time Chamber for better speed...",
|
||||
"Collecting chakra for the ultimate data retrieval...",
|
||||
"Waiting for the next hiatus to end... oh wait, just loading.",
|
||||
"Reading the manga faster than you can... hold on.",
|
||||
"Asking the Shinigami for the right data...",
|
||||
"Is this a Jojo reference? No, it's just loading.",
|
||||
"Surpassing our limits... Right here! Right now!",
|
||||
"Hunting for the rarest manga volumes in the digital world...",
|
||||
"Dodging spoilers while fetching your manga...",
|
||||
"Preparing the transmutation circle for your results...",
|
||||
];
|
||||
|
||||
export const MangaLoadingState = () => {
|
||||
const loadingMessage = useRef(
|
||||
CHEEKY_MESSAGES[Math.floor(Math.random() * CHEEKY_MESSAGES.length)],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col items-center justify-center gap-4">
|
||||
<Spinner className="size-12 text-primary" />
|
||||
<p className="animate-pulse text-muted-foreground font-medium text-center px-4">
|
||||
{loadingMessage.current}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -3,12 +3,14 @@ import { useEffect, useRef } from "react";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { useGetMangas } from "@/api/generated/catalog/catalog.ts";
|
||||
import { AuthHeader } from "@/components/AuthHeader.tsx";
|
||||
import { Spinner } from "@/components/ui/spinner.tsx";
|
||||
import { Pagination } from "@/components/Pagination.tsx";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle.tsx";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { useUIState } from "@/contexts/UIStateContext.tsx";
|
||||
import { FilterSidebar } from "@/features/home/components/FilterSidebar.tsx";
|
||||
import { MangaGrid } from "@/features/home/components/MangaGrid.tsx";
|
||||
import { MangaLoadingState } from "@/features/home/components/MangaLoadingState.tsx";
|
||||
import { SortDropdown } from "@/features/home/components/SortDropdown.tsx";
|
||||
import { useDynamicPageSize } from "@/hooks/useDynamicPageSize.ts";
|
||||
|
||||
@ -37,7 +39,12 @@ const Home = () => {
|
||||
|
||||
const [debouncedSearchText] = useDebounce(searchText, 500);
|
||||
|
||||
const { data: mangasData, queryKey: mangasQueryKey } = useGetMangas({
|
||||
const {
|
||||
data: mangasData,
|
||||
queryKey: mangasQueryKey,
|
||||
isPending,
|
||||
isFetching,
|
||||
} = useGetMangas({
|
||||
page: currentPage - 1,
|
||||
size: itemsPerPage,
|
||||
sort: ["id"],
|
||||
@ -75,7 +82,7 @@ const Home = () => {
|
||||
onShowAdultContentChange={setShowAdultContent}
|
||||
/>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 flex flex-col">
|
||||
<header className="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="px-8 py-6">
|
||||
<div className="flex flex-col gap-6">
|
||||
@ -86,9 +93,14 @@ const Home = () => {
|
||||
<h1 className="text-3xl font-bold tracking-tight text-foreground">
|
||||
MangaMochi
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{mangasData?.data?.totalElements} titles available
|
||||
</p>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{mangasData?.data?.totalElements ?? 0} titles available
|
||||
</p>
|
||||
{isFetching && (
|
||||
<Spinner className="size-3 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
@ -114,8 +126,10 @@ const Home = () => {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="px-8 py-8">
|
||||
{mangasData?.data?.content && mangasData.data.content.length > 0 ? (
|
||||
<main className="flex-1 px-8 py-8 flex flex-col">
|
||||
{isPending ? (
|
||||
<MangaLoadingState />
|
||||
) : mangasData?.data?.content && mangasData.data.content.length > 0 ? (
|
||||
<>
|
||||
{mangasData.data?.totalElements && (
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
@ -149,7 +163,7 @@ const Home = () => {
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex min-h-[400px] items-center justify-center">
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<p className="text-lg text-muted-foreground">
|
||||
No manga found matching your filters.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user