| 1 | import React from 'react'
|
|---|
| 2 | import { useParams, useNavigate } from 'react-router-dom'
|
|---|
| 3 | import { ArrowLeft } from 'lucide-react'
|
|---|
| 4 | import { useStoryStore } from '../../store/storyStore'
|
|---|
| 5 | import { StoryGrid } from '../../components/story/StoryGrid'
|
|---|
| 6 | import { getGenreGradient } from '../../components/story/GenreBadge'
|
|---|
| 7 |
|
|---|
| 8 | function toTitleCase(s: string) {
|
|---|
| 9 | return s.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
|
|---|
| 10 | }
|
|---|
| 11 |
|
|---|
| 12 | export const GenrePage: React.FC = () => {
|
|---|
| 13 | const { genre } = useParams<{ genre: string }>()
|
|---|
| 14 | const navigate = useNavigate()
|
|---|
| 15 | const { stories } = useStoryStore()
|
|---|
| 16 |
|
|---|
| 17 | const genreName = toTitleCase(genre || '')
|
|---|
| 18 | const gradient = getGenreGradient(genreName)
|
|---|
| 19 | const filtered = stories.filter(
|
|---|
| 20 | s => s.status === 'published' && s.genres.some(g => g.toLowerCase() === genreName.toLowerCase())
|
|---|
| 21 | )
|
|---|
| 22 |
|
|---|
| 23 | return (
|
|---|
| 24 | <div className="max-w-7xl mx-auto px-4 py-8">
|
|---|
| 25 | {/* Hero */}
|
|---|
| 26 | <div className={`relative rounded-2xl overflow-hidden mb-8 h-40 bg-gradient-to-br ${gradient}`}>
|
|---|
| 27 | <div className="absolute inset-0 flex flex-col justify-end p-8">
|
|---|
| 28 | <button
|
|---|
| 29 | onClick={() => navigate('/genres')}
|
|---|
| 30 | className="flex items-center gap-2 text-white/70 hover:text-white text-sm mb-3 transition-colors w-fit"
|
|---|
| 31 | >
|
|---|
| 32 | <ArrowLeft size={16} />
|
|---|
| 33 | All Genres
|
|---|
| 34 | </button>
|
|---|
| 35 | <h1 className="font-serif text-3xl font-bold text-white">{genreName}</h1>
|
|---|
| 36 | <p className="text-white/70 text-sm mt-1">{filtered.length} stories</p>
|
|---|
| 37 | </div>
|
|---|
| 38 | <div className="absolute top-4 right-8 w-32 h-32 rounded-full bg-white/5 blur-xl" />
|
|---|
| 39 | </div>
|
|---|
| 40 |
|
|---|
| 41 | <StoryGrid
|
|---|
| 42 | stories={filtered}
|
|---|
| 43 | emptyMessage={`No ${genreName} stories published yet.`}
|
|---|
| 44 | />
|
|---|
| 45 | </div>
|
|---|
| 46 | )
|
|---|
| 47 | }
|
|---|
| 48 |
|
|---|
| 49 | export const GenresListPage: React.FC = () => {
|
|---|
| 50 | const navigate = useNavigate()
|
|---|
| 51 | const { genres, stories } = useStoryStore()
|
|---|
| 52 | const publishedStories = stories.filter(s => s.status === 'published')
|
|---|
| 53 |
|
|---|
| 54 | return (
|
|---|
| 55 | <div className="max-w-7xl mx-auto px-4 py-8">
|
|---|
| 56 | <div className="mb-8">
|
|---|
| 57 | <h1 className="font-serif text-3xl font-bold text-white mb-2">Explore Genres</h1>
|
|---|
| 58 | <p className="text-slate-400">Find stories by your favorite genre</p>
|
|---|
| 59 | </div>
|
|---|
| 60 | <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
|---|
| 61 | {genres.map(genre => {
|
|---|
| 62 | const gradient = getGenreGradient(genre.name)
|
|---|
| 63 | return (
|
|---|
| 64 | <button
|
|---|
| 65 | key={genre.genre_id}
|
|---|
| 66 | onClick={() => navigate(`/genres/${genre.name.toLowerCase().replace(' ', '-')}`)}
|
|---|
| 67 | className={`relative rounded-2xl overflow-hidden h-32 bg-gradient-to-br ${gradient} hover:scale-105 transition-transform group`}
|
|---|
| 68 | >
|
|---|
| 69 | <div className="absolute inset-0 flex flex-col items-center justify-center p-4">
|
|---|
| 70 | <p className="font-serif font-semibold text-white text-center">{genre.name}</p>
|
|---|
| 71 | <p className="text-white/60 text-xs mt-1">{publishedStories.filter(s => s.genres.some(g => g.toLowerCase() === genre.name.toLowerCase())).length} stories</p>
|
|---|
| 72 | </div>
|
|---|
| 73 | <div className="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition-colors" />
|
|---|
| 74 | </button>
|
|---|
| 75 | )
|
|---|
| 76 | })}
|
|---|
| 77 | </div>
|
|---|
| 78 | </div>
|
|---|
| 79 | )
|
|---|
| 80 | }
|
|---|