Files
full_encoach_platform/frontend/src/pages/FaqPage.tsx
Yamen Ahmad f1c4953a63 feat(v3): restructure project + add complete frontend
- Restructure: move backend from new_project/ to backend/
- Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks)
- Add docs/ with SRS specs, user stories, and workflow documentation
- Update .gitignore for new directory layout

Workflows implemented:
  WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration,
  WF4 General English Exam, WF5 Course Generation,
  WF6 Entity Student Onboarding, AI Course Generation,
  Adaptive Learning Engine UI, White-Label Branding, Score Release

Made-with: Cursor
2026-04-10 17:26:42 +04:00

97 lines
4.1 KiB
TypeScript

import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Search, HelpCircle, Video } from "lucide-react";
import { useFaqCategories, useFaqItems } from "@/hooks/queries";
export default function FaqPage() {
const [search, setSearch] = useState("");
const { data: categories = [], isLoading: lc } = useFaqCategories();
const { data: allItems = [], isLoading: li } = useFaqItems(search ? { search } : undefined);
const filteredCategories = categories.filter(cat =>
allItems.some(item => item.category_id === cat.id),
);
const getItemsForCategory = (catId: number) =>
allItems.filter(item => item.category_id === catId);
if (lc || li) return <div className="flex items-center justify-center min-h-[400px]"><div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" /></div>;
return (
<div className="space-y-6 max-w-3xl mx-auto">
<div className="text-center">
<h1 className="text-3xl font-bold">Frequently Asked Questions</h1>
<p className="text-muted-foreground mt-2">Find answers to common questions about EnCoach.</p>
</div>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
className="pl-10"
placeholder="Search questions..."
value={search}
onChange={e => setSearch(e.target.value)}
/>
</div>
{filteredCategories.length > 0 ? (
<div className="space-y-6">
{filteredCategories.map(cat => {
const catItems = getItemsForCategory(cat.id);
if (catItems.length === 0) return null;
return (
<Card key={cat.id}>
<CardHeader className="pb-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-5 w-5 text-primary" />
<CardTitle className="text-lg">{cat.name}</CardTitle>
<Badge variant="secondary" className="text-xs">{cat.item_count} questions</Badge>
</div>
</CardHeader>
<CardContent>
<Accordion type="multiple">
{catItems
.sort((a, b) => a.sequence - b.sequence)
.map(item => (
<AccordionItem key={item.id} value={String(item.id)}>
<AccordionTrigger className="text-left text-sm font-medium">
{item.question}
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2">
<p className="text-sm text-muted-foreground whitespace-pre-wrap">{item.answer}</p>
{item.video_url && (
<a
href={item.video_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
>
<Video className="h-4 w-4" /> Watch video
</a>
)}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</CardContent>
</Card>
);
})}
</div>
) : (
<Card>
<CardContent className="py-12 text-center text-muted-foreground">
<HelpCircle className="h-12 w-12 mx-auto mb-3 opacity-40" />
<p>{search ? "No questions match your search." : "No FAQ items available yet."}</p>
</CardContent>
</Card>
)}
</div>
);
}