82 lines
2.2 KiB
Svelte
82 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { client } from '$lib/graphql/client';
|
|
import { NovelsDocument, type NovelsQuery } from '$lib/graphql/__generated__/graphql';
|
|
import NovelCard from './NovelCard.svelte';
|
|
import Clock from '@lucide/svelte/icons/clock';
|
|
|
|
type NovelEdge = NonNullable<NovelsQuery['novels']>['edges'][number];
|
|
|
|
let edges: NovelEdge[] = $state([]);
|
|
let fetching = $state(true);
|
|
let error: string | null = $state(null);
|
|
|
|
const novels = $derived(edges.map((edge) => edge.node).filter(Boolean).slice(0, 5));
|
|
|
|
async function fetchRecentNovels() {
|
|
fetching = true;
|
|
error = null;
|
|
|
|
try {
|
|
const result = await client.query(NovelsDocument, { first: 5 }).toPromise();
|
|
|
|
if (result.error) {
|
|
error = result.error.message;
|
|
return;
|
|
}
|
|
|
|
if (result.data?.novels) {
|
|
edges = result.data.novels.edges;
|
|
}
|
|
} catch (e) {
|
|
error = e instanceof Error ? e.message : 'Unknown error';
|
|
} finally {
|
|
fetching = false;
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
fetchRecentNovels();
|
|
});
|
|
</script>
|
|
|
|
<section class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<Clock class="h-5 w-5 text-muted-foreground" />
|
|
<h2 class="text-xl font-semibold">Recently Updated</h2>
|
|
</div>
|
|
<a
|
|
href="/novels"
|
|
class="text-sm text-muted-foreground transition-colors hover:text-primary"
|
|
>
|
|
View all
|
|
</a>
|
|
</div>
|
|
|
|
{#if fetching}
|
|
<div class="flex items-center justify-center py-8">
|
|
<div
|
|
class="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent"
|
|
aria-label="Loading novels"
|
|
></div>
|
|
</div>
|
|
{:else if error}
|
|
<div class="rounded-xl border border-destructive/40 bg-destructive/5 p-4">
|
|
<p class="text-sm text-destructive">Could not load novels: {error}</p>
|
|
</div>
|
|
{:else if novels.length === 0}
|
|
<div class="rounded-xl border bg-muted/50 p-4">
|
|
<p class="text-sm text-muted-foreground">No novels found yet.</p>
|
|
</div>
|
|
{:else}
|
|
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
|
|
{#each novels as novel (novel.id)}
|
|
<a href="/novels/{novel.id}" class="block">
|
|
<NovelCard {novel} />
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</section>
|