From 50b5a5e145fd0f9fd0b20f5fb6ec55983527459b Mon Sep 17 00:00:00 2001 From: mortezaei Date: Sat, 15 Aug 2026 23:17:32 +0330 Subject: [PATCH] f --- src/app/layout.tsx | 6 +- src/components/Componentes/token-switcher.tsx | 236 +++++++++++++++--- 2 files changed, 197 insertions(+), 45 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5f53462..33d61f6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -211,11 +211,7 @@ export default function RootLayout({ suppressHydrationWarning > - {isDevelopment ? ( -
- -
- ) : null} + {isDevelopment ? : null}
{children}
{isDevelopment ? : null} diff --git a/src/components/Componentes/token-switcher.tsx b/src/components/Componentes/token-switcher.tsx index 2d00828..98beb24 100644 --- a/src/components/Componentes/token-switcher.tsx +++ b/src/components/Componentes/token-switcher.tsx @@ -1,7 +1,7 @@ "use client"; -import { useEffect, useState } from "react"; -import { MdOutlineSwitchAccount } from "react-icons/md"; +import { useEffect, useRef, useState } from "react"; +import { MdOutlineSwitchAccount, MdDragIndicator } from "react-icons/md"; import { getClientCookie, setClientCookie } from "@/lib/cookies"; import { setCachedMarriageEntryPath } from "@/lib/entry-route-cache"; @@ -14,22 +14,37 @@ 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 [isOpen, setIsOpen] = useState(false); const [currentToken, setCurrentToken] = useState(""); const [customTokenInput, setCustomTokenInput] = useState(""); - const [isCustomInputOpen, setIsCustomInputOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const [position, setPosition] = useState<{ x: number; y: number } | null>(null); + const [isDragging, setIsDragging] = useState(false); + + const containerRef = useRef(null); + const dragStart = useRef({ + x: 0, + y: 0, + startX: 0, + startY: 0, + hasMoved: false, + }); useEffect(() => { + setMounted(true); if (typeof window !== "undefined") { const token = (window as any).HABIB_TOKEN ?? @@ -38,8 +53,45 @@ export function TokenSwitcher({ 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; @@ -52,7 +104,6 @@ export function TokenSwitcher({ if (!trimmed) return; if (typeof window !== "undefined") { - // Clear local storage and session storage so answers and cached state from previous user are removed try { window.localStorage.clear(); window.sessionStorage.clear(); @@ -72,7 +123,6 @@ export function TokenSwitcher({ setCurrentToken(trimmed); setIsOpen(false); - // Navigate to the beginning of the flow (intro page) window.location.replace("/"); } }; @@ -88,7 +138,6 @@ export function TokenSwitcher({ } try { - // 1. Clear local storage and session storage completely if (typeof window !== "undefined") { try { window.localStorage.clear(); @@ -98,7 +147,6 @@ export function TokenSwitcher({ } } - // 2. Call backend reset script via API route if it is a real DB user if (userId !== null) { const response = await fetch("/api/dev-reset-profile", { method: "POST", @@ -115,7 +163,6 @@ export function TokenSwitcher({ } } - // 3. Set the target token in the cookies and redirect to / const targetToken = userId === 17119 ? MALE_TOKEN @@ -146,41 +193,150 @@ export function TokenSwitcher({ } }; + // Pointer drag handlers + const handlePointerDown = (e: React.PointerEvent) => { + 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) => { + 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) => { + 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 + ? "خانم 👩" + : isNoToken + ? "بدون توکن 👤" + : "سفارشی 🔑"; + + if (!mounted && isFloating) { + return null; + } + + const badgeContent = ( +
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 && ( +
+ +
+ )} + + {/* Role icon & selected account label only */} + + {currentRoleLabel} +
+ ); + return ( <> - + {badgeContent} {isOpen && ( -
+
setIsOpen(false)} + >
e.stopPropagation()} >
@@ -191,7 +347,7 @@ export function TokenSwitcher({