import * as React from "react"; import { useContext, useEffect, useState } from "react"; import { Dialog, DialogContent, DialogTitle, Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery, Button, Card, Typography, List, ListItemIcon, ListItemText, Box, DialogContentText, DialogActions, } from "@mui/material"; import { Trans, useTranslation } from "react-i18next"; import { Check, Close } from "@mui/icons-material"; import { NavLink } from "react-router-dom"; import { UnauthorizedError } from "../app/errors"; import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils"; import { AccountContext } from "./App"; import routes from "./routes"; import session from "../app/Session"; import accountApi, { SubscriptionInterval } from "../app/AccountApi"; import theme from "./theme"; const Feature = (props) => {props.children}; const NoFeature = (props) => {props.children}; const FeatureItem = (props) => ( {props.feature && } {!props.feature && } {props.children}} /> ); const Action = { REDIRECT_SIGNUP: 1, CREATE_SUBSCRIPTION: 2, UPDATE_SUBSCRIPTION: 3, CANCEL_SUBSCRIPTION: 4, }; const Banner = { CANCEL_WARNING: 1, PRORATION_INFO: 2, RESERVATIONS_WARNING: 3, }; const UpgradeDialog = (props) => { const { t } = useTranslation(); const { account } = useContext(AccountContext); // May be undefined! const [error, setError] = useState(""); const [tiers, setTiers] = useState(null); const [interval, setInterval] = useState(account?.billing?.interval || SubscriptionInterval.YEAR); const [newTierCode, setNewTierCode] = useState(account?.tier?.code); // May be undefined const [loading, setLoading] = useState(false); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); useEffect(() => { const fetchTiers = async () => { setTiers(await accountApi.billingTiers()); }; fetchTiers(); // Dangle }, []); if (!tiers) { return <>; } const tiersMap = Object.assign(...tiers.map((tier) => ({ [tier.code]: tier }))); const newTier = tiersMap[newTierCode]; // May be undefined const currentTier = account?.tier; // May be undefined const currentInterval = account?.billing?.interval; // May be undefined const currentTierCode = currentTier?.code; // May be undefined // Figure out buttons, labels and the submit action let submitAction; let submitButtonLabel; let banner; if (!account) { submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup"); submitAction = Action.REDIRECT_SIGNUP; banner = null; } else if (currentTierCode === newTierCode && (currentInterval === undefined || currentInterval === interval)) { submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); submitAction = null; banner = currentTierCode ? Banner.PRORATION_INFO : null; } else if (!currentTierCode) { submitButtonLabel = t("account_upgrade_dialog_button_pay_now"); submitAction = Action.CREATE_SUBSCRIPTION; banner = null; } else if (!newTierCode) { submitButtonLabel = t("account_upgrade_dialog_button_cancel_subscription"); submitAction = Action.CANCEL_SUBSCRIPTION; banner = Banner.CANCEL_WARNING; } else { submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); submitAction = Action.UPDATE_SUBSCRIPTION; banner = Banner.PRORATION_INFO; } // Exceptional conditions if (loading) { submitAction = null; } else if (newTier?.code && account?.reservations?.length > newTier?.limits?.reservations) { submitAction = null; banner = Banner.RESERVATIONS_WARNING; } const handleSubmit = async () => { if (submitAction === Action.REDIRECT_SIGNUP) { window.location.href = routes.signup; return; } try { setLoading(true); if (submitAction === Action.CREATE_SUBSCRIPTION) { const response = await accountApi.createBillingSubscription(newTierCode, interval); window.location.href = response.redirect_url; } else if (submitAction === Action.UPDATE_SUBSCRIPTION) { await accountApi.updateBillingSubscription(newTierCode, interval); } else if (submitAction === Action.CANCEL_SUBSCRIPTION) { await accountApi.deleteBillingSubscription(); } props.onCancel(); } catch (e) { console.log(`[UpgradeDialog] Error changing billing subscription`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } finally { setLoading(false); } }; // Figure out discount let discount = 0; let upto = false; if (newTier?.prices) { discount = Math.round(((newTier.prices.month * 12) / newTier.prices.year - 1) * 100); } else { let n = 0; for (const tier of tiers) { if (tier.prices) { const tierDiscount = Math.round(((tier.prices.month * 12) / tier.prices.year - 1) * 100); if (tierDiscount > discount) { discount = tierDiscount; n += 1; } } } upto = n > 1; } return (
{t("account_upgrade_dialog_title")}
{t("account_upgrade_dialog_interval_monthly")} setInterval(ev.target.checked ? SubscriptionInterval.YEAR : SubscriptionInterval.MONTH)} /> {t("account_upgrade_dialog_interval_yearly")} {discount > 0 && ( )}
{tiers.map((tier) => ( setNewTierCode(tier.code)} // tier.code may be undefined! /> ))}
{banner === Banner.CANCEL_WARNING && ( )} {banner === Banner.PRORATION_INFO && ( )} {banner === Banner.RESERVATIONS_WARNING && ( , }} /> )}
{config.billing_contact.indexOf("@") !== -1 && ( <> , }} />{" "} )} {config.billing_contact.match(`^http?s://`) && ( <> , }} />{" "} )} {error}
); }; const TierCard = (props) => { const { t } = useTranslation(); const { tier } = props; let cardStyle; let labelStyle; let labelText; if (props.selected) { cardStyle = { background: "#eee", border: "3px solid #338574" }; labelStyle = { background: "#338574", color: "white" }; labelText = t("account_upgrade_dialog_tier_selected_label"); } else if (props.current) { cardStyle = { border: "3px solid #eee" }; labelStyle = { background: "#eee", color: "black" }; labelText = t("account_upgrade_dialog_tier_current_label"); } else { cardStyle = { border: "3px solid transparent" }; } let monthlyPrice; if (!tier.prices) { monthlyPrice = 0; } else if (props.interval === SubscriptionInterval.YEAR) { monthlyPrice = tier.prices.year / 12; } else if (props.interval === SubscriptionInterval.MONTH) { monthlyPrice = tier.prices.month; } return ( {labelStyle && (
{labelText}
)} {tier.name || t("account_basics_tier_free")}
{formatPrice(monthlyPrice)} {monthlyPrice > 0 && <>/ {t("account_upgrade_dialog_tier_price_per_month")}}
{tier.limits.reservations > 0 && ( {t("account_upgrade_dialog_tier_features_reservations", { reservations: tier.limits.reservations, count: tier.limits.reservations, })} )} {t("account_upgrade_dialog_tier_features_messages", { messages: formatNumber(tier.limits.messages), count: tier.limits.messages, })} {t("account_upgrade_dialog_tier_features_emails", { emails: formatNumber(tier.limits.emails), count: tier.limits.emails, })} {tier.limits.calls > 0 && ( {t("account_upgrade_dialog_tier_features_calls", { calls: formatNumber(tier.limits.calls), count: tier.limits.calls, })} )} {t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0), })} {tier.limits.reservations === 0 && {t("account_upgrade_dialog_tier_features_no_reservations")}} {tier.limits.calls === 0 && {t("account_upgrade_dialog_tier_features_no_calls")}} {tier.prices && props.interval === SubscriptionInterval.MONTH && ( {t("account_upgrade_dialog_tier_price_billed_monthly", { price: formatPrice(tier.prices.month * 12), })} )} {tier.prices && props.interval === SubscriptionInterval.YEAR && ( {t("account_upgrade_dialog_tier_price_billed_yearly", { price: formatPrice(tier.prices.year), save: formatPrice(tier.prices.month * 12 - tier.prices.year), })} )}
); }; export default UpgradeDialog;