Browse Source
feat: add marriage profile page with dynamic fields, consent sheets, and submission path handling
Dev
feat: add marriage profile page with dynamic fields, consent sheets, and submission path handling
Dev
4 changed files with 50 additions and 166 deletions
-
3src/app/new-match/profile/page.tsx
-
12src/components/Componentes/question-answer-storage.tsx
-
195src/components/Componentes/token-switcher.tsx
-
6src/lib/get-submit-path.ts
@ -2,42 +2,38 @@ |
|||
|
|||
import { useEffect, useState } from "react"; |
|||
import { MdOutlineSwitchAccount } 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 = "[email protected]"; |
|||
export const MALE_EMAIL = "[email protected]"; |
|||
export const MALE_2_EMAIL = "[email protected]"; |
|||
|
|||
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<string>(""); |
|||
const [customTokenInput, setCustomTokenInput] = useState<string>(""); |
|||
const [isCustomInputOpen, setIsCustomInputOpen] = useState(false); |
|||
|
|||
useEffect(() => { |
|||
setMounted(true); |
|||
if (typeof window !== "undefined") { |
|||
const token = |
|||
(window as any).HABIB_TOKEN ?? |
|||
@ -46,59 +42,15 @@ 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; |
|||
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; |
|||
!isMale && !isMale2 && !isFemale && !isNoToken; |
|||
|
|||
const handleSelectToken = (newToken: string) => { |
|||
const trimmed = newToken.trim(); |
|||
@ -221,67 +173,38 @@ 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, |
|||
}, |
|||
]; |
|||
|
|||
return ( |
|||
<> |
|||
<button |
|||
type="button" |
|||
onClick={() => setIsOpen(true)} |
|||
aria-label="تغییر توکن کاربر (تست)" |
|||
title="تغییر توکن (آقا / خانم)" |
|||
className={[ |
|||
"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" |
|||
? "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, |
|||
] |
|||
.filter(Boolean) |
|||
.join(" ")} |
|||
> |
|||
<MdOutlineSwitchAccount className="size-5 text-rose-500" /> |
|||
<span> |
|||
{isMale |
|||
? "آقا 👨" |
|||
: isMale2 |
|||
? "آقا ۲ 👨" |
|||
: isFemale |
|||
? "خانم 👩" |
|||
: isNoToken |
|||
? "بدون توکن 👤" |
|||
: "سفارشی 🔑"} |
|||
</span> |
|||
</button> |
|||
<div className="fixed top-4 left-4 z-[9999]"> |
|||
<button |
|||
type="button" |
|||
onClick={() => setIsOpen(true)} |
|||
aria-label="تغییر توکن کاربر (تست)" |
|||
title="تغییر توکن (آقا / خانم)" |
|||
className={[ |
|||
"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 cursor-pointer", |
|||
variant === "transparent" |
|||
? "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, |
|||
] |
|||
.filter(Boolean) |
|||
.join(" ")} |
|||
> |
|||
<MdOutlineSwitchAccount className="size-5 text-rose-500" /> |
|||
<span> |
|||
{isMale |
|||
? "آقا 👨" |
|||
: isMale2 |
|||
? "آقا ۲ 👨" |
|||
: isFemale |
|||
? "خانم 👩" |
|||
: isNoToken |
|||
? "بدون توکن 👤" |
|||
: "سفارشی 🔑"} |
|||
</span> |
|||
</button> |
|||
</div> |
|||
|
|||
{isOpen && ( |
|||
<div className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/50 p-4 backdrop-blur-xs animate-in fade-in duration-200"> |
|||
@ -370,50 +293,6 @@ export function TokenSwitcher({ |
|||
</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"> |
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue