diff --git a/apps/webapp/app/components/billing/v3/UsageBar.tsx b/apps/webapp/app/components/billing/v3/UsageBar.tsx
new file mode 100644
index 000000000..843e60cb1
--- /dev/null
+++ b/apps/webapp/app/components/billing/v3/UsageBar.tsx
@@ -0,0 +1,156 @@
+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;
+ billingLimit?: number;
+ tierLimit?: number;
+ projectedUsage: number;
+};
+
+export function UsageBar({ current, billingLimit, tierLimit, projectedUsage }: UsageBarProps) {
+ const getLargestNumber = Math.max(
+ current,
+ tierLimit ?? -Infinity,
+ projectedUsage,
+ billingLimit ?? -Infinity
+ );
+ //creates a maximum range for the progress bar, add 10% to the largest number so the bar doesn't reach the end
+ const maxRange = Math.round(getLargestNumber * 1.1);
+ const tierRunLimitPercentage = tierLimit ? Math.round((tierLimit / maxRange) * 100) : 0;
+ const projectedRunsPercentage = Math.round((projectedUsage / maxRange) * 100);
+ const billingLimitPercentage =
+ billingLimit !== undefined ? Math.round((billingLimit / maxRange) * 100) : 0;
+ const usagePercentage = Math.round((current / maxRange) * 100);
+
+ //cap the usagePercentage to the freeRunLimitPercentage
+ const usageCappedToLimitPercentage = Math.min(usagePercentage, tierRunLimitPercentage);
+
+ return (
+
+
+ {billingLimit && (
+
+
+
+ )}
+ {tierLimit && (
+
+
+
+ )}
+ {projectedUsage !== 0 && (
+
+
+
+ )}
+ tierLimit ? "bg-rose-600" : "bg-green-600"
+ )}
+ >
+
+
+
+
+
+ );
+}
+
+const positions = {
+ topRow1: "bottom-0 h-9",
+ topRow2: "bottom-0 h-14",
+ bottomRow1: "top-0 h-9 items-end",
+ bottomRow2: "top-0 h-14 items-end",
+};
+
+type LegendProps = {
+ text: string;
+ value: number | string;
+ percentage: number;
+ position: keyof typeof positions;
+ tooltipContent: string;
+};
+
+function Legend({ text, value, position, percentage, tooltipContent }: LegendProps) {
+ const flipLegendPositionValue = 80;
+ const flipLegendPosition = percentage > flipLegendPositionValue ? true : false;
+ return (
+
+
+ {text}
+ {value}
+
+ }
+ variant="dark"
+ side="top"
+ content={tooltipContent}
+ className="z-50 h-fit"
+ />
+
+ );
+}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
index c7f68c364..47a74e7db 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.v3.usage/route.tsx
@@ -1,26 +1,19 @@
+import { ArrowRightIcon } from "@heroicons/react/24/solid";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
-import {
- Bar,
- BarChart,
- CartesianGrid,
- Label,
- Legend,
- Rectangle,
- ResponsiveContainer,
- Tooltip,
- XAxis,
- YAxis,
-} from "recharts";
+import { Bar, BarChart, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
+import { UsageBar } from "~/components/billing/v3/UsageBar";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
-import { Header3 } from "~/components/primitives/Headers";
+import { Header2, Header3 } from "~/components/primitives/Headers";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
-import { getUsageSeries } from "~/services/platform.v3.server";
+import { getUsage, getUsageSeries } from "~/services/platform.v3.server";
import { requireUserId } from "~/services/session.server";
import { createTimeSeriesData } from "~/utils/graphs";
+import { formatCurrency } from "~/utils/numberFormatter";
import { OrganizationParamsSchema, organizationPath } from "~/utils/pathBuilder";
+import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
export async function loader({ params, request }: LoaderFunctionArgs) {
await requireUserId(request);
@@ -40,31 +33,43 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
}
//periods
- const periodStart = new Date();
- periodStart.setDate(periodStart.getDate() - 30);
- periodStart.setHours(0, 0, 0, 0);
- const periodEnd = new Date();
- periodEnd.setDate(periodEnd.getDate() + 1);
- periodEnd.setHours(23, 59, 59, 999);
+ const thirtyDaysAgo = new Date();
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
+ thirtyDaysAgo.setHours(0, 0, 0, 0);
+ const endOfToday = new Date();
+ endOfToday.setDate(endOfToday.getDate() + 1);
+ endOfToday.setHours(23, 59, 59, 999);
- const creditUsage = await getUsageSeries(organization.id, {
- from: periodStart,
- to: periodEnd,
+ const past30Days = await getUsageSeries(organization.id, {
+ from: thirtyDaysAgo,
+ to: endOfToday,
window: "DAY",
});
- console.log(JSON.stringify(creditUsage, null, 2));
+ const startOfMonth = new Date();
+ startOfMonth.setDate(1);
+ startOfMonth.setHours(0, 0, 0, 0);
+
+ const now = new Date();
+ const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
+
+ const usageData = await getUsage(organization.id, { from: startOfMonth, to: endOfMonth });
+
+ const current = (usageData?.cents ?? 0) / 100;
+ const percentageThroughMonth = new Date().getDate() / endOfMonth.getDate();
+ const usage = {
+ current: current,
+ projected: current / percentageThroughMonth,
+ };
return typedjson({
- periodStart,
- periodEnd,
- creditUsage: creditUsage
+ past30Days: past30Days
? createTimeSeriesData({
- startDate: periodStart,
- endDate: periodEnd,
+ startDate: thirtyDaysAgo,
+ endDate: endOfToday,
window: "DAY",
data:
- creditUsage.data.map((period) => ({
+ past30Days.data.map((period) => ({
date: new Date(period.windowStart),
value: period.value,
})) ?? [],
@@ -73,6 +78,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
dollars: (period.value ?? 0) / 100,
}))
: [],
+ usage,
});
}
@@ -84,7 +90,7 @@ const dateFormatter = new Intl.DateTimeFormat("en-US", {
const tooltipStyle = {
display: "flex",
alignItems: "center",
- gap: "0.5rem",
+ gap: "0rem",
borderRadius: "0.25rem",
border: "1px solid #1A2434",
backgroundColor: "#0B1018",
@@ -94,7 +100,8 @@ const tooltipStyle = {
};
export default function ChoosePlanPage() {
- const { periodStart, periodEnd, creditUsage } = useTypedLoaderData();
+ const { usage, past30Days } = useTypedLoaderData();
+ const currentPlan = useCurrentPlan();
return (
@@ -102,15 +109,37 @@ export default function ChoosePlanPage() {
-
-
Usage (past 30 days)
+
This month
+
+
+
+
Month-to-date
+
+ {formatCurrency(usage.current, false)}
+
+
+
+
+
Projected
+
{formatCurrency(usage.projected, false)}
+
+
+
+
+
Past 30 days
+
+ Usage
@@ -148,6 +177,7 @@ export default function ChoosePlanPage() {
return dateFormatter.format(new Date(dateString));
}}
+ formatter={(value, data) => [`$${value.toLocaleString()}`, ""]}
/>
} />