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

مرکزِ پشتیبانیِ داخلِ اپ: سه کارتِ راهِ تماس، پرسش‌های متداول با جست‌وجو و حالتِ بی‌نتیجه، و فرمِ ثبتِ تیکت با زمانِ پاسخ.

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

این بلاک از ۱۳ آیتم دیگ استفاده می‌کند: accordionbuttoncardcontrol-sizeempty-stateiconslabelpersiansearch-fieldselecttext-fieldtextareatoast

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.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/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.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/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.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/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/accordion.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/card.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/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json

۲. کد صفحه

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

help-center.tsx
"use client"

import * as React from "react"

import { ChatIcon, CheckIcon, ChevronLeftIcon, EnvelopeIcon, GuideIcon, PhoneIcon, TicketIcon } from "@/components/icons"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { Label } from "@/components/ui/label"
import { SearchField } from "@/components/ui/search-field"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { TextField } from "@/components/ui/text-field"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/toast"
import { toFaDigits, toSearchKey } from "@/lib/persian"

/** هر راهِ تماس رنگِ خودش را دارد تا در یک نگاه از هم جدا شوند. */
const CHANNELS = [
  { icon: <ChatIcon />, tone: "violet", title: "گفت‌وگوی آنلاین", text: "پاسخ در کمتر از ۵ دقیقه", action: "شروع گفت‌وگو" },
  { icon: <EnvelopeIcon />, tone: "green", title: "ایمیل", text: "support@hesabyar.example", ltr: true, action: "ارسال ایمیل" },
  { icon: <PhoneIcon />, tone: "blue", title: "تلفن", text: toFaDigits("021-91001234"), ltr: true, action: "تماس" },
]

const FAQ = [
  { q: "سفارشم کی می‌رسد؟", a: "در تهران همان روز با پیکِ سریع و در شهرستان‌ها ۳ تا ۵ روزِ کاری با پستِ پیشتاز." },
  { q: "آدرسِ تحویل را می‌توانم عوض کنم؟", a: "تا پیش از تحویل به پست، از صفحهٔ سفارش گزینهٔ «تغییر آدرس» را بزنید." },
  { q: "پول کسر شد ولی سفارش ثبت نشد", a: "مبلغ حداکثر تا ۷۲ ساعت به حسابتان برمی‌گردد؛ اگر برنگشت، از فرمِ کنار تیکت ثبت کنید." },
  { q: "تا چند روز می‌توانم کالا را برگردانم؟", a: "تا ۷ روز پس از تحویل، به شرطِ سالم‌بودنِ بسته‌بندی." },
  { q: "فاکتورِ رسمی چطور بگیرم؟", a: "از سفارش‌ها › جزئیات، «دریافتِ فاکتور» را بزنید؛ کدِ اقتصادی را یک بار در پروفایل وارد کنید." },
]

function IconTile({ tone, children }: { tone: string; children: React.ReactNode }) {
  return (
    <span
      aria-hidden
      className="flex size-11 shrink-0 items-center justify-center rounded-xl [&>svg]:size-5"
      style={{ background: `color-mix(in oklab, var(--dig-${tone}-500) 12%, var(--card))`, color: `var(--dig-${tone}-600)` }}
    >
      {children}
    </span>
  )
}

/**
 * مرکزِ پشتیبانی داخلِ اپ: سه راهِ تماس، پرسش‌های متداول با جست‌وجو و فرمِ
 * ثبتِ تیکت کنارِ هم؛ کاربر اول جواب را می‌گردد و اگر نبود، همان‌جا تیکت می‌زند.
 */
