Files
FictionArchive/fictionarchive-web-astro/src/lib/components/ImportNovelModal.svelte
gamer147 e70c39ea75
All checks were successful
CI / build-backend (pull_request) Successful in 1m45s
CI / build-frontend (pull_request) Successful in 39s
[FA-misc] Refresh button, UI mostly gold
2025-12-09 09:11:39 -05:00

142 lines
3.2 KiB
Svelte

<script lang="ts">
import { client } from '$lib/graphql/client';
import { ImportNovelDocument } from '$lib/graphql/__generated__/graphql';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
interface Props {
open: boolean;
onClose: () => void;
onSuccess?: () => void;
}
let { open = $bindable(), onClose, onSuccess }: Props = $props();
let novelUrl = $state('');
let submitting = $state(false);
let error: string | null = $state(null);
let success = $state(false);
async function handleSubmit(e: Event) {
e.preventDefault();
if (!novelUrl.trim()) {
error = 'Please enter a URL';
return;
}
submitting = true;
error = null;
try {
const result = await client
.mutation(ImportNovelDocument, { input: { novelUrl: novelUrl.trim() } })
.toPromise();
if (result.error) {
error = result.error.message;
return;
}
if (result.data?.importNovel?.novelUpdateRequestedEvent) {
success = true;
setTimeout(() => {
handleClose();
onSuccess?.();
}, 1500);
}
} catch (e) {
error = e instanceof Error ? e.message : 'Unknown error occurred';
} finally {
submitting = false;
}
}
function handleClose() {
novelUrl = '';
error = null;
success = false;
onClose();
}
function handleBackdropClick(e: MouseEvent) {
if (e.target === e.currentTarget) {
handleClose();
}
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') {
handleClose();
}
}
</script>
<svelte:window onkeydown={handleKeydown} />
{#if open}
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
onclick={handleBackdropClick}
onkeydown={handleKeydown}
role="dialog"
aria-modal="true"
aria-labelledby="import-modal-title"
tabindex="-1"
>
<Card class="w-full max-w-md mx-4 shadow-xl">
<CardHeader>
<CardTitle id="import-modal-title">Import Novel</CardTitle>
<p class="text-muted-foreground text-sm">
Enter the URL of the novel you want to import
</p>
</CardHeader>
<CardContent>
<form onsubmit={handleSubmit} class="space-y-4">
<div class="space-y-2">
<label for="novel-url" class="text-sm font-medium">Novel URL</label>
<Input
id="novel-url"
type="url"
placeholder="https://example.com/novel/..."
bind:value={novelUrl}
disabled={submitting || success}
/>
</div>
{#if error}
<p class="text-destructive text-sm">{error}</p>
{/if}
{#if success}
<p class="text-green-600 dark:text-green-400 text-sm">
Import request submitted successfully!
</p>
{/if}
<div class="flex justify-end gap-2 pt-2">
<Button
type="button"
variant="outline"
onclick={handleClose}
disabled={submitting}
>
Cancel
</Button>
<Button type="submit" disabled={submitting || success || !novelUrl.trim()}>
{#if submitting}
Importing...
{:else if success}
Done
{:else}
Import
{/if}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
{/if}