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 && }
); }