|
|
@ -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 = "m.a.ghorbani01@gmail.com"; |
|
|
export const FEMALE_EMAIL = "m.a.ghorbani01@gmail.com"; |
|
|
export const MALE_EMAIL = "muhammadamin.ghorbani@gmail.com"; |
|
|
export const MALE_EMAIL = "muhammadamin.ghorbani@gmail.com"; |
|
|
export const MALE_2_EMAIL = "habibwabackup@gmail.com"; |
|
|
export const MALE_2_EMAIL = "habibwabackup@gmail.com"; |
|
|
|
|
|
|
|
|
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"> |
|
|
|