mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
ff80d995c5
Add support for hiding audiobooks that are already available by introducing a hideAvailable query flag and excluding matching ASINs at the DB level. Implemented getAvailableAsins() in audiobook-matcher to gather ASINs from the library and completed requests, and wired it into the popular and new-releases API routes to apply a notIn filter. Propagated the hideAvailable flag through useAudiobooks so client requests include the parameter, and adjusted the homepage to reset pagination when the flag changes. Replaced two StickyPagination instances with a new UnifiedPagination component (new file) that provides a single context-aware floating paginator which tracks the dominant section and allows switching between Popular and New Releases. Also removed client-side filtering in favor of server-side exclusion and made small imports/cleanup in page.tsx.
210 lines
8.8 KiB
TypeScript
210 lines
8.8 KiB
TypeScript
/**
|
|
* Component: Homepage - Audiobook Discovery
|
|
* Documentation: documentation/frontend/components.md
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import { Header } from '@/components/layout/Header';
|
|
import { AudiobookGrid } from '@/components/audiobooks/AudiobookGrid';
|
|
import { useAudiobooks } from '@/lib/hooks/useAudiobooks';
|
|
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
|
|
import { UnifiedPagination } from '@/components/ui/UnifiedPagination';
|
|
import { SectionToolbar } from '@/components/ui/SectionToolbar';
|
|
import { usePreferences } from '@/contexts/PreferencesContext';
|
|
|
|
export default function HomePage() {
|
|
const [popularPage, setPopularPage] = useState(1);
|
|
const [newReleasesPage, setNewReleasesPage] = useState(1);
|
|
const { cardSize, setCardSize, squareCovers, setSquareCovers, hideAvailable, setHideAvailable } = usePreferences();
|
|
|
|
// Refs for auto-scrolling to section tops
|
|
const popularSectionRef = useRef<HTMLElement>(null);
|
|
const newReleasesSectionRef = useRef<HTMLElement>(null);
|
|
const footerRef = useRef<HTMLElement>(null);
|
|
|
|
const {
|
|
audiobooks: popular,
|
|
isLoading: loadingPopular,
|
|
totalPages: popularTotalPages,
|
|
message: popularMessage,
|
|
} = useAudiobooks('popular', 20, popularPage, hideAvailable);
|
|
|
|
const {
|
|
audiobooks: newReleases,
|
|
isLoading: loadingNewReleases,
|
|
totalPages: newReleasesTotalPages,
|
|
message: newReleasesMessage,
|
|
} = useAudiobooks('new-releases', 20, newReleasesPage, hideAvailable);
|
|
|
|
// Reset to page 1 when hideAvailable changes (total pages may differ)
|
|
useEffect(() => {
|
|
setPopularPage(1);
|
|
setNewReleasesPage(1);
|
|
}, [hideAvailable]);
|
|
|
|
// Handle page changes with auto-scroll to section top
|
|
const handlePopularPageChange = (page: number) => {
|
|
setPopularPage(page);
|
|
popularSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
};
|
|
|
|
const handleNewReleasesPageChange = (page: number) => {
|
|
setNewReleasesPage(page);
|
|
newReleasesSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
};
|
|
|
|
return (
|
|
<ProtectedRoute>
|
|
<div className="min-h-screen">
|
|
<Header />
|
|
|
|
<main className="container mx-auto px-4 py-6 sm:py-8 max-w-7xl space-y-8 sm:space-y-12">
|
|
{/* Popular Audiobooks Section */}
|
|
<section ref={popularSectionRef} className="relative">
|
|
{/* Sticky Section Header */}
|
|
<div className="sticky top-14 sm:top-16 z-30 mb-4 sm:mb-6">
|
|
<div className="bg-white/90 dark:bg-gray-800/90 backdrop-blur-md rounded-2xl px-4 sm:px-6 py-3 border border-gray-200/50 dark:border-gray-700/50 shadow-sm">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-1 h-6 bg-gradient-to-b from-blue-500 to-purple-500 rounded-full" />
|
|
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
Popular Audiobooks
|
|
</h2>
|
|
<SectionToolbar
|
|
hideAvailable={hideAvailable}
|
|
onToggleHideAvailable={setHideAvailable}
|
|
squareCovers={squareCovers}
|
|
onToggleSquareCovers={setSquareCovers}
|
|
cardSize={cardSize}
|
|
onCardSizeChange={setCardSize}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Content */}
|
|
<div className="bg-white/40 dark:bg-gray-800/40 backdrop-blur-sm rounded-2xl p-4 sm:p-6 border border-gray-200/50 dark:border-gray-700/50 shadow-sm">
|
|
{popularMessage && !loadingPopular && popular.length === 0 ? (
|
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-6 text-center">
|
|
<p className="text-yellow-800 dark:text-yellow-200 mb-2 font-medium">
|
|
No popular audiobooks found
|
|
</p>
|
|
<p className="text-yellow-700 dark:text-yellow-300 text-sm">
|
|
{popularMessage}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<AudiobookGrid
|
|
audiobooks={popular}
|
|
isLoading={loadingPopular}
|
|
emptyMessage="No popular audiobooks available"
|
|
cardSize={cardSize}
|
|
squareCovers={squareCovers}
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
{/* New Releases Section */}
|
|
<section ref={newReleasesSectionRef} className="relative">
|
|
{/* Sticky Section Header */}
|
|
<div className="sticky top-14 sm:top-16 z-30 mb-4 sm:mb-6">
|
|
<div className="bg-white/90 dark:bg-gray-800/90 backdrop-blur-md rounded-2xl px-4 sm:px-6 py-3 border border-gray-200/50 dark:border-gray-700/50 shadow-sm">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-1 h-6 bg-gradient-to-b from-emerald-500 to-teal-500 rounded-full" />
|
|
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
New Releases
|
|
</h2>
|
|
<SectionToolbar
|
|
hideAvailable={hideAvailable}
|
|
onToggleHideAvailable={setHideAvailable}
|
|
squareCovers={squareCovers}
|
|
onToggleSquareCovers={setSquareCovers}
|
|
cardSize={cardSize}
|
|
onCardSizeChange={setCardSize}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Content */}
|
|
<div className="bg-white/40 dark:bg-gray-800/40 backdrop-blur-sm rounded-2xl p-4 sm:p-6 border border-gray-200/50 dark:border-gray-700/50 shadow-sm">
|
|
{newReleasesMessage && !loadingNewReleases && newReleases.length === 0 ? (
|
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-6 text-center">
|
|
<p className="text-yellow-800 dark:text-yellow-200 mb-2 font-medium">
|
|
No new releases found
|
|
</p>
|
|
<p className="text-yellow-700 dark:text-yellow-300 text-sm">
|
|
{newReleasesMessage}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<AudiobookGrid
|
|
audiobooks={newReleases}
|
|
isLoading={loadingNewReleases}
|
|
emptyMessage="No new releases available"
|
|
cardSize={cardSize}
|
|
squareCovers={squareCovers}
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Call to Action */}
|
|
<section className="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-6 sm:p-8 text-center border border-blue-200/50 dark:border-blue-800/50 shadow-sm">
|
|
<h3 className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
Can't find what you're looking for?
|
|
</h3>
|
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
Use our search to find any audiobook from Audible
|
|
</p>
|
|
<a
|
|
href="/search"
|
|
className="inline-flex items-center px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-md hover:shadow-lg"
|
|
>
|
|
Search Audiobooks
|
|
</a>
|
|
</section>
|
|
</main>
|
|
|
|
{/* Footer */}
|
|
<footer ref={footerRef} className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-16">
|
|
<div className="container mx-auto px-4 py-6 max-w-7xl">
|
|
<div className="text-center text-sm text-gray-600 dark:text-gray-400">
|
|
<p>ReadMeABook - Audiobook Library Management System</p>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
{/* Unified Pagination — single context-aware pill for both sections */}
|
|
<UnifiedPagination
|
|
footerRef={footerRef}
|
|
sections={[
|
|
{
|
|
label: 'Popular Audiobooks',
|
|
accentColor: 'bg-blue-500',
|
|
currentPage: popularPage,
|
|
totalPages: popularTotalPages,
|
|
onPageChange: handlePopularPageChange,
|
|
sectionRef: popularSectionRef,
|
|
onScrollToSection: () =>
|
|
popularSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),
|
|
},
|
|
{
|
|
label: 'New Releases',
|
|
accentColor: 'bg-emerald-500',
|
|
currentPage: newReleasesPage,
|
|
totalPages: newReleasesTotalPages,
|
|
onPageChange: handleNewReleasesPageChange,
|
|
sectionRef: newReleasesSectionRef,
|
|
onScrollToSection: () =>
|
|
newReleasesSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
</ProtectedRoute>
|
|
);
|
|
}
|