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. - - - ); - }} + )) + )} + + + )}