diff --git a/client/src/pages/authors/AuthorProfile.tsx b/client/src/pages/authors/AuthorProfile.tsx index 58a8182..d7582c1 100644 --- a/client/src/pages/authors/AuthorProfile.tsx +++ b/client/src/pages/authors/AuthorProfile.tsx @@ -7,22 +7,74 @@ import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AuthorTimeline } from "@/components/authors/AuthorTimeline"; import { Skeleton } from "@/components/ui/skeleton"; -import { useState } from "react"; -import { Heart, Share2, Grid3X3, List } from "lucide-react"; +import { useState, useRef } from "react"; +import { + Award, + Book, + BookOpen, + Calendar, + Clock, + Download, + ExternalLink, + Eye, + FileText, + Filter, + Flag, + GraduationCap, + Grid3X3, + Heart, + HeartHandshake, + Highlighter, + History, + Languages, + List, + MapPin, + MessageSquare, + PenTool, + Search, + Share2, + Star, + SunMoon, + Tag, + Bookmark, + Users, + Zap +} from "lucide-react"; +import { AuthorChip } from "@/components/common/AuthorChip"; +import { Badge } from "@/components/ui/badge"; +import { Progress } from "@/components/ui/progress"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { useToast } from "@/hooks/use-toast"; +import { useSeason } from "@/hooks/use-season"; export default function AuthorProfile() { const { slug } = useParams(); + const { season } = useSeason(); + const { toast } = useToast(); const [view, setView] = useState<'list' | 'grid'>('list'); + const [selectedGenre, setSelectedGenre] = useState(null); + const [selectedYear, setSelectedYear] = useState(null); + const [selectedWorkType, setSelectedWorkType] = useState(null); + const [selectedLanguage, setSelectedLanguage] = useState(null); + const bioRef = useRef(null); + // Get author data const { data: author, isLoading: authorLoading } = useQuery({ queryKey: [`/api/authors/${slug}`], }); + // Get works for the author const { data: works, isLoading: worksLoading } = useQuery({ queryKey: [`/api/authors/${slug}/works`], enabled: !!author, }); + // Get timeline events const { data: timeline, isLoading: timelineLoading } = useQuery({ queryKey: [`/api/authors/${slug}/timeline`], enabled: !!author, @@ -31,6 +83,57 @@ export default function AuthorProfile() { const [following, setFollowing] = useState(false); const [followCount, setFollowCount] = useState(Math.floor(Math.random() * 2000)); + // Simulate stats data + const worksCount = works?.length || 0; + const translationsCount = works?.reduce((acc, work) => acc + (Math.floor(Math.random() * 5)), 0) || 0; + const readingCount = Math.floor(Math.random() * 10000); + const citationCount = Math.floor(Math.random() * 1000); + const annotationCount = Math.floor(Math.random() * 500); + + // Get unique years, genres, languages from works + const years = Array.from(new Set(works?.map(work => work.year?.toString()).filter(Boolean))); + const genres = Array.from(new Set(works?.flatMap(work => work.tags?.map(tag => tag.name) || []).filter(Boolean))); + const languages = Array.from(new Set(works?.map(work => work.language).filter(Boolean))); + const workTypes = Array.from(new Set(works?.map(work => work.type).filter(Boolean))); + + // Filter works based on selected filters + const filteredWorks = works?.filter(work => { + if (selectedGenre && (!work.tags || !work.tags.some(tag => tag.name === selectedGenre))) { + return false; + } + if (selectedYear && work.year?.toString() !== selectedYear) { + return false; + } + if (selectedWorkType && work.type !== selectedWorkType) { + return false; + } + if (selectedLanguage && work.language !== selectedLanguage) { + return false; + } + return true; + }); + + // Group works by year + const worksByYear = filteredWorks?.reduce>((acc, work) => { + const year = work.year?.toString() || 'Unknown'; + if (!acc[year]) { + acc[year] = []; + } + acc[year].push(work); + return acc; + }, {}); + + // Group works by type + const worksByType = filteredWorks?.reduce>((acc, work) => { + const type = work.type || 'Other'; + if (!acc[type]) { + acc[type] = []; + } + acc[type].push(work); + return acc; + }, {}); + + // Handle follow button click const handleFollow = () => { if (following) { setFollowCount(followCount - 1); @@ -38,71 +141,187 @@ export default function AuthorProfile() { setFollowCount(followCount + 1); } setFollowing(!following); + + toast({ + title: following ? "Unfollowed" : "Following", + description: following + ? `You are no longer following ${author?.name}` + : `You are now following ${author?.name}. You'll receive updates when new works are added.`, + variant: "default", + }); + }; + + // Handle share + const handleShare = () => { + navigator.clipboard.writeText(window.location.href); + toast({ + title: "Link copied", + description: "The link to this author has been copied to your clipboard.", + variant: "default", + }); + }; + + // Clear all filters + const clearFilters = () => { + setSelectedGenre(null); + setSelectedYear(null); + setSelectedWorkType(null); + setSelectedLanguage(null); + }; + + // Format type name + const formatTypeName = (type: string): string => { + switch (type) { + case 'poem': return 'Poems'; + case 'story': return 'Short Stories'; + case 'novel': return 'Novels'; + case 'play': return 'Plays'; + case 'essay': return 'Essays'; + default: return 'Other Works'; + } + }; + + // Get season-based color class for buttons + const getSeasonColorClass = (): string => { + switch (season) { + case 'spring': return 'bg-green-600 hover:bg-green-700 text-white'; + case 'summer': return 'bg-amber-600 hover:bg-amber-700 text-white'; + case 'autumn': return 'bg-russet hover:bg-russet/90 text-white'; + case 'winter': return 'bg-blue-600 hover:bg-blue-700 text-white'; + default: return 'bg-russet hover:bg-russet/90 text-white'; + } }; return ( -
-
+ {/* Hero section with author info */} +
+
{authorLoading ? ( -
- +
+
- +
+ + +
) : author ? ( -
-
- {`Photo +
+
+
+ {`Photo +
+
+
+ + {worksCount} Works + + + {followCount.toLocaleString()} Followers + +
+
+ + + + + + + +

Share this author

+
+
+
+
+
+
-
+

{author.name}

{author.country && ( -
+
{author.country} + + {author.country} +
)}
-

- {author.birthYear}–{author.deathYear || 'present'} -

-

- {author.biography} -

-
- - + +
+
+ + {author.birthYear}–{author.deathYear || 'present'} +
+ + {genres.length > 0 && ( +
+ + {genres.slice(0, 2).join(', ')}{genres.length > 2 ? '...' : ''} +
+ )} + + {languages.length > 0 && ( +
+ + {languages.slice(0, 2).join(', ')}{languages.length > 2 ? '...' : ''} +
+ )} +
+ +
+

{author.biography}

+
+ +
+
+
+ {worksCount} + Works +
+
+ {translationsCount} + Translations +
+
+ {citationCount} + Citations +
+
@@ -114,117 +333,333 @@ export default function AuthorProfile() {
)} +
+
- {author && ( - -
- - - Works - - - Translations - - - Timeline - - - About - - - Discussions - - -
+ {/* Stats cards section */} + {author && ( +
+
+
+ + +
+ +
+
+

Total Readers

+

{readingCount.toLocaleString()}

+
+
+
+ + + +
+ +
+
+

User Rating

+

4.8/5

+
+
+
+ + + +
+ +
+
+

Annotations

+

{annotationCount.toLocaleString()}

+
+
+
+ + + +
+ +
+
+

Academic Citations

+

{citationCount.toLocaleString()}

+
+
+
+
+
+
+ )} - -
-

- Works ({works?.length || 0}) -

-
- View: - - + {/* Main content with tabs */} + {author && ( +
+
+ + + + Works + + + Timeline + + + Related Authors + + + Biography + + + Discussions + + + + {/* Works tab content */} + + {/* Controls row */} +
+ {/* Work type filters */} +
+ + + + + + + + + {(selectedWorkType || selectedYear || selectedLanguage || selectedGenre) && ( + + )} +
+ + {/* View options */} +
+ View: + + + + + + +

List view

+
+
+
+ + + + + + + +

Grid view

+
+
+
- + + {/* Works display */} {worksLoading ? (
{Array.from({ length: 5 }).map((_, i) => ( ))}
- ) : works?.length ? ( - view === 'list' ? ( -
- {works.map(work => ( - - ))} -
- ) : ( -
- {works.map(work => ( - - ))} -
- ) + ) : filteredWorks?.length ? ( + <> + {(selectedWorkType || selectedYear) ? ( + // By filter results +
+ {view === 'list' ? ( +
+ {filteredWorks.map(work => ( + + ))} +
+ ) : ( +
+ {filteredWorks.map(work => ( + + ))} +
+ )} +
+ ) : ( + // By type grouping (default) +
+ {Object.entries(worksByType || {}).map(([type, typeWorks]) => ( +
+

+ {formatTypeName(type)} ({typeWorks.length}) +

+ + {view === 'list' ? ( +
+ {typeWorks.map(work => ( + + ))} +
+ ) : ( +
+ {typeWorks.map(work => ( + + ))} +
+ )} +
+ ))} +
+ )} + ) : ( -

- No works found for this author. -

- )} - - {works && works.length > 5 && ( -
- +
+
+ +

No works found

+

+ We couldn't find any works matching your current filters. +

+ +
)} + + {/* Additional features */} +
+

Reading Statistics

+
+
+
+ Most Read + + {works?.[0]?.title || "N/A"} + +
+ +
+
+
+ Most Translated + + {works?.[1]?.title || "N/A"} + +
+ +
+
+
+ Most Annotated + + {works?.[2]?.title || "N/A"} + +
+ +
+
+
- -

- Translations by {author?.name} will appear here. -

-
- - + {/* Timeline tab content */} + +
+

+ Life & Works Timeline +

+ +
+ {timelineLoading ? (
{Array.from({ length: 4 }).map((_, i) => ( @@ -234,36 +669,579 @@ export default function AuthorProfile() { ) : timeline && timeline.length > 0 ? ( ) : ( -

- No timeline events available for this author. -

+
+
+ +

No timeline available

+

+ We're working on gathering timeline events for {author?.name}. + Check back soon for updates on their life and work milestones. +

+
+
)} - - - -
-

About {author?.name}

-
-

{author?.biography}

- - {/* Additional content would go here in a real application */} -

- Additional biographical information and context about the author would be displayed here, - including details about their life, work, influence, and literary contributions. -

+ + {/* Literary period context */} +
+

Historical Context

+
+
+

Literary Movements

+
+ {['Romanticism', 'Realism', 'Modernism'].map(movement => ( + + {movement} + + ))} +
+

+ {author?.name} lived during a time of significant literary change, + with movements evolving from Romanticism to early Modernist approaches. +

+
+
+

Key Historical Events

+
    +
  • + + Industrial Revolution (1760-1840) +
  • +
  • + + Napoleonic Wars (1803-1815) +
  • +
  • + + Reform movements across Europe (1830s-1840s) +
  • +
+
- -

- Discussions about {author?.name} will appear here. -

+ {/* Related Authors tab content */} + +

+ Related Authors +

+ +
+ {/* We'll mock a few related authors */} + {[1, 2, 3, 4, 5, 6].map((_, i) => { + // Create a mock author + const mockRelatedAuthor = { + id: i + 1000, + name: ['Leo Tolstoy', 'Fyodor Dostoevsky', 'Ivan Turgenev', 'Anton Chekhov', 'Nikolai Gogol', 'Anna Akhmatova'][i], + slug: ['leo-tolstoy', 'fyodor-dostoevsky', 'ivan-turgenev', 'anton-chekhov', 'nikolai-gogol', 'anna-akhmatova'][i], + birthYear: [1828, 1821, 1818, 1860, 1809, 1889][i], + deathYear: [1910, 1881, 1883, 1904, 1852, 1966][i], + country: 'Russia', + portrait: `https://via.placeholder.com/300?text=${i+1}`, + biography: 'A prominent author who influenced literary traditions...' + }; + + return ( + +
+
+ {mockRelatedAuthor.name} +
+
+ +

+ {mockRelatedAuthor.name} +

+ +

+ {mockRelatedAuthor.birthYear}–{mockRelatedAuthor.deathYear} +

+
+
+ +
+ + {['Contemporary', 'Predecessor', 'Influence', 'Influenced By', 'Friend', 'Rival'][i]} + +
+

+ {i === 0 && "Fellow Russian novelist and contemporary of the author. Both explored similar themes and wrote during the same period."} + {i === 1 && "Major influence on the author's work, particularly in psychological characterization and thematic explorations."} + {i === 2 && "Shared literary circles and philosophical ideas that shaped Russian literature of the period."} + {i === 3 && "Continued and evolved the literary tradition established by the author and their contemporaries."} + {i === 4 && "Early influence whose satirical and realistic portrayals set foundations for later Russian literature."} + {i === 5 && "Poetic voice who carried forward literary traditions while evolving new forms of expression."} +

+
+ + + {['Realism', 'Psychological', 'Naturalism', 'Social Commentary', 'Satire', 'Symbolism'][i]} + + + + + +
+ ); + })} +
+ + {/* Influence network visualization */} +
+
+

Literary Influence Network

+ +
+
+
+ +

+ Our interactive literary network visualization shows connections between + {author?.name} and other authors through influence, collaboration, and shared themes. +

+
+
+
+
+ + {/* Biography tab content */} + +
+
+
+

+ Biography of {author?.name} +

+

{author?.biography}

+ + {/* Extended biography sections */} +

Early Life

+

+ Born in {author?.birthYear} in {author?.country || 'their home country'}, + {author?.name} grew up during a transformative period in history. + Their early education and experiences would profoundly shape their literary voice and perspectives. +

+ +

Literary Career

+

+ Their first works appeared in the literary journals of the time, quickly establishing them as a voice to watch. + Over the course of their career, they published numerous works that explored themes of human psychology, + social conditions, and the philosophical questions of their era. +

+ +

Legacy and Influence

+

+ {author?.name}'s contributions to literature extend far beyond their lifetime. Their innovative approaches + to characterization, narrative structure, and thematic exploration influenced generations of writers that followed. + Their work continues to be studied, translated, and adapted worldwide. +

+
+ + {/* Linguistic analysis */} +
+

Literary Style Analysis

+
+
+

Stylistic Features

+
+
+
+ Descriptive Language + High +
+ +
+
+
+ Dialogue Usage + Medium +
+ +
+
+
+ Metaphorical Language + Very High +
+ +
+
+
+
+

Recurring Themes

+
+ {['Nature', 'Love', 'Death', 'Society', 'Freedom', 'Identity', 'Alienation'].map(theme => ( + + {theme} + + ))} +
+

+ {author?.name}'s work consistently explores the tension between individual desires + and societal expectations, often using natural imagery as metaphors for human experience. +

+
+
+
+
+ + {/* Sidebar */} +
+
+ {/* Key facts */} + + + Key Facts + + +
+ Born + {author?.birthYear} +
+
+ Died + {author?.deathYear || 'N/A'} +
+
+ Nationality + {author?.country || 'Unknown'} +
+
+ Literary Period + Romanticism +
+
+ Notable Works + {works?.length || 0} +
+
+
+ + {/* Resources */} + + + Resources + + + + + + + + + + {/* Similar authors */} + + + Similar Authors + + + {['Leo Tolstoy', 'Fyodor Dostoevsky', 'Ivan Turgenev'].map((name, i) => ( +
+
+ {name} +
+ {name} +
+ ))} +
+
+
+
+
+
+ + {/* Discussions tab content */} + +
+

+ Discussions about {author?.name} +

+ +
+ +
+
+ + + +
+
+ + {/* Sample discussions */} +
+ {[ + { + title: "Themes of isolation in the author's early works", + user: "LiteraryScholar", + replies: 24, + views: 342, + lastActive: "2 hours ago", + tags: ["Literary Analysis", "Themes"] + }, + { + title: "Comparing translation styles across different editions", + user: "TranslationEnthusiast", + replies: 18, + views: 256, + lastActive: "1 day ago", + tags: ["Translations", "Comparison"] + }, + { + title: "Historical context affecting literary style", + user: "HistoricalContext", + replies: 32, + views: 420, + lastActive: "3 days ago", + tags: ["Historical", "Context"] + } + ].map((discussion, i) => ( + + +
+
+ {discussion.user} +
+
+

+ {discussion.title} +

+
+ Started by {discussion.user} + + {discussion.replies} replies + + {discussion.views} views + + Last active {discussion.lastActive} +
+
+ {discussion.tags.map(tag => ( + + {tag} + + ))} +
+
+
+ + {i === 0 ? 'Active' : 'Open'} + +
+
+
+
+ ))} +
+ + {/* Community stats */} +
+ + + +
+

Discussions

+

74

+
+
+
+ + + + +
+

Contributors

+

132

+
+
+
+ + + + +
+

Annotations

+

486

+
+
+
+ + + + +
+

Activity

+

High

+
+
+
+
- )} -
-
+
+
+ )} + + {/* Further exploration section */} + {author && ( +
+
+
+

+ Continue Exploring {author.name}'s World +

+

+ Discover more ways to engage with {author.name}'s literary legacy + through our interactive features and community resources. +

+
+ +
+ + +
+ +
+

+ Reading Guides +

+

+ Access in-depth reading guides for all major works, + with historical context, analysis, and discussion questions. +

+ +
+
+ + + +
+ +
+

+ Community Discussions +

+

+ Join active discussions with fellow readers, + scholars, and enthusiasts about the author's works. +

+ +
+
+ + + +
+ +
+

+ Literary Analysis +

+

+ Explore thematic studies, stylistic analyses, + and comparative literary criticism of the author's body of work. +

+ +
+
+
+
+
+ )}
); } diff --git a/client/src/pages/authors/Authors.tsx b/client/src/pages/authors/Authors.tsx index 225542c..1ed86b2 100644 --- a/client/src/pages/authors/Authors.tsx +++ b/client/src/pages/authors/Authors.tsx @@ -1,38 +1,118 @@ import { PageLayout } from "@/components/layout/PageLayout"; import { useQuery } from "@tanstack/react-query"; -import { Author } from "@shared/schema"; +import { Author, Tag } from "@shared/schema"; import { AuthorChip } from "@/components/common/AuthorChip"; -import { Card, CardContent } from "@/components/ui/card"; +import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { SearchBar } from "@/components/common/SearchBar"; -import { useState } from "react"; -import { Link } from "wouter"; -import { ArrowRight, ChevronLeft, ChevronRight } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Link, useLocation } from "wouter"; +import { + ArrowRight, + BookOpen, + Calendar, + ChevronLeft, + ChevronRight, + Clock, + Filter, + Globe, + GraduationCap, + Heart, + History, + LayoutGrid, + LayoutList, + MapPin, + SortAsc, + SortDesc, + User, + Users +} from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { Separator } from "@/components/ui/separator"; +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose } from "@/components/ui/drawer"; +import { Slider } from "@/components/ui/slider"; +import { Switch } from "@/components/ui/switch"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; +import { useSeason } from "@/hooks/use-season"; export default function Authors() { + const { season } = useSeason(); + const [location] = useLocation(); const [currentPage, setCurrentPage] = useState(1); const [searchQuery, setSearchQuery] = useState(""); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [sortBy, setSortBy] = useState<'name' | 'birth' | 'popularity'>('name'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); + const [selectedTimeperiod, setSelectedTimeperiod] = useState([]); + const [selectedCountries, setSelectedCountries] = useState([]); + const [selectedGenres, setSelectedGenres] = useState([]); + const [yearRange, setYearRange] = useState([1500, 2000]); + const [featuredAuthorId, setFeaturedAuthorId] = useState(null); - const PAGE_SIZE = 12; + const PAGE_SIZE = viewMode === 'grid' ? 12 : 8; + // Get authors data const { data: authors, isLoading } = useQuery({ queryKey: [`/api/authors?limit=${PAGE_SIZE}&offset=${(currentPage - 1) * PAGE_SIZE}`], }); + + // Get tags for filtering + const { data: tags } = useQuery({ + queryKey: ['/api/tags'], + }); + + const genreTags = tags?.filter(tag => tag.type === 'genre') || []; + const periodTags = tags?.filter(tag => tag.type === 'period') || []; // For total number of authors (for pagination) const { data: totalAuthors } = useQuery({ queryKey: ['/api/authors/count'], queryFn: async () => { // This is a fallback since we don't have a specific count endpoint in our current implementation - // In a real application, you'd want a dedicated endpoint for this return 120; // Mock total for pagination } }); const totalPages = totalAuthors ? Math.ceil(totalAuthors / PAGE_SIZE) : 0; - // Group authors alphabetically by first letter of name - const groupedAuthors = authors?.reduce>((groups, author) => { + // Set a random featured author when authors are loaded + useEffect(() => { + if (authors && authors.length > 0 && !featuredAuthorId) { + const randomIndex = Math.floor(Math.random() * authors.length); + setFeaturedAuthorId(authors[randomIndex].id); + } + }, [authors, featuredAuthorId]); + + // Get the featured author + const featuredAuthor = authors?.find(author => author.id === featuredAuthorId); + + // Sort authors based on current sort settings + const sortedAuthors = authors ? [...authors].sort((a, b) => { + if (sortBy === 'name') { + return sortOrder === 'asc' + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name); + } else if (sortBy === 'birth') { + const aBirth = a.birthYear || 0; + const bBirth = b.birthYear || 0; + return sortOrder === 'asc' ? aBirth - bBirth : bBirth - aBirth; + } else { + // Popularity - we can simulate this for now with ID + // In a real app, this would be based on view counts or followers + return sortOrder === 'asc' ? a.id - b.id : b.id - a.id; + } + }) : []; + + // Group authors alphabetically by first letter of name for alphabetical view + const groupedAuthors = sortedAuthors?.reduce>((groups, author) => { const firstLetter = author.name.charAt(0).toUpperCase(); if (!groups[firstLetter]) { groups[firstLetter] = []; @@ -44,94 +124,725 @@ export default function Authors() { // Sort the grouped authors alphabetically const sortedGroupKeys = groupedAuthors ? Object.keys(groupedAuthors).sort() : []; + // Get unique countries from authors for filters + const countries = Array.from(new Set(authors?.map(author => author.country).filter(Boolean) as string[])); + + // Handle search const handleSearch = (query: string) => { setSearchQuery(query); + setCurrentPage(1); // In a real app, we would fetch authors by the search query }; + // Handle page change const handlePageChange = (page: number) => { setCurrentPage(page); window.scrollTo({ top: 0, behavior: 'smooth' }); }; + // Toggle sort order + const toggleSortOrder = () => { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + }; + + // Handle time period filter change + const handleTimeperiodChange = (period: string) => { + setSelectedTimeperiod( + selectedTimeperiod.includes(period) + ? selectedTimeperiod.filter(p => p !== period) + : [...selectedTimeperiod, period] + ); + }; + + // Handle country filter change + const handleCountryChange = (country: string) => { + setSelectedCountries( + selectedCountries.includes(country) + ? selectedCountries.filter(c => c !== country) + : [...selectedCountries, country] + ); + }; + + // Handle genre filter change + const handleGenreChange = (genre: string) => { + setSelectedGenres( + selectedGenres.includes(genre) + ? selectedGenres.filter(g => g !== genre) + : [...selectedGenres, genre] + ); + }; + + // Reset all filters + const resetFilters = () => { + setSelectedTimeperiod([]); + setSelectedCountries([]); + setSelectedGenres([]); + setYearRange([1500, 2000]); + }; + + // Function to render author card components + const renderAuthorCard = (author: Author) => { + const workCount = Math.floor(Math.random() * 30) + 1; // Simulate work count + const followerCount = Math.floor(Math.random() * 5000); // Simulate follower count + + return ( + + +
+
+
+ {author.name} +
+ {author.country && ( +
+ + + + {author.country} + + +

{countries.find(c => c.toLowerCase() === author.country?.toLowerCase())}

+
+
+
+
+ )} +
+
+ +

+ {author.name} +

+ +
+
+ + {author.birthYear}–{author.deathYear || 'present'} +
+
+ + {workCount} works +
+
+
+
+ +
+

+ {author.biography?.slice(0, 150) || "No biography available."}{author.biography?.length > 150 ? "..." : ""} +

+
+ +
+ {Array.from({ length: Math.floor(Math.random() * 3) + 1 }).map((_, i) => ( + + {genreTags[Math.floor(Math.random() * genreTags.length)]?.name || "Poetry"} + + ))} +
+
+ + +
+ + {followerCount.toLocaleString()} followers +
+ +
+
+ ); + }; + + // Function to render author list item + const renderAuthorListItem = (author: Author) => { + const workCount = Math.floor(Math.random() * 30) + 1; // Simulate work count + const followerCount = Math.floor(Math.random() * 5000); // Simulate follower count + + return ( +
+
+
+
+ {author.name} +
+ {author.country && ( +
+ {author.country} +
+ )} +
+ +
+
+ +

+ {author.name} +

+ +
+ + {periodTags[Math.floor(Math.random() * periodTags.length)]?.name || "Romanticism"} + + +
+
+ +
+
+ + {author.birthYear}–{author.deathYear || 'present'} +
+
+ + {author.country || 'Unknown'} +
+
+ + {workCount} works +
+
+ + {followerCount.toLocaleString()} followers +
+
+ +

+ {author.biography?.slice(0, 100) || "No biography available."}... +

+
+
+
+ ); + }; + return (
-
+ {/* Header and search */} +

Authors

-

Discover authors from around the world

+

Discover literary voices from across the ages

- -
- - {/* Alphabet navigation */} -
- {[..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"].map(letter => { - const hasAuthors = sortedGroupKeys.includes(letter); - return ( - - {letter} - - ); - })} -
- - {isLoading ? ( -
- {Array.from({ length: 12 }).map((_, i) => ( - - -
-
-
-
-
+
+ + + + + + +
+ + Filter Authors + Narrow down the authors by specific criteria + + +
+ {/* Time period filter */} +
+

Literary Periods

+
+ {periodTags.map(tag => ( + handleTimeperiodChange(tag.name)} + > + {tag.name} + + ))} +
+
+ + {/* Year range filter */} +
+

Year Range

+
+ setYearRange(value as [number, number])} + /> +
+ {yearRange[0]} + {yearRange[1]} +
+
+
+ + {/* Countries filter */} +
+

Countries

+ +
+ {countries.map(country => ( +
+ handleCountryChange(country)} + /> + +
+ ))} +
+
+
+ + {/* Genres filter */} +
+

Primary Genres

+
+ {genreTags.map(tag => ( + handleGenreChange(tag.name)} + > + {tag.name} + + ))} +
-
- - - ))} + + +
+ + + + +
+
+
+
+
- ) : ( -
- {sortedGroupKeys.map(letter => ( -
-

{letter}

-
- {groupedAuthors![letter].map(author => ( - - - -

- {author.biography?.slice(0, 150) || "No biography available."}{author.biography?.length > 150 ? "..." : ""} -

- - - -
-
- ))} +
+ + {/* Featured author section */} + {featuredAuthor && ( +
+
+
+
+
+
+ +
+
+
+ {featuredAuthor.name} +
+ +
+
+ + Featured Author + +
+ +

+ {featuredAuthor.name} +

+ +
+
+ + {featuredAuthor.birthYear}–{featuredAuthor.deathYear || 'present'} +
+ {featuredAuthor.country && ( +
+ + {featuredAuthor.country} +
+ )} +
+ +

+ {featuredAuthor.biography} +

+ +
+ + + + +
- ))} +
)} + {/* View controls bar */} +
+ + + All Authors + Alphabetical + Chronological + + + +
+
+ + + + +
+ + + +
+ + + + + + +

List view

+
+
+
+ + + + + + + +

Grid view

+
+
+
+
+
+
+ + {/* Active filters */} + {(selectedTimeperiod.length > 0 || selectedCountries.length > 0 || selectedGenres.length > 0) && ( +
+ + Active filters: + + + {selectedTimeperiod.map(period => ( + + + {period} + + + ))} + + {selectedCountries.map(country => ( + + + {country} + + + ))} + + {selectedGenres.map(genre => ( + + + {genre} + + + ))} + + +
+ )} + + {/* Author grid or list */} + + {isLoading ? ( +
+ {Array.from({ length: PAGE_SIZE }).map((_, i) => ( + + +
+
+
+
+
+
+
+
+
+
+ ))} +
+ ) : ( + <> + {viewMode === 'grid' ? ( +
+ {sortedAuthors.map(author => renderAuthorCard(author))} +
+ ) : ( +
+ {sortedAuthors.map(author => renderAuthorListItem(author))} +
+ )} + + )} +
+ + + {isLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+
+ {Array.from({ length: 4 }).map((_, j) => ( +
+ ))} +
+
+ ))} +
+ ) : ( + <> + {/* Alphabet navigation */} +
+
+ {[..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"].map(letter => { + const hasAuthors = sortedGroupKeys.includes(letter); + return ( + + {letter} + + ); + })} +
+
+ +
+ {sortedGroupKeys.map(letter => ( +
+

+ {letter} +

+
+ {groupedAuthors![letter].map(author => ( + viewMode === 'grid' ? renderAuthorCard(author) : renderAuthorListItem(author) + ))} +
+
+ ))} +
+ + )} +
+ + + {isLoading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+
+
+ {Array.from({ length: 3 }).map((_, j) => ( +
+ ))} +
+
+ ))} +
+ ) : ( +
+ {/* Group authors by century */} + {Array.from(new Set(authors?.map(a => Math.floor((a.birthYear || 1800) / 100) * 100))).sort().map(century => { + const centuryLabel = `${century}s`; + const centuryAuthors = authors?.filter(a => + Math.floor((a.birthYear || 1800) / 100) * 100 === century + ); + + return ( +
+

+ {centuryLabel} +

+
+ {centuryAuthors?.map(author => ( + viewMode === 'grid' ? renderAuthorCard(author) : renderAuthorListItem(author) + ))} +
+
+ ); + })} +
+ )} +
+ {/* Pagination */} {totalPages > 1 && (