frontend.blogwoman.de/src/components/blocks/FAQBlock.tsx
CCS Admin 12c8461108 fix: resolve lint errors (server.js ignore, unused vars)
- Add server.js to ESLint globalIgnores (CJS file for Passenger)
- Prefix unused destructured vars with underscore
- Comment out unused PAYLOAD_URL constant
- Configure underscore-prefix pattern for unused vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:49:23 +00:00

198 lines
5.4 KiB
TypeScript

'use client'
import { useState } from 'react'
import Script from 'next/script'
import { cn } from '@/lib/utils'
import { RichTextRenderer } from './RichTextRenderer'
import type { FAQBlock as FAQBlockType, FAQ } from '@/lib/types'
type FAQBlockProps = Omit<FAQBlockType, 'blockType'> & {
faqs?: FAQ[]
}
export function FAQBlock({
title,
subtitle,
displayMode,
selectedFaqs,
_filterCategory,
layout = 'accordion',
expandFirst = false,
showSchema = true,
faqs: externalFaqs,
}: FAQBlockProps) {
// Use selectedFaqs if displayMode is 'selected', otherwise use externalFaqs
const items = displayMode === 'selected' ? selectedFaqs : externalFaqs
if (!items || items.length === 0) return null
// Generate JSON-LD schema data
const schemaData = showSchema
? {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: items.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: extractTextFromRichText(faq.answer),
},
})),
}
: 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>
)}
{/* FAQ Items */}
<div className="max-w-3xl mx-auto">
{layout === 'accordion' ? (
<AccordionFAQ items={items} expandFirst={expandFirst} />
) : layout === 'grid' ? (
<GridFAQ items={items} />
) : (
<ListFAQ items={items} />
)}
</div>
{/* JSON-LD Schema using Next.js Script component for safety */}
{schemaData && (
<Script
id="faq-schema"
type="application/ld+json"
strategy="afterInteractive"
>
{JSON.stringify(schemaData)}
</Script>
)}
</div>
</section>
)
}
function AccordionFAQ({
items,
expandFirst,
}: {
items: FAQ[]
expandFirst: boolean
}) {
const [openIndex, setOpenIndex] = useState<number | null>(
expandFirst ? 0 : null
)
return (
<div className="space-y-4">
{items.map((faq, index) => (
<div
key={faq.id}
className="border border-warm-gray rounded-xl overflow-hidden"
>
<button
type="button"
className="w-full px-6 py-4 flex items-center justify-between text-left bg-soft-white hover:bg-ivory transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brass focus-visible:ring-inset"
onClick={() => setOpenIndex(openIndex === index ? null : index)}
aria-expanded={openIndex === index}
>
<span className="font-headline text-lg font-medium text-espresso pr-4">
{faq.question}
</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className={cn(
'w-5 h-5 text-brass transition-transform duration-200 flex-shrink-0',
openIndex === index && 'rotate-180'
)}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</button>
<div
className={cn(
'grid transition-all duration-300 ease-out',
openIndex === index
? 'grid-rows-[1fr] opacity-100'
: 'grid-rows-[0fr] opacity-0'
)}
>
<div className="overflow-hidden">
<div className="px-6 pb-6 pt-2">
<RichTextRenderer content={faq.answer} />
</div>
</div>
</div>
</div>
))}
</div>
)
}
function ListFAQ({ items }: { items: FAQ[] }) {
return (
<div className="space-y-8">
{items.map((faq) => (
<div key={faq.id}>
<h3 className="text-xl font-semibold mb-3">{faq.question}</h3>
<RichTextRenderer content={faq.answer} />
</div>
))}
</div>
)
}
function GridFAQ({ items }: { items: FAQ[] }) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{items.map((faq) => (
<div
key={faq.id}
className="bg-soft-white border border-warm-gray rounded-xl p-6"
>
<h3 className="text-lg font-semibold mb-3">{faq.question}</h3>
<RichTextRenderer content={faq.answer} />
</div>
))}
</div>
)
}
// Helper to extract plain text from RichText for schema
function extractTextFromRichText(richText: FAQ['answer']): string {
if (!richText?.root?.children) return ''
function extractFromNode(node: Record<string, unknown>): string {
if (node.text) return node.text as string
const children = node.children as Record<string, unknown>[] | undefined
if (children) {
return children.map(extractFromNode).join('')
}
return ''
}
return richText.root.children
.map((node) => extractFromNode(node as Record<string, unknown>))
.join(' ')
.trim()
}