diff --git a/apps/webapp/app/components/billing/UsageBar.tsx b/apps/webapp/app/components/billing/UsageBar.tsx
index 6cc5f6f93de..c77d2f28d97 100644
--- a/apps/webapp/app/components/billing/UsageBar.tsx
+++ b/apps/webapp/app/components/billing/UsageBar.tsx
@@ -2,7 +2,6 @@ import { cn } from "~/utils/cn";
import { formatCurrency } from "~/utils/numberFormatter";
import { Paragraph } from "../primitives/Paragraph";
import { SimpleTooltip } from "../primitives/Tooltip";
-import { motion } from "framer-motion";
type UsageBarProps = {
current: number;
@@ -11,8 +10,6 @@ type UsageBarProps = {
isPaying: boolean;
};
-const startFactor = 4;
-
export function UsageBar({ current, billingLimit, tierLimit, isPaying }: UsageBarProps) {
const getLargestNumber = Math.max(current, tierLimit ?? -Infinity, billingLimit ?? -Infinity, 5);
//creates a maximum range for the progress bar, add 10% to the largest number so the bar doesn't reach the end
@@ -26,13 +23,10 @@ export function UsageBar({ current, billingLimit, tierLimit, isPaying }: UsageBa
const usageCappedToLimitPercentage = Math.min(usagePercentage, tierRunLimitPercentage);
return (
-
+
{billingLimit !== undefined && (
-
@@ -42,12 +36,9 @@ export function UsageBar({ current, billingLimit, tierLimit, isPaying }: UsageBa
position="bottomRow2"
percentage={billingLimitPercentage}
/>
-
+
)}
-
-
+
{tierLimit !== undefined && (
-
@@ -75,12 +63,9 @@ export function UsageBar({ current, billingLimit, tierLimit, isPaying }: UsageBa
position="bottomRow1"
percentage={tierRunLimitPercentage}
/>
-
+
)}
-
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx
index 05e7fef8860..35e06518712 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.usage/route.tsx
@@ -1,4 +1,3 @@
-import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { Await } from "@remix-run/react";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
@@ -9,17 +8,18 @@ import { UsageBar } from "~/components/billing/UsageBar";
import { getUsageBarBillingLimitDollars } from "~/components/billing/billingAlertsFormat";
import { PageContainer } from "~/components/layout/AppLayout";
import { MetricsLayout } from "~/components/layout/MetricsLayout";
+import { LinkButton } from "~/components/primitives/Buttons";
import { Card } from "~/components/primitives/charts/Card";
import type { ChartConfig } from "~/components/primitives/charts/Chart";
import { Chart } from "~/components/primitives/charts/ChartCompound";
import { Header2 } from "~/components/primitives/Headers";
-import { InfoPanel } from "~/components/primitives/InfoPanel";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Select, SelectItem } from "~/components/primitives/Select";
import { Spinner } from "~/components/primitives/Spinner";
import {
Table,
+ TableBlankRow,
TableBody,
TableCell,
TableHeader,
@@ -33,8 +33,12 @@ import { UsagePresenter, type UsageSeriesData } from "~/presenters/v3/UsagePrese
import { getPromoCredits } from "~/services/platform.v3.server";
import { requireUserId } from "~/services/session.server";
import { formatCurrency, formatCurrencyAccurate, formatNumber } from "~/utils/numberFormatter";
-import { useBillingLimit } from "~/hooks/useOrganizations";
-import { OrganizationParamsSchema, organizationPath } from "~/utils/pathBuilder";
+import { useBillingLimit, useOrganization } from "~/hooks/useOrganizations";
+import {
+ OrganizationParamsSchema,
+ organizationPath,
+ v3BillingLimitsPath,
+} from "~/utils/pathBuilder";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { pageMeta } from "~/utils/pageTitle";
@@ -108,7 +112,10 @@ export default function Page() {
const { usage, tasks, months, isCurrentMonth, promoCredits } =
useTypedLoaderData();
const currentPlan = useCurrentPlan();
+ const organization = useOrganization();
const billingLimit = useBillingLimit();
+ const hasBillingLimit =
+ billingLimit !== undefined && billingLimit.isConfigured && billingLimit.mode === "custom";
const planLimitCents = currentPlan?.v3Subscription?.plan?.limits.includedUsage ?? 0;
// Enterprise bills against prepaid credits, not a per-month included-usage tier,
// so the "Included usage" marker doesn't apply.
@@ -156,75 +163,89 @@ export default function Page() {
{promoCredits && (
-
-
-
-
Credits
-
- {formatCurrency(promoCredits.remainingCents / 100, false)}
-
-
-
-
-
0
- ? Math.min(
- 100,
- Math.max(
- 0,
- (promoCredits.remainingCents / promoCredits.grantedCents) * 100
+
+
+
+
+
+ {formatCurrency(promoCredits.remainingCents / 100, false)}
+
+
credits
+
+
+
+
0
+ ? Math.min(
+ 100,
+ Math.max(
+ 0,
+ (promoCredits.remainingCents / promoCredits.grantedCents) * 100
+ )
)
- )
- : 0
- }%`,
- }}
- />
+ : 0
+ }%`,
+ }}
+ />
+
+
+ {formatCurrency(promoCredits.remainingCents / 100, false)} of{" "}
+ {formatCurrency(promoCredits.grantedCents / 100, false)} remaining
+ {promoCredits.expiresAt
+ ? `. Expires ${creditExpiryFormatter.format(new Date(promoCredits.expiresAt))}`
+ : ""}
+
-
- {formatCurrency(promoCredits.remainingCents / 100, false)} of{" "}
- {formatCurrency(promoCredits.grantedCents / 100, false)} remaining
- {promoCredits.expiresAt
- ? ` ยท expires ${creditExpiryFormatter.format(new Date(promoCredits.expiresAt))}`
- : ""}
-
-
-
+
+
)}
-
-
}>
-
- Failed to load graph.
-
- }
- >
- {(usage) => (
-
-
-
- {isCurrentMonth ? "Month-to-date" : "Usage"}
-
-
- {formatCurrency(usage.overall.current, false)}
-
+
+
+ }>
+
+ Failed to load graph.
-
-
- )}
-
-
-
+ }
+ >
+ {(usage) => (
+
+
+
+
+ {formatCurrency(usage.overall.current, false)}
+
+
+ {isCurrentMonth ? "month-to-date" : "usage"}
+
+
+
+ {hasBillingLimit ? "Update billing limit" : "Set billing limit"}
+
+
+
+
+ )}
+
+
+
+
@@ -253,7 +274,13 @@ export default function Page() {
- Tasks
+
+
Tasks
+
+ Dev environment runs are excluded from the usage data above, since they do not have an
+ associated compute cost.
+
+
}>
}
>
- {(tasks) => {
- return (
- <>
-
-
-
- Task
- Runs
- Average duration
- Average cost
- Total duration
- Total cost
+ {(tasks) => (
+
+
+
+ Task
+ Runs
+ Average duration
+ Average cost
+ Total duration
+ Total cost
+
+
+
+ {tasks.length === 0 ? (
+
+
+ No runs for this period
+
+
+ ) : (
+ tasks.map((task) => (
+
+ {task.taskIdentifier}
+
+ {formatNumber(task.runCount)}
+
+
+ {formatDurationMilliseconds(task.averageDuration, {
+ style: "short",
+ })}
+
+
+ {formatCurrencyAccurate(task.averageCost)}
+
+
+ {formatDurationMilliseconds(task.totalDuration, {
+ style: "short",
+ })}
+
+
+ {formatCurrencyAccurate(task.totalCost)}
+
-
-
- {tasks.length === 0 ? (
-
-
-
-
No runs for this period
-
-
-
- ) : (
- tasks.map((task) => (
-
- {task.taskIdentifier}
-
- {formatNumber(task.runCount)}
-
-
- {formatDurationMilliseconds(task.averageDuration, {
- style: "short",
- })}
-
-
- {formatCurrencyAccurate(task.averageCost)}
-
-
- {formatDurationMilliseconds(task.totalDuration, {
- style: "short",
- })}
-
-
- {formatCurrencyAccurate(task.totalCost)}
-
-
- ))
- )}
-
-
-
- Dev environment runs are excluded from the usage data above, since they do not
- have an associated compute cost.
-
- >
- );
- }}
+ ))
+ )}
+
+
+ )}