feat: loading indicator on browse cards and improved footer
Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Cleanup Preview (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 1s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Lint (pull_request) Successful in 17s
CI / Scraper / Test (pull_request) Successful in 18s
CI / Scraper / Build (pull_request) Successful in 17s

This commit is contained in:
Admin
2026-03-06 18:26:14 +05:00
parent 76d616a308
commit 60a9540ef7
3 changed files with 65 additions and 8 deletions

View File

@@ -1,10 +1,23 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { navigating } from '$app/state';
import type { PageData, ActionData } from './$types';
import type { NovelListing } from './+page.server';
let { data, form }: { data: PageData; form: ActionData } = $props();
// Track which novel card is currently being navigated to
let loadingSlug = $state<string | null>(null);
// Clear loading state when navigation ends (success or failure)
$effect(() => {
if (!navigating) loadingSlug = null;
});
function handleNovelClick(slug: string) {
loadingSlug = slug;
}
// Filter options
const genres = [
{ value: 'all', label: 'All Genres' },
@@ -282,9 +295,12 @@
<!-- ── Grid view ─────────────────────────────────────────────────────── -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{#each data.novels as novel}
{@const isLoading = loadingSlug === novel.slug}
<a
href="/books/{novel.slug}"
class="group flex flex-col rounded-lg overflow-hidden bg-zinc-800 border border-zinc-700 hover:border-zinc-500 transition-colors relative"
onclick={() => handleNovelClick(novel.slug)}
class="group flex flex-col rounded-lg overflow-hidden bg-zinc-800 border transition-colors relative
{isLoading ? 'border-amber-400/60' : 'border-zinc-700 hover:border-zinc-500'}"
>
<!-- Cover -->
<div class="aspect-[2/3] bg-zinc-900 overflow-hidden relative">
@@ -313,6 +329,15 @@
{novel.rating}
</span>
{/if}
<!-- Loading overlay -->
{#if isLoading}
<div class="absolute inset-0 bg-zinc-900/70 flex items-center justify-center">
<svg class="w-8 h-8 animate-spin text-amber-400" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
{/if}
</div>
<!-- Info -->
@@ -355,14 +380,18 @@
<!-- ── List view ─────────────────────────────────────────────────────── -->
<div class="flex flex-col gap-2">
{#each data.novels as novel}
<div class="flex items-center gap-4 bg-zinc-800 border border-zinc-700 rounded-lg px-4 py-3 hover:border-zinc-500 transition-colors">
{@const isLoading = loadingSlug === novel.slug}
<div
class="flex items-center gap-4 bg-zinc-800 border rounded-lg px-4 py-3 transition-colors
{isLoading ? 'border-amber-400/60' : 'border-zinc-700 hover:border-zinc-500'}"
>
<!-- Rank / index -->
{#if novel.rank}
<span class="text-amber-400 font-bold text-sm w-8 shrink-0 text-right">{novel.rank}</span>
{/if}
<!-- Cover thumbnail -->
<div class="w-10 h-14 shrink-0 rounded overflow-hidden bg-zinc-900">
<div class="w-10 h-14 shrink-0 rounded overflow-hidden bg-zinc-900 relative">
{#if novel.cover}
<img src={novel.cover} alt={novel.title} class="w-full h-full object-cover" loading="lazy" />
{:else}
@@ -373,6 +402,14 @@
</svg>
</div>
{/if}
{#if isLoading}
<div class="absolute inset-0 bg-zinc-900/70 flex items-center justify-center">
<svg class="w-4 h-4 animate-spin text-amber-400" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
{/if}
</div>
<!-- Title + meta -->
@@ -380,7 +417,9 @@
{#if novel.slug}
<a
href="/books/{novel.slug}"
class="text-sm font-semibold text-zinc-100 hover:text-amber-400 transition-colors line-clamp-1"
onclick={() => handleNovelClick(novel.slug)}
class="text-sm font-semibold transition-colors line-clamp-1
{isLoading ? 'text-amber-400' : 'text-zinc-100 hover:text-amber-400'}"
>
{novel.title}
</a>