frontend.blogwoman.de/src/components/blocks/SeriesBlock.tsx

278 lines
7.9 KiB
TypeScript

import Image from 'next/image'
import Link from 'next/link'
import { cn, getImageUrl } from '@/lib/utils'
import { SeriesPill } from '@/components/ui'
import { getSeries } from '@/lib/api'
import { RichTextRenderer } from './RichTextRenderer'
import type { SeriesBlock as SeriesBlockType, Series } from '@/lib/types'
type SeriesBlockProps = Omit<SeriesBlockType, 'blockType'>
export async function SeriesBlock({
title,
subtitle,
displayMode,
selectedSeries,
layout = 'grid',
showDescription = true,
}: SeriesBlockProps) {
// Fetch series if not using selected mode
let items: Series[] = []
if (displayMode === 'selected' && selectedSeries) {
items = selectedSeries
} else {
const seriesData = await getSeries()
items = seriesData.docs
}
if (!items || items.length === 0) return null
return (
<section className="py-16 md:py-20">
<div className="container">
{/* Section Header */}
{(title || subtitle) && (
<div className="text-center max-w-2xl mx-auto mb-12">
{title && <h2 className="mb-4">{title}</h2>}
{subtitle && (
<p className="text-lg text-espresso/80">{subtitle}</p>
)}
</div>
)}
{/* Series */}
{layout === 'featured' ? (
<FeaturedLayout items={items} showDescription={showDescription} />
) : layout === 'list' ? (
<ListLayout items={items} showDescription={showDescription} />
) : (
<GridLayout items={items} showDescription={showDescription} />
)}
</div>
</section>
)
}
function GridLayout({
items,
showDescription,
}: {
items: Series[]
showDescription?: boolean
}) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((series) => (
<SeriesCard
key={series.id}
series={series}
showDescription={showDescription}
/>
))}
</div>
)
}
function ListLayout({
items,
showDescription,
}: {
items: Series[]
showDescription?: boolean
}) {
return (
<div className="max-w-4xl mx-auto space-y-6">
{items.map((series) => (
<Link
key={series.id}
href={`/serien/${series.slug}`}
className="group block"
>
<article className="flex gap-6 bg-soft-white border border-warm-gray rounded-xl p-6 transition-all duration-300 hover:shadow-lg">
{/* Logo/Image */}
<div className="relative w-24 h-24 flex-shrink-0 rounded-lg overflow-hidden bg-ivory flex items-center justify-center">
{series.logo ? (
<Image
src={series.logo.url}
alt={series.title}
fill
className="object-contain p-2"
/>
) : series.coverImage ? (
<Image
src={series.coverImage.url}
alt={series.title}
fill
className="object-cover"
/>
) : (
<SeriesPill series={series.slug} size="lg">
{series.title.slice(0, 2).toUpperCase()}
</SeriesPill>
)}
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<h3 className="text-xl font-semibold mb-2 group-hover:text-brass transition-colors">
{series.title}
</h3>
{showDescription && series.description && (
<div className="text-espresso/80 line-clamp-2">
<RichTextRenderer content={series.description} />
</div>
)}
</div>
</article>
</Link>
))}
</div>
)
}
function FeaturedLayout({
items,
showDescription,
}: {
items: Series[]
showDescription?: boolean
}) {
const [featured, ...rest] = items
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Featured Series */}
<Link
href={`/serien/${featured.slug}`}
className="group block lg:row-span-2"
>
<article
className="relative h-full min-h-[400px] rounded-2xl overflow-hidden"
style={{
backgroundColor: featured.brandColor || '#C6A47E',
}}
>
{featured.coverImage && (
<Image
src={featured.coverImage.url}
alt={featured.title}
fill
className="object-cover opacity-30"
/>
)}
<div className="absolute inset-0 flex flex-col justify-end p-8">
{featured.logo && (
<div className="relative w-32 h-16 mb-4">
<Image
src={featured.logo.url}
alt=""
fill
className="object-contain object-left"
/>
</div>
)}
<h3 className="text-2xl lg:text-3xl font-semibold text-soft-white mb-3">
{featured.title}
</h3>
{showDescription && featured.description && (
<div className="text-soft-white/80 line-clamp-3">
<RichTextRenderer content={featured.description} />
</div>
)}
</div>
</article>
</Link>
{/* Other Series */}
<div className="space-y-6">
{rest.slice(0, 3).map((series) => (
<SeriesCard
key={series.id}
series={series}
showDescription={false}
compact
/>
))}
</div>
</div>
)
}
interface SeriesCardProps {
series: Series
showDescription?: boolean
compact?: boolean
}
function SeriesCard({ series, showDescription, compact }: SeriesCardProps) {
const imageUrl = getImageUrl(series.coverImage) || getImageUrl(series.logo)
return (
<Link href={`/serien/${series.slug}`} className="group block">
<article
className={cn(
'bg-soft-white border border-warm-gray rounded-2xl overflow-hidden transition-all duration-300 hover:-translate-y-1 hover:shadow-xl',
compact && 'flex items-center gap-4 p-4'
)}
>
{/* Image */}
{!compact && (
<div
className="relative aspect-video"
style={{ backgroundColor: series.brandColor || '#C6A47E' }}
>
{imageUrl && (
<Image
src={imageUrl}
alt={series.title}
fill
className={cn(
series.logo ? 'object-contain p-8' : 'object-cover opacity-50'
)}
/>
)}
</div>
)}
{compact && (
<div
className="relative w-16 h-16 flex-shrink-0 rounded-lg overflow-hidden flex items-center justify-center"
style={{ backgroundColor: series.brandColor || '#C6A47E' }}
>
{series.logo ? (
<Image
src={series.logo.url}
alt=""
fill
className="object-contain p-2"
/>
) : (
<span className="text-soft-white font-bold text-lg">
{series.title.slice(0, 2).toUpperCase()}
</span>
)}
</div>
)}
{/* Content */}
<div className={cn(!compact && 'p-6', compact && 'flex-1 min-w-0')}>
<h3
className={cn(
'font-semibold group-hover:text-brass transition-colors',
compact ? 'text-lg' : 'text-xl mb-2'
)}
>
{series.title}
</h3>
{showDescription && !compact && series.description && (
<div className="text-espresso/80 line-clamp-2">
<RichTextRenderer content={series.description} />
</div>
)}
</div>
</article>
</Link>
)
}