Initial commit

This commit is contained in:
kikootwo
2026-01-28 11:41:24 -05:00
commit a3ba192fbd
257 changed files with 89482 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
/**
* Component: Search Page
* Documentation: documentation/frontend/components.md
*/
'use client';
import { useState, useEffect, useCallback } from 'react';
import { Header } from '@/components/layout/Header';
import { AudiobookGrid } from '@/components/audiobooks/AudiobookGrid';
import { useSearch } from '@/lib/hooks/useAudiobooks';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
export default function SearchPage() {
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState('');
const [page, setPage] = useState(1);
// Debounce search query
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
setPage(1); // Reset to first page on new search
}, 500);
return () => clearTimeout(timer);
}, [query]);
const { results, totalResults, hasMore, isLoading } = useSearch(debouncedQuery, page);
const handleSearch = useCallback((e: React.FormEvent) => {
e.preventDefault();
setPage(1);
}, []);
const handleLoadMore = useCallback(() => {
setPage((prev) => prev + 1);
}, []);
return (
<ProtectedRoute>
<div className="min-h-screen">
<Header />
<main className="container mx-auto px-4 py-8 max-w-7xl space-y-8">
{/* Search Header */}
<div className="text-center space-y-4">
<h1 className="text-4xl font-bold text-gray-900 dark:text-gray-100">
Search Audiobooks
</h1>
<p className="text-gray-600 dark:text-gray-400">
Find and request any audiobook from Audible
</p>
</div>
{/* Search Form */}
<form onSubmit={handleSearch} className="max-w-3xl mx-auto">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search by title, author, or narrator..."
className="w-full pl-12 pr-12 py-4 text-lg border-2 border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400"
autoFocus
/>
{query && (
<button
type="button"
onClick={() => setQuery('')}
className="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
>
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
</div>
</form>
{/* Results */}
{debouncedQuery ? (
<div className="space-y-6">
{/* Results Count */}
{!isLoading && totalResults > 0 && (
<div className="text-center text-gray-600 dark:text-gray-400">
Found {totalResults.toLocaleString()} result{totalResults !== 1 ? 's' : ''} for "{debouncedQuery}"
</div>
)}
{/* Results Grid */}
<AudiobookGrid
audiobooks={results}
isLoading={!!(isLoading && page === 1)}
emptyMessage={`No results found for "${debouncedQuery}"`}
/>
{/* Load More */}
{hasMore && !isLoading && (
<div className="flex justify-center">
<button
onClick={handleLoadMore}
className="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors"
>
Load More Results
</button>
</div>
)}
{/* Loading More Indicator */}
{isLoading && page > 1 && (
<div className="flex justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
)}
</div>
) : (
/* Empty State */
<div className="text-center py-16 space-y-4">
<svg
className="mx-auto h-16 w-16 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<p className="text-xl text-gray-600 dark:text-gray-400">
Start typing to search for audiobooks
</p>
<p className="text-sm text-gray-500 dark:text-gray-500">
Search by title, author, or narrator name
</p>
</div>
)}
</main>
</div>
</ProtectedRoute>
);
}