From 9ff863db5edc82cd5245710f9f82d2874da814d0 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 10 Feb 2026 18:30:01 -0800 Subject: [PATCH] Continue to clean things up --- messages/en-US.json | 2 + server/middlewares/verifyLimits.ts | 4 - .../hooks/handleSubscriptionUpdated.ts | 2 +- .../settings/(private)/billing/page.tsx | 199 +++++++++++++----- src/components/PaidFeaturesAlert.tsx | 4 +- src/hooks/usePaidStatus.ts | 1 + 6 files changed, 158 insertions(+), 54 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index f442635e..e6510fba 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1545,6 +1545,7 @@ "billingLimitViolationDescription": "Your current usage exceeds the limits of this plan. After downgrading, all actions will be disabled until you reduce usage within the new limits. Please review the features below that are currently over the limits. Limits in violation:", "billingFeatureLossWarning": "Feature Availability Notice", "billingFeatureLossDescription": "By downgrading, features not available in the new plan will be automatically disabled. Some settings and configurations may be lost. Please review the pricing matrix to understand which features will no longer be available.", + "billingUsageExceedsLimit": "Current usage ({current}) exceeds limit ({limit})", "signUpTerms": { "IAgreeToThe": "I agree to the", "termsOfService": "terms of service", @@ -1959,6 +1960,7 @@ "orgAuthBackToSignIn": "Back to standard sign in", "orgAuthNoAccount": "Don't have an account?", "subscriptionRequiredToUse": "A subscription is required to use this feature.", + "mustUpgradeToUse": "You must upgrade your subscription to use this feature.", "idpDisabled": "Identity providers are disabled.", "orgAuthPageDisabled": "Organization auth page is disabled.", "domainRestartedDescription": "Domain verification restarted successfully", diff --git a/server/middlewares/verifyLimits.ts b/server/middlewares/verifyLimits.ts index 49c5f38a..66789530 100644 --- a/server/middlewares/verifyLimits.ts +++ b/server/middlewares/verifyLimits.ts @@ -1,10 +1,6 @@ import { Request, Response, NextFunction } from "express"; -import { db, orgs } from "@server/db"; -import { userOrgs } from "@server/db"; -import { and, eq } from "drizzle-orm"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; -import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; import { usageService } from "@server/lib/billing/usageService"; import { build } from "@server/build"; diff --git a/server/private/routers/billing/hooks/handleSubscriptionUpdated.ts b/server/private/routers/billing/hooks/handleSubscriptionUpdated.ts index c431f386..0305e7f1 100644 --- a/server/private/routers/billing/hooks/handleSubscriptionUpdated.ts +++ b/server/private/routers/billing/hooks/handleSubscriptionUpdated.ts @@ -188,7 +188,7 @@ export async function handleSubscriptionUpdated( const orgId = customer.orgId; if (!orgId) { - logger.warn( + logger.debug( `No orgId found in subscription metadata for subscription ${subscription.id}. Skipping usage reset.` ); continue; diff --git a/src/app/[orgId]/settings/(private)/billing/page.tsx b/src/app/[orgId]/settings/(private)/billing/page.tsx index ad5c3260..f64c9557 100644 --- a/src/app/[orgId]/settings/(private)/billing/page.tsx +++ b/src/app/[orgId]/settings/(private)/billing/page.tsx @@ -40,6 +40,11 @@ import { AlertTitle, AlertDescription } from "@app/components/ui/alert"; +import { + Tooltip, + TooltipTrigger, + TooltipContent +} from "@app/components/ui/tooltip"; import { GetOrgSubscriptionResponse, GetOrgUsageResponse @@ -527,6 +532,13 @@ export default function BillingPage() { return limit?.value ?? null; }; + // Check if usage exceeds limit for a specific feature + const isOverLimit = (featureId: string): boolean => { + const usage = getUsageValue(featureId); + const limit = getLimitValue(featureId); + return limit !== null && usage > limit; + }; + // Calculate current usage cost for display const getUserCount = () => getUsageValue(USERS); const getPricePerUser = () => { @@ -746,11 +758,33 @@ export default function BillingPage() { {t("billingUsers") || "Users"} - {getLimitValue(USERS) ?? - t("billingUnlimited") ?? - "∞"}{" "} - {getLimitValue(USERS) !== null && - "users"} + {isOverLimit(USERS) ? ( + + + + + {getLimitValue(USERS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(USERS) !== null && + "users"} + + + +

{t("billingUsageExceedsLimit", { current: getUsageValue(USERS), limit: getLimitValue(USERS) ?? 0 }) || `Current usage (${getUsageValue(USERS)}) exceeds limit (${getLimitValue(USERS)})`}

+
+
+ ) : ( + <> + {getLimitValue(USERS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(USERS) !== null && + "users"} + + )}
@@ -758,11 +792,33 @@ export default function BillingPage() { {t("billingSites") || "Sites"} - {getLimitValue(SITES) ?? - t("billingUnlimited") ?? - "∞"}{" "} - {getLimitValue(SITES) !== null && - "sites"} + {isOverLimit(SITES) ? ( + + + + + {getLimitValue(SITES) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(SITES) !== null && + "sites"} + + + +

{t("billingUsageExceedsLimit", { current: getUsageValue(SITES), limit: getLimitValue(SITES) ?? 0 }) || `Current usage (${getUsageValue(SITES)}) exceeds limit (${getLimitValue(SITES)})`}

+
+
+ ) : ( + <> + {getLimitValue(SITES) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(SITES) !== null && + "sites"} + + )}
@@ -770,11 +826,33 @@ export default function BillingPage() { {t("billingDomains") || "Domains"} - {getLimitValue(DOMAINS) ?? - t("billingUnlimited") ?? - "∞"}{" "} - {getLimitValue(DOMAINS) !== null && - "domains"} + {isOverLimit(DOMAINS) ? ( + + + + + {getLimitValue(DOMAINS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(DOMAINS) !== null && + "domains"} + + + +

{t("billingUsageExceedsLimit", { current: getUsageValue(DOMAINS), limit: getLimitValue(DOMAINS) ?? 0 }) || `Current usage (${getUsageValue(DOMAINS)}) exceeds limit (${getLimitValue(DOMAINS)})`}

+
+
+ ) : ( + <> + {getLimitValue(DOMAINS) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(DOMAINS) !== null && + "domains"} + + )}
@@ -783,11 +861,33 @@ export default function BillingPage() { "Remote Nodes"} - {getLimitValue(REMOTE_EXIT_NODES) ?? - t("billingUnlimited") ?? - "∞"}{" "} - {getLimitValue(REMOTE_EXIT_NODES) !== - null && "remote nodes"} + {isOverLimit(REMOTE_EXIT_NODES) ? ( + + + + + {getLimitValue(REMOTE_EXIT_NODES) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(REMOTE_EXIT_NODES) !== + null && "remote nodes"} + + + +

{t("billingUsageExceedsLimit", { current: getUsageValue(REMOTE_EXIT_NODES), limit: getLimitValue(REMOTE_EXIT_NODES) ?? 0 }) || `Current usage (${getUsageValue(REMOTE_EXIT_NODES)}) exceeds limit (${getLimitValue(REMOTE_EXIT_NODES)})`}

+
+
+ ) : ( + <> + {getLimitValue(REMOTE_EXIT_NODES) ?? + t("billingUnlimited") ?? + "∞"}{" "} + {getLimitValue(REMOTE_EXIT_NODES) !== + null && "remote nodes"} + + )}
@@ -809,31 +909,34 @@ export default function BillingPage() { -
-
-
- {t("billingCurrentKeys") || "Current Keys"} -
-
- - {getLicenseKeyCount()} - - - {getLicenseKeyCount() === 1 - ? "key" - : "keys"} - +
+
+
+
+ {t("billingCurrentKeys") || "Current Keys"} +
+
+ + {getLicenseKeyCount()} + + + {getLicenseKeyCount() === 1 + ? "key" + : "keys"} + +
+
-
@@ -864,12 +967,14 @@ export default function BillingPage() { {pendingTier && pendingTier.tier && (
-
-
+
+
{pendingTier.planName}
-
- {pendingTier.price} +
+ + {pendingTier.price} +
diff --git a/src/components/PaidFeaturesAlert.tsx b/src/components/PaidFeaturesAlert.tsx index 19b35ee0..c94043b6 100644 --- a/src/components/PaidFeaturesAlert.tsx +++ b/src/components/PaidFeaturesAlert.tsx @@ -21,7 +21,7 @@ type Props = { export function PaidFeaturesAlert({ tiers }: Props) { const t = useTranslations(); - const { hasSaasSubscription, hasEnterpriseLicense } = usePaidStatus(); + const { hasSaasSubscription, hasEnterpriseLicense, isActive } = usePaidStatus(); const { env } = useEnvContext(); if (env.flags.disableEnterpriseFeatures) { @@ -35,7 +35,7 @@ export function PaidFeaturesAlert({ tiers }: Props) {
- {t("subscriptionRequiredToUse")} + {isActive ? t("mustUpgradeToUse") : t("subscriptionRequiredToUse")}
diff --git a/src/hooks/usePaidStatus.ts b/src/hooks/usePaidStatus.ts index 5d5fec2e..54f6257f 100644 --- a/src/hooks/usePaidStatus.ts +++ b/src/hooks/usePaidStatus.ts @@ -33,6 +33,7 @@ export function usePaidStatus() { hasEnterpriseLicense, hasSaasSubscription, isPaidUser, + isActive: tierData?.active, subscriptionTier: tierData?.tier }; }