diff --git a/public/assets/images/diamond-color.svg b/public/assets/images/diamond-color.svg new file mode 100644 index 0000000..58e69e0 --- /dev/null +++ b/public/assets/images/diamond-color.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/new-match/new-match-client.tsx b/src/app/new-match/new-match-client.tsx index 14ce94a..ae1c0d7 100644 --- a/src/app/new-match/new-match-client.tsx +++ b/src/app/new-match/new-match-client.tsx @@ -26,6 +26,7 @@ import MatchProfileOverlay, { import PageHeader from "@/components/Componentes/page-header"; import { PageBackground } from "@/components/Componentes/page-background"; import InformationSheet from "@/components/Componentes/information-sheet"; +import SwipeButton from "@/components/Componentes/swipe-button"; import { LoadingSkeleton } from "@/components/Componentes/loading-skeleton"; import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import { DiscountWidget } from "@/components/Componentes/discount-widget"; @@ -235,29 +236,28 @@ function useMatchSummaryDisplay( const fields = matchSummary?.public_info ?? []; const usedIndexes = new Set(); - // 1. Name: Combine first_name and last_name if available, or find general name + // 1. Name: Show only first_name (do not show last_name) let displayName: string | null = null; const firstNameIdx = fields.findIndex( (f) => f.key === "personal_identity.first_name" || - f.key?.endsWith(".first_name"), + f.key?.endsWith(".first_name") || + f.key === "first_name", ); const lastNameIdx = fields.findIndex( (f) => f.key === "personal_identity.last_name" || - f.key?.endsWith(".last_name"), + f.key?.endsWith(".last_name") || + f.key === "last_name", ); + if (lastNameIdx !== -1) { + usedIndexes.add(lastNameIdx); + } + if (firstNameIdx !== -1 && fields[firstNameIdx].value) { usedIndexes.add(firstNameIdx); - const firstName = formatFieldValue(fields[firstNameIdx].value); - if (lastNameIdx !== -1 && fields[lastNameIdx].value) { - usedIndexes.add(lastNameIdx); - const lastName = formatFieldValue(fields[lastNameIdx].value); - displayName = `${firstName} ${lastName}`.trim(); - } else { - displayName = firstName; - } + displayName = formatFieldValue(fields[firstNameIdx].value); } else { const nameField = pickField( fields, @@ -266,7 +266,8 @@ function useMatchSummaryDisplay( t, ); if (nameField) { - displayName = nameField.value; + const rawName = String(nameField.value).trim(); + displayName = rawName.split(/\s+/)[0] || rawName; } } @@ -454,26 +455,31 @@ function ProfileInfoItem({ {icon} -
+
{field.label} {field.value} @@ -492,6 +498,7 @@ export default function NewMatchClient() { const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); const { data: profile, isError, isLoading } = useMarriageProfileQuery(); const [isPaymentSheetOpen, setIsPaymentSheetOpen] = useState(false); + const [isDeclineConfirmOpen, setIsDeclineConfirmOpen] = useState(false); const [paymentError, setPaymentError] = useState(null); const [isInsufficientCoins, setIsInsufficientCoins] = useState(false); @@ -559,6 +566,16 @@ export default function NewMatchClient() { } }; + const openDeclineConfirm = () => { + setIsPaymentSheetOpen(false); + setIsDeclineConfirmOpen(true); + }; + + const cancelDeclineConfirm = () => { + setIsDeclineConfirmOpen(false); + setIsPaymentSheetOpen(true); + }; + useEffect(() => { console.log("🔍 [NewMatchClient] Current React Query Profile State:", { isLoading, @@ -933,7 +950,7 @@ export default function NewMatchClient() { {profile?.can_edit_profile === false && (
{respondMutation.isPending ? ( @@ -1106,6 +1123,46 @@ export default function NewMatchClient() { /> )} + {isDeclineConfirmOpen && ( + +

+ {t[ + "Are you sure you've fully reviewed the profile and want to reject this profile?" + ] || + (locale === "fa" + ? "آیا از رد این پیشنهاد مطمئن هستید؟ در صورت رد، این مورد دیگر در دسترس نخواهد بود." + : "Are you sure you want to decline this proposal? Once declined, this match will no longer be available.")} +

+
+ } + buttons={({ close }) => ( + { + close(); + cancelDeclineConfirm(); + }} + onSuccess={async () => { + await handleDecline(); + close(); + setIsDeclineConfirmOpen(false); + setIsPaymentSheetOpen(false); + }} + /> + )} + onClose={cancelDeclineConfirm} + /> + )} + diff --git a/src/app/request-accepted/request-accepted-client.tsx b/src/app/request-accepted/request-accepted-client.tsx index 45684de..ae175f1 100644 --- a/src/app/request-accepted/request-accepted-client.tsx +++ b/src/app/request-accepted/request-accepted-client.tsx @@ -266,7 +266,7 @@ export default function RequestAcceptedClient() { const secondaryActionText = isFemaleProfile ? t["Contact Received"] : caseStatus === "payment_done" || caseStatus === "contacted" - ? t["View contact number"] + ? t["Contact"] : t["Pay and get contact"]; const contactInfoPhoneItems = getContactInfoPhoneItems( contactInfoQuery.data?.contact_info, @@ -549,29 +549,34 @@ export default function RequestAcceptedClient() {
) : ( <> -
- {t["Request + {/* Illustration */} +
+ {/* soft glow */} +
+ +
+ {t["Request +
-

+

{titleText}

{caseStatus === "contacted" || isFemaleContactConfirmed || (isFemaleProfile && contactStatusMutation.isPending) ? ( -
+
{isFemaleProfile && contactStatusMutation.isPending ? ( ) : ( -

+

{isFemaleProfile ? t[ "Thank you for your feedback. To complete the process, please submit the final outcome of this introduction/contact so that the final status can be determined. If the final status is not yet determined, you can stay in this state until it is finalized." @@ -583,7 +588,7 @@ export default function RequestAcceptedClient() { )}

) : ( -

+

{noContactReportedSuccess ? t[ "Thank you for your feedback. Our support team will investigate the matter and notify you of the result. Please wait patiently during the review; our support will contact you." @@ -602,14 +607,14 @@ export default function RequestAcceptedClient() { {caseStatus === "contacted" || isFemaleContactConfirmed || (isFemaleProfile && contactStatusMutation.isPending) ? ( -

+
{isFemaleProfile && contactStatusMutation.isPending ? null : isFemaleProfile ? ( @@ -631,7 +636,7 @@ export default function RequestAcceptedClient() { type="button" onClick={() => setIsOutcomeSheetOpen(true)} disabled={outcomeMutation.isPending} - className="flex-1 h-[52px] rounded-[15px] bg-gradient-to-r from-[#FE6F82] to-[#E03950] text-white font-semibold group-14 flex items-center justify-center shadow-lg shadow-[#FE6F82]/30 transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50" + className="flex-1 h-[50px] px-3 rounded-full bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] flex items-center justify-center shadow-sm transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50" > {outcomeMutation.isPending ? ( @@ -643,7 +648,7 @@ export default function RequestAcceptedClient() { )}
) : ( -
+
{isFemaleProfile ? ( diff --git a/src/components/Componentes/advisor-actions-card.tsx b/src/components/Componentes/advisor-actions-card.tsx index 518453b..1fde0ac 100644 --- a/src/components/Componentes/advisor-actions-card.tsx +++ b/src/components/Componentes/advisor-actions-card.tsx @@ -1,7 +1,9 @@ "use client"; +import Link from "next/link"; import { useMarriageAdvisorsQuery } from "@/hooks/marriage/use-marriage-advisors"; -import Button from "./button"; +import { localizePath } from "@/translations/config"; +import { useI18n } from "@/translations/provider"; import NetworkImage from "./network-image"; const FALLBACK_AVATAR = "/assets/images/Avatar Image.png"; @@ -33,13 +35,14 @@ export function AdvisorActionsCard({ onGetAdvisor, className, }: AdvisorActionsCardProps) { + const { locale } = useI18n(); const { data, isLoading } = useMarriageAdvisorsQuery(); const advisors = data?.results ?? []; - // Derive real avatars from API response (pick first 3 with an avatar). + // Derive real avatars from API response (pick up to 4 with an avatar). const realAvatars: AdvisorAvatar[] = advisors .filter((a) => a.avatar_url) - .slice(0, 3) + .slice(0, 4) .map((a) => ({ id: a.username, src: a.avatar_url ?? FALLBACK_AVATAR, @@ -64,8 +67,8 @@ export function AdvisorActionsCard({ {description}

-
-
+
+
{isLoading ? /* ── Shimmer circles while API loads ── */ Array.from({ length: 3 }).map((_, i) => ( @@ -96,13 +99,22 @@ export function AdvisorActionsCard({ )}
- + {getAdvisorHref ? ( + + {getAdvisorLabel} + + ) : ( + + )}
diff --git a/src/components/Componentes/information-sheet.tsx b/src/components/Componentes/information-sheet.tsx index 53b72d8..cfcfafb 100644 --- a/src/components/Componentes/information-sheet.tsx +++ b/src/components/Componentes/information-sheet.tsx @@ -16,6 +16,12 @@ type InformationSheetPresetIcon = | "warning" | "coin" | "check" + | "diamond" + | "diamond-color" + | "diamond-color.svg" + | "diamond-color.png" + | "/assets/images/diamond-color.png" + | "/assets/images/diamond-color.svg" | "stash_play-solid.svg" | "warning.svg" | "coin.svg" @@ -42,6 +48,7 @@ export type InformationSheetProps = Omit< closeOnOutside?: boolean; onClose?: () => void; isLoading?: boolean; + showCloseButton?: boolean; }; const DEFAULT_ICON = { @@ -98,6 +105,42 @@ const ICON_PRESETS: Record< width: 36, height: 36, }, + diamond: { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, + "diamond-color": { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, + "diamond-color.svg": { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, + "diamond-color.png": { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, + "/assets/images/diamond-color.png": { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, + "/assets/images/diamond-color.svg": { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }, }; function resolveIcon(icon: InformationSheetIcon | null | undefined) { @@ -110,6 +153,16 @@ function resolveIcon(icon: InformationSheetIcon | null | undefined) { } if (typeof icon === "string") { + // If diamond PNG was requested, automatically upgrade to high-res vector SVG + if (icon.includes("diamond-color")) { + return { + src: "/assets/images/diamond-color.svg", + alt: "Subscription", + width: 48, + height: 48, + }; + } + return ( ICON_PRESETS[icon as InformationSheetPresetIcon] ?? { src: icon, @@ -137,6 +190,7 @@ export function InformationSheet({ onClose, className, isLoading = false, + showCloseButton = true, ...props }: InformationSheetProps) { const { locale, dictionary: t } = useI18n(); @@ -241,17 +295,68 @@ export function InformationSheet({
+ {showCloseButton && ( + + )} +
{isLoading ? ( -
- +
+ {/* Icon Skeleton */} + + + {/* Title Skeleton */} + + + {/* Description Lines Skeleton */} +
+ + + +
+ + {/* Middle Box Skeleton (e.g. Plan / Info) */} +
+ + +
+ + {/* Subtext Skeleton */} +
+ + +
+ + {/* Action / Swipe Buttons Skeleton */} +
+ + +
) : ( <> diff --git a/src/components/Componentes/navigation-button.tsx b/src/components/Componentes/navigation-button.tsx index ae4e5d7..3942773 100644 --- a/src/components/Componentes/navigation-button.tsx +++ b/src/components/Componentes/navigation-button.tsx @@ -135,7 +135,7 @@ export function NavigationButton({ return hasActiveSubscription ? (