Compare commits

...

2 Commits

Author SHA1 Message Date
0ef91b6f6f chore: update gitignore file 2026-04-05 19:51:30 -03:00
db94d2480d feat: add loading states to home page 2026-04-05 19:45:59 -03:00
5 changed files with 79 additions and 11 deletions

View File

@ -1,2 +0,0 @@
VITE_API_BASE_URL=http://localhost:8080
VITE_OMV_BASE_URL=http://omv.badger-pirarucu.ts.net:9000/mangamochi-dev

2
.gitignore vendored
View File

@ -222,7 +222,7 @@ web_modules/
.yarn-integrity
# dotenv environment variable files
.env
.env*
.env.development.local
.env.test.local
.env.production.local

View 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 }

View 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>
);
};

View File

@ -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.