You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
494 lines
17 KiB
494 lines
17 KiB
"use client";
|
|
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/navigation";
|
|
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { GoArrowLeft } from "react-icons/go";
|
|
import { HiEllipsisVertical } from "react-icons/hi2";
|
|
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
|
|
import {
|
|
extractHabcoinPaymentUrl,
|
|
useHabcoinPaymentMutation,
|
|
} from "@/hooks/marriage/use-habcoin-payment";
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
|
|
import {
|
|
buyHabibCoinPackages,
|
|
isInFlutterWebView,
|
|
} from "@/lib/webview-actions";
|
|
import { useI18n } from "@/translations/provider";
|
|
import ErrorToast from "./error-toast";
|
|
import HelpModal from "./help-modal";
|
|
import InformationSheet from "./information-sheet";
|
|
import { hasSupportAccess } from "./support-access";
|
|
import SupportSheet from "./support-sheet";
|
|
import { UiIcon } from "./ui-icon";
|
|
|
|
type NavigationButtonIcon =
|
|
| "back"
|
|
| "support"
|
|
| "close"
|
|
| "info"
|
|
| "document"
|
|
| "subscription"
|
|
| "consultation"
|
|
| "more";
|
|
type NavigationButtonVariant = "default" | "transparent";
|
|
|
|
export type NavigationButtonProps = Omit<
|
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
"children"
|
|
> & {
|
|
icon: NavigationButtonIcon;
|
|
iconLabel?: string;
|
|
variant?: NavigationButtonVariant;
|
|
helpTitle?: ReactNode;
|
|
helpDescription?: ReactNode;
|
|
helpButtonText?: ReactNode;
|
|
disableHelpModal?: boolean;
|
|
};
|
|
|
|
export function NavigationButton({
|
|
icon,
|
|
iconLabel,
|
|
variant = "default",
|
|
type = "button",
|
|
className,
|
|
helpTitle,
|
|
helpDescription,
|
|
helpButtonText,
|
|
disableHelpModal = false,
|
|
...props
|
|
}: NavigationButtonProps) {
|
|
const router = useRouter();
|
|
const { dictionary: t } = useI18n();
|
|
const [isHelpOpen, setIsHelpOpen] = useState(false);
|
|
const [isSupportOpen, setIsSupportOpen] = useState(false);
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
const [isSubscriptionInfoOpen, setIsSubscriptionInfoOpen] = useState(false);
|
|
const [isSubscriptionLoading, setIsSubscriptionLoading] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Only fetch profile for icons that actually need profile data.
|
|
// Simple icons like back, close, info, document don't need it.
|
|
const needsProfile =
|
|
icon === "support" ||
|
|
icon === "subscription" ||
|
|
icon === "more" ||
|
|
icon === "consultation";
|
|
const { data: profile, refetch } = useMarriageProfileQuery({
|
|
enabled: needsProfile,
|
|
});
|
|
const paymentMutation = useHabcoinPaymentMutation();
|
|
const queryClient = useQueryClient();
|
|
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
|
const [toastVariant, setToastVariant] = useState<"error" | "success">(
|
|
"error",
|
|
);
|
|
const [isRenewing, setIsRenewing] = useState(false);
|
|
const [isInsufficientCoins, setIsInsufficientCoins] = useState(false);
|
|
|
|
const renderSubscriptionModal = () => {
|
|
if (!isSubscriptionInfoOpen) return null;
|
|
if (isSubscriptionLoading) {
|
|
return (
|
|
<InformationSheet
|
|
isLoading={true}
|
|
onClose={() => setIsSubscriptionInfoOpen(false)}
|
|
/>
|
|
);
|
|
}
|
|
return hasActiveSubscription ? (
|
|
<>
|
|
<InformationSheet
|
|
icon="/assets/images/diamond-color.png"
|
|
title={t["Subscription Status"]}
|
|
description={t["{days} days remaining of your subscription."].replace(
|
|
"{days}",
|
|
String(profile?.active_subscription?.remaining_days ?? 0),
|
|
)}
|
|
onClose={() => setIsSubscriptionInfoOpen(false)}
|
|
buttons={({ close }) => (
|
|
<div className="space-y-3">
|
|
{isInsufficientCoins && isInFlutterWebView() && (
|
|
<button
|
|
type="button"
|
|
className="appearance-none border-0 bg-transparent p-0 text-left w-full"
|
|
onClick={() => buyHabibCoinPackages()}
|
|
>
|
|
<div className="inline-flex w-full items-center justify-center gap-2 rounded-[18px] bg-[#00AC78] px-4 h-[52px] text-[16px] font-semibold text-white shadow-[0_10px_18px_rgba(0,172,120,0.28)] transition-opacity active:opacity-90">
|
|
<Image
|
|
src="/assets/images/Inner Plugdsain Iframe.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
width={18}
|
|
height={18}
|
|
className="shrink-0"
|
|
/>
|
|
<span className="truncate">
|
|
{t["Buy Habib Coins"] || "Buy Habib Coins"}
|
|
</span>
|
|
</div>
|
|
</button>
|
|
)}
|
|
<div className="grid w-full grid-cols-[33fr_67fr] gap-3">
|
|
<button
|
|
type="button"
|
|
className="appearance-none border-0 bg-transparent p-0 text-left min-w-0"
|
|
onClick={close}
|
|
>
|
|
<div className="inline-flex w-full items-center justify-center rounded-[18px] border border-[#9A9A9A] bg-[#F7F7F7] px-2 h-[52px] text-[16px] font-bold text-[#8B8B8B] shadow-[inset_0_1px_0_rgba(255,255,255,0.8)] transition-opacity active:opacity-90 min-w-0">
|
|
<span className="truncate">{t["Back"]}</span>
|
|
</div>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
disabled={!profile?.recommended_plan?.id || isRenewing}
|
|
className="appearance-none border-0 bg-transparent p-0 text-left disabled:cursor-not-allowed disabled:opacity-70 min-w-0"
|
|
onClick={async () => {
|
|
const planId = profile?.recommended_plan?.id;
|
|
if (!planId) return;
|
|
setIsRenewing(true);
|
|
setToastMessage(null);
|
|
setToastVariant("error");
|
|
setIsInsufficientCoins(false);
|
|
try {
|
|
const paymentResponse =
|
|
await paymentMutation.mutateAsync(planId);
|
|
const paymentUrl =
|
|
extractHabcoinPaymentUrl(paymentResponse);
|
|
if (paymentUrl) {
|
|
window.location.assign(paymentUrl);
|
|
return;
|
|
}
|
|
// Success! Refetch profile query to update remaining_days in modal.
|
|
await queryClient.refetchQueries({
|
|
queryKey: marriageQueryKeys.profile(),
|
|
});
|
|
// Close the modal and show success toast
|
|
setIsSubscriptionInfoOpen(false);
|
|
setToastVariant("success");
|
|
setToastMessage(
|
|
t["Payment successful"] || "Payment successful",
|
|
);
|
|
} catch (err: any) {
|
|
console.error("Renewal payment failed", err);
|
|
const errMessage =
|
|
err?.response?.data?.error || err?.message || "";
|
|
setToastVariant("error");
|
|
if (
|
|
errMessage.includes("Not enough coins") ||
|
|
err?.response?.status === 400
|
|
) {
|
|
setIsInsufficientCoins(true);
|
|
setToastMessage(
|
|
t[
|
|
"Insufficient coin balance. Please recharge your account."
|
|
] ||
|
|
"Insufficient coin balance. Please recharge your account.",
|
|
);
|
|
} else {
|
|
setToastMessage(errMessage || "Payment failed");
|
|
}
|
|
} finally {
|
|
setIsRenewing(false);
|
|
}
|
|
}}
|
|
>
|
|
<div className="inline-flex w-full items-center justify-center gap-2 rounded-[18px] bg-[#F0445B] px-4 h-[52px] text-[16px] font-semibold text-white shadow-[0_10px_18px_rgba(240,68,91,0.28)] transition-opacity active:opacity-90 min-w-0">
|
|
<span className="truncate">
|
|
{isRenewing ? t["Renewing..."] : t["Renew Subscription"]}
|
|
</span>
|
|
|
|
{!isRenewing && (
|
|
<span className="inline-flex items-center gap-1 rounded-full bg-[#E43B51] p-1.5 text-xs font-semibold leading-none text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.12)] shrink-0 min-w-0 whitespace-nowrap">
|
|
<Image
|
|
src="/assets/images/Inner Plugdsain Iframe.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
width={18}
|
|
height={18}
|
|
className="shrink-0"
|
|
/>
|
|
<span className="truncate">
|
|
{profile?.recommended_plan?.price || "100"}
|
|
</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
/>
|
|
</>
|
|
) : (
|
|
<InformationSheet
|
|
icon="/assets/images/icon-park-outline_diamond.svg"
|
|
title={t["No Active Subscription"]}
|
|
description={
|
|
t[
|
|
"You currently do not have an active subscription. Activation of subscription is only possible when your first case is introduced to you."
|
|
]
|
|
}
|
|
buttons={t["Got it"]}
|
|
onClose={() => setIsSubscriptionInfoOpen(false)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const isFemale = profile?.gender === "female";
|
|
const hasActiveSubscription = !!profile?.active_subscription;
|
|
|
|
useEffect(() => {
|
|
if (!isDropdownOpen) return;
|
|
const handleOutsideClick = (event: MouseEvent) => {
|
|
if (
|
|
dropdownRef.current &&
|
|
!dropdownRef.current.contains(event.target as Node)
|
|
) {
|
|
setIsDropdownOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
}, [isDropdownOpen]);
|
|
|
|
const canAccessSupport = hasSupportAccess(profile);
|
|
|
|
if (icon === "subscription" && isFemale) {
|
|
return <div className="size-10" />;
|
|
}
|
|
|
|
if (icon === "support" && !canAccessSupport) {
|
|
return <div className="size-10" />;
|
|
}
|
|
|
|
const iconNode = (() => {
|
|
switch (icon) {
|
|
case "back":
|
|
case "close":
|
|
return (
|
|
<GoArrowLeft
|
|
aria-hidden="true"
|
|
className={`size-6 rtl:rotate-180 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
|
|
/>
|
|
);
|
|
case "support":
|
|
case "consultation":
|
|
return <UiIcon name="support" aria-hidden="true" className="size-6" />;
|
|
case "info":
|
|
return <UiIcon name="info" aria-hidden="true" className="size-6" />;
|
|
case "document":
|
|
return <UiIcon name="document" aria-hidden="true" className="size-6" />;
|
|
case "subscription":
|
|
return hasActiveSubscription ? (
|
|
<Image
|
|
src="/assets/images/diamond-color.png"
|
|
alt=""
|
|
aria-hidden="true"
|
|
className="size-6"
|
|
width={24}
|
|
height={24}
|
|
/>
|
|
) : (
|
|
<UiIcon name="diamond" aria-hidden="true" className="size-6" />
|
|
);
|
|
case "more":
|
|
return (
|
|
<HiEllipsisVertical
|
|
aria-hidden="true"
|
|
className={`size-6 ${variant === "transparent" ? "text-white" : "text-[#111111]"}`}
|
|
/>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
})();
|
|
|
|
const isHelpTrigger =
|
|
icon === "info" ||
|
|
icon === "document" ||
|
|
helpTitle !== undefined ||
|
|
helpDescription !== undefined;
|
|
|
|
const resolvedHelpDescription =
|
|
helpDescription ??
|
|
(icon === "document"
|
|
? (t as any)[
|
|
"Taking this test is not mandatory, but it will help you better search for a spouse."
|
|
]
|
|
: undefined);
|
|
|
|
if (icon === "more") {
|
|
return (
|
|
<div className="relative inline-block text-left" ref={dropdownRef}>
|
|
<button
|
|
{...props}
|
|
type={type}
|
|
aria-label={iconLabel ?? ((t as any)["More"] || "More")}
|
|
onClick={(event) => {
|
|
props.onClick?.(event);
|
|
if (!event.defaultPrevented) {
|
|
setIsDropdownOpen((prev) => !prev);
|
|
}
|
|
}}
|
|
className={[
|
|
"inline-flex items-center justify-center rounded-[15px] p-2 cursor-pointer transition-transform active:scale-95",
|
|
variant === "transparent" ? "bg-white/20" : "bg-[#FFFFFF]",
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
>
|
|
{iconNode}
|
|
</button>
|
|
|
|
{isDropdownOpen && (
|
|
<div className="absolute right-0 mt-2 w-48 origin-top-right rounded-2xl bg-white shadow-[0_12px_30px_rgba(0,0,0,0.15)] ring-1 ring-black/5 focus:outline-hidden z-50 overflow-hidden divide-y divide-gray-100 animate-in fade-in slide-in-from-top-2 duration-200">
|
|
<div className="py-1">
|
|
{canAccessSupport && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setIsDropdownOpen(false);
|
|
setIsSupportOpen(true);
|
|
}}
|
|
className="flex w-full items-center gap-3 px-4 py-3 text-start text-sm font-semibold text-gray-700 hover:bg-gray-50 cursor-pointer border-0 bg-transparent"
|
|
>
|
|
<UiIcon
|
|
name="support"
|
|
aria-hidden="true"
|
|
className="size-5 shrink-0"
|
|
/>
|
|
<span>{t["Support"]}</span>
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setIsDropdownOpen(false);
|
|
setIsSubscriptionInfoOpen(true);
|
|
setIsSubscriptionLoading(true);
|
|
refetch().finally(() => {
|
|
setIsSubscriptionLoading(false);
|
|
});
|
|
}}
|
|
className="flex w-full items-center gap-3 px-4 py-3 text-start text-sm font-semibold text-gray-700 hover:bg-gray-50 cursor-pointer border-0 bg-transparent"
|
|
>
|
|
<Image
|
|
src="/assets/images/diamond-color.png"
|
|
alt=""
|
|
width={20}
|
|
height={20}
|
|
className="size-5 shrink-0"
|
|
/>
|
|
<span>{t["Subscription"]}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<SupportSheet
|
|
isOpen={isSupportOpen}
|
|
onClose={() => setIsSupportOpen(false)}
|
|
/>
|
|
|
|
{renderSubscriptionModal()}
|
|
|
|
{toastMessage && (
|
|
<ErrorToast
|
|
message={toastMessage}
|
|
variant={toastVariant}
|
|
onClose={() => setToastMessage(null)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
{...props}
|
|
type={type}
|
|
aria-label={
|
|
iconLabel ??
|
|
(icon === "back"
|
|
? t["Back"]
|
|
: icon === "consultation"
|
|
? (t as any)["Consultation"]
|
|
: icon)
|
|
}
|
|
onClick={(event) => {
|
|
props.onClick?.(event);
|
|
|
|
if (!event.defaultPrevented) {
|
|
if (isHelpTrigger && !disableHelpModal) {
|
|
setIsHelpOpen(true);
|
|
} else if (!props.onClick) {
|
|
if (icon === "back") {
|
|
if (typeof window !== "undefined" && (window as any).HabibApp) {
|
|
(window as any).HabibApp.postMessage(
|
|
JSON.stringify({ action: "close_service" }),
|
|
);
|
|
} else {
|
|
router.back();
|
|
}
|
|
} else if (icon === "close") {
|
|
router.back();
|
|
} else if (icon === "support") {
|
|
setIsSupportOpen(true);
|
|
} else if (icon === "subscription") {
|
|
setIsSubscriptionInfoOpen(true);
|
|
setIsSubscriptionLoading(true);
|
|
refetch().finally(() => {
|
|
setIsSubscriptionLoading(false);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
className={[
|
|
"inline-flex items-center justify-center rounded-[15px] p-2 cursor-pointer transition-transform active:scale-95",
|
|
variant === "transparent" ? "bg-white/20" : "bg-[#FFFFFF]",
|
|
className,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
>
|
|
{iconNode}
|
|
</button>
|
|
|
|
{isHelpTrigger && !disableHelpModal ? (
|
|
<HelpModal
|
|
isOpen={isHelpOpen}
|
|
onClose={() => setIsHelpOpen(false)}
|
|
title={helpTitle}
|
|
description={resolvedHelpDescription}
|
|
buttonText={helpButtonText}
|
|
/>
|
|
) : null}
|
|
|
|
<SupportSheet
|
|
isOpen={isSupportOpen}
|
|
onClose={() => setIsSupportOpen(false)}
|
|
/>
|
|
|
|
{renderSubscriptionModal()}
|
|
|
|
{toastMessage && (
|
|
<ErrorToast
|
|
message={toastMessage}
|
|
variant={toastVariant}
|
|
onClose={() => setToastMessage(null)}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default NavigationButton;
|