Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 1 addition & 43 deletions src/app/dash/[userid]/book/[bookid]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,7 @@ type Props = {
function Layout({ children }: Props) {
return (
<section className='page anna-fade-in'>
<div className='w-full mt-4 lg:mt-24 mb-8'>
{/* Single elegant container for book content */}
<div className='relative w-full rounded-3xl overflow-hidden'>
{/* Subtle texture overlay */}
<div className='absolute inset-0 opacity-[0.008] dark:opacity-[0.012] pointer-events-none z-10'>
<svg width='100%' height='100%'>
<pattern
id='book-texture'
x='0'
y='0'
width='8'
height='8'
patternUnits='userSpaceOnUse'
>
<circle
cx='2'
cy='2'
r='1'
fill='currentColor'
className='text-gray-900 dark:text-white'
/>
<circle
cx='6'
cy='6'
r='1'
fill='currentColor'
className='text-gray-900 dark:text-white'
/>
</pattern>
<rect width='100%' height='100%' fill='url(#book-texture)' />
</svg>
</div>

{/* Main card with gradient background */}
<div className='relative bg-gradient-to-br from-white/95 via-gray-50/90 to-blue-50/50 dark:from-zinc-900/95 dark:via-zinc-900/90 dark:to-blue-950/30 shadow-xl border border-gray-200/60 dark:border-zinc-800/50 backdrop-blur-md'>
{/* Additional gradient overlay for depth */}
<div className='absolute inset-0 bg-gradient-to-t from-transparent via-transparent to-blue-400/[0.015] dark:to-blue-400/[0.025] pointer-events-none' />

{/* Content */}
<div className='relative z-10 p-6 lg:p-10'>{children}</div>
</div>
</div>
</div>
<div className='w-full mt-4 lg:mt-8 mb-8'>{children}</div>
</section>
)
}
Expand Down
13 changes: 12 additions & 1 deletion src/app/dash/[userid]/book/[bookid]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,26 @@ async function Page(props: PageProps) {
duration = result || 0
}

const clippingsCount = clippingsData.book.clippingsCount ?? 0

return (
<>
<BookInfo
book={bookData}
uid={uid}
duration={duration}
isLastReadingBook={clippingsData.book.isLastReadingBook}
clippingsCount={clippingsCount}
startReadingAt={clippingsData.book.startReadingAt}
lastReadingAt={clippingsData.book.lastReadingAt}
/>
<Divider
title={
clippingsCount > 0
? `${clippingsCount} ${t('app.book.title')}`
: t('app.book.title')
}
/>
<Divider title={t('app.book.title')} />
<BookPageContent book={bookData} userid={userid} />
</>
)
Expand Down
142 changes: 78 additions & 64 deletions src/app/dash/[userid]/book/[bookid]/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,111 @@
function BookPageSkeleton() {
return (
<div className='w-full space-y-8 animate-pulse'>
{/* Book info section skeleton */}
<div className='grid grid-cols-1 lg:grid-cols-[300px,1fr] gap-8'>
{/* Book cover and details */}
<div className='space-y-4'>
{/* Book cover skeleton */}
<div className='w-full h-96 bg-gradient-to-br from-gray-200 via-gray-300 to-gray-200 dark:from-zinc-700 dark:via-zinc-600 dark:to-zinc-700 rounded-2xl shadow-sm'></div>

{/* Action buttons skeleton */}
<div className='space-y-3'>
<div className='h-12 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-xl'></div>
<div className='h-10 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg'></div>
{/* Book info hero section skeleton */}
<div className='grid grid-cols-1 md:grid-cols-[280px,1fr] lg:grid-cols-[320px,1fr] gap-6 lg:gap-10 py-6 lg:py-8'>
{/* Book cover skeleton */}
<div className='mx-auto w-full max-w-[320px]'>
<div className='w-full aspect-[4/5] bg-gradient-to-br from-gray-200 via-gray-300 to-gray-200 dark:from-zinc-700 dark:via-zinc-600 dark:to-zinc-700 rounded-xl shadow-sm' />
{/* Mobile share button skeleton */}
<div className='mt-6 md:hidden'>
<div className='h-12 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-xl' />
</div>
</div>

{/* Book info content */}
{/* Book details skeleton */}
<div className='space-y-6'>
{/* Title and author */}
{/* Title */}
<div className='space-y-3'>
<div className='h-8 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-3/4'></div>
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-1/2'></div>
</div>

{/* Rating and badges */}
<div className='flex items-center gap-4'>
<div className='flex gap-1'>
{[...Array(5)].map((_, i) => (
<div className='h-10 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-3/4' />
<div className='flex items-center gap-2'>
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-1/3' />
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-full w-16' />
</div>
{/* Tags skeleton */}
<div className='flex gap-2'>
{[...Array(3)].map((_, i) => (
<div
key={i}
className='w-4 h-4 bg-gray-300 dark:bg-zinc-600 rounded-full'
></div>
className='h-6 bg-gray-200 dark:bg-zinc-700 rounded-full w-16'
/>
))}
</div>
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-full w-16'></div>
</div>

{/* Description */}
<div className='space-y-2'>
<div className='h-4 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-full'></div>
<div className='h-4 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-5/6'></div>
<div className='h-4 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-4/5'></div>
{/* Stats bar skeleton */}
<div className='flex flex-wrap gap-3'>
{[...Array(3)].map((_, i) => (
<div
key={i}
className='flex items-center gap-3 rounded-xl border border-gray-200/40 dark:border-zinc-700/40 bg-white/40 dark:bg-zinc-800/40 px-4 py-3'
>
<div className='h-8 w-8 bg-gray-200 dark:bg-zinc-700 rounded-lg' />
<div className='space-y-1'>
<div className='h-5 bg-gray-200 dark:bg-zinc-700 rounded w-12' />
<div className='h-3 bg-gray-200 dark:bg-zinc-700 rounded w-16' />
</div>
</div>
))}
</div>

{/* Stats section */}
<div className='bg-white/40 dark:bg-zinc-800/40 backdrop-blur-sm rounded-xl p-4 border border-gray-200/40 dark:border-zinc-700/40'>
<div className='grid grid-cols-3 gap-4'>
{[...Array(3)].map((_, i) => (
<div key={i} className='text-center space-y-2'>
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-12 mx-auto'></div>
<div className='h-4 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-16 mx-auto'></div>
{/* Share button skeleton (desktop) */}
<div className='hidden md:block'>
<div className='h-10 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-xl w-32' />
</div>

{/* Meta section skeleton */}
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
{[...Array(2)].map((_, i) => (
<div
key={i}
className='flex items-center gap-3 rounded-xl border border-gray-200/40 dark:border-zinc-700/40 bg-white/40 dark:bg-zinc-800/40 p-3'
>
<div className='h-9 w-9 bg-gray-200 dark:bg-zinc-700 rounded-lg' />
<div className='space-y-1'>
<div className='h-3 bg-gray-200 dark:bg-zinc-700 rounded w-16' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-24' />
</div>
))}
</div>
))}
</div>

{/* Summary skeleton */}
<div className='rounded-xl border border-gray-200/40 dark:border-zinc-700/40 bg-white/40 dark:bg-zinc-800/40 p-6 space-y-3'>
<div className='h-6 bg-gray-200 dark:bg-zinc-700 rounded w-24' />
<div className='space-y-2'>
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-full' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-5/6' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-4/5' />
</div>
</div>
</div>
</div>

{/* Elegant divider */}
{/* Divider skeleton */}
<div className='relative h-px my-8 bg-gradient-to-r from-transparent via-gray-300 dark:via-zinc-700 to-transparent overflow-hidden'>
<div
className='absolute inset-0 bg-gradient-to-r from-transparent via-blue-400/25 to-transparent animate-pulse'
style={{ animationDuration: '3s' }}
/>
</div>

{/* Clippings section skeleton */}
<div className='space-y-6'>
<div className='flex items-center justify-between'>
<div className='h-7 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-32'></div>
<div className='h-8 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded-lg w-24'></div>
</div>

{/* Clippings grid */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'>
{new Array(6).fill(1).map((_, i) => (
<div
key={i}
className='bg-white/30 dark:bg-zinc-800/30 backdrop-blur-sm rounded-2xl p-4 border border-gray-200/30 dark:border-zinc-700/30 space-y-3'
>
<div className='h-4 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-3/4'></div>
<div className='space-y-2'>
<div className='h-3 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-full'></div>
<div className='h-3 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-5/6'></div>
<div className='h-3 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-4/5'></div>
</div>
<div className='flex justify-between items-center'>
<div className='h-3 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-16'></div>
<div className='h-6 bg-gradient-to-r from-gray-200 to-gray-300 dark:from-zinc-700 dark:to-zinc-600 rounded w-6'></div>
</div>
{/* Clippings grid skeleton */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'>
{new Array(6).fill(1).map((_, i) => (
<div
key={i}
className='bg-slate-50/80 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-6 border border-slate-100/50 dark:border-slate-700/50 space-y-4'
>
<div className='h-6 bg-gray-200 dark:bg-zinc-700 rounded w-3/4' />
<div className='h-px w-full bg-gradient-to-r from-transparent via-gray-200 dark:via-zinc-700 to-transparent' />
<div className='space-y-2'>
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-full' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-5/6' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-4/5' />
<div className='h-4 bg-gray-200 dark:bg-zinc-700 rounded w-3/4' />
</div>
))}
</div>
</div>
))}
</div>
</div>
)
Expand Down
9 changes: 5 additions & 4 deletions src/components/book-info/book-cover-column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ type Props = {
function BookCoverColumn({ book, togglePreviewVisible }: Props) {
const { t } = useTranslation()
return (
<div className='md:col-span-1'>
<div className='relative -mt-12 md:-mt-20 mx-auto w-64 md:w-full max-w-xs transform transition-all duration-500 hover:scale-[1.02] hover:-rotate-1'>
<div className='absolute inset-0 -z-10 blur-md opacity-30 scale-95 translate-y-4'></div>
<div>
<div className='relative mx-auto w-full max-w-[320px] transform transition-all duration-500 hover:scale-[1.02] hover:-rotate-1'>
{/* Colored shadow behind cover */}
<div className='absolute inset-4 -z-10 rounded-2xl bg-blue-400/30 opacity-40 blur-2xl translate-y-4 scale-95 dark:bg-blue-500/20' />
<BlurhashView
blurhashValue={
book.edges?.imageInfo?.blurHashValue ??
Expand All @@ -22,7 +23,7 @@ function BookCoverColumn({ book, togglePreviewVisible }: Props) {
src={book.image}
height={384}
width={320}
className='w-full aspect-[4/5] object-cover rounded-xl shadow-xl transition-all duration-300'
className='w-full aspect-[4/5] object-cover rounded-xl shadow-2xl transition-all duration-300'
alt={book.title}
/>
</div>
Expand Down
80 changes: 48 additions & 32 deletions src/components/book-info/book-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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 (
<div className='relative mt-16 w-full md:mt-24'>
{/* Background decoration */}
<div className='absolute inset-0 -z-10 bg-gradient-to-br from-white/30 via-white/20 to-white/5 backdrop-blur-lg dark:from-gray-800/30 dark:via-gray-800/20 dark:to-gray-900/5'></div>

{/* Main content container */}
<div className='mx-auto w-full max-w-7xl rounded-2xl border border-gray-200 bg-gradient-to-br from-white/70 via-white/60 to-white/40 shadow-xl backdrop-blur-lg dark:border-gray-700 dark:from-gray-900/70 dark:via-gray-800/60 dark:to-gray-900/40'>
{/* Book content grid */}
<div className='grid grid-cols-1 gap-8 p-8 md:grid-cols-3 lg:grid-cols-4'>
{/* Book cover column */}
<BookCoverColumn
book={book}
togglePreviewVisible={togglePreviewVisible}
/>
return (
<div className='relative w-full'>
{/* Subtle background wash */}
<div className='absolute inset-0 -z-10 rounded-2xl bg-gradient-to-b from-blue-50/40 via-white/20 to-transparent dark:from-blue-950/30 dark:via-zinc-900/20 dark:to-transparent' />

{/* Book details column */}
<div className='font-lxgw md:col-span-2 lg:col-span-3'>
{/* Title and rating */}
<BookTitleSection book={book} duration={duration} />
<div className='grid grid-cols-1 gap-6 py-6 md:grid-cols-[280px,1fr] lg:grid-cols-[320px,1fr] lg:gap-10 lg:py-8'>
{/* Book cover column */}
<BookCoverColumn
book={book}
togglePreviewVisible={togglePreviewVisible}
/>

{/* Action buttons (desktop) */}
<div className='mt-6 hidden gap-4 md:flex'>
<button
onClick={() => togglePreviewVisible()}
className='flex items-center gap-2 rounded-xl bg-gradient-to-r from-blue-500 to-blue-600 px-5 py-2.5 text-white shadow-md transition-all duration-300 hover:from-blue-600 hover:to-blue-700 hover:shadow-lg'
>
<Share2 className='h-4 w-4' />
<span>{t('app.book.share')}</span>
</button>
</div>
{/* Book details column */}
<div className='font-lxgw space-y-6'>
{/* Title and rating */}
<BookTitleSection book={book} duration={duration} />

{/* Book metadata */}
<BookMetaSection book={book} />
{/* Reading stats bar */}
<BookStatsBar
clippingsCount={clippingsCount}
duration={duration}
startReadingAt={startReadingAt}
lastReadingAt={lastReadingAt}
/>

{/* Book summary */}
<BookSummarySection book={book} />
{/* Action buttons (desktop) */}
<div className='hidden gap-4 md:flex'>
<button
onClick={() => togglePreviewVisible()}
className='flex items-center gap-2 rounded-xl bg-gradient-to-r from-blue-500 to-blue-600 px-5 py-2.5 text-white shadow-md transition-all duration-300 hover:from-blue-600 hover:to-blue-700 hover:shadow-lg'
>
<Share2 className='h-4 w-4' />
<span>{t('app.book.share')}</span>
</button>
</div>

{/* Book metadata */}
<BookMetaSection book={book} />

{/* Book summary */}
<BookSummarySection book={book} />
</div>
</div>

Expand Down
Loading
Loading