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,92 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18n-lite";
|
||||
import styles from "./FinishedBooksShelf.module.css";
|
||||
|
||||
export interface ShelfBook {
|
||||
id: number;
|
||||
title: string;
|
||||
author: string;
|
||||
cover_image: string | null;
|
||||
format: string;
|
||||
genre: string;
|
||||
}
|
||||
|
||||
interface FinishedBooksShelfProps {
|
||||
books: ShelfBook[];
|
||||
defaultExpanded?: boolean;
|
||||
onOpenBook: (book: ShelfBook) => void;
|
||||
onContextMenu: (e: React.MouseEvent, book: ShelfBook) => void;
|
||||
}
|
||||
|
||||
export function FinishedBooksShelf({
|
||||
books,
|
||||
defaultExpanded = false,
|
||||
onOpenBook,
|
||||
onContextMenu,
|
||||
}: FinishedBooksShelfProps) {
|
||||
const { t } = useTranslation();
|
||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultExpanded) setExpanded(true);
|
||||
}, [defaultExpanded]);
|
||||
|
||||
if (books.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.shelf} ${expanded ? styles.shelfExpanded : styles.shelfCollapsed}`}
|
||||
aria-label={t("library.finishedSection")}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.shelfHandle}
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
aria-expanded={expanded}
|
||||
>
|
||||
<span className={styles.shelfHandleTitle}>{t("library.finishedSection")}</span>
|
||||
<span className={styles.shelfHandleCount}>
|
||||
{t("library.finishedCount", { count: String(books.length) })}
|
||||
</span>
|
||||
<span className={styles.shelfChevron} aria-hidden>
|
||||
{expanded ? "▾" : "▴"}
|
||||
</span>
|
||||
</button>
|
||||
{expanded && (
|
||||
<div className={styles.shelfBody}>
|
||||
<div className={styles.shelfGrid}>
|
||||
{books.map((book) => (
|
||||
<div
|
||||
key={book.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.shelfCard}
|
||||
onClick={() => onOpenBook(book)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onOpenBook(book);
|
||||
}
|
||||
}}
|
||||
onContextMenu={(e) => onContextMenu(e, book)}
|
||||
>
|
||||
<div className={styles.shelfCover}>
|
||||
{book.cover_image ? (
|
||||
<img src={book.cover_image} alt="" />
|
||||
) : (
|
||||
<span className={styles.shelfCoverPlaceholder}>📖</span>
|
||||
)}
|
||||
<span className={styles.shelfBadge}>{t("library.readingStatus.finished")}</span>
|
||||
</div>
|
||||
<div className={styles.shelfMeta}>
|
||||
<p className={styles.shelfTitle}>{book.title}</p>
|
||||
<p className={styles.shelfAuthor}>{book.author}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user