import { useMemo, useState, Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion'; import { useAppContext } from '@/hooks/useAppContext'; import { getFAQCategories, type FAQCategory, type FAQItem } from '@/lib/helpContent'; import { renderInlineMarkup } from '@/lib/helpMarkup'; import { cn } from '@/lib/utils'; // ── Component ───────────────────────────────────────────────────────────────── interface HelpFAQSectionProps { /** Show only these category IDs. Omit to show all. */ categories?: string[]; /** Show only these item IDs (across all categories). Omit to show all. */ items?: string[]; /** Hide category headings (useful when showing a single category or filtered items). */ hideHeadings?: boolean; /** Additional class names for the wrapper. */ className?: string; /** * Rendering variant. * * - `'list'` (default): flat accordion list grouped by category. Used * inside contextual settings pages where the FAQ is a small inline * reference. * - `'cards'`: each FAQ item renders as its own rounded card with a * single-item accordion inside. Used by the About page so the FAQ * reads as a designed section rather than an undifferentiated dump. */ variant?: 'list' | 'cards'; /** * When `variant='cards'`, render a row of category-tab pills above the * cards. Only one category is shown at a time. No effect on `'list'`. */ tabs?: boolean; /** * When `variant='list'`, choose between the default pill-tinted category * heading (used by inline contextual FAQs in settings pages) and a * quieter 'reference' tone used by the About page's `Need help?` * section where the FAQ is the main content rather than a sidebar. */ listTone?: 'default' | 'reference'; } /** * Reusable FAQ accordion section. * * Renders FAQ items from `helpContent.ts` in collapsible accordions grouped by * category. Accepts filter props so it can be dropped into any page to show a * relevant subset of questions. * * @example * // Full FAQ (legacy list layout) * * * // About-page integrated FAQ (cards + category tabs) * * * // Only payments questions (wallet settings page) * * * // Specific questions (onboarding) * */ export function HelpFAQSection({ categories, items, hideHeadings, className, variant = 'list', tabs = false, listTone = 'default', }: HelpFAQSectionProps) { const { config } = useAppContext(); const { i18n } = useTranslation(); // `getFAQCategories` resolves strings against `i18n.language` at call // time. Capturing the language in a local lets us include it as a memo // dep so a language switch re-runs the resolver. const lng = i18n.language; const filteredCategories = useMemo(() => { let cats: FAQCategory[] = getFAQCategories(config.appName); // Drop hidden categories from the default render. They still exist in // the underlying template so `HelpTip` can look up individual items by // ID, but they don't show up in the FAQ accordion. if (!categories && !items) { cats = cats.filter((c) => !c.hidden); } // Filter to specific categories if (categories) { cats = cats.filter((c) => categories.includes(c.id)); } // Filter to specific items if (items) { cats = cats .map((c) => ({ ...c, items: c.items.filter((i) => items.includes(i.id)), })) .filter((c) => c.items.length > 0); } return cats; // `lng` is in the dep list to force re-resolution on a language switch. // eslint-disable-next-line react-hooks/exhaustive-deps }, [categories, items, config.appName, lng]); // Tab state: first category is selected by default. const [activeTab, setActiveTab] = useState( filteredCategories[0]?.id ?? null, ); if (filteredCategories.length === 0) return null; // ── Card variant ───────────────────────────────────────────────────────── if (variant === 'cards') { const showTabs = tabs && filteredCategories.length > 1; const visibleCategories = showTabs ? filteredCategories.filter((c) => c.id === activeTab) : filteredCategories; return (
{/* Category tab pills */} {showTabs && (
{filteredCategories.map((category) => { const active = category.id === activeTab; return ( ); })}
)} {visibleCategories.map((category) => ( {!hideHeadings && !showTabs && (

{category.label}

)} {/* Masonry-style two-column grid on md+ */}
{category.items.map((item) => ( ))}
))}
); } // ── List variant (default, unchanged behavior) ─────────────────────────── const reference = listTone === 'reference'; return (
{filteredCategories.map((category, catIndex) => ( {/* Category heading */} {!hideHeadings && ( reference ? (

{category.label}

) : (

{category.label}

) )} {category.items.map((item) => ( ))}
))}
); } function FAQAccordionItem({ item, reference, }: { item: FAQItem; reference?: boolean; }) { if (reference) { // 'reference' mode: each Q&A reads as a substantial card-row with // a left orange-accent rule that lights up on hover and open. Used // by the About page where the FAQ is a first-class chapter rather // than a sidebar. return ( {/* Left accent rule: orange when open, transparent otherwise */} {item.question} {item.answer.map((paragraph, i) => (

{renderInlineMarkup(paragraph)}

))}
); } return ( {item.question} {item.answer.map((paragraph, i) => (

{renderInlineMarkup(paragraph)}

))}
); } /** * A single FAQ entry rendered as its own rounded card. Wraps a one-item * Radix Accordion so the click-to-expand UX is preserved. Used by the * About page's card-variant FAQ section. */ function FAQCard({ item }: { item: FAQItem }) { return (
{item.question} {item.answer.map((paragraph, i) => (

{renderInlineMarkup(paragraph)}

))}
); }