بلاک‌ها · پشتیبانی و محتوا

پرسش‌های متداول

پیش‌نمایش تمام‌صفحه

معرفی با جست‌وجوی بزرگ، دسته‌ها کنارِ فهرست (موبایل: نوارِ افقی)، آکاردئونِ نرم با نشانگرِ به‌علاوه، حالتِ خالی و کارتِ «هنوز سؤال دارید؟».

۱. نصب کامپوننت‌ها

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: accordionavatarbadgebuttoncontrol-sizeempty-stateiconspersiansearch-fieldtoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/toggle-group.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/toggle-group.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/toggle-group.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

faq.tsx
"use client"

import * as React from "react"

import { AvatarGenderIcon, ChevronLeftIcon } from "@/components/icons"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Avatar, AvatarGroup } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { SearchField } from "@/components/ui/search-field"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { cn } from "@/lib/utils"
import { stockImages } from "@/lib/stock-images"
import { toFaDigits, toSearchKey } from "@/lib/persian"

const TOPICS = [
  {
    id: "general",
    title: "عمومی",
    faq: [
      { q: "نسخهٔ رایگان دارید؟", a: "بله؛ ۱۴ روز همهٔ امکانات را بدونِ کارتِ بانکی امتحان کنید. اگر خواستید، یک جلسهٔ ۳۰ دقیقه‌ای راه‌اندازی هم رایگان برایتان می‌گذاریم." },
      { q: "برای کارِ تیمی هم مناسب است؟", a: "تا ۵ نفر در پلنِ پایه و بی‌نهایت در پلنِ سازمانی؛ هر نفر نقش و دسترسی خودش را دارد." },
      { q: "اطلاعاتم کجا نگه‌داری می‌شود؟", a: "روی سرورهای داخلِ ایران با پشتیبان‌گیری روزانه و رمزنگاری." },
    ],
  },
  {
    id: "billing",
    title: "پرداخت و پلن",
    faq: [
      { q: "می‌توانم پلن را بعداً عوض کنم؟", a: "هر وقت خواستید؛ مبلغ به نسبتِ روزهای باقی‌مانده حساب می‌شود." },
      { q: "فاکتورِ رسمی می‌دهید؟", a: "بله؛ فاکتورِ رسمی با کدِ اقتصادی، بعد از هر پرداخت در بخشِ صورت‌حساب‌ها." },
      { q: "سیاستِ لغو چیست؟", a: "تا ۷ روز پس از خرید، کلِ مبلغ برمی‌گردد؛ بعد از آن تا پایانِ دوره فعال می‌ماند." },
    ],
  },
  {
    id: "account",
    title: "حساب کاربری",
    faq: [
      { q: "ایمیلِ حسابم را چطور عوض کنم؟", a: "از تنظیمات › حساب، ایمیلِ جدید را بنویسید و لینکِ تأییدی را که می‌فرستیم باز کنید." },
      { q: "رمزم را فراموش کرده‌ام", a: "در صفحهٔ ورود «فراموشی رمز» را بزنید؛ کدِ یک‌بارمصرف به موبایلتان پیامک می‌شود." },
    ],
  },
  {
    id: "security",
    title: "امنیت",
    faq: [{ q: "ورودِ دومرحله‌ای دارید؟", a: "بله؛ با پیامک یا اپ‌های رمزِ یک‌بارمصرف، از تنظیمات › امنیت فعالش کنید." }],
  },
]

/**
 * صفحهٔ پرسش‌های متداول: معرفی با جست‌وجو، دسته‌ها کنارِ فهرست (در موبایل
 * نوارِ افقی)، آکاردئونِ نرم با نشانگرِ به‌علاوه و کارتِ «هنوز سؤال دارید؟».
 * با جست‌وجو، نتیجه از همهٔ دسته‌ها می‌آید و دسته‌ها کنار می‌روند.
 */
