import React, { useEffect, useState } from "react";
import { useBrowserTitleAndIcon, useTranslation } from "@firecms/core";
import { AutoAwesomeIcon, Button, Card, Chip, CircularProgress, cls, Typography, } from "@firecms/ui";
import { useSubscriptionsForUserController } from "../../hooks/useSubscriptionsForUserController";
import { useProjectSubscriptions } from "../../hooks/useProjectSubscriptions";
import { UpgradeCloudSubscriptionView } from "./UpgradeCloudSubscriptionView";
import { formatPrice, getPriceString, getSubscriptionStatusText } from "../settings/common";
import { Subscription } from "../../types";
import { StripeDisclaimer } from "./StripeDisclaimer";
import { useFireCMSBackend, useProjectConfig } from "../../hooks";
export function ProjectSubscriptionPlans() {
const {
subscriptionPlan,
projectId,
trialValidUntil,
isGCPMarketplace
} = useProjectConfig();
const { t } = useTranslation();
if (!subscriptionPlan)
throw new Error("No subscription plan");
useBrowserTitleAndIcon("Plans")
const {
products,
} = useSubscriptionsForUserController();
// Use project-level subscriptions (projects/{projectId}/subscriptions)
// instead of user-level (customers/{userId}/subscriptions) so that
// any team member can see the subscription status, not just the user
// who originally created the subscription.
const {
subscriptions: projectSubscriptions,
loading: projectSubscriptionsLoading
} = useProjectSubscriptions(projectId);
const loading = projectSubscriptionsLoading || products === undefined;
const cloudProducts = (products ?? []).filter(p => p.metadata?.type === "cloud_plus" || p.metadata?.type === "pro");
const plusProduct = cloudProducts.find(p => p.metadata?.type === "cloud_plus");
const plusSubscription = (projectSubscriptions ?? []).find(s => s.product.metadata?.type === "cloud_plus");
const trialIsActive = Boolean(trialValidUntil && trialValidUntil > new Date());
const activePlusSubscription = (projectSubscriptions ?? []).find(s => s.product.metadata?.type === "cloud_plus" && s.status === "active");
const isSubscribed = Boolean(activePlusSubscription) || isGCPMarketplace;
return (
{loading && !isGCPMarketplace &&
}
{t("settings_subscription_plan")}
{isGCPMarketplace && }
{!isGCPMarketplace && isSubscribed &&
{t("settings_subscribed_to")} FireCMS Cloud.
}
{!isGCPMarketplace && !isSubscribed &&
{t("settings_no_active_subscription")}
}
{!isGCPMarketplace && !isSubscribed && trialIsActive && trialValidUntil &&
{t("settings_trial_valid_until", { date: trialValidUntil.toLocaleDateString() })}
}
{!isGCPMarketplace && !isSubscribed && plusProduct && }
{!isGCPMarketplace && isSubscribed && plusSubscription &&
}
{!isGCPMarketplace && }
{t("settings_features_intro")}
-
{t("settings_feature_managed_service")}
-
{t("settings_feature_local_text_search")}
-
{t("settings_feature_unlimited_users_roles")}
-
{t("settings_feature_theme_logo")}
-
{t("settings_feature_custom_fields_views")}
-
{t("settings_feature_secondary_databases")}
-
{t("settings_feature_ai_content")}
-
{t("settings_feature_unlimited_export")}
-
{t("settings_feature_appcheck")}
);
}
const GCP_CONSOLE_MARKETPLACE_URL = "https://console.cloud.google.com/marketplace/orders";
function GCPMarketplaceSubscriptionView() {
const { t } = useTranslation();
const { subscriptionPlan } = useProjectConfig();
return (
GCP Marketplace
{subscriptionPlan === "cloud_plus" ? "Cloud Plus" : subscriptionPlan === "pro" ? "Pro" : subscriptionPlan}
{t("marketplace_managed_by_gcp")}
{t("marketplace_billing_note")}
{t("marketplace_plan_changes_note")}
);
}
interface CurrentSubscriptionViewProps {
subscription: Subscription;
projectId: string;
}
function CurrentCloudSubscriptionView({
subscription,
projectId,
}: CurrentSubscriptionViewProps) {
const {
getBackendAuthToken,
projectsApi
} = useFireCMSBackend();
const { t } = useTranslation();
const statusText = getSubscriptionStatusText(subscription);
const [cancelLinkUrl, setCancelLinkUrl] = useState(undefined);
useEffect(() => {
if (!cancelLinkUrl && !subscription.canceled_at) {
projectsApi.getStripeCancelLinkForSubscription(subscription.id, projectId)
.then(setCancelLinkUrl);
}
}, [subscription.canceled_at]);
const [stripeUpdatePaymentUrl, setStripeUpdatePaymentUrl] = useState(undefined);
useEffect(() => {
if (subscription.id) {
projectsApi.getStripeUpdateLinkForPaymentMethod(subscription.id, projectId)
.then(setStripeUpdatePaymentUrl);
}
}, []);
// Detect if this is a per-seat subscription vs legacy metered billing
const isPerSeatBilling = subscription.items?.[0]?.price?.lookup_key === "cloud_per_seat";
const seatCount = subscription.items?.[0]?.quantity ?? subscription.quantity;
const seatPrice = subscription.items?.[0]?.price?.unit_amount;
const seatCurrency = subscription.items?.[0]?.price?.currency ?? subscription.price?.currency ?? "eur";
return (
{t("settings_subscription_is")}
{statusText} .
{subscription.current_period_end && !subscription.canceled_at && <> {t("settings_next_payment_on", { date: subscription.current_period_end.toDate().toLocaleDateString() })} >}
{isPerSeatBilling ? (
<>
{t("settings_seats_count")}
{seatCount} {seatCount === 1 ? t("settings_seat") : t("settings_seats")}
{seatPrice && <> {t("settings_per_seat", { price: formatPrice(seatPrice, seatCurrency), interval: subscription.interval ?? "month" })}>}.
>
) : (
<>
{t("settings_current_price")}
{getPriceString(subscription.price)}
{t("settings_per_user_usage")}
>
)}
{subscription.cancel_at && <>
{" "}{t("settings_cancelled_active_until", { date: subscription.cancel_at.toDate().toLocaleDateString() })}
{isPerSeatBilling && <> {t("settings_no_additional_charges")}>}
>}
{!subscription.canceled_at &&
{
" " + t("settings_manage_subscription")
}}
{stripeUpdatePaymentUrl &&
}
);
}