diff --git a/apps/web/src/app/(dashboard)/settings/usage/usage.tsx b/apps/web/src/app/(dashboard)/settings/usage/usage.tsx index 4612d37..5207e1e 100644 --- a/apps/web/src/app/(dashboard)/settings/usage/usage.tsx +++ b/apps/web/src/app/(dashboard)/settings/usage/usage.tsx @@ -14,6 +14,8 @@ import { useTeam } from "~/providers/team-context"; import { EmailUsageType } from "@prisma/client"; import { PlanDetails } from "~/components/payments/PlanDetails"; import { UpgradeButton } from "~/components/payments/UpgradeButton"; +import { Progress } from "@unsend/ui/src/progress"; + const FREE_PLAN_LIMIT = 3000; function FreePlanUsage({ @@ -152,16 +154,23 @@ function PaidPlanUsage({ ))} -
-
-
Minimum spend
-
- {currentTeam?.plan} +
+
+
+
Available credit
+
+ {currentTeam?.plan} +
+
+
+ {totalCost > planCreditCost + ? "0" + : `$${(planCreditCost - totalCost).toFixed(2)}`}
-
- ${planCreditCost.toFixed(2)} -
+
@@ -185,15 +194,14 @@ export default function UsagePage() { const { data: usage, isLoading } = api.billing.getThisMonthUsage.useQuery(); const { currentTeam } = useTeam(); - // Calculate the current billing period + const { data: subscription } = api.billing.getSubscriptionDetails.useQuery(); + + // Calculate the billing period based on subscription if available const today = new Date(); - const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); - const firstDayOfNextMonth = new Date( - today.getFullYear(), - today.getMonth() + 1, - 1 - ); - const billingPeriod = `${format(firstDayOfMonth, "MMM dd")} - ${format(firstDayOfNextMonth, "MMM dd")}`; + const billingPeriod = + subscription?.currentPeriodStart && subscription?.currentPeriodEnd + ? `${format(new Date(subscription.currentPeriodStart), "MMM dd")} - ${format(new Date(subscription.currentPeriodEnd), "MMM dd")}` + : `${format(new Date(today.getFullYear(), today.getMonth(), 1), "MMM dd")} - ${format(new Date(today.getFullYear(), today.getMonth() + 1, 1), "MMM dd")}`; return (
diff --git a/packages/ui/package.json b/packages/ui/package.json index 03d94fd..100c408 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", diff --git a/packages/ui/src/progress.tsx b/packages/ui/src/progress.tsx new file mode 100644 index 0000000..32f8b5b --- /dev/null +++ b/packages/ui/src/progress.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as React from "react"; +import * as ProgressPrimitive from "@radix-ui/react-progress"; + +import { cn } from "../lib/utils"; + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2a924e..6df8175 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -539,6 +539,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-progress': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-select': specifier: ^2.0.0 version: 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) @@ -4411,6 +4414,19 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-compose-refs@1.1.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.1(@types/react@18.2.66)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: @@ -4452,6 +4468,19 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-context@1.1.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} peerDependencies: @@ -5189,6 +5218,47 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-primitive@2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-slot': 1.1.2(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + '@types/react-dom': 18.2.22 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-progress@1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.66 + '@types/react-dom': 18.2.22 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: @@ -5392,6 +5462,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-slot@1.1.2(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + /@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} peerDependencies: