feat: FAQ management from admin, public /faq, homepage section, llms.txt
- Backend: faq_questions table (schema + migration), CRUD + reorder API, Swagger docs - Admin: FAQ page with create/edit, enable/disable, show on homepage, drag reorder - Public /faq page fetches enabled FAQs from API; layout builds dynamic JSON-LD - Homepage: FAQ section under Stay updated (homepage-enabled only) with See full FAQ link - llms.txt: FAQ section uses homepage FAQs from API - i18n: home.faq title/seeFull, admin FAQ nav Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,11 @@ import { NextResponse } from 'next/server';
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://spanglish.com.py';
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
interface LlmsFaq {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
interface LlmsEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -68,10 +73,27 @@ function formatPrice(price: number, currency: string): string {
|
||||
return `${price.toLocaleString()} ${currency}`;
|
||||
}
|
||||
|
||||
async function getHomepageFaqs(): Promise<LlmsFaq[]> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/faq?homepage=true`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const data = await response.json();
|
||||
return (data.faqs || []).map((f: any) => ({
|
||||
question: f.question,
|
||||
answer: f.answer,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const [nextEvent, upcomingEvents] = await Promise.all([
|
||||
const [nextEvent, upcomingEvents, faqs] = await Promise.all([
|
||||
getNextUpcomingEvent(),
|
||||
getUpcomingEvents(),
|
||||
getHomepageFaqs(),
|
||||
]);
|
||||
|
||||
const lines: string[] = [];
|
||||
@@ -145,11 +167,16 @@ export async function GET() {
|
||||
lines.push('');
|
||||
lines.push('## Frequently Asked Questions');
|
||||
lines.push('');
|
||||
lines.push('- **What is Spanglish?** A language exchange community that hosts social events to practice English and Spanish.');
|
||||
lines.push('- **Where are events held?** In Asunción, Paraguay. Specific venues are listed on each event page.');
|
||||
lines.push('- **How do I attend an event?** Visit the events page to see upcoming events and book tickets.');
|
||||
lines.push('- **How much do events cost?** Prices vary by event. Some are free, others have a small cover charge.');
|
||||
lines.push(`- **How do I stay updated?** Follow us on Instagram${instagram ? ` (@${instagram})` : ''}, join our Telegram${telegram ? ` (@${telegram})` : ''}, or check ${siteUrl}/events regularly.`);
|
||||
if (faqs.length > 0) {
|
||||
for (const faq of faqs) {
|
||||
lines.push(`- **${faq.question}** ${faq.answer}`);
|
||||
}
|
||||
} else {
|
||||
lines.push('- **What is Spanglish?** A language exchange community that hosts social events to practice English and Spanish.');
|
||||
lines.push('- **Where are events held?** In Asunción, Paraguay. Specific venues are listed on each event page.');
|
||||
lines.push('- **How do I attend an event?** Visit the events page to see upcoming events and book tickets.');
|
||||
}
|
||||
lines.push(`- More FAQ: ${siteUrl}/faq`);
|
||||
lines.push('');
|
||||
|
||||
const content = lines.join('\n');
|
||||
|
||||
Reference in New Issue
Block a user