feat: uv config other feats

- add uv configuration for the backend
- update frontend to make auth work
- add new auth endpoints
- add bookmars feat
- add reader feat
This commit is contained in:
2026-06-03 22:06:01 -05:00
parent 730c748f5f
commit 6b4c0c43f8
137 changed files with 20319 additions and 2340 deletions
@@ -1,53 +1,83 @@
/**
* TableOfContents — slide-in drawer listing all chapters.
* Tap a chapter to navigate. Current chapter is highlighted.
* TableOfContents — slide-in drawer for epub.js navigation tree.
*/
import type { ChapterSummary } from "../../types/reader";
import type { KeyboardEvent } from "react";
import { useTranslation } from "react-i18n-lite";
export interface EpubTocItem {
label: string;
href: string;
subitems?: EpubTocItem[];
}
interface TableOfContentsProps {
chapters: ChapterSummary[];
currentChapterNumber: number;
items: EpubTocItem[];
isOpen: boolean;
onClose: () => void;
onNavigate: (number: number) => void;
onNavigate: (href: string) => void;
}
function TocEntry({
item,
depth,
onNavigate,
}: {
item: EpubTocItem;
depth: number;
onNavigate: (href: string) => void;
}) {
return (
<>
<button
type="button"
className="toc-item"
style={{ paddingLeft: `${16 + depth * 14}px` }}
onClick={() => onNavigate(item.href)}
>
<span className="toc-item-title">{item.label}</span>
</button>
{item.subitems?.map((sub) => (
<TocEntry key={sub.href} item={sub} depth={depth + 1} onNavigate={onNavigate} />
))}
</>
);
}
export default function TableOfContents({
chapters,
currentChapterNumber,
items,
isOpen,
onClose,
onNavigate,
}: TableOfContentsProps) {
const handleChapterClick = (number: number) => {
onNavigate(number);
const { t } = useTranslation();
const handleNavigate = (href: string) => {
onNavigate(href);
onClose();
};
return (
<>
{/* Overlay */}
{isOpen && (
<div
className="toc-overlay"
onClick={onClose}
onKeyDown={(e: React.KeyboardEvent) => {
onKeyDown={(e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
}}
role="presentation"
/>
)}
{/* Drawer */}
<aside className={`toc-drawer ${isOpen ? "toc-drawer--open" : ""}`}>
<div className="toc-header">
<h2 className="toc-title">Contents</h2>
<h2 className="toc-title">{t("reader.tocTitle")}</h2>
<button
type="button"
className="toc-close-btn"
onClick={onClose}
aria-label="Close table of contents"
aria-label={t("reader.closeTocAria")}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="18" y1="6" x2="6" y2="18" />
@@ -57,24 +87,15 @@ export default function TableOfContents({
</div>
<nav className="toc-list">
{chapters.length === 0 && (
<p className="toc-empty">No chapters available.</p>
{items.length === 0 ? (
<p className="toc-empty">{t("reader.tocEmpty")}</p>
) : (
items.map((item) => (
<TocEntry key={item.href} item={item} depth={0} onNavigate={handleNavigate} />
))
)}
{chapters.map((chapter) => (
<button
key={chapter.number}
type="button"
className={`toc-item ${
chapter.number === currentChapterNumber ? "toc-item--active" : ""
}`}
onClick={() => handleChapterClick(chapter.number)}
>
<span className="toc-item-number">{chapter.number}</span>
<span className="toc-item-title">{chapter.title}</span>
</button>
))}
</nav>
</aside>
</>
);
}
}