export default function FaqBlock() {
  const [topic, setTopic] = React.useState(TOPICS[0].id)
  const [query, setQuery] = React.useState("")
  const key = toSearchKey(query)
  const active = TOPICS.find((item) => item.id === topic) ?? TOPICS[0]
  const items = query
    ? TOPICS.flatMap((item) => item.faq.filter((faq) => toSearchKey(faq.q + faq.a).includes(key)))
    : active.faq

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="flex flex-col">
        {/* معرفی روی زمینهٔ ته‌رنگ با علامت‌های سؤالِ بزرگ و تزئینی. */}
        <section className="relative overflow-hidden bg-[color-mix(in_oklab,var(--brand-secondary)_8%,var(--background))] px-4 py-20 sm:px-6">
          <span aria-hidden className="pointer-events-none absolute -top-10 start-[8%] text-[12rem] leading-none font-black text-[color-mix(in_oklab,var(--brand-secondary)_14%,var(--background))] select-none">
            ؟
          </span>
          <span aria-hidden className="pointer-events-none absolute end-[6%] -bottom-16 text-[16rem] leading-none font-black text-[color-mix(in_oklab,var(--brand-secondary)_14%,var(--background))] select-none">
            ؟
          </span>
          <div className="relative mx-auto flex max-w-2xl flex-col items-center gap-4 text-center">
            <Badge variant="soft">پشتیبانی</Badge>
            <h1 className="text-4xl leading-snug font-bold sm:text-5xl sm:leading-snug">چطور کمکتان کنیم؟</h1>
            <p className="text-lg leading-8 text-muted-foreground">پاسخِ پرسش‌های رایج این‌جاست؛ اگر پیدا نکردید، تیمِ پشتیبانی همان روز جواب می‌دهد.</p>
            <ControlSizeProvider size="xl" className="mt-4 w-full max-w-lg">
              <SearchField aria-label="جست‌وجو در پرسش‌ها" placeholder="مثلاً «لغو اشتراک»" value={query} onValueChange={setQuery} className="w-full bg-background" />
            </ControlSizeProvider>
          </div>
        </section>

        <section aria-labelledby="faq-title" className="mx-auto grid w-full max-w-6xl grid-cols-1 gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[16rem_minmax(0,1fr)]">
          <div className="flex flex-col gap-8">
            <div className="flex flex-col gap-2">
              <h2 id="faq-title" className="text-2xl font-bold">
                پرسش‌های متداول
              </h2>
              <p className="text-sm leading-7 text-muted-foreground">دسته را انتخاب کنید یا بالای صفحه جست‌وجو کنید.</p>
            </div>

            {/* دسته‌ها: در دسکتاپ فهرستِ عمودی با تعداد، در موبایل نوارِ افقی. */}
            {!query && (
              <>
                <nav aria-label="دسته‌بندی پرسش‌ها" className="hidden flex-col gap-1 lg:flex">
                  {TOPICS.map((item) => (
                    <Button
                      key={item.id}
                      variant={item.id === topic ? "outline" : "ghost"}
                      fullWidth
                      aria-current={item.id === topic ? "true" : undefined}
                      onClick={() => setTopic(item.id)}
                      className={cn("justify-between rounded-xl", item.id === topic ? "border-foreground font-semibold" : "font-normal text-muted-foreground")}
                    >
                      {item.title}
                      <span className="flex items-center gap-2 text-xs text-muted-foreground">
                        {toFaDigits(item.faq.length)}
                        <ChevronLeftIcon aria-hidden className="size-4" />
                      </span>
                    </Button>
                  ))}
                </nav>
                <div className="max-w-full overflow-x-auto lg:hidden">
                  <ToggleGroup type="single" variant="segmented" value={topic} onValueChange={(value) => value && setTopic(value as string)} aria-label="دسته‌بندی پرسش‌ها">
                    {TOPICS.map((item) => (
                      <ToggleGroupItem key={item.id} value={item.id} className="whitespace-nowrap">
                        {item.title}
                      </ToggleGroupItem>
                    ))}
                  </ToggleGroup>
                </div>
              </>
            )}
          </div>

          <div aria-live="polite">
            {query && <p className="mb-4 text-sm text-muted-foreground">{toFaDigits(items.length)} نتیجه برای «{query}»</p>}
            {items.length === 0 ? (
              <EmptyState variant="bordered">
                <EmptyStateTitle>پاسخی برای «{query}» پیدا نشد</EmptyStateTitle>
                <EmptyStateDescription>کلمهٔ دیگری امتحان کنید یا از پایینِ صفحه با پشتیبانی گفت‌وگو کنید.</EmptyStateDescription>
              </EmptyState>
            ) : (
              <Accordion key={query ? "search" : topic} type="single" collapsible variant="soft" indicator="plus" defaultValue={items[0].q}>
                {items.map((item) => (
                  <AccordionItem key={item.q} value={item.q}>
                    <AccordionTrigger className="text-base font-semibold">{item.q}</AccordionTrigger>
                    <AccordionContent className="leading-8 text-muted-foreground">{item.a}</AccordionContent>
                  </AccordionItem>
                ))}
              </Accordion>
            )}
          </div>
        </section>

        {/* هنوز سؤال دارید؟ آواتارِ تیم، متن و دو راهِ تماس. */}
        <section className="mx-auto w-full max-w-6xl px-4 pb-20 sm:px-6">
          <div className="flex flex-col items-center gap-6 rounded-xl bg-muted px-6 py-12 text-center">
            <AvatarGroup>
              <Avatar alt="نیما" src={stockImages.personMan1.src} icon={<AvatarGenderIcon gender="male" />} className="ring-2 ring-muted" />
              <Avatar size="lg" alt="مهسا" src={stockImages.personWoman1.src} icon={<AvatarGenderIcon gender="female" />} className="ring-2 ring-muted" />
              <Avatar alt="آرش" src={stockImages.personMan2.src} icon={<AvatarGenderIcon gender="male" />} className="ring-2 ring-muted" />
            </AvatarGroup>
            <div className="flex flex-col gap-2">
              <h2 className="text-xl font-bold">هنوز سؤال دارید؟</h2>
              <p className="text-sm leading-7 text-muted-foreground">جوابتان را پیدا نکردید؟ تیمِ پشتیبانی هر روز از ۸ تا ۲۲ پاسخ‌گوست.</p>
            </div>
            {/* در موبایل دو دکمهٔ هم‌عرض، اقدامِ اصلی بالا. */}
            <ControlSizeProvider size="xl" className="flex w-full flex-col gap-3 sm:w-auto sm:flex-row-reverse">
              <Button>گفت‌وگو با پشتیبانی</Button>
              <Button variant="outline" className="bg-background">
                ارسالِ ایمیل
              </Button>
            </ControlSizeProvider>
          </div>
        </section>
      </main>
    </ControlSizeProvider>
  )
}