You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
667 lines
27 KiB
667 lines
27 KiB
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { MdOutlineSwitchAccount, MdDragIndicator } from "react-icons/md";
|
|
import { IoClose, IoTrashOutline, IoKeyOutline } from "react-icons/io5";
|
|
import { getClientCookie, setClientCookie } from "@/lib/cookies";
|
|
import { setCachedMarriageEntryPath } from "@/lib/entry-route-cache";
|
|
|
|
export const FEMALE_TOKEN = "545f61bb3e061ccb9b19f84715eb1b1ed4740331";
|
|
export const MALE_TOKEN = "f3a7543b44ef0a713d1ee0d4f7866b3825cf1308";
|
|
export const MALE_2_TOKEN = "56cd0fceb93f7cecf7ffa84c590135026cf54a1d";
|
|
export const GUEST_MALE_1_TOKEN = "7490550734175c3935c54c808eba4933ee9c7633";
|
|
export const GUEST_MALE_2_TOKEN = "88813b0434f2207582f515f2c38de0cafd517fb7";
|
|
export const GUEST_FEMALE_1_TOKEN = "d9f0d61b2a7bbea89266eded6ad8d81a112f7ffe";
|
|
export const GUEST_FEMALE_2_TOKEN = "da44906159958b3f071096160b509e1479ccbc1a";
|
|
|
|
export const FEMALE_EMAIL = "m.a.ghorbani01@gmail.com";
|
|
export const MALE_EMAIL = "muhammadamin.ghorbani@gmail.com";
|
|
export const MALE_2_EMAIL = "habibwabackup@gmail.com";
|
|
|
|
const TOKEN_COOKIE_NAME = "HABIB_TOKEN";
|
|
const STORAGE_POS_KEY = "DEV_TOKEN_SWITCHER_POS";
|
|
|
|
type TokenSwitcherProps = {
|
|
variant?: "default" | "transparent";
|
|
className?: string;
|
|
isFloating?: boolean;
|
|
};
|
|
|
|
export function TokenSwitcher({
|
|
variant = "default",
|
|
className,
|
|
isFloating = true,
|
|
}: TokenSwitcherProps) {
|
|
const [mounted, setMounted] = useState(false);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [currentToken, setCurrentToken] = useState<string>("");
|
|
const [customTokenInput, setCustomTokenInput] = useState<string>("");
|
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const dragStart = useRef<{
|
|
x: number;
|
|
y: number;
|
|
startX: number;
|
|
startY: number;
|
|
hasMoved: boolean;
|
|
}>({ x: 0, y: 0, startX: 12, startY: 12, hasMoved: false });
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
if (typeof window !== "undefined") {
|
|
const token =
|
|
(window as any).HABIB_TOKEN ??
|
|
getClientCookie(TOKEN_COOKIE_NAME) ??
|
|
getClientCookie("habib_token") ??
|
|
sessionStorage.getItem(TOKEN_COOKIE_NAME) ??
|
|
"";
|
|
setCurrentToken(token);
|
|
|
|
if (isFloating) {
|
|
try {
|
|
const savedPos = localStorage.getItem(STORAGE_POS_KEY);
|
|
if (savedPos) {
|
|
const parsed = JSON.parse(savedPos);
|
|
if (typeof parsed.x === "number" && typeof parsed.y === "number") {
|
|
const clampedX = Math.max(10, Math.min(window.innerWidth - 180, parsed.x));
|
|
const clampedY = Math.max(10, Math.min(window.innerHeight - 50, parsed.y));
|
|
setPosition({ x: clampedX, y: clampedY });
|
|
return;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("Failed to load dev token switcher position:", e);
|
|
}
|
|
// Default initial position (top-left)
|
|
setPosition({ x: 12, y: 12 });
|
|
}
|
|
}
|
|
}, [isFloating]);
|
|
|
|
// Keep floating position bounded on window resize
|
|
useEffect(() => {
|
|
if (!isFloating) return;
|
|
const handleResize = () => {
|
|
setPosition((prev) => {
|
|
if (!prev) return null;
|
|
const width = containerRef.current?.offsetWidth || 180;
|
|
const height = containerRef.current?.offsetHeight || 50;
|
|
return {
|
|
x: Math.max(10, Math.min(window.innerWidth - width - 10, prev.x)),
|
|
y: Math.max(10, Math.min(window.innerHeight - height - 10, prev.y)),
|
|
};
|
|
});
|
|
};
|
|
window.addEventListener("resize", handleResize);
|
|
return () => window.removeEventListener("resize", handleResize);
|
|
}, [isFloating]);
|
|
|
|
const isMale = currentToken === MALE_TOKEN;
|
|
const isMale2 = currentToken === MALE_2_TOKEN;
|
|
const isFemale = currentToken === FEMALE_TOKEN;
|
|
const isNoToken = currentToken === "NO_TOKEN" || !currentToken;
|
|
const guestTokens = [
|
|
GUEST_MALE_1_TOKEN,
|
|
GUEST_MALE_2_TOKEN,
|
|
GUEST_FEMALE_1_TOKEN,
|
|
GUEST_FEMALE_2_TOKEN,
|
|
];
|
|
const isGuestToken = guestTokens.includes(currentToken);
|
|
const isCustomToken =
|
|
!isMale && !isMale2 && !isFemale && !isNoToken && !isGuestToken;
|
|
|
|
const handleSelectToken = (newToken: string) => {
|
|
const trimmed = newToken.trim();
|
|
if (!trimmed) return;
|
|
|
|
if (typeof window !== "undefined") {
|
|
try {
|
|
window.localStorage.clear();
|
|
window.sessionStorage.clear();
|
|
} catch (e) {
|
|
console.error("Failed to clear storage", e);
|
|
}
|
|
|
|
setCachedMarriageEntryPath(null);
|
|
setClientCookie(TOKEN_COOKIE_NAME, trimmed);
|
|
setClientCookie("habib_token", trimmed);
|
|
try {
|
|
sessionStorage.setItem(TOKEN_COOKIE_NAME, trimmed);
|
|
} catch (e) {
|
|
console.warn("sessionStorage is not accessible:", e);
|
|
}
|
|
(window as any).HABIB_TOKEN = trimmed;
|
|
setCurrentToken(trimmed);
|
|
setIsOpen(false);
|
|
|
|
window.location.replace("/");
|
|
}
|
|
};
|
|
|
|
const handleResetUser = async (userId: number | null, label: string) => {
|
|
const message =
|
|
userId !== null
|
|
? `آیا از پاک کردن تمامی اطلاعات هویتی، پاسخها و پیشرفت این کاربر (${label}) و شروع مجدد از مرحله اول آنبوردینگ مطمئن هستید؟`
|
|
: `آیا از پاک کردن تمامی اطلاعات محلی و کوکیهای مربوط به این حالت (بدون توکن) و شروع مجدد مطمئن هستید؟`;
|
|
|
|
if (!window.confirm(message)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (typeof window !== "undefined") {
|
|
try {
|
|
window.localStorage.clear();
|
|
window.sessionStorage.clear();
|
|
} catch (e) {
|
|
console.error("Failed to clear storage", e);
|
|
}
|
|
}
|
|
|
|
if (userId !== null) {
|
|
const response = await fetch("/api/dev-reset-profile", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ userId }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errData = await response.json();
|
|
alert(`خطا در ریست پروفایل: ${errData.error || response.statusText}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const targetToken =
|
|
userId === 17119
|
|
? MALE_TOKEN
|
|
: userId === 147714
|
|
? FEMALE_TOKEN
|
|
: userId === 146024
|
|
? MALE_2_TOKEN
|
|
: "NO_TOKEN";
|
|
|
|
setCachedMarriageEntryPath(null);
|
|
setClientCookie(TOKEN_COOKIE_NAME, targetToken);
|
|
setClientCookie("habib_token", targetToken);
|
|
if (typeof window !== "undefined") {
|
|
try {
|
|
sessionStorage.setItem(TOKEN_COOKIE_NAME, targetToken);
|
|
} catch (e) {
|
|
console.warn("sessionStorage is not accessible:", e);
|
|
}
|
|
(window as any).HABIB_TOKEN = targetToken;
|
|
setCurrentToken(targetToken);
|
|
}
|
|
|
|
setIsOpen(false);
|
|
window.location.replace("/");
|
|
} catch (error) {
|
|
console.error("Failed to reset user:", error);
|
|
alert("خطایی در انجام عملیات رخ داد.");
|
|
}
|
|
};
|
|
|
|
const mainAccounts = [
|
|
{
|
|
id: 17119,
|
|
token: MALE_TOKEN,
|
|
title: "حساب آقا",
|
|
subtitle: MALE_EMAIL,
|
|
emoji: "👨",
|
|
isActive: isMale,
|
|
},
|
|
{
|
|
id: 146024,
|
|
token: MALE_2_TOKEN,
|
|
title: "حساب آقا ۲",
|
|
subtitle: MALE_2_EMAIL,
|
|
emoji: "👨",
|
|
isActive: isMale2,
|
|
},
|
|
{
|
|
id: 147714,
|
|
token: FEMALE_TOKEN,
|
|
title: "حساب خانم",
|
|
subtitle: FEMALE_EMAIL,
|
|
emoji: "👩",
|
|
isActive: isFemale,
|
|
},
|
|
];
|
|
|
|
const guestAccounts = [
|
|
{
|
|
token: GUEST_MALE_1_TOKEN,
|
|
title: "حساب مهمان آقا ۱",
|
|
subtitle: "مهمان / بدون اطلاعات",
|
|
emoji: "👨",
|
|
isActive: currentToken === GUEST_MALE_1_TOKEN,
|
|
},
|
|
{
|
|
token: GUEST_MALE_2_TOKEN,
|
|
title: "حساب مهمان آقا ۲",
|
|
subtitle: "مهمان / بدون اطلاعات",
|
|
emoji: "👨",
|
|
isActive: currentToken === GUEST_MALE_2_TOKEN,
|
|
},
|
|
{
|
|
token: GUEST_FEMALE_1_TOKEN,
|
|
title: "حساب مهمان خانم ۱",
|
|
subtitle: "مهمان / بدون اطلاعات",
|
|
emoji: "👩",
|
|
isActive: currentToken === GUEST_FEMALE_1_TOKEN,
|
|
},
|
|
{
|
|
token: GUEST_FEMALE_2_TOKEN,
|
|
title: "حساب مهمان خانم ۲",
|
|
subtitle: "مهمان / بدون اطلاعات",
|
|
emoji: "👩",
|
|
isActive: currentToken === GUEST_FEMALE_2_TOKEN,
|
|
},
|
|
];
|
|
|
|
// Pointer drag handlers
|
|
const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
if (!isFloating || isOpen) return;
|
|
|
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
setIsDragging(true);
|
|
const startX = position?.x ?? 12;
|
|
const startY = position?.y ?? 12;
|
|
dragStart.current = {
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
startX,
|
|
startY,
|
|
hasMoved: false,
|
|
};
|
|
};
|
|
|
|
const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
if (!isDragging || !isFloating) return;
|
|
|
|
const dx = e.clientX - dragStart.current.x;
|
|
const dy = e.clientY - dragStart.current.y;
|
|
|
|
if (Math.hypot(dx, dy) > 4) {
|
|
dragStart.current.hasMoved = true;
|
|
}
|
|
|
|
const width = containerRef.current?.offsetWidth || 140;
|
|
const height = containerRef.current?.offsetHeight || 40;
|
|
|
|
const newX = Math.max(
|
|
10,
|
|
Math.min(window.innerWidth - width - 10, dragStart.current.startX + dx)
|
|
);
|
|
const newY = Math.max(
|
|
10,
|
|
Math.min(window.innerHeight - height - 10, dragStart.current.startY + dy)
|
|
);
|
|
|
|
setPosition({ x: newX, y: newY });
|
|
};
|
|
|
|
const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
|
|
if (!isDragging || !isFloating) return;
|
|
try {
|
|
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
} catch (err) {}
|
|
setIsDragging(false);
|
|
|
|
if (dragStart.current.hasMoved) {
|
|
if (position) {
|
|
try {
|
|
localStorage.setItem(STORAGE_POS_KEY, JSON.stringify(position));
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleBadgeClick = (e: React.MouseEvent) => {
|
|
if (dragStart.current.hasMoved) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return;
|
|
}
|
|
setIsOpen(true);
|
|
};
|
|
|
|
const currentRoleLabel = isMale
|
|
? "آقا 👨"
|
|
: isMale2
|
|
? "آقا ۲ 👨"
|
|
: isFemale
|
|
? "خانم 👩"
|
|
: isGuestToken
|
|
? (guestAccounts.find((g) => g.token === currentToken)?.title ?? "مهمان 👤")
|
|
: isNoToken
|
|
? "بدون توکن 👤"
|
|
: "سفارشی 🔑";
|
|
|
|
if (!mounted && isFloating) {
|
|
return null;
|
|
}
|
|
|
|
const badgeContent = (
|
|
<div
|
|
ref={containerRef}
|
|
onPointerDown={handlePointerDown}
|
|
onPointerMove={handlePointerMove}
|
|
onPointerUp={handlePointerUp}
|
|
onPointerCancel={() => setIsDragging(false)}
|
|
onClick={handleBadgeClick}
|
|
style={
|
|
isFloating
|
|
? {
|
|
position: "fixed",
|
|
left: position ? `${position.x}px` : "12px",
|
|
top: position ? `${position.y}px` : "12px",
|
|
zIndex: 99999,
|
|
touchAction: "none",
|
|
userSelect: "none",
|
|
}
|
|
: undefined
|
|
}
|
|
className={[
|
|
isFloating
|
|
? "fixed flex items-center gap-1.5 px-3 py-2 rounded-[15px] backdrop-blur-md shadow-md border select-none transition-shadow cursor-pointer"
|
|
: "inline-flex items-center gap-1.5 rounded-[15px] px-3 py-2 shadow-md border backdrop-blur-md cursor-pointer",
|
|
variant === "transparent"
|
|
? "bg-black/60 text-white border-white/20 hover:bg-black/70"
|
|
: "bg-white/95 text-slate-800 border-slate-200/80 hover:bg-white dark:bg-slate-900/95 dark:text-slate-100 dark:border-slate-700/80",
|
|
isDragging
|
|
? "cursor-grabbing shadow-2xl ring-2 ring-rose-500/50 scale-[1.02]"
|
|
: "cursor-grab active:scale-95",
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
title="تغییر توکن کاربر (تست) - برای جابجایی بکشید / برای انتخاب کلیک کنید"
|
|
>
|
|
{/* Drag handle */}
|
|
{isFloating && (
|
|
<div
|
|
className="flex items-center justify-center text-slate-400 dark:text-slate-500 shrink-0"
|
|
title="برای جابجایی درگ کنید"
|
|
>
|
|
<MdDragIndicator className="size-4" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Role icon & selected account label */}
|
|
<MdOutlineSwitchAccount className="size-5 text-rose-500 shrink-0" />
|
|
<span className="text-xs font-bold whitespace-nowrap">{currentRoleLabel}</span>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{badgeContent}
|
|
|
|
{isOpen && (
|
|
<div
|
|
className="fixed inset-0 z-[100000] flex items-center justify-center bg-black/60 p-4 backdrop-blur-xs animate-in fade-in duration-200"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
<div
|
|
className="w-full max-w-sm rounded-2xl bg-white p-5 shadow-2xl dark:bg-slate-900 border border-slate-100 dark:border-slate-800 max-h-[90vh] flex flex-col"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Modal Header */}
|
|
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100 dark:border-slate-800 shrink-0 bg-slate-50/50 dark:bg-slate-900/50">
|
|
<div className="flex items-center gap-3">
|
|
<div className="size-10 rounded-xl bg-rose-500/10 text-rose-500 flex items-center justify-center">
|
|
<MdOutlineSwitchAccount className="size-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-extrabold text-slate-900 dark:text-white text-base leading-snug">
|
|
انتخاب حساب کاربری (تست)
|
|
</h3>
|
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
|
تغییر سریع پروفایل و توکن جهت تست
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(false)}
|
|
className="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 text-sm p-1"
|
|
>
|
|
<IoClose className="size-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Scrollable Account Cards */}
|
|
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-2.5 min-h-0">
|
|
{/* Main Predefined Accounts */}
|
|
{mainAccounts.map((acc) => (
|
|
<div
|
|
key={acc.token}
|
|
className={[
|
|
"flex items-center justify-between gap-3 p-3 rounded-2xl border transition-all select-none",
|
|
acc.isActive
|
|
? "border-rose-500 bg-rose-50/70 dark:bg-rose-950/40 shadow-xs ring-1 ring-rose-500/30"
|
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800/80 hover:bg-slate-50/60 dark:hover:bg-slate-800",
|
|
].join(" ")}
|
|
>
|
|
<div
|
|
onClick={() => handleSelectToken(acc.token)}
|
|
className="flex-1 min-w-0 flex items-center gap-3 text-right cursor-pointer"
|
|
>
|
|
<div className="size-11 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-2xl shrink-0 border border-slate-200/60 dark:border-slate-700/60 shadow-3xs">
|
|
{acc.emoji}
|
|
</div>
|
|
<div className="flex-1 min-w-0 flex flex-col justify-center gap-0.5 text-right">
|
|
<span className="text-sm font-bold text-slate-900 dark:text-white leading-tight">
|
|
{acc.title}
|
|
</span>
|
|
<span
|
|
className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate block leading-tight text-right"
|
|
dir="ltr"
|
|
>
|
|
{acc.subtitle}
|
|
</span>
|
|
<span
|
|
className="text-[10px] font-mono text-slate-400 dark:text-slate-500 truncate block text-left leading-tight"
|
|
dir="ltr"
|
|
>
|
|
{acc.token}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5 shrink-0 pr-1">
|
|
{acc.isActive && (
|
|
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-rose-500 text-white shadow-xs">
|
|
فعال
|
|
</span>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => handleResetUser(acc.id, acc.title)}
|
|
title="پاک کردن اطلاعات و شروع مجدد"
|
|
className="size-8 rounded-lg flex items-center justify-center text-slate-400 hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition-colors cursor-pointer"
|
|
>
|
|
<IoTrashOutline className="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{/* Guest Accounts */}
|
|
{guestAccounts.map((acc) => (
|
|
<div
|
|
key={acc.token}
|
|
className={[
|
|
"flex items-center justify-between gap-3 p-3 rounded-2xl border transition-all select-none",
|
|
acc.isActive
|
|
? "border-rose-500 bg-rose-50/70 dark:bg-rose-950/40 shadow-xs ring-1 ring-rose-500/30"
|
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800/80 hover:bg-slate-50/60 dark:hover:bg-slate-800",
|
|
].join(" ")}
|
|
>
|
|
<div
|
|
onClick={() => handleSelectToken(acc.token)}
|
|
className="flex-1 min-w-0 flex items-center gap-3 text-right cursor-pointer"
|
|
>
|
|
<div className="size-11 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-2xl shrink-0 border border-slate-200/60 dark:border-slate-700/60 shadow-3xs">
|
|
{acc.emoji}
|
|
</div>
|
|
<div className="flex-1 min-w-0 flex flex-col justify-center gap-0.5 text-right">
|
|
<span className="text-sm font-bold text-slate-900 dark:text-white leading-tight">
|
|
{acc.title}
|
|
</span>
|
|
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate block leading-tight">
|
|
{acc.subtitle}
|
|
</span>
|
|
<span
|
|
className="text-[10px] font-mono text-slate-400 dark:text-slate-500 truncate block text-left leading-tight"
|
|
dir="ltr"
|
|
>
|
|
{acc.token}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5 shrink-0 pr-1">
|
|
{acc.isActive && (
|
|
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-rose-500 text-white shadow-xs">
|
|
فعال
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{/* Custom Active Token Card (if any custom token is set) */}
|
|
{isCustomToken && (
|
|
<div className="flex items-center justify-between gap-3 p-3 rounded-2xl border border-rose-500 bg-rose-50/70 dark:bg-rose-950/40 shadow-xs ring-1 ring-rose-500/30 select-none">
|
|
<div className="flex-1 min-w-0 flex items-center gap-3 text-right">
|
|
<div className="size-11 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-2xl shrink-0 border border-slate-200/60 dark:border-slate-700/60 shadow-3xs">
|
|
🔑
|
|
</div>
|
|
<div className="flex-1 min-w-0 flex flex-col justify-center gap-0.5 text-right">
|
|
<span className="text-sm font-bold text-slate-900 dark:text-white leading-tight">
|
|
توکن سفارشی
|
|
</span>
|
|
<span
|
|
className="text-[10px] font-mono text-slate-500 dark:text-slate-400 truncate block text-left leading-tight"
|
|
dir="ltr"
|
|
>
|
|
{currentToken}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5 shrink-0 pr-1">
|
|
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-rose-500 text-white shadow-xs">
|
|
فعال
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleResetUser(null, "توکن سفارشی")}
|
|
title="پاک کردن اطلاعات و شروع مجدد"
|
|
className="size-8 rounded-lg flex items-center justify-center text-slate-400 hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition-colors cursor-pointer"
|
|
>
|
|
<IoTrashOutline className="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* No Token Option Card */}
|
|
<div
|
|
className={[
|
|
"flex items-center justify-between gap-3 p-3 rounded-2xl border transition-all select-none",
|
|
isNoToken
|
|
? "border-rose-500 bg-rose-50/70 dark:bg-rose-950/40 shadow-xs ring-1 ring-rose-500/30"
|
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800/80 hover:bg-slate-50/60 dark:hover:bg-slate-800",
|
|
].join(" ")}
|
|
>
|
|
<div
|
|
onClick={() => handleSelectToken("NO_TOKEN")}
|
|
className="flex-1 min-w-0 flex items-center gap-3 text-right cursor-pointer"
|
|
>
|
|
<div className="size-11 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-2xl shrink-0 border border-slate-200/60 dark:border-slate-700/60 shadow-3xs">
|
|
👤
|
|
</div>
|
|
<div className="flex-1 min-w-0 flex flex-col justify-center gap-0.5 text-right">
|
|
<span className="text-sm font-bold text-slate-900 dark:text-white leading-tight">
|
|
کاربر بدون توکن
|
|
</span>
|
|
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate block leading-tight">
|
|
حالت مهمان / بدون احراز هویت
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5 shrink-0 pr-1">
|
|
{isNoToken && (
|
|
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-rose-500 text-white shadow-xs">
|
|
فعال
|
|
</span>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => handleResetUser(null, "کاربر بدون توکن")}
|
|
title="پاک کردن اطلاعات و شروع مجدد"
|
|
className="size-8 rounded-lg flex items-center justify-center text-slate-400 hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition-colors cursor-pointer"
|
|
>
|
|
<IoTrashOutline className="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Custom Token Input Section */}
|
|
<div className="rounded-2xl border border-dashed border-slate-300 dark:border-slate-700 p-3.5 bg-slate-50/70 dark:bg-slate-800/40 space-y-2.5 mt-3">
|
|
<div className="flex items-center gap-2 text-xs font-bold text-slate-700 dark:text-slate-300">
|
|
<IoKeyOutline className="size-4 text-amber-500" />
|
|
<span>افزودن یا تنظیم توکن دلخواه:</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="text"
|
|
value={customTokenInput}
|
|
onChange={(e) => setCustomTokenInput(e.target.value)}
|
|
placeholder="توکن ۳۲/۴۰ کاراکتری..."
|
|
dir="ltr"
|
|
className="flex-1 min-w-0 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-3 py-2 text-xs font-mono text-slate-800 dark:text-slate-200 outline-none focus:border-rose-500 focus:ring-1 focus:ring-rose-500/20 transition-all placeholder:text-slate-400 placeholder:text-[11px]"
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && customTokenInput.trim()) {
|
|
handleSelectToken(customTokenInput);
|
|
}
|
|
}}
|
|
/>
|
|
<button
|
|
type="button"
|
|
disabled={!customTokenInput.trim()}
|
|
onClick={() => handleSelectToken(customTokenInput)}
|
|
className="rounded-xl bg-rose-500 hover:bg-rose-600 active:scale-95 px-4 py-2 text-xs font-bold text-white shadow-xs disabled:opacity-40 disabled:cursor-not-allowed transition-all shrink-0 cursor-pointer"
|
|
>
|
|
اعمال
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modal Footer */}
|
|
<div className="px-5 py-3 border-t border-slate-100 dark:border-slate-800 shrink-0 bg-slate-50/50 dark:bg-slate-900/50">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(false)}
|
|
className="w-full py-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 font-bold text-xs transition-colors cursor-pointer"
|
|
>
|
|
بستن پنجره
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default TokenSwitcher;
|