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
@@ -0,0 +1,176 @@
import { useTranslation } from "react-i18n-lite";
import { readingStatusKey } from "@/locales";
import styles from "../../pages/Library.module.css";
export const LIBRARY_STATUS_COLORS: Record<string, { bg: string; text: string }> = {
want_to_read: { bg: "#dbeafe", text: "#1d4ed8" },
reading: { bg: "#dcfce7", text: "#16a34a" },
finished: { bg: "#f3e8ff", text: "#9333ea" },
dnf: { bg: "#fef3c7", text: "#b45309" },
};
export interface LibraryBookCardData {
id: number;
title: string;
author: string;
genre: string;
format: string;
cover_image: string | null;
reading_status: string;
progressPercent: number | null;
}
function badgeLabel(
book: LibraryBookCardData,
t: (key: string, params?: Record<string, string>) => string,
): string {
if (book.reading_status === "reading" && book.progressPercent != null && book.progressPercent > 0) {
return t("library.readingStatus.readingWithProgress", {
percent: String(book.progressPercent),
});
}
return t(readingStatusKey(book.reading_status));
}
interface LibraryBookCardProps {
book: LibraryBookCardData;
isMobile: boolean;
onOpen: () => void;
onContextMenu: (e: React.MouseEvent) => void;
}
export function LibraryBookCard({
book,
isMobile,
onOpen,
onContextMenu,
}: LibraryBookCardProps) {
const { t } = useTranslation();
const sc = LIBRARY_STATUS_COLORS[book.reading_status] ?? { bg: "#f3f4f6", text: "#6b7280" };
const showProgressBar =
book.reading_status === "reading" &&
book.progressPercent != null &&
book.progressPercent > 0;
return (
<div
onClick={onOpen}
onContextMenu={onContextMenu}
className={styles.bookCard}
style={{ display: isMobile ? "flex" : "block" }}
>
<div
style={{
width: isMobile ? 80 : "100%",
height: isMobile ? 120 : 180,
background: "#f0f0f0",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
flexShrink: 0,
}}
>
{book.cover_image ? (
<img
src={book.cover_image}
alt={book.title}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
) : (
<span style={{ fontSize: isMobile ? 32 : 48 }}>📖</span>
)}
{showProgressBar && (
<div className={styles.coverProgressBar}>
<div
className={styles.coverProgressFill}
style={{ width: `${book.progressPercent}%` }}
/>
</div>
)}
{!isMobile && (
<span
style={{
position: "absolute",
top: 8,
right: 8,
background: sc.bg,
color: sc.text,
fontSize: 11,
fontWeight: 600,
padding: "2px 8px",
borderRadius: 999,
lineHeight: "18px",
maxWidth: "calc(100% - 16px)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{badgeLabel(book, t)}
</span>
)}
</div>
<div style={{ padding: isMobile ? "8px 12px" : 12, flex: 1 }}>
<div
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 8,
}}
>
<div style={{ minWidth: 0, flex: 1 }}>
<h3
style={{
fontSize: 14,
fontWeight: 600,
color: "#1f2937",
margin: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{book.title}
</h3>
<p style={{ fontSize: 12, color: "#6b7280", margin: "2px 0" }}>
{book.author || t("common.unknownAuthor")}
</p>
</div>
{isMobile && (
<span
style={{
background: sc.bg,
color: sc.text,
fontSize: 10,
fontWeight: 600,
padding: "2px 6px",
borderRadius: 999,
whiteSpace: "nowrap",
flexShrink: 0,
}}
>
{badgeLabel(book, t)}
</span>
)}
</div>
{book.genre && (
<span
style={{
fontSize: 11,
color: "#4f46e5",
background: "#eef2ff",
padding: "1px 6px",
borderRadius: 4,
display: "inline-block",
marginTop: 4,
}}
>
{book.genre}
</span>
)}
</div>
</div>
);
}