611 lines
20 KiB
TypeScript
611 lines
20 KiB
TypeScript
"use client";
|
||
|
||
import {
|
||
ArrowDownRight,
|
||
ArrowLeftRight,
|
||
ArrowUpRight,
|
||
BarChart3,
|
||
Bell,
|
||
ChevronDown,
|
||
Copy,
|
||
Gift,
|
||
HelpCircle,
|
||
LayoutGrid,
|
||
LogOut,
|
||
type LucideIcon,
|
||
Menu,
|
||
Moon,
|
||
Search,
|
||
Settings,
|
||
Star,
|
||
TrendingUp,
|
||
Wallet,
|
||
} from "lucide-react";
|
||
|
||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
DropdownMenu,
|
||
DropdownMenuContent,
|
||
DropdownMenuItem,
|
||
DropdownMenuSeparator,
|
||
DropdownMenuTrigger,
|
||
} from "@/components/ui/dropdown-menu";
|
||
import { Input } from "@/components/ui/input";
|
||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||
import {
|
||
Sheet,
|
||
SheetContent,
|
||
SheetHeader,
|
||
SheetTitle,
|
||
SheetTrigger,
|
||
} from "@/components/ui/sheet";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
type NavLink = {
|
||
label: string;
|
||
href: string;
|
||
isActive?: boolean;
|
||
icon: LucideIcon;
|
||
};
|
||
|
||
type TokenItem = {
|
||
name: string;
|
||
symbol: string;
|
||
color: string;
|
||
};
|
||
|
||
type NotificationItem = {
|
||
title: string;
|
||
detail: string;
|
||
time: string;
|
||
tone: string;
|
||
};
|
||
|
||
const data = {
|
||
brand: {
|
||
name: "NexoChain",
|
||
},
|
||
user: {
|
||
name: "علی رضایی",
|
||
email: "ali@example.com",
|
||
avatar:
|
||
"/block-media/shadcnblocks/block/avatar-2.webp",
|
||
},
|
||
wallet: {
|
||
address: "0x1cf2...9a56",
|
||
balance: "$460,890.02",
|
||
status: "متصل",
|
||
},
|
||
};
|
||
|
||
const navLinks: NavLink[] = [
|
||
{ label: "نمای کلی", href: "#", isActive: true, icon: LayoutGrid },
|
||
{ label: "بازارها", href: "#", icon: BarChart3 },
|
||
{ label: "معامله", href: "#", icon: ArrowLeftRight },
|
||
{ label: "پاداشها", href: "#", icon: TrendingUp },
|
||
];
|
||
|
||
const favorites: TokenItem[] = [
|
||
{ name: "Bitcoin", symbol: "BTC", color: "bg-orange-500" },
|
||
{ name: "Ethereum", symbol: "ETH", color: "bg-indigo-500" },
|
||
{ name: "XRP", symbol: "XRP", color: "bg-neutral-900" },
|
||
{ name: "TetherUS", symbol: "IRRT", color: "bg-emerald-500" },
|
||
{ name: "Tron", symbol: "TRX", color: "bg-red-500" },
|
||
{ name: "Solana", symbol: "SOL", color: "bg-violet-500" },
|
||
{ name: "IRRC", symbol: "IRRC", color: "bg-blue-500" },
|
||
{ name: "BNB", symbol: "BNB", color: "bg-yellow-500" },
|
||
{ name: "Avalanche", symbol: "AVAX", color: "bg-rose-500" },
|
||
{ name: "Cardano", symbol: "ADA", color: "bg-sky-500" },
|
||
];
|
||
|
||
const walletAssets: TokenItem[] = [
|
||
{ name: "Bitcoin", symbol: "BTC", color: "bg-orange-500" },
|
||
{ name: "Ethereum", symbol: "ETH", color: "bg-indigo-500" },
|
||
{ name: "XRP", symbol: "XRP", color: "bg-neutral-900" },
|
||
{ name: "TetherUS", symbol: "IRRT", color: "bg-emerald-500" },
|
||
];
|
||
|
||
const notifications: NotificationItem[] = [
|
||
{
|
||
title: "برای امنیت حساب، احراز دو مرحلهای را فعال کنید",
|
||
detail: "کروم روی macOS، استانبول",
|
||
time: "۵ دقیقه",
|
||
tone: "text-rose-500",
|
||
},
|
||
{
|
||
title: "مبادله انجام شد",
|
||
detail: "۰٫۵ BTC - ۱۲٬۳۵۰ IRRC",
|
||
time: "اکنون",
|
||
tone: "text-emerald-500",
|
||
},
|
||
{
|
||
title: "۱۵ IRRC پاداش معاملاتی دریافت کردید",
|
||
detail: "در انتظار تأیید بلاک",
|
||
time: "۱ ساعت",
|
||
tone: "text-emerald-500",
|
||
},
|
||
{
|
||
title: "ورود جدید",
|
||
detail: "کروم روی macOS، استانبول",
|
||
time: "۱۰ دقیقه",
|
||
tone: "text-orange-500",
|
||
},
|
||
{
|
||
title: "تعمیرات برنامهریزیشده",
|
||
detail: "فردا ساعت ۰۳:۰۰ UTC",
|
||
time: "دیروز",
|
||
tone: "text-muted-foreground",
|
||
},
|
||
];
|
||
|
||
function TokenBadge({ item }: { item: TokenItem }) {
|
||
return (
|
||
<span
|
||
className={cn(
|
||
"flex size-6 items-center justify-center rounded-full text-[11px] font-semibold text-white",
|
||
item.color,
|
||
)}
|
||
>
|
||
{item.symbol.slice(0, 1)}
|
||
</span>
|
||
);
|
||
}
|
||
|
||
function HeaderTab({ item }: { item: NavLink }) {
|
||
return (
|
||
<a
|
||
href={item.href}
|
||
className={cn(
|
||
"rounded-full px-3 py-1 text-sm",
|
||
item.isActive
|
||
? "bg-accent text-foreground"
|
||
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
||
)}
|
||
>
|
||
{item.label}
|
||
</a>
|
||
);
|
||
}
|
||
|
||
function SearchField({ className }: { className?: string }) {
|
||
return (
|
||
<div className={cn("relative", className)}>
|
||
<Search className="pointer-events-none absolute top-1/2 start-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||
<Input
|
||
placeholder="جستجوی توکن یا جفت..."
|
||
className="h-9 w-full rounded-full bg-muted ps-9"
|
||
aria-label="جستجوی توکن"
|
||
/>
|
||
<span className="pointer-events-none absolute top-1/2 end-2.5 -translate-y-1/2 rounded-full bg-accent px-2 py-0.5 text-[10px] text-muted-foreground">
|
||
/
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function WalletMenu() {
|
||
return (
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button
|
||
variant="ghost"
|
||
className="gap-2 rounded-full border border-border bg-background px-3"
|
||
>
|
||
<span className="flex size-5 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||
<Wallet className="size-3.5" />
|
||
</span>
|
||
<span className="text-sm font-medium text-foreground">
|
||
{data.wallet.address}
|
||
</span>
|
||
<ChevronDown className="size-3 text-muted-foreground" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="start" className="w-80 p-3 text-start">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<span className="flex size-9 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||
<Wallet className="size-4" />
|
||
</span>
|
||
<div>
|
||
<div className="text-sm font-medium text-foreground">
|
||
{data.wallet.address}
|
||
</div>
|
||
<div className="text-xs text-primary">{data.wallet.status}</div>
|
||
</div>
|
||
</div>
|
||
<Button variant="outline" size="sm" className="h-7 gap-1">
|
||
<Copy className="size-3" />
|
||
کپی
|
||
</Button>
|
||
</div>
|
||
<div className="mt-4 rounded-lg border border-border bg-muted px-3 py-2">
|
||
<div className="text-xs text-muted-foreground">موجودی کل</div>
|
||
<div className="mt-1 text-xl font-semibold text-foreground">
|
||
{data.wallet.balance}
|
||
</div>
|
||
<Button variant="outline" size="sm" className="mt-2 h-7 gap-1">
|
||
<span className="flex size-4 items-center justify-center rounded-full bg-primary text-[10px] text-primary-foreground">
|
||
E
|
||
</span>
|
||
ارز دیجیتال
|
||
<ChevronDown className="size-3" />
|
||
</Button>
|
||
</div>
|
||
<div className="mt-3 flex items-center gap-2">
|
||
<Button variant="outline" size="sm" className="h-8 gap-1">
|
||
<ArrowLeftRight className="size-3.5" />
|
||
مبادله
|
||
</Button>
|
||
<Button variant="outline" size="sm" className="h-8 gap-1">
|
||
<ArrowDownRight className="size-3.5" />
|
||
خرید
|
||
</Button>
|
||
<Button variant="outline" size="sm" className="h-8 gap-1">
|
||
<ArrowUpRight className="size-3.5" />
|
||
ارسال
|
||
</Button>
|
||
</div>
|
||
<div className="mt-4 flex items-center justify-between text-xs font-medium text-muted-foreground">
|
||
<span>داراییهای برتر</span>
|
||
<span className="text-primary">مشاهده همه</span>
|
||
</div>
|
||
<div className="mt-2 space-y-2">
|
||
{walletAssets.map((asset) => (
|
||
<div
|
||
key={asset.symbol}
|
||
className="flex items-center justify-between rounded-md px-2 py-1 text-sm hover:bg-accent/50"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<TokenBadge item={asset} />
|
||
<span>
|
||
{asset.name}
|
||
<span className="ms-1 text-xs text-muted-foreground">
|
||
{asset.symbol}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<span className="text-xs text-muted-foreground">۰٫۰۰۹۵</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<DropdownMenuSeparator className="my-3" />
|
||
<DropdownMenuItem className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-sm">تأیید ۲FA</div>
|
||
<div className="text-xs text-muted-foreground">
|
||
برای امنیت بیشتر ۲FA را فعال کنید
|
||
</div>
|
||
</div>
|
||
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-xs text-primary">
|
||
تأیید
|
||
</span>
|
||
</DropdownMenuItem>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem>تنظیمات کیف پول</DropdownMenuItem>
|
||
<DropdownMenuItem className="text-rose-500">
|
||
قطع اتصال کیف پول
|
||
</DropdownMenuItem>
|
||
<div className="mt-2 text-[11px] text-muted-foreground">
|
||
نسخه ۱٫۵٫۶۹ قوانین و مقررات
|
||
</div>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
);
|
||
}
|
||
|
||
function FavoritesMenu() {
|
||
return (
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button variant="ghost" size="icon" className="rounded-full">
|
||
<Star className="size-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="start" className="w-80 p-3 text-start">
|
||
<div className="flex items-center justify-between text-sm">
|
||
<span className="font-medium">علاقهمندیهای من (۴)</span>
|
||
<button className="text-xs text-muted-foreground">پاک کردن</button>
|
||
</div>
|
||
<div className="relative mt-3">
|
||
<Search className="pointer-events-none absolute top-1/2 start-2.5 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
||
<Input
|
||
placeholder="جستجوی توکن..."
|
||
className="h-8 ps-8 text-sm"
|
||
aria-label="جستجوی توکن"
|
||
/>
|
||
</div>
|
||
<div className="mt-3 max-h-56 space-y-2 overflow-auto pe-1">
|
||
{favorites.map((token) => (
|
||
<div
|
||
key={token.symbol}
|
||
className="flex items-center justify-between rounded-md px-2 py-1 text-sm hover:bg-accent/50"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<TokenBadge item={token} />
|
||
<span>
|
||
{token.name}
|
||
<span className="ms-1 text-xs text-muted-foreground">
|
||
{token.symbol}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<Star className="size-3 text-amber-400" />
|
||
</div>
|
||
))}
|
||
</div>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
);
|
||
}
|
||
|
||
function NotificationsMenu() {
|
||
return (
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button variant="ghost" size="icon" className="rounded-full">
|
||
<Bell className="size-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="start" className="w-80 p-3 text-start">
|
||
<div className="flex items-center justify-between text-sm">
|
||
<span className="font-medium">اعلانها</span>
|
||
<button className="text-xs text-primary">علامتگذاری همه بهعنوان خواندهشده</button>
|
||
</div>
|
||
<div className="mt-3 space-y-2">
|
||
{notifications.map((item) => (
|
||
<div
|
||
key={item.title}
|
||
className="flex items-start gap-2 rounded-md border border-border px-2 py-2"
|
||
>
|
||
<span
|
||
aria-hidden="true"
|
||
className={cn(
|
||
"mt-1 flex size-7 items-center justify-center rounded-full bg-accent text-xs",
|
||
item.tone,
|
||
)}
|
||
/>
|
||
<div className="flex-1">
|
||
<div className="text-sm font-medium text-foreground">
|
||
{item.title}
|
||
</div>
|
||
<div className="text-xs text-muted-foreground">
|
||
{item.detail}
|
||
</div>
|
||
</div>
|
||
<span className="text-xs text-muted-foreground">{item.time}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<button className="mt-3 w-full rounded-md border border-border py-1 text-xs text-muted-foreground">
|
||
مشاهده همه اعلانها
|
||
</button>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
);
|
||
}
|
||
|
||
function UserMenu() {
|
||
return (
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<button className="flex items-center gap-2 rounded-full border border-transparent px-2 py-1 text-sm hover:bg-accent/50">
|
||
<Avatar className="size-7">
|
||
<AvatarImage src={data.user.avatar} alt={data.user.name} />
|
||
<AvatarFallback className="text-xs">
|
||
{data.user.name
|
||
.split(" ")
|
||
.map((part) => part[0])
|
||
.join("")}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
</button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="start" className="w-64 p-3 text-start">
|
||
<div className="flex items-center gap-3">
|
||
<Avatar className="size-9">
|
||
<AvatarImage src={data.user.avatar} alt={data.user.name} />
|
||
<AvatarFallback className="text-xs">
|
||
{data.user.name
|
||
.split(" ")
|
||
.map((part) => part[0])
|
||
.join("")}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
<div>
|
||
<div className="text-sm font-medium text-foreground">
|
||
{data.user.name}
|
||
</div>
|
||
<div className="text-xs text-muted-foreground">
|
||
{data.user.email}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<DropdownMenuSeparator className="my-2" />
|
||
<DropdownMenuItem className="gap-2">
|
||
<Moon className="size-4" />
|
||
حالت تیره
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem className="gap-2">
|
||
<Settings className="size-4" />
|
||
تنظیمات حساب
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem className="gap-2">
|
||
<Wallet className="size-4" />
|
||
ترجیحها
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem className="gap-2">
|
||
<HelpCircle className="size-4" />
|
||
راهنمای کاربران
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem className="gap-2">
|
||
<Gift className="size-4" />
|
||
انجمن
|
||
</DropdownMenuItem>
|
||
<DropdownMenuSeparator className="my-2" />
|
||
<DropdownMenuItem className="gap-2 text-rose-500">
|
||
<LogOut className="size-4" />
|
||
خروج
|
||
</DropdownMenuItem>
|
||
<div className="mt-2 text-[11px] text-muted-foreground">
|
||
نسخه ۲٫۸٫۶۹ قوانین و مقررات
|
||
</div>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
);
|
||
}
|
||
|
||
function MobileMenu() {
|
||
return (
|
||
<Sheet>
|
||
<SheetTrigger asChild>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="md:hidden"
|
||
aria-label="باز کردن منو"
|
||
>
|
||
<Menu className="size-5" />
|
||
</Button>
|
||
</SheetTrigger>
|
||
<SheetContent side="right" dir="rtl" className="w-72 p-0 text-start">
|
||
<SheetHeader className="px-4 pt-4">
|
||
<SheetTitle className="flex items-center gap-2">
|
||
<span className="flex size-8 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||
<Wallet className="size-4" />
|
||
</span>
|
||
{data.brand.name}
|
||
</SheetTitle>
|
||
</SheetHeader>
|
||
<ScrollArea className="min-h-0 flex-1">
|
||
<div className="flex flex-col gap-2 px-4 py-4">
|
||
{navLinks.map((link) => (
|
||
<a
|
||
key={link.label}
|
||
href={link.href}
|
||
className={cn(
|
||
"rounded-md px-3 py-2 text-sm",
|
||
link.isActive
|
||
? "bg-accent font-medium"
|
||
: "text-muted-foreground hover:bg-accent/50",
|
||
)}
|
||
>
|
||
{link.label}
|
||
</a>
|
||
))}
|
||
</div>
|
||
<div className="px-4 pb-4">
|
||
<WalletMenu />
|
||
</div>
|
||
</ScrollArea>
|
||
</SheetContent>
|
||
</Sheet>
|
||
);
|
||
}
|
||
|
||
function BottomNav() {
|
||
return (
|
||
<nav className="fixed inset-x-0 bottom-0 z-40 border-t border-border bg-background/95 backdrop-blur md:hidden">
|
||
<div className="grid grid-cols-4">
|
||
{navLinks.map((link) => {
|
||
const Icon = link.icon;
|
||
return (
|
||
<a
|
||
key={link.label}
|
||
href={link.href}
|
||
className={cn(
|
||
"flex flex-col items-center gap-1 py-2 text-xs",
|
||
link.isActive
|
||
? "text-primary"
|
||
: "text-muted-foreground hover:text-foreground",
|
||
)}
|
||
>
|
||
<Icon className="size-5" aria-hidden="true" />
|
||
<span className="sr-only">{link.label}</span>
|
||
<span aria-hidden="true">{link.label}</span>
|
||
</a>
|
||
);
|
||
})}
|
||
</div>
|
||
</nav>
|
||
);
|
||
}
|
||
|
||
interface ApplicationShell14Props {
|
||
className?: string;
|
||
}
|
||
|
||
export function ApplicationShell14({ className }: ApplicationShell14Props) {
|
||
return (
|
||
<div
|
||
dir="rtl"
|
||
lang="fa"
|
||
className={cn("min-h-svh bg-background", className)}
|
||
>
|
||
<div className="min-h-svh w-full bg-background">
|
||
<div className="border-b border-border">
|
||
<div className="mx-auto w-full max-w-7xl px-4">
|
||
<div className="grid grid-cols-[1fr_auto_1fr] items-center gap-3 py-2 text-xs text-muted-foreground">
|
||
<div />
|
||
<div className="flex flex-wrap items-center justify-center gap-2 text-center">
|
||
<Gift className="size-3.5 text-primary" />
|
||
<span>
|
||
۲۵ دلار پاداش خوشآمدگویی اولین مبادله را دریافت کنید
|
||
</span>
|
||
<button className="text-primary">دریافت کنید</button>
|
||
</div>
|
||
<div className="flex justify-end">
|
||
<button className="text-muted-foreground" aria-label="بستن">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="border-b border-border">
|
||
<div className="mx-auto w-full max-w-7xl px-4">
|
||
<div className="flex items-center justify-between gap-4 py-3">
|
||
<div className="flex items-center gap-2 md:gap-3">
|
||
<MobileMenu />
|
||
<div className="flex size-9 items-center justify-center rounded-full bg-primary/10 text-primary md:hidden">
|
||
<Wallet className="size-4" />
|
||
</div>
|
||
<div className="hidden size-9 items-center justify-center rounded-full bg-primary/10 text-primary md:flex">
|
||
<Wallet className="size-5" />
|
||
</div>
|
||
<nav className="hidden items-center gap-1 md:flex">
|
||
{navLinks.map((link) => (
|
||
<HeaderTab key={link.label} item={link} />
|
||
))}
|
||
</nav>
|
||
</div>
|
||
<div className="hidden w-full justify-self-center md:block md:max-w-md">
|
||
<SearchField />
|
||
</div>
|
||
<div className="flex items-center gap-1 md:gap-2">
|
||
<WalletMenu />
|
||
<div className="hidden md:block">
|
||
<FavoritesMenu />
|
||
</div>
|
||
<div className="hidden md:block">
|
||
<NotificationsMenu />
|
||
</div>
|
||
<UserMenu />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="border-b border-border md:hidden">
|
||
<div className="mx-auto w-full max-w-7xl px-4 py-3">
|
||
<SearchField />
|
||
</div>
|
||
</div>
|
||
<div className="mx-auto w-full max-w-7xl px-4">
|
||
<div className="min-h-[360px]" />
|
||
</div>
|
||
</div>
|
||
<BottomNav />
|
||
</div>
|
||
);
|
||
}
|