Browse Source
feat: implement comprehensive marriage profile viewing system with multi-language support and custom UI components
front-test-2
feat: implement comprehensive marriage profile viewing system with multi-language support and custom UI components
front-test-2
43 changed files with 1804 additions and 613 deletions
-
2docker-compose.yml
-
10public/assets/images/female_avatar.svg
-
9public/assets/images/islamic_pattern_2_2892_3864.svg
-
2src/app/api/dev-reset-profile/route.ts
-
233src/app/candidate-contact/page.tsx
-
222src/app/new-match/page.tsx
-
146src/app/new-match/profile/page.tsx
-
65src/app/request-accepted/page.tsx
-
2src/app/request-sent/page.tsx
-
25src/components/Componentes/navigation-button.tsx
-
147src/components/Componentes/payment-swipe-modal.tsx
-
6src/components/Componentes/question-answer-storage.tsx
-
5src/components/Componentes/question-birthplace.tsx
-
133src/components/Componentes/question-phone.tsx
-
7src/components/Componentes/question-section-flow.tsx
-
2src/components/Componentes/subscription-required-sheet.tsx
-
69src/components/Componentes/token-switcher.tsx
-
9src/data/questions/en.json
-
9src/data/questions/fa.json
-
48src/translations/locales/ar.json
-
48src/translations/locales/az.json
-
48src/translations/locales/bn.json
-
48src/translations/locales/da.json
-
48src/translations/locales/de.json
-
48src/translations/locales/en.json
-
48src/translations/locales/es.json
-
46src/translations/locales/fa.json
-
48src/translations/locales/fr.json
-
48src/translations/locales/gu.json
-
48src/translations/locales/ha.json
-
48src/translations/locales/he.json
-
48src/translations/locales/hi.json
-
48src/translations/locales/id.json
-
48src/translations/locales/ks.json
-
48src/translations/locales/pt.json
-
48src/translations/locales/ru.json
-
48src/translations/locales/sw.json
-
48src/translations/locales/tg.json
-
48src/translations/locales/tr.json
-
48src/translations/locales/ul.json
-
48src/translations/locales/ur.json
-
48src/translations/locales/uz.json
-
48src/translations/locales/zh.json
10
public/assets/images/female_avatar.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
9
public/assets/images/islamic_pattern_2_2892_3864.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -50,12 +50,28 @@ export function NavigationButton({ |
|||||
const { data: profile } = useMarriageProfileQuery(); |
const { data: profile } = useMarriageProfileQuery(); |
||||
|
|
||||
const isFemale = profile?.gender === "female"; |
const isFemale = profile?.gender === "female"; |
||||
|
const isMale = profile?.gender === "male"; |
||||
const hasActiveSubscription = !!profile?.active_subscription; |
const hasActiveSubscription = !!profile?.active_subscription; |
||||
|
|
||||
|
// خانمها: باید وضعیت پرونده Waiting یا بعد از آن باشد (کامل کردن مشخصات اولیه)
|
||||
|
const hasFemaleSupportAccess = |
||||
|
isFemale && |
||||
|
profile?.status !== "pending_onboarding" && |
||||
|
profile?.status !== "pending_info"; |
||||
|
|
||||
|
// آقایان: باید حسابشان Pro باشد (دارای اشتراک فعال)
|
||||
|
const hasMaleSupportAccess = isMale && hasActiveSubscription; |
||||
|
|
||||
|
const hasSupportAccess = hasFemaleSupportAccess || hasMaleSupportAccess; |
||||
|
|
||||
if (icon === "subscription" && isFemale) { |
if (icon === "subscription" && isFemale) { |
||||
return <div className="size-10" />; |
return <div className="size-10" />; |
||||
} |
} |
||||
|
|
||||
|
if (icon === "support" && !hasSupportAccess) { |
||||
|
return <div className="size-10" />; |
||||
|
} |
||||
|
|
||||
const iconNode = (() => { |
const iconNode = (() => { |
||||
switch (icon) { |
switch (icon) { |
||||
case "back": |
case "back": |
||||
@ -162,6 +178,15 @@ export function NavigationButton({ |
|||||
} |
} |
||||
} else if (icon === "close") { |
} else if (icon === "close") { |
||||
router.back(); |
router.back(); |
||||
|
} else if (icon === "support") { |
||||
|
if (typeof window !== "undefined" && (window as any).HabibApp) { |
||||
|
(window as any).HabibApp.postMessage( |
||||
|
JSON.stringify({ |
||||
|
action: "open_consultant_page", |
||||
|
data: { consultant: "[email protected]" }, |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,147 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import Image from "next/image"; |
|
||||
import { authBridge } from "@/lib/auth-bridge"; |
|
||||
import { useI18n } from "@/translations/provider"; |
|
||||
import { DotsLoader } from "./button"; |
|
||||
import SwipeButton from "./swipe-button"; |
|
||||
|
|
||||
type PaymentSwipeModalProps = { |
|
||||
onSuccess: () => void; |
|
||||
isPaymentPending: boolean; |
|
||||
errorMessage: string | null; |
|
||||
}; |
|
||||
|
|
||||
export function PaymentSwipeModal({ |
|
||||
onSuccess, |
|
||||
isPaymentPending, |
|
||||
errorMessage, |
|
||||
}: PaymentSwipeModalProps) { |
|
||||
const { dictionary: t } = useI18n(); |
|
||||
const modalT = (t as any).paymentModal || {}; |
|
||||
|
|
||||
const currentCoins = authBridge.getCoins(); |
|
||||
|
|
||||
const handleCloseService = () => { |
|
||||
if ( |
|
||||
typeof window !== "undefined" && |
|
||||
(window as any).HabibApp?.postMessage |
|
||||
) { |
|
||||
(window as any).HabibApp.postMessage( |
|
||||
JSON.stringify({ action: "close_service" }), |
|
||||
); |
|
||||
} else { |
|
||||
window.history.back(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div |
|
||||
className="fixed inset-0 z-50 flex items-end justify-center bg-[#171717]/55 backdrop-blur-xs transition-opacity duration-300" |
|
||||
role="dialog" |
|
||||
aria-modal="true" |
|
||||
> |
|
||||
<section className="w-full sm:max-w-[375px] rounded-t-[24px] bg-[#FAF9F9] px-5 pt-6 pb-8 text-center shadow-[0_-8px_30px_rgb(0,0,0,0.12)] animate-slide-up"> |
|
||||
{/* Diamond Icon Indicator */} |
|
||||
<div className="mx-auto flex h-[64px] w-[64px] items-center justify-center rounded-full bg-[#FFECEF] shadow-sm mb-3"> |
|
||||
<Image |
|
||||
src="/assets/images/icon-park-outline_diamond.svg" |
|
||||
alt="Diamond" |
|
||||
width={36} |
|
||||
height={36} |
|
||||
className="shrink-0" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* Modal Title */} |
|
||||
<h2 className="text-[18px] font-bold text-[#1C1C1E] leading-tight"> |
|
||||
{modalT.title || "Verification & Subscription"} |
|
||||
</h2> |
|
||||
|
|
||||
{/* Verification Info Panel */} |
|
||||
<div className="mt-4 rounded-[16px] bg-white border border-[#E5E5EA] p-4 text-right"> |
|
||||
<p className="text-[13px] leading-[1.6] font-medium text-[#3A3A3C]"> |
|
||||
{modalT.verificationText || |
|
||||
"This payment acts as a verification process for your account and activates a 3-month subscription to receive new case introductions. The cost is 50 Habib Coins."} |
|
||||
</p> |
|
||||
|
|
||||
<div className="mt-3 flex items-center justify-between border-t border-[#F2F2F7] pt-2.5"> |
|
||||
<span className="text-[12px] font-semibold text-[#8E8E93]"> |
|
||||
{modalT.activeFor3Months || "Validity duration"} |
|
||||
</span> |
|
||||
<span className="text-[13px] font-bold text-[#F0445B]"> |
|
||||
3 {(t.common as any)?.months || "Months"} |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-1.5 flex items-center justify-between"> |
|
||||
<span className="text-[12px] font-semibold text-[#8E8E93]"> |
|
||||
{modalT.cost || "Subscription fee"} |
|
||||
</span> |
|
||||
<span className="inline-flex items-center gap-1 text-[13px] font-bold text-[#F0445B]"> |
|
||||
<span>50</span> |
|
||||
<span className="text-[11px] font-medium text-[#8E8E93]"> |
|
||||
{(t.common as any)?.coins || "Coins"} |
|
||||
</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* Quantitative Disclaimer Box */} |
|
||||
<div className="mt-3 rounded-[16px] bg-[#FFF2F4] border border-[#FFCCD4] p-3 text-right"> |
|
||||
<p className="text-[11px] leading-[1.5] font-semibold text-[#FF4F67]"> |
|
||||
{modalT.disclaimerText || |
|
||||
"Please note that there is no guarantee for a specific number of cases, and the volume of incoming cases is solely subject to profile compatibility with other users."} |
|
||||
</p> |
|
||||
</div> |
|
||||
|
|
||||
{/* User Balance Status */} |
|
||||
<div className="mt-4 flex items-center justify-between px-1.5 text-sm"> |
|
||||
<span className="font-semibold text-[#8E8E93]"> |
|
||||
{(t.common as any)?.balance || "Your balance"}: |
|
||||
</span> |
|
||||
<span |
|
||||
className={`font-bold ${currentCoins < 50 ? "text-[#FF4F67]" : "text-[#1C1C1E]"}`} |
|
||||
> |
|
||||
{currentCoins} {(t.common as any)?.coins || "Coins"} |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* Error Message Box */} |
|
||||
{errorMessage && ( |
|
||||
<div className="mt-3 rounded-[12px] border border-[#FF3B30]/30 bg-[#FF3B30]/10 p-3 text-right"> |
|
||||
<p className="text-[12px] font-semibold text-[#FF3B30]"> |
|
||||
{errorMessage} |
|
||||
</p> |
|
||||
</div> |
|
||||
)} |
|
||||
|
|
||||
{/* Swipe Button / Pending Indicator */} |
|
||||
<div className="mt-5"> |
|
||||
{isPaymentPending ? ( |
|
||||
<div className="flex h-[56px] w-full items-center justify-center rounded-full bg-[#FFECEF]"> |
|
||||
<DotsLoader /> |
|
||||
</div> |
|
||||
) : ( |
|
||||
<SwipeButton |
|
||||
onSuccess={onSuccess} |
|
||||
text={modalT.swipeToPay || "Swipe to pay 50 Habib Coins"} |
|
||||
disabled={currentCoins < 50} |
|
||||
/> |
|
||||
)} |
|
||||
</div> |
|
||||
|
|
||||
{/* Close/Exit link */} |
|
||||
<button |
|
||||
type="button" |
|
||||
onClick={handleCloseService} |
|
||||
className="mt-4 inline-block text-[14px] font-bold text-[#8E8E93] hover:text-[#48484A] transition-colors" |
|
||||
> |
|
||||
{modalT.close || "Exit"} |
|
||||
</button> |
|
||||
</section> |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
export default PaymentSwipeModal; |
|
||||
@ -6,9 +6,11 @@ import { getClientCookie, setClientCookie } from "@/lib/cookies"; |
|||||
|
|
||||
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 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]"; |
||||
|
|
||||
const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; |
const TOKEN_COOKIE_NAME = "HABIB_TOKEN"; |
||||
|
|
||||
@ -38,6 +40,7 @@ export function TokenSwitcher({ |
|||||
}, []); |
}, []); |
||||
|
|
||||
const isMale = currentToken === MALE_TOKEN; |
const isMale = currentToken === MALE_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; |
||||
|
|
||||
@ -103,6 +106,8 @@ export function TokenSwitcher({ |
|||||
? MALE_TOKEN |
? MALE_TOKEN |
||||
: userId === 147714 |
: userId === 147714 |
||||
? FEMALE_TOKEN |
? FEMALE_TOKEN |
||||
|
: userId === 146024 |
||||
|
? MALE_2_TOKEN |
||||
: "NO_TOKEN"; |
: "NO_TOKEN"; |
||||
|
|
||||
setClientCookie(TOKEN_COOKIE_NAME, targetToken); |
setClientCookie(TOKEN_COOKIE_NAME, targetToken); |
||||
@ -142,6 +147,8 @@ export function TokenSwitcher({ |
|||||
<span> |
<span> |
||||
{isMale |
{isMale |
||||
? "آقا 👨" |
? "آقا 👨" |
||||
|
: isMale2 |
||||
|
? "آقا ۲ 👨" |
||||
: isFemale |
: isFemale |
||||
? "خانم 👩" |
? "خانم 👩" |
||||
: isNoToken |
: isNoToken |
||||
@ -183,15 +190,15 @@ export function TokenSwitcher({ |
|||||
<button |
<button |
||||
type="button" |
type="button" |
||||
onClick={() => handleSelectToken(MALE_TOKEN)} |
onClick={() => handleSelectToken(MALE_TOKEN)} |
||||
className="flex-1 flex items-center gap-3 p-3.5 text-right" |
|
||||
|
className="flex-1 flex items-center gap-3 p-3.5 text-right min-w-0" |
||||
> |
> |
||||
<span className="text-2xl">👨</span> |
|
||||
<div> |
|
||||
|
<span className="text-2xl shrink-0">👨</span> |
||||
|
<div className="min-w-0 flex-1"> |
||||
<div className="text-sm font-bold">حساب آقا</div> |
<div className="text-sm font-bold">حساب آقا</div> |
||||
<div className="text-xs text-slate-500 dark:text-slate-400 font-medium"> |
|
||||
|
<div className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate"> |
||||
{MALE_EMAIL} |
{MALE_EMAIL} |
||||
</div> |
</div> |
||||
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left mt-0.5 select-all"> |
|
||||
|
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left mt-0.5 select-all truncate"> |
||||
{MALE_TOKEN} |
{MALE_TOKEN} |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
@ -213,6 +220,48 @@ export function TokenSwitcher({ |
|||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|
||||
|
{/* Male 2 option card */} |
||||
|
<div |
||||
|
className={[ |
||||
|
"flex items-center justify-between rounded-xl border transition-all dir-rtl overflow-hidden", |
||||
|
isMale2 |
||||
|
? "border-rose-500 bg-rose-50/60 dark:bg-rose-950/30 text-rose-900 dark:text-rose-100 font-semibold shadow-xs" |
||||
|
: "border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => handleSelectToken(MALE_2_TOKEN)} |
||||
|
className="flex-1 flex items-center gap-3 p-3.5 text-right min-w-0" |
||||
|
> |
||||
|
<span className="text-2xl shrink-0">👨</span> |
||||
|
<div className="min-w-0 flex-1"> |
||||
|
<div className="text-sm font-bold">حساب آقا ۲</div> |
||||
|
<div className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate"> |
||||
|
{MALE_2_EMAIL} |
||||
|
</div> |
||||
|
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left mt-0.5 select-all truncate"> |
||||
|
{MALE_2_TOKEN} |
||||
|
</div> |
||||
|
</div> |
||||
|
</button> |
||||
|
<div className="flex items-center gap-1.5 pl-3"> |
||||
|
{isMale2 && ( |
||||
|
<span className="text-[10px] px-2 py-0.5 rounded-full bg-rose-500 text-white font-medium"> |
||||
|
فعال |
||||
|
</span> |
||||
|
)} |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => handleResetUser(146024, "حساب آقا ۲")} |
||||
|
title="پاک کردن اطلاعات و شروع مجدد" |
||||
|
className="p-2 text-slate-400 hover:text-rose-500 hover:bg-rose-50 dark:hover:bg-rose-950/20 rounded-lg transition-colors" |
||||
|
> |
||||
|
🗑️ |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
{/* Female option card */} |
{/* Female option card */} |
||||
<div |
<div |
||||
className={[ |
className={[ |
||||
@ -225,15 +274,15 @@ export function TokenSwitcher({ |
|||||
<button |
<button |
||||
type="button" |
type="button" |
||||
onClick={() => handleSelectToken(FEMALE_TOKEN)} |
onClick={() => handleSelectToken(FEMALE_TOKEN)} |
||||
className="flex-1 flex items-center gap-3 p-3.5 text-right" |
|
||||
|
className="flex-1 flex items-center gap-3 p-3.5 text-right min-w-0" |
||||
> |
> |
||||
<span className="text-2xl">👩</span> |
|
||||
<div> |
|
||||
|
<span className="text-2xl shrink-0">👩</span> |
||||
|
<div className="min-w-0 flex-1"> |
||||
<div className="text-sm font-bold">حساب خانم</div> |
<div className="text-sm font-bold">حساب خانم</div> |
||||
<div className="text-xs text-slate-500 dark:text-slate-400 font-medium"> |
|
||||
|
<div className="text-xs text-slate-500 dark:text-slate-400 font-medium truncate"> |
||||
{FEMALE_EMAIL} |
{FEMALE_EMAIL} |
||||
</div> |
</div> |
||||
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left mt-0.5 select-all"> |
|
||||
|
<div className="text-[11px] font-mono text-slate-400 dark:text-slate-500 dir-ltr text-left mt-0.5 select-all truncate"> |
||||
{FEMALE_TOKEN} |
{FEMALE_TOKEN} |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue