Archived
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user