From a067caf4c89891a09bfe73c567ef47c418ff5ad2 Mon Sep 17 00:00:00 2001 From: AnnatarHe Date: Sat, 21 Mar 2026 23:02:34 +0800 Subject: [PATCH] refactor(book): redesign book detail page for spacious layout Remove triple-nested card containment that compressed content width. Flatten layout wrapper, switch to fixed-width cover grid, add reading stats bar with highlights count/duration/date range, enlarge title, display book tags, and improve section contrast. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../dash/[userid]/book/[bookid]/layout.tsx | 44 +----- src/app/dash/[userid]/book/[bookid]/page.tsx | 13 +- .../dash/[userid]/book/[bookid]/skeleton.tsx | 142 ++++++++++-------- .../book-info/book-cover-column.tsx | 9 +- src/components/book-info/book-info.tsx | 80 ++++++---- .../book-info/book-meta-section.tsx | 4 +- src/components/book-info/book-stats-bar.tsx | 78 ++++++++++ .../book-info/book-summary-section.tsx | 2 +- .../book-info/book-title-section.tsx | 30 ++-- src/locales/en.json | 5 +- src/locales/ko.json | 5 +- src/locales/zhCN.json | 5 +- 12 files changed, 254 insertions(+), 163 deletions(-) create mode 100644 src/components/book-info/book-stats-bar.tsx diff --git a/src/app/dash/[userid]/book/[bookid]/layout.tsx b/src/app/dash/[userid]/book/[bookid]/layout.tsx index 03a52cd7..e1914dad 100644 --- a/src/app/dash/[userid]/book/[bookid]/layout.tsx +++ b/src/app/dash/[userid]/book/[bookid]/layout.tsx @@ -5,49 +5,7 @@ type Props = { function Layout({ children }: Props) { return (
-
- {/* Single elegant container for book content */} -
- {/* Subtle texture overlay */} -
- - - - - - - -
- - {/* Main card with gradient background */} -
- {/* Additional gradient overlay for depth */} -
- - {/* Content */} -
{children}
-
-
-
+
{children}
) } diff --git a/src/app/dash/[userid]/book/[bookid]/page.tsx b/src/app/dash/[userid]/book/[bookid]/page.tsx index 20bb51f4..bcf739a4 100644 --- a/src/app/dash/[userid]/book/[bookid]/page.tsx +++ b/src/app/dash/[userid]/book/[bookid]/page.tsx @@ -110,6 +110,8 @@ async function Page(props: PageProps) { duration = result || 0 } + const clippingsCount = clippingsData.book.clippingsCount ?? 0 + return ( <> + 0 + ? `${clippingsCount} ${t('app.book.title')}` + : t('app.book.title') + } /> - ) diff --git a/src/app/dash/[userid]/book/[bookid]/skeleton.tsx b/src/app/dash/[userid]/book/[bookid]/skeleton.tsx index 63265376..4396fb25 100644 --- a/src/app/dash/[userid]/book/[bookid]/skeleton.tsx +++ b/src/app/dash/[userid]/book/[bookid]/skeleton.tsx @@ -1,63 +1,87 @@ function BookPageSkeleton() { return (
- {/* Book info section skeleton */} -
- {/* Book cover and details */} -
- {/* Book cover skeleton */} -
- - {/* Action buttons skeleton */} -
-
-
+ {/* Book info hero section skeleton */} +
+ {/* Book cover skeleton */} +
+
+ {/* Mobile share button skeleton */} +
+
- {/* Book info content */} + {/* Book details skeleton */}
- {/* Title and author */} + {/* Title */}
-
-
-
- - {/* Rating and badges */} -
-
- {[...Array(5)].map((_, i) => ( +
+
+
+
+
+ {/* Tags skeleton */} +
+ {[...Array(3)].map((_, i) => (
+ className='h-6 bg-gray-200 dark:bg-zinc-700 rounded-full w-16' + /> ))}
-
- {/* Description */} -
-
-
-
+ {/* Stats bar skeleton */} +
+ {[...Array(3)].map((_, i) => ( +
+
+
+
+
+
+
+ ))}
- {/* Stats section */} -
-
- {[...Array(3)].map((_, i) => ( -
-
-
+ {/* Share button skeleton (desktop) */} +
+
+
+ + {/* Meta section skeleton */} +
+ {[...Array(2)].map((_, i) => ( +
+
+
+
+
- ))} +
+ ))} +
+ + {/* Summary skeleton */} +
+
+
+
+
+
- {/* Elegant divider */} + {/* Divider skeleton */}
- {/* Clippings section skeleton */} -
-
-
-
-
- - {/* Clippings grid */} -
- {new Array(6).fill(1).map((_, i) => ( -
-
-
-
-
-
-
-
-
-
-
+ {/* Clippings grid skeleton */} +
+ {new Array(6).fill(1).map((_, i) => ( +
+
+
+
+
+
+
+
- ))} -
+
+ ))}
) diff --git a/src/components/book-info/book-cover-column.tsx b/src/components/book-info/book-cover-column.tsx index 39a837e2..50f80cb2 100644 --- a/src/components/book-info/book-cover-column.tsx +++ b/src/components/book-info/book-cover-column.tsx @@ -11,9 +11,10 @@ type Props = { function BookCoverColumn({ book, togglePreviewVisible }: Props) { const { t } = useTranslation() return ( -
-
-
+
+
+ {/* Colored shadow behind cover */} +
diff --git a/src/components/book-info/book-info.tsx b/src/components/book-info/book-info.tsx index 01169461..94f2f8d1 100644 --- a/src/components/book-info/book-info.tsx +++ b/src/components/book-info/book-info.tsx @@ -6,6 +6,7 @@ import type { WenquBook } from '../../services/wenqu' import BookSharePreview from '../preview/preview-book' import BookCoverColumn from './book-cover-column' import BookMetaSection from './book-meta-section' +import BookStatsBar from './book-stats-bar' import BookSummarySection from './book-summary-section' import BookTitleSection from './book-title-section' @@ -14,52 +15,67 @@ type TBookInfoProp = { book: WenquBook duration?: number isLastReadingBook?: boolean + clippingsCount?: number + startReadingAt?: string + lastReadingAt?: string } -function BookInfo({ book, uid, duration }: TBookInfoProp) { +function BookInfo({ + book, + uid, + duration, + clippingsCount, + startReadingAt, + lastReadingAt, +}: TBookInfoProp) { const { t } = useTranslation() const [sharePreviewVisible, setSharePreviewVisible] = useState(false) const togglePreviewVisible = useCallback(() => { setSharePreviewVisible((v) => !v) }, []) - return ( -
- {/* Background decoration */} -
- {/* Main content container */} -
- {/* Book content grid */} -
- {/* Book cover column */} - + return ( +
+ {/* Subtle background wash */} +
- {/* Book details column */} -
- {/* Title and rating */} - +
+ {/* Book cover column */} + - {/* Action buttons (desktop) */} -
- -
+ {/* Book details column */} +
+ {/* Title and rating */} + - {/* Book metadata */} - + {/* Reading stats bar */} + - {/* Book summary */} - + {/* Action buttons (desktop) */} +
+
+ + {/* Book metadata */} + + + {/* Book summary */} +
diff --git a/src/components/book-info/book-meta-section.tsx b/src/components/book-info/book-meta-section.tsx index 954952dc..2228b49d 100644 --- a/src/components/book-info/book-meta-section.tsx +++ b/src/components/book-info/book-meta-section.tsx @@ -14,7 +14,7 @@ function BookMetaSection({ book }: Props) { return (
{book.pubdate && ( -
+
@@ -30,7 +30,7 @@ function BookMetaSection({ book }: Props) { )} {book.totalPages > 0 && ( -
+
diff --git a/src/components/book-info/book-stats-bar.tsx b/src/components/book-info/book-stats-bar.tsx new file mode 100644 index 00000000..9b1c617c --- /dev/null +++ b/src/components/book-info/book-stats-bar.tsx @@ -0,0 +1,78 @@ +'use client' + +import { Bookmark, CalendarRange, Clock } from 'lucide-react' +import { useTranslation } from '@/i18n/client' +import dayjs from '@/utils/dayjs' + +type Props = { + clippingsCount?: number + duration?: number + startReadingAt?: string + lastReadingAt?: string +} + +function BookStatsBar({ + clippingsCount, + duration, + startReadingAt, + lastReadingAt, +}: Props) { + const { t } = useTranslation() + + const hasDateRange = startReadingAt && lastReadingAt + + return ( +
+ {clippingsCount != null && clippingsCount > 0 && ( +
+
+ +
+
+

+ {clippingsCount} +

+

+ {t('app.book.highlights')} +

+
+
+ )} + + {duration != null && duration > 0 && ( +
+
+ +
+
+

+ {duration} +

+

+ {t('app.book.readingDays')} +

+
+
+ )} + + {hasDateRange && ( +
+
+ +
+
+

+ {dayjs(startReadingAt).format('YYYY/MM/DD')} –{' '} + {dayjs(lastReadingAt).format('YYYY/MM/DD')} +

+

+ {t('app.book.readingPeriod')} +

+
+
+ )} +
+ ) +} + +export default BookStatsBar diff --git a/src/components/book-info/book-summary-section.tsx b/src/components/book-info/book-summary-section.tsx index be8f0a38..e0c75012 100644 --- a/src/components/book-info/book-summary-section.tsx +++ b/src/components/book-info/book-summary-section.tsx @@ -10,7 +10,7 @@ function BookSummarySection({ book }: Props) { const { t } = useTranslation(undefined, 'book') const [summaryExpanded, setSummaryExpanded] = useState(false) return ( -
+

{t('app.book.summary.title')} diff --git a/src/components/book-info/book-title-section.tsx b/src/components/book-info/book-title-section.tsx index 12d7d49c..6331f6ec 100644 --- a/src/components/book-info/book-title-section.tsx +++ b/src/components/book-info/book-title-section.tsx @@ -1,5 +1,4 @@ -import { Clock, Star } from 'lucide-react' -import { useTranslation } from '@/i18n/client' +import { Star } from 'lucide-react' import type { WenquBook } from '@/services/wenqu' type Props = { @@ -7,17 +6,16 @@ type Props = { duration?: number } -function BookTitleSection({ book, duration }: Props) { - const { t } = useTranslation() +function BookTitleSection({ book }: Props) { return (
-

+

{book.title}

-
- +
+ {book.rating?.toFixed(1)}/10 @@ -25,7 +23,7 @@ function BookTitleSection({ book, duration }: Props) {
{/* Author and publication info */} -
+

{book.author}

{book.press && ( @@ -33,11 +31,17 @@ function BookTitleSection({ book, duration }: Props) { )}
- {/* Reading stats */} - {duration && ( -
- - {t('app.book.readingDuration', { count: duration })} + {/* Tags */} + {book.tags && book.tags.length > 0 && ( +
+ {book.tags.slice(0, 6).map((tag) => ( + + {tag} + + ))}
)}
diff --git a/src/locales/en.json b/src/locales/en.json index 1bc2108b..caf0719c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -76,7 +76,10 @@ "title": "Clippings", "share": "Share the book", "readingDuration": "Read for {{ count }} day", - "readingDuration_plural": "Read for {{ count }} days" + "readingDuration_plural": "Read for {{ count }} days", + "highlights": "Highlights", + "readingDays": "Reading Days", + "readingPeriod": "Reading Period" }, "auth": { "signin": "Sign in", diff --git a/src/locales/ko.json b/src/locales/ko.json index cbdabe5b..2d71412b 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -76,7 +76,10 @@ "title": "책 다이제스트", "share": "이 책 공유", "readingDuration": "이 책을 {{ count }} 일 동안 읽으십시오", - "readingDuration_plural": "이 책을 {{ count }} 일 동안 읽으십시오" + "readingDuration_plural": "이 책을 {{ count }} 일 동안 읽으십시오", + "highlights": "하이라이트", + "readingDays": "독서 일수", + "readingPeriod": "독서 기간" }, "auth": { "signin": "로그인", diff --git a/src/locales/zhCN.json b/src/locales/zhCN.json index dccad0fa..bc95beae 100644 --- a/src/locales/zhCN.json +++ b/src/locales/zhCN.json @@ -76,7 +76,10 @@ "title": "书摘", "share": "分享这本书", "readingDuration": "这本书看了 {{ count }} 天", - "readingDuration_plural": "这本书看了 {{ count }} 天" + "readingDuration_plural": "这本书看了 {{ count }} 天", + "highlights": "高亮", + "readingDays": "阅读天数", + "readingPeriod": "阅读时间" }, "auth": { "signin": "登陆",