|
|
@ -1,27 +1,14 @@ |
|
|
"use client"; |
|
|
"use client"; |
|
|
|
|
|
|
|
|
import { useQueryClient } from "@tanstack/react-query"; |
|
|
|
|
|
import Image from "next/image"; |
|
|
import Image from "next/image"; |
|
|
import { useRouter } from "next/navigation"; |
|
|
import { useRouter } from "next/navigation"; |
|
|
import type { ButtonHTMLAttributes, ReactNode } from "react"; |
|
|
import type { ButtonHTMLAttributes, ReactNode } from "react"; |
|
|
import { useEffect, useRef, useState } from "react"; |
|
|
import { useEffect, useRef, useState } from "react"; |
|
|
import { GoArrowLeft } from "react-icons/go"; |
|
|
import { GoArrowLeft } from "react-icons/go"; |
|
|
import { HiEllipsisVertical } from "react-icons/hi2"; |
|
|
import { HiEllipsisVertical } from "react-icons/hi2"; |
|
|
import { marriageQueryKeys } from "@/hooks/marriage/query-keys"; |
|
|
|
|
|
import { useHabcoinInventoryQuery } from "@/hooks/marriage/use-habcoin-inventory"; |
|
|
|
|
|
import { |
|
|
|
|
|
extractHabcoinPaymentUrl, |
|
|
|
|
|
useHabcoinPaymentMutation, |
|
|
|
|
|
} from "@/hooks/marriage/use-habcoin-payment"; |
|
|
|
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; |
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; |
|
|
import { |
|
|
|
|
|
buyHabibCoinPackages, |
|
|
|
|
|
isInFlutterWebView, |
|
|
|
|
|
} from "@/lib/webview-actions"; |
|
|
|
|
|
|
|
|
import { isInFlutterWebView } from "@/lib/webview-actions"; |
|
|
import { useI18n } from "@/translations/provider"; |
|
|
import { useI18n } from "@/translations/provider"; |
|
|
import { DiscountWidget } from "./discount-widget"; |
|
|
|
|
|
import type { CheckDiscountResult } from "@/hooks/marriage/use-validate-discount"; |
|
|
|
|
|
import ErrorToast from "./error-toast"; |
|
|
|
|
|
import HelpModal from "./help-modal"; |
|
|
import HelpModal from "./help-modal"; |
|
|
import InformationSheet from "./information-sheet"; |
|
|
import InformationSheet from "./information-sheet"; |
|
|
import { hasSupportAccess } from "./support-access"; |
|
|
import { hasSupportAccess } from "./support-access"; |
|
|
@ -50,6 +37,7 @@ export type NavigationButtonProps = Omit< |
|
|
helpDescription?: ReactNode; |
|
|
helpDescription?: ReactNode; |
|
|
helpButtonText?: ReactNode; |
|
|
helpButtonText?: ReactNode; |
|
|
disableHelpModal?: boolean; |
|
|
disableHelpModal?: boolean; |
|
|
|
|
|
badge?: ReactNode; |
|
|
profile?: any; |
|
|
profile?: any; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@ -63,19 +51,20 @@ export function NavigationButton({ |
|
|
helpDescription, |
|
|
helpDescription, |
|
|
helpButtonText, |
|
|
helpButtonText, |
|
|
disableHelpModal = false, |
|
|
disableHelpModal = false, |
|
|
|
|
|
badge, |
|
|
profile: profileProp, |
|
|
profile: profileProp, |
|
|
...props |
|
|
...props |
|
|
}: NavigationButtonProps) { |
|
|
}: NavigationButtonProps) { |
|
|
const router = useRouter(); |
|
|
const router = useRouter(); |
|
|
const { dictionary: t } = useI18n(); |
|
|
|
|
|
|
|
|
const { dictionary: t, locale } = useI18n(); |
|
|
|
|
|
const isRtl = locale === "fa" || locale === "ar" || locale === "ur"; |
|
|
const [isHelpOpen, setIsHelpOpen] = useState(false); |
|
|
const [isHelpOpen, setIsHelpOpen] = useState(false); |
|
|
const [isSupportOpen, setIsSupportOpen] = useState(false); |
|
|
const [isSupportOpen, setIsSupportOpen] = useState(false); |
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false); |
|
|
|
|
|
const [isSubscriptionInfoOpen, setIsSubscriptionInfoOpen] = useState(false); |
|
|
const [isSubscriptionInfoOpen, setIsSubscriptionInfoOpen] = useState(false); |
|
|
const [isSubscriptionLoading, setIsSubscriptionLoading] = useState(false); |
|
|
const [isSubscriptionLoading, setIsSubscriptionLoading] = useState(false); |
|
|
|
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false); |
|
|
const dropdownRef = useRef<HTMLDivElement>(null); |
|
|
const dropdownRef = useRef<HTMLDivElement>(null); |
|
|
|
|
|
|
|
|
// Only fetch profile for icons that actually need profile data if not provided via prop.
|
|
|
|
|
|
const needsProfile = |
|
|
const needsProfile = |
|
|
!profileProp && |
|
|
!profileProp && |
|
|
(icon === "support" || |
|
|
(icon === "support" || |
|
|
@ -88,29 +77,12 @@ export function NavigationButton({ |
|
|
}); |
|
|
}); |
|
|
const profile = profileProp !== undefined ? profileProp : queriedProfile; |
|
|
const profile = profileProp !== undefined ? profileProp : queriedProfile; |
|
|
const refetch = refetchQuery; |
|
|
const refetch = refetchQuery; |
|
|
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 [appliedDiscount, setAppliedDiscount] = |
|
|
|
|
|
useState<CheckDiscountResult | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
const { data: inventory } = useHabcoinInventoryQuery({ |
|
|
|
|
|
enabled: isSubscriptionInfoOpen, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const planPrice = Number(profile?.recommended_plan?.price) || 0; |
|
|
|
|
|
const finalPrice = appliedDiscount?.valid |
|
|
|
|
|
? appliedDiscount.discountedPrice |
|
|
|
|
|
: planPrice; |
|
|
|
|
|
const coinBalance = inventory?.coin_balance ?? 0; |
|
|
|
|
|
const hasEnoughCoins = |
|
|
|
|
|
!isInsufficientCoins && |
|
|
|
|
|
(inventory === undefined ? true : coinBalance >= finalPrice); |
|
|
|
|
|
|
|
|
const isFemale = profile?.gender === "female"; |
|
|
|
|
|
const hasActiveSubscription = |
|
|
|
|
|
!!profile?.active_subscription && |
|
|
|
|
|
profile.active_subscription.is_active !== false && |
|
|
|
|
|
profile.active_subscription.is_valid !== false; |
|
|
|
|
|
|
|
|
const handleOpenSubscriptionModal = () => { |
|
|
const handleOpenSubscriptionModal = () => { |
|
|
setIsSubscriptionInfoOpen(true); |
|
|
setIsSubscriptionInfoOpen(true); |
|
|
@ -128,10 +100,7 @@ export function NavigationButton({ |
|
|
return ( |
|
|
return ( |
|
|
<InformationSheet |
|
|
<InformationSheet |
|
|
isLoading={true} |
|
|
isLoading={true} |
|
|
onClose={() => { |
|
|
|
|
|
setIsSubscriptionInfoOpen(false); |
|
|
|
|
|
setAppliedDiscount(null); |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
onClose={() => setIsSubscriptionInfoOpen(false)} |
|
|
/> |
|
|
/> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
@ -165,171 +134,29 @@ export function NavigationButton({ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return hasActiveSubscription ? ( |
|
|
return hasActiveSubscription ? ( |
|
|
<> |
|
|
|
|
|
<InformationSheet |
|
|
<InformationSheet |
|
|
icon="/assets/images/diamond-color.png" |
|
|
icon="/assets/images/diamond-color.png" |
|
|
title={t["Subscription Status"]} |
|
|
|
|
|
|
|
|
title={t["Subscription Status"] || "Subscription Status"} |
|
|
description={subscriptionDescription} |
|
|
description={subscriptionDescription} |
|
|
onClose={() => { |
|
|
|
|
|
setIsSubscriptionInfoOpen(false); |
|
|
|
|
|
setAppliedDiscount(null); |
|
|
|
|
|
}} |
|
|
|
|
|
buttons={({ close }) => ( |
|
|
|
|
|
<div className="space-y-3"> |
|
|
|
|
|
{profile?.recommended_plan?.id ? ( |
|
|
|
|
|
<DiscountWidget |
|
|
|
|
|
objectId={profile.recommended_plan.id} |
|
|
|
|
|
onDiscountApplied={(res) => setAppliedDiscount(res)} |
|
|
|
|
|
onDiscountCleared={() => setAppliedDiscount(null)} |
|
|
|
|
|
/> |
|
|
|
|
|
) : null} |
|
|
|
|
|
|
|
|
|
|
|
<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-[11px] border border-[#747474] bg-transparent px-2 h-[52px] text-[16px] font-semibold text-[#747474] transition-opacity active:opacity-90 min-w-0"> |
|
|
|
|
|
<span className="truncate">{t["Back"]}</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
</button> |
|
|
|
|
|
|
|
|
|
|
|
{!hasEnoughCoins ? ( |
|
|
|
|
|
<button |
|
|
|
|
|
type="button" |
|
|
|
|
|
className="appearance-none border-0 bg-transparent p-0 text-left min-w-0" |
|
|
|
|
|
onClick={() => buyHabibCoinPackages()} |
|
|
|
|
|
> |
|
|
|
|
|
<div className="inline-flex w-full items-center justify-center gap-2 rounded-[11px] 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"> |
|
|
|
|
|
{t["Buy Habib Coins"] || "Buy Habib Coins"} |
|
|
|
|
|
</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({ |
|
|
|
|
|
objectId: planId, |
|
|
|
|
|
discountCode: appliedDiscount?.valid |
|
|
|
|
|
? appliedDiscount.code |
|
|
|
|
|
: undefined, |
|
|
|
|
|
}); |
|
|
|
|
|
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); |
|
|
|
|
|
setAppliedDiscount(null); |
|
|
|
|
|
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-[11px] 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.5 rounded-[12px] bg-black/15 px-[9px] py-[4px] text-xs font-semibold leading-none text-white shrink-0 min-w-0 whitespace-nowrap"> |
|
|
|
|
|
<Image |
|
|
|
|
|
src="/assets/images/Inner Plugdsain Iframe.svg" |
|
|
|
|
|
alt="" |
|
|
|
|
|
aria-hidden="true" |
|
|
|
|
|
width={16} |
|
|
|
|
|
height={16} |
|
|
|
|
|
className="shrink-0" |
|
|
|
|
|
/> |
|
|
|
|
|
{appliedDiscount?.valid ? ( |
|
|
|
|
|
<> |
|
|
|
|
|
<span className="line-through text-white/50 text-[11px]"> |
|
|
|
|
|
{planPrice} |
|
|
|
|
|
</span> |
|
|
|
|
|
<span className="font-bold text-white text-[12px]"> |
|
|
|
|
|
{finalPrice} |
|
|
|
|
|
</span> |
|
|
|
|
|
</> |
|
|
|
|
|
) : ( |
|
|
|
|
|
<span className="truncate"> |
|
|
|
|
|
{planPrice || "100"} |
|
|
|
|
|
</span> |
|
|
|
|
|
)} |
|
|
|
|
|
</span> |
|
|
|
|
|
)} |
|
|
|
|
|
</div> |
|
|
|
|
|
</button> |
|
|
|
|
|
)} |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
buttons={t["Got it"] || t["Back"] || "Back"} |
|
|
|
|
|
onClose={() => setIsSubscriptionInfoOpen(false)} |
|
|
/> |
|
|
/> |
|
|
</> |
|
|
|
|
|
) : ( |
|
|
) : ( |
|
|
<InformationSheet |
|
|
<InformationSheet |
|
|
icon="/assets/images/icon-park-outline_diamond.svg" |
|
|
icon="/assets/images/icon-park-outline_diamond.svg" |
|
|
title={t["No Active Subscription"]} |
|
|
|
|
|
|
|
|
title={t["No Active Subscription"] || "No Active Subscription"} |
|
|
description={ |
|
|
description={ |
|
|
t[ |
|
|
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." |
|
|
] |
|
|
|
|
|
|
|
|
] || |
|
|
|
|
|
"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"]} |
|
|
|
|
|
|
|
|
buttons={t["Got it"] || t["Back"] || "Back"} |
|
|
onClose={() => setIsSubscriptionInfoOpen(false)} |
|
|
onClose={() => setIsSubscriptionInfoOpen(false)} |
|
|
/> |
|
|
/> |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const isFemale = profile?.gender === "female"; |
|
|
|
|
|
const hasActiveSubscription = |
|
|
|
|
|
!!profile?.active_subscription && |
|
|
|
|
|
profile.active_subscription.is_active !== false && |
|
|
|
|
|
profile.active_subscription.is_valid !== false; |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
if (!isDropdownOpen) return; |
|
|
if (!isDropdownOpen) return; |
|
|
const handleOutsideClick = (event: MouseEvent) => { |
|
|
const handleOutsideClick = (event: MouseEvent) => { |
|
|
@ -483,14 +310,6 @@ export function NavigationButton({ |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
{renderSubscriptionModal()} |
|
|
{renderSubscriptionModal()} |
|
|
|
|
|
|
|
|
{toastMessage && ( |
|
|
|
|
|
<ErrorToast |
|
|
|
|
|
message={toastMessage} |
|
|
|
|
|
variant={toastVariant} |
|
|
|
|
|
onClose={() => setToastMessage(null)} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
@ -560,14 +379,6 @@ export function NavigationButton({ |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
{renderSubscriptionModal()} |
|
|
{renderSubscriptionModal()} |
|
|
|
|
|
|
|
|
{toastMessage && ( |
|
|
|
|
|
<ErrorToast |
|
|
|
|
|
message={toastMessage} |
|
|
|
|
|
variant={toastVariant} |
|
|
|
|
|
onClose={() => setToastMessage(null)} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
</> |
|
|
</> |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|