Continue to clean things up

This commit is contained in:
Owen
2026-02-10 18:30:01 -08:00
parent e2ac6e6d4d
commit 9ff863db5e
6 changed files with 158 additions and 54 deletions

View File

@@ -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",

View File

@@ -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";

View File

@@ -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;

View File

@@ -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"}
</InfoSectionTitle>
<InfoSectionContent className="text-sm">
{getLimitValue(USERS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(USERS) !== null &&
"users"}
{isOverLimit(USERS) ? (
<Tooltip>
<TooltipTrigger className="flex items-center gap-1">
<AlertTriangle className="h-3 w-3 text-orange-400" />
<span className={cn(
"text-orange-600 dark:text-orange-400 font-medium"
)}>
{getLimitValue(USERS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(USERS) !== null &&
"users"}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{t("billingUsageExceedsLimit", { current: getUsageValue(USERS), limit: getLimitValue(USERS) ?? 0 }) || `Current usage (${getUsageValue(USERS)}) exceeds limit (${getLimitValue(USERS)})`}</p>
</TooltipContent>
</Tooltip>
) : (
<>
{getLimitValue(USERS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(USERS) !== null &&
"users"}
</>
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
@@ -758,11 +792,33 @@ export default function BillingPage() {
{t("billingSites") || "Sites"}
</InfoSectionTitle>
<InfoSectionContent className="text-sm">
{getLimitValue(SITES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(SITES) !== null &&
"sites"}
{isOverLimit(SITES) ? (
<Tooltip>
<TooltipTrigger className="flex items-center gap-1">
<AlertTriangle className="h-3 w-3 text-orange-400" />
<span className={cn(
"text-orange-600 dark:text-orange-400 font-medium"
)}>
{getLimitValue(SITES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(SITES) !== null &&
"sites"}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{t("billingUsageExceedsLimit", { current: getUsageValue(SITES), limit: getLimitValue(SITES) ?? 0 }) || `Current usage (${getUsageValue(SITES)}) exceeds limit (${getLimitValue(SITES)})`}</p>
</TooltipContent>
</Tooltip>
) : (
<>
{getLimitValue(SITES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(SITES) !== null &&
"sites"}
</>
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
@@ -770,11 +826,33 @@ export default function BillingPage() {
{t("billingDomains") || "Domains"}
</InfoSectionTitle>
<InfoSectionContent className="text-sm">
{getLimitValue(DOMAINS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(DOMAINS) !== null &&
"domains"}
{isOverLimit(DOMAINS) ? (
<Tooltip>
<TooltipTrigger className="flex items-center gap-1">
<AlertTriangle className="h-3 w-3 text-orange-400" />
<span className={cn(
"text-orange-600 dark:text-orange-400 font-medium"
)}>
{getLimitValue(DOMAINS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(DOMAINS) !== null &&
"domains"}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{t("billingUsageExceedsLimit", { current: getUsageValue(DOMAINS), limit: getLimitValue(DOMAINS) ?? 0 }) || `Current usage (${getUsageValue(DOMAINS)}) exceeds limit (${getLimitValue(DOMAINS)})`}</p>
</TooltipContent>
</Tooltip>
) : (
<>
{getLimitValue(DOMAINS) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(DOMAINS) !== null &&
"domains"}
</>
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
@@ -783,11 +861,33 @@ export default function BillingPage() {
"Remote Nodes"}
</InfoSectionTitle>
<InfoSectionContent className="text-sm">
{getLimitValue(REMOTE_EXIT_NODES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(REMOTE_EXIT_NODES) !==
null && "remote nodes"}
{isOverLimit(REMOTE_EXIT_NODES) ? (
<Tooltip>
<TooltipTrigger className="flex items-center gap-1">
<AlertTriangle className="h-3 w-3 text-orange-400" />
<span className={cn(
"text-orange-600 dark:text-orange-400 font-medium"
)}>
{getLimitValue(REMOTE_EXIT_NODES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(REMOTE_EXIT_NODES) !==
null && "remote nodes"}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{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)})`}</p>
</TooltipContent>
</Tooltip>
) : (
<>
{getLimitValue(REMOTE_EXIT_NODES) ??
t("billingUnlimited") ??
"∞"}{" "}
{getLimitValue(REMOTE_EXIT_NODES) !==
null && "remote nodes"}
</>
)}
</InfoSectionContent>
</InfoSection>
</InfoSections>
@@ -809,31 +909,34 @@ export default function BillingPage() {
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4 bg-muted/30">
<div>
<div className="text-sm text-muted-foreground mb-1">
{t("billingCurrentKeys") || "Current Keys"}
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-bold">
{getLicenseKeyCount()}
</span>
<span className="text-lg">
{getLicenseKeyCount() === 1
? "key"
: "keys"}
</span>
<div className="w-full md:w-1/2">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4">
<div>
<div className="text-sm text-muted-foreground mb-1">
{t("billingCurrentKeys") || "Current Keys"}
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-bold">
{getLicenseKeyCount()}
</span>
<span className="text-lg">
{getLicenseKeyCount() === 1
? "key"
: "keys"}
</span>
</div>
</div>
<Button
variant="outline"
onClick={handleModifySubscription}
disabled={isLoading}
loading={isLoading}
>
<CreditCard className="mr-2 h-4 w-4" />
{t("billingModifyCurrentPlan") ||
"Modify Current Plan"}
</Button>
</div>
<Button
variant="outline"
onClick={handleModifySubscription}
disabled={isLoading}
>
<CreditCard className="mr-2 h-4 w-4" />
{t("billingModifyCurrentPlan") ||
"Modify Current Plan"}
</Button>
</div>
</SettingsSectionBody>
</SettingsSection>
@@ -864,12 +967,14 @@ export default function BillingPage() {
<CredenzaBody>
{pendingTier && pendingTier.tier && (
<div className="space-y-4">
<div className="border rounded-lg p-4 bg-muted/30">
<div className="font-semibold text-lg mb-2">
<div className="border rounded-lg p-4">
<div className="text-2xl">
{pendingTier.planName}
</div>
<div className="text-2xl font-bold">
{pendingTier.price}
<div className="mt-1">
<span className="text-xl">
{pendingTier.price}
</span>
</div>
</div>

View File

@@ -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) {
<CardContent className={bannerContentClassName}>
<div className={bannerRowClassName}>
<KeyRound className="size-4 shrink-0 text-primary" />
<span>{t("subscriptionRequiredToUse")}</span>
<span>{isActive ? t("mustUpgradeToUse") : t("subscriptionRequiredToUse")}</span>
</div>
</CardContent>
</Card>

View File

@@ -33,6 +33,7 @@ export function usePaidStatus() {
hasEnterpriseLicense,
hasSaasSubscription,
isPaidUser,
isActive: tierData?.active,
subscriptionTier: tierData?.tier
};
}