First Commit. AI Generated App.
This commit is contained in:
@@ -36,15 +36,15 @@
|
||||
<p class="text-tokyo-fg-dark">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if ebooks.length === 0}
|
||||
<div class="card max-w-lg mx-auto text-center">
|
||||
<div class="card-body">
|
||||
<div class="text-6xl mb-4">📖</div>
|
||||
<h3 class="text-2xl font-semibold text-tokyo-fg mb-2">Welcome to your library!</h3>
|
||||
<p class="text-tokyo-fg-dark mb-6">You haven't added any ebooks yet. Start building your collection by uploading your favorite books.</p>
|
||||
<button class="btn-primary">Add Your First Ebook</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if ebooks.length === 0}
|
||||
<div class="card max-w-lg mx-auto text-center">
|
||||
<div class="card-body">
|
||||
<div class="text-6xl mb-4">📖</div>
|
||||
<h3 class="text-2xl font-semibold text-tokyo-fg mb-2">Welcome to your library!</h3>
|
||||
<p class="text-tokyo-fg-dark mb-6">You haven't added any ebooks yet. Start building your collection by uploading your favorite books.</p>
|
||||
<a href="/upload" class="btn-primary inline-block">Add Your First Ebook</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
||||
{#each ebooks as ebook (ebook.id)}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Actions } from './$types';
|
||||
import { createEbook } from '$lib/api';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request, fetch }) => {
|
||||
const data = await request.formData();
|
||||
const title = data.get('title') as string;
|
||||
const author = data.get('author') as string;
|
||||
const description = data.get('description') as string;
|
||||
const file = data.get('file') as File;
|
||||
|
||||
if (!title || !file) {
|
||||
return { success: false, error: 'Title and file are required' };
|
||||
}
|
||||
|
||||
try {
|
||||
// For now, simulate upload - in reality, the backend would handle file upload
|
||||
// and return the ebook data
|
||||
const ebookData = {
|
||||
title,
|
||||
author: author || null,
|
||||
description: description || null,
|
||||
file_path: `/uploads/${file.name}`, // Placeholder
|
||||
};
|
||||
|
||||
await createEbook(ebookData);
|
||||
throw redirect(303, '/');
|
||||
} catch (err) {
|
||||
if (err instanceof Response) throw err; // Redirect
|
||||
return { success: false, error: err instanceof Error ? err.message : 'Upload failed' };
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { showError } from '$lib/toast';
|
||||
|
||||
let loading = false;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Upload Ebook - Bookmann</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-tokyo-bg to-tokyo-bg-dark py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full animate-slide-in">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<div class="text-4xl mb-4">📤</div>
|
||||
<h2 class="text-2xl font-bold text-tokyo-fg">
|
||||
Upload Ebook
|
||||
</h2>
|
||||
<p class="text-tokyo-fg-dark mt-2">Add a new book to your library</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data" use:enhance={() => {
|
||||
loading = true;
|
||||
return async ({ update }) => {
|
||||
try {
|
||||
await update();
|
||||
goto('/');
|
||||
} catch (err) {
|
||||
showError('Upload failed');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
}}>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-tokyo-fg mb-2">
|
||||
Title *
|
||||
</label>
|
||||
<input
|
||||
id="title"
|
||||
name="title"
|
||||
type="text"
|
||||
required
|
||||
class="input-field w-full"
|
||||
placeholder="Enter book title"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="author" class="block text-sm font-medium text-tokyo-fg mb-2">
|
||||
Author
|
||||
</label>
|
||||
<input
|
||||
id="author"
|
||||
name="author"
|
||||
type="text"
|
||||
class="input-field w-full"
|
||||
placeholder="Enter author name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-tokyo-fg mb-2">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
rows="3"
|
||||
class="input-field w-full resize-none"
|
||||
placeholder="Brief description of the book"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="file" class="block text-sm font-medium text-tokyo-fg mb-2">
|
||||
Ebook File *
|
||||
</label>
|
||||
<input
|
||||
id="file"
|
||||
name="file"
|
||||
type="file"
|
||||
accept=".pdf,.epub,.mobi,.txt"
|
||||
required
|
||||
class="input-field w-full file:mr-4 file:py-2 file:px-4 file:rounded-l file:border-0 file:text-sm file:font-semibold file:bg-tokyo-bg-highlight file:text-tokyo-fg hover:file:bg-tokyo-bg-selection"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="btn-primary w-full flex justify-center items-center mt-6 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{#if loading}
|
||||
<div class="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent mr-2"></div>
|
||||
{/if}
|
||||
{loading ? 'Uploading...' : 'Upload Ebook'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user