export default function HelpCenterBlock() {
  const [query, setQuery] = React.useState("")
  const [sending, setSending] = React.useState(false)
  const key = toSearchKey(query)
  const items = FAQ.filter((item) => toSearchKey(item.q + item.a).includes(key))

  async function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const form = event.currentTarget
    setSending(true)
    await new Promise((resolve) => setTimeout(resolve, 800))
    setSending(false)
    form.reset()
    toast.success(`تیکتِ شمارهٔ ${toFaDigits(1042)} ثبت شد؛ پاسخ را ایمیل می‌کنیم.`)
  }

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6">
        <header className="flex flex-col gap-1">
          <h1 className="text-2xl font-bold">مرکز پشتیبانی</h1>
          <p className="text-muted-foreground">جوابتان را پیدا کنید یا با ما در تماس باشید.</p>
        </header>

        <ul className="grid grid-cols-1 gap-3 sm:gap-4 md:grid-cols-3">
          {CHANNELS.map((channel) => (
            <li key={channel.title}>
              <Card className="h-full">
                {/* در موبایل هر راهِ ارتباط یک ردیفِ کوتاه است و دکمه کنارِ متن؛ در دسکتاپ دکمه زیرِ متن. */}
                <CardContent className="flex items-center gap-4 sm:items-start">
                  <IconTile tone={channel.tone}>{channel.icon}</IconTile>
                  <div className="flex min-w-0 flex-1 items-center justify-between gap-3 sm:flex-col sm:items-start">
                    <div className="flex min-w-0 flex-col gap-0.5">
                      <span className="font-semibold">{channel.title}</span>
                      <span className="truncate text-sm text-muted-foreground">{channel.ltr ? <bdi dir="ltr">{channel.text}</bdi> : channel.text}</span>
                    </div>
                    <Button variant="outline" className="hidden sm:inline-flex">
                      {channel.action}
                    </Button>
                    <Button variant="ghost" size="icon" aria-label={channel.action} className="shrink-0 sm:hidden">
                      <ChevronLeftIcon />
                    </Button>
                  </div>
                </CardContent>
              </Card>
            </li>
          ))}
        </ul>

        <div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)]">
          {/* پرسش‌های متداول: جست‌وجو بالای فهرست، هر پرسش یک کارتِ جدا. */}
          <Card>
            <CardHeader className="flex items-center gap-3">
              <IconTile tone="pink">
                <GuideIcon />
              </IconTile>
              <div className="flex flex-col gap-0.5">
                <CardTitle className="text-base">پرسش‌های متداول</CardTitle>
                <CardDescription>جوابِ سریعِ پرسش‌های رایج</CardDescription>
              </div>
            </CardHeader>
            <CardContent className="flex flex-col gap-4">
              <SearchField aria-label="جست‌وجو در پرسش‌ها" placeholder="جست‌وجو در پرسش‌ها…" value={query} onValueChange={setQuery} />
              <div aria-live="polite">
                {items.length === 0 ? (
                  <EmptyState variant="bordered">
                    <EmptyStateTitle>پاسخی برای «{query}» پیدا نشد</EmptyStateTitle>
                    <EmptyStateDescription>از فرمِ «ثبتِ تیکت» بپرسید؛ همان روز جواب می‌دهیم.</EmptyStateDescription>
                  </EmptyState>
                ) : (
                  <Accordion type="single" collapsible variant="splitted" defaultValue={query ? undefined : FAQ[0].q}>
                    {items.map((item) => (
                      <AccordionItem key={item.q} value={item.q} className="border border-border px-4 shadow-none">
                        <AccordionTrigger className="text-sm font-semibold">{item.q}</AccordionTrigger>
                        <AccordionContent className="leading-7 text-muted-foreground">{item.a}</AccordionContent>
                      </AccordionItem>
                    ))}
                  </Accordion>
                )}
              </div>
            </CardContent>
          </Card>

          {/* ثبتِ تیکت: دسته، موضوع و شرح؛ زمانِ پاسخ زیرِ دکمه. */}
          <form onSubmit={submit}>
            <Card>
              <CardHeader className="flex items-center gap-3">
                <IconTile tone="amber">
                  <TicketIcon />
                </IconTile>
                <div className="flex flex-col gap-0.5">
                  <CardTitle className="text-base">ثبت تیکت</CardTitle>
                  <CardDescription>تا ۲۴ ساعت پاسخ می‌دهیم</CardDescription>
                </div>
              </CardHeader>
              <CardContent className="flex flex-col gap-4">
                <div className="flex flex-col gap-2">
                  <Label id="ticket-category-label">دسته</Label>
                  <Select name="category" defaultValue="orders">
                    <SelectTrigger aria-labelledby="ticket-category-label" className="w-full">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="orders">سفارش و ارسال</SelectItem>
                      <SelectItem value="payment">پرداخت</SelectItem>
                      <SelectItem value="account">حساب کاربری</SelectItem>
                      <SelectItem value="other">سایر</SelectItem>
                    </SelectContent>
                  </Select>
                </div>
                <TextField label="موضوع" name="subject" placeholder="خلاصهٔ مشکل در یک خط" required />
                <div className="flex flex-col gap-2">
                  <Label htmlFor="ticket-message">شرح</Label>
                  <Textarea id="ticket-message" name="message" rows={5} required placeholder="چه اتفاقی افتاد؟ شمارهٔ سفارش را هم بنویسید." />
                </div>
              </CardContent>
              <CardFooter className="flex-col items-stretch gap-4">
                <Button type="submit" loading={sending} fullWidth>
                  ثبت تیکت
                </Button>
                <p className="flex items-center gap-2 border-t border-border pt-4 text-sm text-muted-foreground">
                  <CheckIcon aria-hidden className="size-4 text-success" />
                  میانگینِ زمانِ پاسخ: ۲ تا ۴ ساعت
                </p>
              </CardFooter>
            </Card>
          </form>
        </div>
      </main>
    </ControlSizeProvider>
  )
}