| 52 | 52 | | throw redirect(`/${slug}/-/billing?added=1`); |
| 53 | 53 | | } |
| 54 | 54 | | const group: "day" | "project" = url.searchParams.get("group") === "project" ? "project" : "day"; |
| 55 | | − | const [account, statement, features, deployUsage, limit, invoices] = await Promise.all([ |
| 55 | + | const now = new Date(); |
| 56 | + | const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString(); |
| 57 | + | const [account, statement, features, deployUsage, limit, invoices, thisMonth, allTime] = await Promise.all([ |
| 56 | 58 | | billing.account(slug, viewer), |
| 57 | 59 | | billing.statement(slug, viewer, url.searchParams.get("month"), group), |
| 58 | 60 | | billing.features(slug, viewer), |
| 59 | 61 | | deployments.usage(slug, viewer), |
| 60 | 62 | | billing.limit(slug, viewer), |
| 61 | 63 | | billing.invoices(slug, viewer).catch(() => null), |
| 64 | + | billing.usage(slug, viewer, monthStart).catch(() => null), |
| 65 | + | billing.usage(slug, viewer, "1970-01-01T00:00:00.000Z").catch(() => null), |
| 62 | 66 | | ]); |
| 63 | 67 | | return { |
| 64 | 68 | | slug, |
| ⋯ |
| 70 | 74 | | deployUsage: deployUsage.ok ? deployUsage.value : null, |
| 71 | 75 | | limit: limit.ok ? limit.value : null, |
| 72 | 76 | | invoices: invoices?.ok ? invoices.value : [], |
| 77 | + | spent: { |
| 78 | + | month: thisMonth?.ok ? thisMonth.value.spentMicros : null, |
| 79 | + | total: allTime?.ok ? allTime.value.spentMicros : null, |
| 80 | + | added: allTime?.ok ? allTime.value.addedMicros : null, |
| 81 | + | }, |
| 73 | 82 | | added: url.searchParams.has("added"), |
| 74 | 83 | | subscribed: url.searchParams.has("subscribed"), |
| 75 | 84 | | }; |
| ⋯ |
| 131 | 140 | | } |
| 132 | 141 | | |
| 133 | 142 | | export default function WorkspaceBilling({ loaderData, actionData }: Route.ComponentProps) { |
| 134 | | − | const { slug, role, account, statement, group, features, deployUsage, limit, invoices, added, subscribed } = loaderData; |
| 143 | + | const { slug, role, account, statement, group, spent, features, deployUsage, limit, invoices, added, subscribed } = loaderData; |
| 135 | 144 | | const { status } = account; |
| 136 | 145 | | const paying = useNavigation().state === "submitting"; |
| 137 | 146 | | const empty = account.balanceMicros <= 0; |
| ⋯ |
| 267 | 276 | | empty && status.enabled ? "border-warn/40 bg-warn/5" : "border-line bg-surface" |
| 268 | 277 | | }`} |
| 269 | 278 | | > |
| 270 | | − | <p className="text-xs text-muted">Balance</p> |
| 271 | | − | <p className="mt-1 text-3xl font-semibold tabular-nums tracking-tight"> |
| 272 | | − | {dollars(account.balanceMicros)} |
| 273 | | − | </p> |
| 279 | + | <dl className="flex flex-wrap items-end gap-x-10 gap-y-4"> |
| 280 | + | <div> |
| 281 | + | <dt className="text-xs text-muted">Balance</dt> |
| 282 | + | <dd className="mt-1 text-3xl font-semibold tabular-nums tracking-tight"> |
| 283 | + | {dollars(account.balanceMicros)} |
| 284 | + | </dd> |
| 285 | + | </div> |
| 286 | + | {spent.month !== null && ( |
| 287 | + | <div> |
| 288 | + | <dt className="text-xs text-muted">Spent this month</dt> |
| 289 | + | <dd className="mt-1 text-xl font-medium tabular-nums">{dollars(spent.month)}</dd> |
| 290 | + | </div> |
| 291 | + | )} |
| 292 | + | {spent.total !== null && ( |
| 293 | + | <div> |
| 294 | + | <dt className="text-xs text-muted">Spent in total</dt> |
| 295 | + | <dd className="mt-1 text-xl font-medium tabular-nums">{dollars(spent.total)}</dd> |
| 296 | + | </div> |
| 297 | + | )} |
| 298 | + | {spent.added !== null && spent.added !== 0 && ( |
| 299 | + | <div> |
| 300 | + | <dt className="text-xs text-muted">Paid and credited in total</dt> |
| 301 | + | <dd className="mt-1 text-xl font-medium tabular-nums text-muted">{dollars(spent.added)}</dd> |
| 302 | + | </div> |
| 303 | + | )} |
| 304 | + | </dl> |
| 274 | 305 | | {added && ( |
| 275 | 306 | | <p className="mt-2 text-sm text-accent">Payment received. Credit added.</p> |
| 276 | 307 | | )} |