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 { useEffect, useState } from "react"; |
||||
import { MdOutlineSwitchAccount } from "react-icons/md"; |
import { MdOutlineSwitchAccount } from "react-icons/md"; |
||||
|
import { |
||||
|
IoClose, |
||||
|
IoTrashOutline, |
||||
|
IoKeyOutline, |
||||
|
} from "react-icons/io5"; |
||||
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 [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 [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 ?? |
||||
@ -46,59 +42,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(); |
||||
@ -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 ( |
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 && ( |
{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"> |
<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> |
</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