canina/frontend/application/components/FAQSection.tsx
parsa aghaei 2c452215c3
All checks were successful
Deploy Canina / deploy (push) Successful in 1m41s
feat: resolve doctors query error, add FAQ, testimonials, blog preview, and full menu management
2026-08-19 17:20:01 +03:30

133 lines
4.7 KiB
TypeScript

"use client";
import React, { useEffect, useState } from "react";
import { HelpCircle, ChevronDown } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import { useSettingsStore } from "../lib/store/settingsStore";
import api from "../lib/services/api";
export interface FAQItem {
id: string;
question: string;
answer: string;
order: number;
}
export default function FAQSection() {
const getText = useSettingsStore((state) => state.getText);
const [faqs, setFaqs] = useState<FAQItem[]>([]);
const [openIndex, setOpenIndex] = useState<number | null>(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function loadFaqs() {
try {
setIsLoading(true);
const res = await api.get("/faq");
const list = Array.isArray(res.data) ? res.data : [];
setFaqs(list);
} catch (err) {
console.error("Failed to load FAQs:", err);
setFaqs([]);
} finally {
setIsLoading(false);
}
}
loadFaqs();
}, []);
if (!isLoading && faqs.length === 0) {
return null;
}
// Schema.org FAQPage JSON-LD structure
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqs.map((faq) => ({
"@type": "Question",
"name": faq.question,
"acceptedAnswer": {
"@type": "Answer",
"text": faq.answer,
},
})),
};
return (
<section className="py-20 bg-medical-gray-50/70 border-t border-medical-gray-100 font-vazir" dir="rtl">
{/* Schema.org FAQPage Rich Snippet */}
{faqs.length > 0 && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
)}
<div className="max-w-4xl mx-auto px-4">
{/* Header */}
<div className="text-center mb-12">
<div className="inline-flex items-center gap-2 px-3 py-1 bg-amber-500/10 text-amber-600 rounded-full text-xs font-black uppercase tracking-widest mb-3">
<HelpCircle className="w-4 h-4" />
<span>{getText("faq_badge", "سوالات پرتکرار")}</span>
</div>
<h2 className="text-3xl md:text-4xl font-black text-medical-gray-900 font-lalezar leading-tight">
{getText("faq_title", "سوالات متداول")}
</h2>
<p className="text-sm md:text-base text-medical-gray-500 font-medium mt-2">
{getText("faq_subtitle", "پاسخ سوالاتی که اغلب از ما می‌پرسید")}
</p>
</div>
{/* FAQ List */}
{isLoading ? (
<div className="space-y-4">
{[1, 2, 3].map((i) => (
<div key={i} className="h-16 bg-white rounded-2xl border border-medical-gray-100 animate-pulse" />
))}
</div>
) : (
<div className="space-y-4">
{faqs.map((faq, idx) => {
const isOpen = openIndex === idx;
return (
<div
key={faq.id || idx}
className="bg-white rounded-2xl border border-medical-gray-100 overflow-hidden shadow-xs hover:border-canina-blue/30 transition-all"
>
<button
onClick={() => setOpenIndex(isOpen ? null : idx)}
className="w-full p-6 text-right flex items-center justify-between gap-4 font-bold text-medical-gray-900 hover:text-canina-blue transition-colors"
>
<span className="text-base font-shabnam">{faq.question}</span>
<ChevronDown
className={`w-5 h-5 shrink-0 text-medical-gray-400 transition-transform duration-300 ${
isOpen ? "rotate-180 text-canina-blue" : ""
}`}
/>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden"
>
<div className="px-6 pb-6 pt-0 text-sm text-medical-gray-600 leading-relaxed font-normal border-t border-medical-gray-50">
{faq.answer}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
)}
</div>
</section>
);
}