import { Modal, Stack, Card, Text, Group, Badge, Button, Alert, } from "@mantine/core"; import { useTranslation } from "react-i18next"; import TrendingUpIcon from "@mui/icons-material/TrendingUp"; import { useSaaSBilling } from "@app/contexts/SaasBillingContext"; import { useSaaSTeam } from "@app/contexts/SaaSTeamContext"; import { BILLING_CONFIG, getCurrencySymbol, getFormattedOveragePrice, } from "@app/config/billing"; import { Z_INDEX_OVER_CONFIG_MODAL } from "@app/styles/zIndex"; import { CreditUsageBanner } from "@app/components/shared/modals/CreditUsageBanner"; import { FeatureListItem } from "@app/components/shared/modals/FeatureListItem"; import { FREE_PLAN_FEATURES, TEAM_PLAN_FEATURES, ENTERPRISE_PLAN_FEATURES, } from "@app/config/planFeatures"; import { useSaaSCheckout } from "@app/contexts/SaaSCheckoutContext"; import { useEnableMeteredBilling } from "@app/hooks/useEnableMeteredBilling"; interface CreditExhaustedModalProps { opened: boolean; onClose: () => void; } /** * Desktop Credit Exhausted Modal * Shows upgrade options when user runs out of credits * Routes to different UI based on user status (free/team/managed member) */ export function CreditExhaustedModal({ opened, onClose, }: CreditExhaustedModalProps) { const { t } = useTranslation(); const { creditBalance, tier, plans, refreshBilling, isManagedTeamMember } = useSaaSBilling(); const { isTeamLeader } = useSaaSTeam(); const { openCheckout } = useSaaSCheckout(); const { enablingMetering, meteringError, handleEnableMetering } = useEnableMeteredBilling(refreshBilling, onClose, "CreditExhaustedModal"); // Managed team members have unlimited credits via team if (isManagedTeamMember) { return ( {t( "credits.modal.managedMemberMessage", "You have unlimited access to credits through your team. If you need assistance, please contact your team leader.", )} ); } // Team users should enable overage billing // Only team leaders can enable metered billing, members see different UI if (tier === "team") { const teamPlan = plans.get("team"); const teamCurrency = teamPlan?.currency ?? "$"; const overagePrice = teamPlan?.overagePrice ?? BILLING_CONFIG.OVERAGE_PRICE_PER_CREDIT; const formattedOveragePrice = getFormattedOveragePrice( teamCurrency, overagePrice, ); return ( {t( "credits.modal.titleExhaustedPro", "You have run out of credits", )} {t( "credits.modal.subtitlePro", "Enable automatic overage billing to never run out of credits.", )} } styles={{ body: { padding: "0rem 0rem 0.5rem 0rem" }, content: { backgroundColor: "light-dark(#FFFFFF, #1A1A1E)", }, header: { backgroundColor: "light-dark(#FFFFFF, #1A1A1E)", }, overlay: { backgroundColor: "light-dark(rgba(0,0,0,0.5), rgba(0,0,0,0.7))", }, }} > {meteringError && ( {meteringError} )} {/* Explanation Card */} {t( "credits.modal.meteringTitle", "Pay-What-You-Use Overage Billing", )} {t("credits.modal.meteringExplanation", { credits: BILLING_CONFIG.INCLUDED_CREDITS_PER_MONTH, defaultValue: `Your Team plan includes ${BILLING_CONFIG.INCLUDED_CREDITS_PER_MONTH} credits per month. When you run out, overage billing automatically provides additional credits so you never have to stop working.`, })} {t("credits.modal.meteringIncluded", { credits: BILLING_CONFIG.INCLUDED_CREDITS_PER_MONTH, defaultValue: `${BILLING_CONFIG.INCLUDED_CREDITS_PER_MONTH} credits/month included with Team`, })} {t( "credits.modal.meteringPrice", "Additional credits at {{price}}/credit", { price: formattedOveragePrice, }, )} {t( "credits.modal.meteringPayAsYouGo", "Only pay for what you use", )} {t( "credits.modal.meteringNoCommitment", "No commitment, cancel anytime", )} {t( "credits.modal.meteringNeverRunOut", "Never run out of credits", )} {t( "credits.modal.meteringBillingNote", "Overage credits are billed monthly alongside your Team subscription. Track your usage anytime in your account settings.", )} {/* Action Buttons */} {!isTeamLeader && ( {t( "credits.modal.teamLeaderOnly", "Only team leaders can enable overage billing", )} )} ); } // Free tier users - show upgrade modal const teamPlan = plans.get("team"); const teamPrice = teamPlan?.price ?? 20; const teamCurrency = teamPlan?.currency ?? "$"; const overagePrice = teamPlan?.overagePrice ?? BILLING_CONFIG.OVERAGE_PRICE_PER_CREDIT; const currencySymbol = getCurrencySymbol(teamCurrency); const formattedOveragePrice = `${currencySymbol}${overagePrice.toFixed(2)}`; return ( {t("credits.modal.titleExhausted", "You've used your free credits")} {t( "credits.modal.subtitle", "Upgrade to Team for 10x the credits and faster processing.", )} } styles={{ body: { padding: "0rem 0rem 0.5rem 0rem" }, content: { backgroundColor: "light-dark(#FFFFFF, #1A1A1E)", }, header: { backgroundColor: "light-dark(#FFFFFF, #1A1A1E)", }, overlay: { backgroundColor: "light-dark(rgba(0,0,0,0.5), rgba(0,0,0,0.7))", }, }} > {/* Free Plan Card */}
{t("credits.modal.freeTier", "Free Tier")} {currencySymbol}0 {t("credits.modal.perMonth", "/month")} {BILLING_CONFIG.FREE_CREDITS_PER_MONTH}{" "} {t("credits.modal.monthlyCredits", "monthly credits")}
{t("credits.modal.forRegularWork", "For regular PDF work:")} {FREE_PLAN_FEATURES.map((feature, index) => ( {t(feature.translationKey, feature.defaultText)} ))}
{/* Team Plan Card */} openCheckout("pro")} onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-4px)"; e.currentTarget.style.boxShadow = "0 12px 48px rgba(59, 130, 246, 0.3)"; }} onMouseLeave={(e) => { e.currentTarget.style.transform = "translateY(0)"; e.currentTarget.style.boxShadow = "0 2px 8px rgba(59, 130, 246, 0.1)"; }} > {t("credits.modal.popular", "Popular")}
{t("credits.modal.teamSubscription", "Team")} {currencySymbol} {teamPrice} {t("credits.modal.perMonth", "/month")} {BILLING_CONFIG.INCLUDED_CREDITS_PER_MONTH}{" "} {t("credits.modal.monthlyCredits", "monthly credits")} +{" "} {formattedOveragePrice}/ {t("credits.modal.overage", "overage")}
{t( "credits.modal.everythingInFree", "Everything in Free, plus:", )} {TEAM_PLAN_FEATURES.map((feature, index) => ( {t(feature.translationKey, feature.defaultText)} ))}
{/* Enterprise Plan Card */}
{t("credits.modal.enterpriseSubscription", "Enterprise")} {t("credits.modal.customPricing", "Custom")} {t("credits.modal.unlimitedMonthlyCredits", "Site License")}
{t( "credits.modal.everythingInCredits", "Everything in Credits, plus:", )} {ENTERPRISE_PLAN_FEATURES.map((feature, index) => ( {t(feature.translationKey, feature.defaultText)} ))}
{t("credits.modal.selfHostPrompt", "Want to self host?")}{" "} { e.currentTarget.style.textDecoration = "underline"; }} onMouseLeave={(e) => { e.currentTarget.style.textDecoration = "none"; }} > {t("credits.modal.selfHostLink", "Review the docs and plans")}
); }