دعوت از دوستان
پیشنمایش تمامصفحهکد و لینک دعوت با دکمهٔ کپی و آمار دعوتها و اعتبار.
۱. نصب کامپوننتها
این بلاک از ۸ آیتم دیگ استفاده میکند: avatarbadgebuttoncardcontrol-sizeiconspersianprogress
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add 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/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.jsonpnpm dlx digdesign@latest add 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/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.jsonyarn dlx digdesign@latest add 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/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.jsonbunx --bun digdesign@latest add 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/card.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
referral.tsx
"use client"
import * as React from "react"
import { BaleLogo, CheckIcon, CopyIcon, ShareIcon, TelegramLogo } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Progress } from "@/components/ui/progress"
import { formatCurrency, formatJalali, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
const CODE = "ARMITA-2405"
const LINK = "https://avand.example/invite/ARMITA-2405"
const REWARD = 200_000
const GOAL = 10
const STEPS = [
{ title: "لینک را بفرست", text: "از تلگرام، بله یا هر جای دیگر." },
{ title: "دوستت ثبتنام کند", text: "با لینک یا واردکردنِ کدِ تو." },
{ title: "هر دو هدیه بگیرید", text: `بعد از اولین خریدش، ${formatCurrency(REWARD)} برای هر کدام.` },
]
const FRIENDS: { name: string; image: StockImageName; date: string; status: "paid" | "joined" }[] = [
{ name: "سارا محمدی", image: "personWoman2", date: "2026-09-30", status: "paid" },
{ name: "امیر رضایی", image: "personMan1", date: "2026-09-21", status: "paid" },
{ name: "نگار احمدی", image: "personWoman3", date: "2026-09-14", status: "joined" },
{ name: "علی کاظمی", image: "personMan2", date: "2026-09-02", status: "paid" },
]
/** دکمهٔ کپی که نتیجه را همانجا روی خودش نشان میدهد، نه در اعلانی دور از چشم. */
function CopyButton({ value, label, className }: { value: string; label: string; className?: string }) {
const [state, setState] = React.useState<"idle" | "copied" | "failed">("idle")
async function copy() {
try {
await navigator.clipboard.writeText(value)
setState("copied")
} catch {
setState("failed")
}
setTimeout(() => setState("idle"), 2000)
}
return (
<Button onClick={copy} aria-label={state === "copied" ? `${label} کپی شد` : `کپی ${label}`} className={`min-w-28 ${className ?? ""}`}>
{state === "copied" ? <CheckIcon /> : <CopyIcon />}
{state === "copied" ? "کپی شد" : state === "failed" ? "کپی نشد" : "کپی لینک"}
<span aria-live="polite" className="sr-only">
{state === "copied" ? `${label} کپی شد` : ""}
</span>
</Button>
)
}
/**
* دعوتِ دوستان: هیروی گرادیانی با تیترِ هدیه، کوپنِ سفیدِ کد با کپی و اشتراک در پیامرسانها؛
* سه قدمِ «چطور کار میکند»، پیشرفت تا هدفِ ده دعوت، و فهرستِ دوستان با عکس و وضعیت.
*/
export default function ReferralBlock() {
const paid = FRIENDS.filter((friend) => friend.status === "paid").length
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-4xl flex-col gap-8 p-4 sm:p-6">
{/* هیروی طراحیشده: گرادیانِ بنفش به نیلی با درخششِ شعاعی، پترنِ نقطهای و حلقههای تزئینی؛
کد روی یک «کوپنِ» سفید با لبهٔ خطچین، نه فیلدی همرنگِ زمینه. */}
<section className="relative isolate grid grid-cols-1 gap-8 overflow-hidden rounded-xl bg-[linear-gradient(135deg,var(--dig-violet-700),var(--dig-indigo-800))] p-5 text-[var(--dig-white)] [--muted-foreground:var(--dig-violet-100)] sm:p-10 lg:grid-cols-[minmax(0,1fr)_20rem] lg:items-center">
<span aria-hidden className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_90%_0%,var(--dig-violet-400),transparent_45%),radial-gradient(circle_at_0%_100%,var(--dig-blue-500),transparent_40%)]" />
<span aria-hidden className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(color-mix(in_oklab,var(--dig-white)_35%,transparent)_1.2px,transparent_1.3px)] [background-size:18px_18px] [mask-image:linear-gradient(to_left,black,transparent_60%)]" />
<span aria-hidden className="pointer-events-none absolute -end-16 -bottom-24 -z-10 size-72 rounded-full border-[28px] border-[color-mix(in_oklab,var(--dig-white)_10%,transparent)]" />
<span aria-hidden className="pointer-events-none absolute end-24 -top-10 -z-10 size-28 rounded-full border-[14px] border-[color-mix(in_oklab,var(--dig-white)_8%,transparent)]" />
<div className="flex flex-col gap-5">
<span className="flex w-fit items-center gap-1.5 rounded-full bg-[var(--dig-white)] px-3 py-1 text-sm font-bold text-[var(--dig-violet-700)]">
دعوتِ دوستان
</span>
<div className="flex flex-col gap-2">
<h1 className="text-2xl leading-snug font-black text-balance sm:text-4xl">
دوستت را بیاور، هر دو {formatCurrency(REWARD)} هدیه بگیرید
</h1>
<p className="text-sm text-muted-foreground sm:text-base">بعد از اولین خریدِ دوستت، هدیه به کیفِ پولِ هر دو مینشیند.</p>
</div>
<div className="grid grid-cols-3 gap-2 sm:flex sm:flex-wrap">
{[
{ label: "تلگرام", icon: <TelegramLogo /> },
{ label: "بله", icon: <BaleLogo /> },
{ label: "اشتراک", icon: <ShareIcon /> },
].map((item) => (
<Button key={item.label} className="text-[var(--dig-white)] bg-[color-mix(in_oklab,var(--dig-white)_14%,var(--dig-violet-800))] hover:bg-[color-mix(in_oklab,var(--dig-white)_22%,var(--dig-violet-800))]" aria-label={`فرستادن با ${item.label}`}>
{item.icon}
{item.label}
</Button>
))}
</div>
</div>
{/* کوپن: سفید، با کد درشت و لبهٔ خطچین؛ کپی همانجاست. */}
<div className="flex flex-col gap-4 rounded-xl bg-[var(--dig-white)] p-5 text-[var(--dig-slate-900)] shadow-card">
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium text-[var(--dig-slate-500)]">کدِ دعوتِ تو</span>
<span className="text-xs font-bold text-[var(--dig-violet-700)]">{toFaDigits(paid)} دعوتِ موفق</span>
</div>
<bdi dir="ltr" className="rounded-lg border-2 border-dashed border-[var(--dig-violet-300)] bg-[var(--dig-violet-50)] py-3 text-center font-mono text-xl font-black tracking-widest text-[var(--dig-violet-800)]">
{CODE}
</bdi>
<CopyButton value={LINK} label="لینک دعوت" className="w-full bg-[var(--dig-violet-700)] text-[var(--dig-white)] hover:bg-[var(--dig-violet-800)]" />
</div>
</section>
<section aria-labelledby="referral-how" className="flex flex-col gap-5">
<h2 id="referral-how" className="text-lg font-bold">
چطور کار میکند؟
</h2>
{/* در موبایل هر قدم یک ردیفِ کوتاه است (عدد کنارِ متن)، نه کارتی بلند. */}
<ol className="grid grid-cols-1 gap-2 sm:grid-cols-3 sm:gap-4">
{STEPS.map((step, index) => (
<li key={step.title} className="flex items-start gap-3 rounded-xl bg-subtle p-4 sm:flex-col sm:gap-2 sm:p-5">
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-[var(--dig-violet-100)] text-sm font-bold text-[var(--dig-violet-800)]">
{toFaDigits(index + 1)}
</span>
<span className="flex flex-col gap-1">
<span className="font-semibold">{step.title}</span>
<span className="text-sm leading-6 text-muted-foreground">{step.text}</span>
</span>
</li>
))}
</ol>
</section>
<div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1fr)_18rem]">
<Card>
<CardHeader>
<CardTitle className="text-base">دوستانِ دعوتشده</CardTitle>
</CardHeader>
<CardContent>
<ul className="flex flex-col divide-y divide-border">
{FRIENDS.map((friend) => (
<li key={friend.name} className="flex items-center gap-3 py-3 first:pt-0 last:pb-0">
<Avatar alt={friend.name} src={stockImages[friend.image].src} />
<div className="flex flex-1 flex-col">
<span className="font-medium">{friend.name}</span>
<span className="text-xs text-muted-foreground">{formatJalali(new Date(friend.date))}</span>
</div>
{friend.status === "paid" ? (
<Badge variant="soft" color="success">
+{formatCurrency(REWARD)}
</Badge>
) : (
<Badge variant="soft">منتظرِ اولین خرید</Badge>
)}
</li>
))}
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">تا هدیهٔ ویژه</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<span className="text-3xl font-black tabular-nums">{formatCurrency(paid * REWARD)}</span>
<div className="flex flex-col gap-2">
<Progress value={(FRIENDS.length / GOAL) * 100} aria-label="پیشرفت تا ده دعوت" />
<span className="text-sm text-muted-foreground">
{toFaDigits(FRIENDS.length)} از {toFaDigits(GOAL)} دعوت؛ با دهمی یک ماه اشتراکِ رایگان.
</span>
</div>
</CardContent>
</Card>
</div>
</main>
</ControlSizeProvider>
)
}