chore: initial commit — monorepo ReadaBook (API NestJS, web PWA, Docker)

This commit is contained in:
Git Agent
2026-08-23 09:56:53 +02:00
commit 8f1140127f
79 changed files with 6456 additions and 0 deletions

View File

@ -0,0 +1,87 @@
import { useEffect, useState } from "react";
import { LibraryBig, ScanLine } from "lucide-react";
import { api } from "../api/client";
import type { DashboardData } from "../api/types";
import { BookCard } from "../components/BookCard";
import { EmptyState, LoadingState, Meter, Panel } from "../components/ui";
import { navigate } from "../router";
export function HomePage() {
const [state, setState] = useState<DashboardData | null>(null);
const [fallback, setFallback] = useState(false);
useEffect(() => {
let alive = true;
Promise.all([api.books(), api.continueReading(), api.libraries(), api.jobs()])
.then(([books, continueReading, libraries, jobs]) => {
if (!alive) return;
setFallback(books.some((book) => book.filePath.startsWith("/library/")) && jobs.length === 1);
setState({ books, continueReading, libraries, jobs });
})
.catch(() => {
if (alive) setState({ books: [], continueReading: [], libraries: [], jobs: [] });
});
return () => {
alive = false;
};
}, []);
if (!state) return <LoadingState />;
return (
<div className="page-grid">
<section className="hero-band">
<div>
<p>Cabinet de curiosites numerique</p>
<h1>Ouvrir, classer, reprendre.</h1>
<span>{fallback ? "API absente ou incomplete : specimens de demonstration actifs." : "Catalogue branche sur le serveur local."}</span>
</div>
<button className="primary-button" onClick={() => navigate("/search")}>
<ScanLine size={18} />
Explorer
</button>
</section>
<Panel className="span-2">
<div className="section-heading">
<h2>Reprise de lecture</h2>
<span>{state.continueReading.length} traces</span>
</div>
{state.continueReading.length ? (
<div className="continue-grid">
{state.continueReading.map((item) => (
<button key={item.book.id} className="continue-tile" onClick={() => navigate(`/reader/${item.book.id}`)}>
<strong>{item.book.title}</strong>
<span>{item.book.author ?? "Auteur inconnu"}</span>
<Meter value={item.progress.percent} />
</button>
))}
</div>
) : (
<EmptyState title="Aucune trace" detail="Les lectures reprises apparaitront ici." />
)}
</Panel>
<Panel>
<div className="section-heading">
<h2>Bibliotheques</h2>
<LibraryBig size={20} />
</div>
<div className="library-list">
{state.libraries.map((library) => (
<button key={library.id} onClick={() => navigate(`/library/${library.id}`)}>
<strong>{library.name}</strong>
<span>{library.path}</span>
</button>
))}
</div>
</Panel>
<section className="book-grid span-3">
{state.books.map((book) => (
<BookCard key={book.id} book={book} />
))}
</section>
</div>
);
}