مرکز پشتیبانی
پیشنمایش تمامصفحهمرکزِ پشتیبانیِ داخلِ اپ: سه کارتِ راهِ تماس، پرسشهای متداول با جستوجو و حالتِ بینتیجه، و فرمِ ثبتِ تیکت با زمانِ پاسخ.
۱. نصب کامپوننتها
این بلاک از ۱۳ آیتم دیگ استفاده میکند: 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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}