Security recovery: hold sweep, dashboard updates, and admin fixes.
This commit is contained in:
@@ -1,116 +1,22 @@
|
||||
'use client';
|
||||
import { FaqItem } from '@/lib/api';
|
||||
import FaqClient from './FaqClient';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import { faqApi, FaqItem } from '@/lib/api';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
import clsx from 'clsx';
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
export default function FAQPage() {
|
||||
const { locale } = useLanguage();
|
||||
const [faqs, setFaqs] = useState<FaqItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
faqApi.getList().then((res) => {
|
||||
if (!cancelled) {
|
||||
setFaqs(res.faqs);
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
const toggleFAQ = (index: number) => {
|
||||
setOpenIndex(openIndex === index ? null : index);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page max-w-3xl flex justify-center py-20">
|
||||
<div className="animate-spin w-10 h-10 border-4 border-primary-yellow border-t-transparent rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Fetch the published FAQ list on the server so the questions and answers are
|
||||
// rendered into the initial HTML (crawlers see the content without running JS).
|
||||
async function getFaqs(): Promise<FaqItem[]> {
|
||||
try {
|
||||
const res = await fetch(`${apiUrl}/api/faq`, { next: { revalidate: 60 } });
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.faqs || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page max-w-3xl">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-primary-dark mb-4">
|
||||
{locale === 'es' ? 'Preguntas Frecuentes' : 'Frequently Asked Questions'}
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
{locale === 'es'
|
||||
? 'Encuentra respuestas a las preguntas más comunes sobre Spanglish'
|
||||
: 'Find answers to the most common questions about Spanglish'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{faqs.length === 0 ? (
|
||||
<Card className="p-8 text-center">
|
||||
<p className="text-gray-600">
|
||||
{locale === 'es'
|
||||
? 'No hay preguntas frecuentes publicadas en este momento.'
|
||||
: 'No FAQ questions are published at the moment.'}
|
||||
</p>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<Card key={faq.id} className="overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleFAQ(index)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between text-left hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<span className="font-semibold text-primary-dark pr-4">
|
||||
{locale === 'es' && faq.questionEs ? faq.questionEs : faq.question}
|
||||
</span>
|
||||
<ChevronDownIcon
|
||||
className={clsx(
|
||||
'w-5 h-5 text-gray-500 flex-shrink-0 transition-transform duration-200',
|
||||
openIndex === index && 'transform rotate-180'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
className={clsx(
|
||||
'overflow-hidden transition-all duration-200',
|
||||
openIndex === index ? 'max-h-96' : 'max-h-0'
|
||||
)}
|
||||
>
|
||||
<div className="px-6 pb-4 text-gray-600">
|
||||
{locale === 'es' && faq.answerEs ? faq.answerEs : faq.answer}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="mt-12 p-8 text-center bg-primary-yellow/10">
|
||||
<h2 className="text-xl font-semibold text-primary-dark mb-2">
|
||||
{locale === 'es' ? '¿Todavía tienes preguntas?' : 'Still have questions?'}
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{locale === 'es'
|
||||
? 'No dudes en contactarnos. ¡Estamos aquí para ayudarte!'
|
||||
: "Don't hesitate to reach out. We're here to help!"}
|
||||
</p>
|
||||
<a
|
||||
href="/contact"
|
||||
className="inline-flex items-center justify-center px-6 py-3 bg-primary-yellow text-primary-dark font-semibold rounded-btn hover:bg-primary-yellow/90 transition-colors"
|
||||
>
|
||||
{locale === 'es' ? 'Contáctanos' : 'Contact Us'}
|
||||
</a>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default async function FAQPage() {
|
||||
const faqs = await getFaqs();
|
||||
return <FaqClient initialFaqs={faqs} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user