2 Commits
c7256e87fa
...
1ee86b4400
4 changed files with 47 additions and 290 deletions
-
3src/app/new-match/profile/page.tsx
-
17src/components/Componentes/question-answer-storage.tsx
-
301src/components/Componentes/token-switcher.tsx
-
6src/lib/get-submit-path.ts
@ -1,54 +1,36 @@ |
|||||
"use client"; |
"use client"; |
||||
|
|
||||
import { useEffect, useRef, useState } from "react"; |
|
||||
import { MdOutlineSwitchAccount, MdDragIndicator } from "react-icons/md"; |
|
||||
import { IoClose, IoTrashOutline, IoKeyOutline } from "react-icons/io5"; |
|
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { MdOutlineSwitchAccount } from "react-icons/md"; |
||||
import { getClientCookie, setClientCookie } from "@/lib/cookies"; |
import { getClientCookie, setClientCookie } from "@/lib/cookies"; |
||||
import { setCachedMarriageEntryPath } from "@/lib/entry-route-cache"; |
import { setCachedMarriageEntryPath } from "@/lib/entry-route-cache"; |
||||
|
|
||||
export const FEMALE_TOKEN = "545f61bb3e061ccb9b19f84715eb1b1ed4740331"; |
export const FEMALE_TOKEN = "545f61bb3e061ccb9b19f84715eb1b1ed4740331"; |
||||
export const MALE_TOKEN = "f3a7543b44ef0a713d1ee0d4f7866b3825cf1308"; |
export const MALE_TOKEN = "f3a7543b44ef0a713d1ee0d4f7866b3825cf1308"; |
||||
export const MALE_2_TOKEN = "56cd0fceb93f7cecf7ffa84c590135026cf54a1d"; |
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 = "[email protected]"; |
export const FEMALE_EMAIL = "[email protected]"; |
||||
export const MALE_EMAIL = "[email protected]"; |
export const MALE_EMAIL = "[email protected]"; |
||||
export const MALE_2_EMAIL = "[email protected]"; |
export const MALE_2_EMAIL = "[email protected]"; |
||||
|
|
||||
const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; |
const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; |
||||
const STORAGE_POS_KEY = "DEV_TOKEN_SWITCHER_POS"; |
|
||||
|
|
||||
type TokenSwitcherProps = { |
type TokenSwitcherProps = { |
||||
variant?: "default" | "transparent"; |
variant?: "default" | "transparent"; |
||||
className?: string; |
className?: string; |
||||
isFloating?: boolean; |
|
||||
}; |
}; |
||||
|
|
||||
export function TokenSwitcher({ |
export function TokenSwitcher({ |
||||
variant = "default", |
variant = "default", |
||||
className, |
className, |
||||
isFloating = true, |
|
||||
}: TokenSwitcherProps) { |
}: TokenSwitcherProps) { |
||||
const [mounted, setMounted] = useState(false); |
const [mounted, setMounted] = useState(false); |
||||
const [isOpen, setIsOpen] = useState(false); |
const [isOpen, setIsOpen] = useState(false); |
||||
const [currentToken, setCurrentToken] = useState<string>(""); |
const [currentToken, setCurrentToken] = useState<string>(""); |
||||
const [customTokenInput, setCustomTokenInput] = 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 }); |
|
||||
|
const [isCustomInputOpen, setIsCustomInputOpen] = useState(false); |
||||
|
|
||||
useEffect(() => { |
useEffect(() => { |
||||
setMounted(true); |
|
||||
if (typeof window !== "undefined") { |
if (typeof window !== "undefined") { |
||||
const token = |
const token = |
||||
(window as any).HABIB_TOKEN ?? |
(window as any).HABIB_TOKEN ?? |
||||
@ -57,59 +39,15 @@ export function TokenSwitcher({ |
|||||
sessionStorage.getItem(TOKEN_COOKIE_NAME) ?? |
sessionStorage.getItem(TOKEN_COOKIE_NAME) ?? |
||||
""; |
""; |
||||
setCurrentToken(token); |
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 isMale = currentToken === MALE_TOKEN; |
||||
const isMale2 = currentToken === MALE_2_TOKEN; |
const isMale2 = currentToken === MALE_2_TOKEN; |
||||
const isFemale = currentToken === FEMALE_TOKEN; |
const isFemale = currentToken === FEMALE_TOKEN; |
||||
const isNoToken = currentToken === "NO_TOKEN" || !currentToken; |
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 = |
const isCustomToken = |
||||
!isMale && !isMale2 && !isFemale && !isNoToken && !isGuestToken; |
|
||||
|
!isMale && !isMale2 && !isFemale && !isNoToken; |
||||
|
|
||||
const handleSelectToken = (newToken: string) => { |
const handleSelectToken = (newToken: string) => { |
||||
const trimmed = newToken.trim(); |
const trimmed = newToken.trim(); |
||||
@ -232,175 +170,36 @@ export function TokenSwitcher({ |
|||||
}, |
}, |
||||
]; |
]; |
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
return ( |
||||
|
<> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setIsOpen(true)} |
||||
|
aria-label="تغییر توکن کاربر (تست)" |
||||
|
title="تغییر توکن (آقا / خانم)" |
||||
className={[ |
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", |
|
||||
|
"inline-flex items-center gap-1.5 rounded-[15px] px-3 py-2 text-xs font-bold transition-all active:scale-95 shadow-md border border-slate-200/80 dark:border-slate-700/80 backdrop-blur-md", |
||||
variant === "transparent" |
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", |
|
||||
|
? "bg-black/40 text-white hover:bg-black/50" |
||||
|
: "bg-white/95 text-slate-800 hover:bg-white dark:bg-slate-900/95 dark:text-slate-100", |
||||
className, |
className, |
||||
] |
] |
||||
.filter(Boolean) |
.filter(Boolean) |
||||
.join(" ")} |
.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} |
|
||||
|
<MdOutlineSwitchAccount className="size-5 text-rose-500" /> |
||||
|
<span> |
||||
|
{isMale |
||||
|
? "آقا 👨" |
||||
|
: isMale2 |
||||
|
? "آقا ۲ 👨" |
||||
|
: isFemale |
||||
|
? "خانم 👩" |
||||
|
: isNoToken |
||||
|
? "بدون توکن 👤" |
||||
|
: "سفارشی 🔑"} |
||||
|
</span> |
||||
|
</button> |
||||
|
|
||||
{isOpen && ( |
{isOpen && ( |
||||
<div |
<div |
||||
@ -492,50 +291,6 @@ export function TokenSwitcher({ |
|||||
</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) */} |
{/* Custom Active Token Card (if any custom token is set) */} |
||||
{isCustomToken && ( |
{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 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"> |
||||
|
|||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue