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.
 
 
 
 
 

386 lines
12 KiB

"use client";
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 { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { isInFlutterWebView } from "@/lib/webview-actions";
import { useI18n } from "@/translations/provider";
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;
badge?: ReactNode;
profile?: any;
};
export function NavigationButton({
icon,
iconLabel,
variant = "default",
type = "button",
className,
helpTitle,
helpDescription,
helpButtonText,
disableHelpModal = false,
badge,
profile: profileProp,
...props
}: NavigationButtonProps) {
const router = useRouter();
const { dictionary: t, locale } = useI18n();
const isRtl = locale === "fa" || locale === "ar" || locale === "ur";
const [isHelpOpen, setIsHelpOpen] = useState(false);
const [isSupportOpen, setIsSupportOpen] = useState(false);
const [isSubscriptionInfoOpen, setIsSubscriptionInfoOpen] = useState(false);
const [isSubscriptionLoading, setIsSubscriptionLoading] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const needsProfile =
!profileProp &&
(icon === "support" ||
icon === "subscription" ||
icon === "more" ||
icon === "consultation");
const { data: queriedProfile, refetch: refetchQuery } =
useMarriageProfileQuery({
enabled: needsProfile,
});
const profile = profileProp !== undefined ? profileProp : queriedProfile;
const refetch = refetchQuery;
const isFemale = profile?.gender === "female";
const hasActiveSubscription =
!!profile?.active_subscription &&
profile.active_subscription.is_active !== false &&
profile.active_subscription.is_valid !== false;
const handleOpenSubscriptionModal = () => {
setIsSubscriptionInfoOpen(true);
if (!profile) {
setIsSubscriptionLoading(true);
refetch().finally(() => {
setIsSubscriptionLoading(false);
});
}
};
const renderSubscriptionModal = () => {
if (!isSubscriptionInfoOpen) return null;
if (isSubscriptionLoading && !profile) {
return (
<InformationSheet
isLoading={true}
onClose={() => setIsSubscriptionInfoOpen(false)}
/>
);
}
const sub = profile?.active_subscription;
const planType = sub?.plan?.plan_type || "time_based";
const remainingDays = sub?.remaining_days ?? 0;
const usageLimit = sub?.plan?.usage_limit ?? 0;
const totalUsages = sub?.total_usages ?? 0;
const remainingUsages = Math.max(0, usageLimit - totalUsages);
let subscriptionDescription = "";
if (planType === "count_based") {
subscriptionDescription = (
t["{count} case introductions remaining of your subscription."] ||
"{count} case introductions remaining of your subscription."
).replace("{count}", String(remainingUsages));
} else if (planType === "combined") {
subscriptionDescription = (
t[
"{days} days and {count} case introductions remaining of your subscription."
] ||
"{days} days and {count} case introductions remaining of your subscription."
)
.replace("{days}", String(remainingDays))
.replace("{count}", String(remainingUsages));
} else {
subscriptionDescription = (
t["{days} days remaining of your subscription."] ||
"{days} days remaining of your subscription."
).replace("{days}", String(remainingDays));
}
return hasActiveSubscription ? (
<InformationSheet
icon="/assets/images/diamond-color.svg"
title={t["Subscription Status"] || "Subscription Status"}
description={subscriptionDescription}
buttons={t["Got it"] || t["Back"] || "Back"}
onClose={() => setIsSubscriptionInfoOpen(false)}
/>
) : (
<InformationSheet
icon="/assets/images/icon-park-outline_diamond.svg"
title={t["No Active Subscription"] || "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."
] ||
"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"] || t["Back"] || "Back"}
onClose={() => setIsSubscriptionInfoOpen(false)}
/>
);
};
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.svg"
alt=""
aria-hidden="true"
className="size-6 object-contain"
width={24}
height={24}
unoptimized
/>
) : (
<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);
handleOpenSubscriptionModal();
}}
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.svg"
alt=""
width={20}
height={20}
className="size-5 shrink-0"
/>
<span>{t["Subscription"]}</span>
</button>
</div>
</div>
)}
<SupportSheet
isOpen={isSupportOpen}
onClose={() => setIsSupportOpen(false)}
/>
{renderSubscriptionModal()}
</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") {
handleOpenSubscriptionModal();
}
}
}
}}
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()}
</>
);
}
export default NavigationButton;