[FA-misc] Astro migration works, probably want to touchup the frontend but that can be in Phase 4
This commit is contained in:
117
fictionarchive-web-astro/src/lib/components/NovelsPage.svelte
Normal file
117
fictionarchive-web-astro/src/lib/components/NovelsPage.svelte
Normal file
@@ -0,0 +1,117 @@
|
||||
<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 { Button } from '$lib/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||
|
||||
const PAGE_SIZE = 12;
|
||||
|
||||
type NovelEdge = NonNullable<NovelsQuery['novels']>['edges'][number];
|
||||
|
||||
let edges: NovelEdge[] = $state([]);
|
||||
let pageInfo: NonNullable<NovelsQuery['novels']>['pageInfo'] | null = $state(null);
|
||||
let fetching = $state(false);
|
||||
let error: string | null = $state(null);
|
||||
let initialLoad = $state(true);
|
||||
|
||||
const hasNextPage = $derived(pageInfo?.hasNextPage ?? false);
|
||||
const novels = $derived(edges.map((edge) => edge.node).filter(Boolean));
|
||||
|
||||
async function fetchNovels(after: string | null = null) {
|
||||
fetching = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const result = await client.query(NovelsDocument, { first: PAGE_SIZE, after }).toPromise();
|
||||
|
||||
if (result.error) {
|
||||
error = result.error.message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.data?.novels) {
|
||||
if (after) {
|
||||
// Append for pagination
|
||||
edges = [...edges, ...result.data.novels.edges];
|
||||
} else {
|
||||
// Initial load
|
||||
edges = result.data.novels.edges;
|
||||
}
|
||||
pageInfo = result.data.novels.pageInfo;
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Unknown error';
|
||||
} finally {
|
||||
fetching = false;
|
||||
initialLoad = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (pageInfo?.endCursor) {
|
||||
fetchNovels(pageInfo.endCursor);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchNovels();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<Card class="shadow-md shadow-primary/10">
|
||||
<CardHeader>
|
||||
<CardTitle>Latest Novels</CardTitle>
|
||||
<p class="text-sm text-muted-foreground">Novels that have recently been updated.</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{#if fetching && initialLoad}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div
|
||||
class="h-10 w-10 animate-spin rounded-full border-2 border-primary border-t-transparent"
|
||||
aria-label="Loading novels"
|
||||
></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<Card class="border-destructive/40 bg-destructive/5">
|
||||
<CardContent>
|
||||
<p class="py-4 text-sm text-destructive">Could not load novels: {error}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if !fetching && novels.length === 0 && !error && !initialLoad}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<p class="py-4 text-sm text-muted-foreground">
|
||||
No novels found yet. Try adding content to the gateway.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if novels.length > 0}
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each novels as novel (novel.id)}
|
||||
<NovelCard {novel} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasNextPage}
|
||||
<div class="flex justify-center">
|
||||
<Button onclick={loadMore} variant="outline" disabled={fetching} class="min-w-[160px]">
|
||||
{fetching ? 'Loading...' : 'Load more'}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user