Skip to content

Commit 6728f28

Browse files
committed
feat(hub): add actors billing preview (#2466)
<!-- Please make sure there is an issue that this PR is correlated to. --> ## Changes <!-- If there are frontend changes, please include screenshots. -->
1 parent 639afa9 commit 6728f28

38 files changed

+1236
-456
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { defineMock } from "vite-plugin-mock-dev-server";
2+
3+
export default defineMock([
4+
{
5+
url: "/api/cloud/bootstrap",
6+
body: {
7+
// use "enterprise" to remove billing
8+
cluster: "oss",
9+
access: "public",
10+
domains: {},
11+
deploy_hash: "0",
12+
},
13+
},
14+
{
15+
url: "/api/cloud/games/:id/billing",
16+
body: {
17+
active_plan: "indie",
18+
// change to different plan, to show downgrade/upgrade warning
19+
plan: "indie",
20+
subscription: {
21+
period_start_ts: "2024-06-06T18:27:45.601Z",
22+
period_end_ts: "2024-07-06T18:27:45.601Z",
23+
},
24+
watch: { index: "0000" },
25+
},
26+
},
27+
{
28+
url: "/api/cloud/groups/:id/billing/usage",
29+
body: {
30+
games: [],
31+
},
32+
},
33+
{
34+
url: "/api/cloud/groups/:id/billing",
35+
body: {
36+
group: {
37+
payment_method_attached_ts: "2024-06-06T18:27:45.601Z",
38+
payment_method_valid_ts: "2024-06-06T18:27:45.601Z",
39+
// show info about missing payment method
40+
// payment_method_attached_ts: null,
41+
// payment_method_valid_ts: null
42+
},
43+
watch: { index: "0000" },
44+
},
45+
},
46+
{
47+
url: "/api/cloud/groups/:id/billing/stripe-portal-session",
48+
method: "POST",
49+
body: {
50+
stripe_session_url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
51+
},
52+
},
53+
]);

frontend/apps/hub/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"typescript": "^5.5.4",
8080
"vite": "^5.2.0",
8181
"vite-bundle-analyzer": "^0.17.0",
82-
"vite-plugin-favicons-inject": "^2.2.0"
82+
"vite-plugin-favicons-inject": "^2.2.0",
83+
"vite-plugin-mock-dev-server": "^1.8.7"
8384
}
8485
}

frontend/apps/hub/src/components/command-panel/command-panel-page/environment-command-panel-page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
environmentByIdQueryOptions,
33
projectByIdQueryOptions,
44
projectEnvironmentQueryOptions,
5-
projectMetadataQueryOptions,
5+
environmentMetadataQueryOptions,
66
projectQueryOptions,
77
} from "@/domains/project/queries";
88
import { GuardEnterprise } from "@/lib/guards";
@@ -21,7 +21,6 @@ import {
2121
faFunction,
2222
faGear,
2323
faGlobe,
24-
faHammer,
2524
faJoystick,
2625
faKey,
2726
faLink,
@@ -62,7 +61,7 @@ export function EnvironmentCommandPanelPage({
6261
] = useSuspenseQueries({
6362
queries: [
6463
projectQueryOptions(projectId),
65-
projectMetadataQueryOptions({ projectId, environmentId }),
64+
environmentMetadataQueryOptions({ projectId, environmentId }),
6665
],
6766
});
6867

frontend/apps/hub/src/components/header/links/header-environment-links.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
environmentByIdQueryOptions,
33
projectByIdQueryOptions,
44
projectEnvironmentQueryOptions,
5-
projectMetadataQueryOptions,
5+
environmentMetadataQueryOptions,
66
} from "@/domains/project/queries";
77
import { GuardEnterprise } from "@/lib/guards";
88
import {
@@ -44,7 +44,7 @@ export function HeaderEnvironmentLinks({
4444
] = useSuspenseQueries({
4545
queries: [
4646
projectEnvironmentQueryOptions({ projectId, environmentId }),
47-
projectMetadataQueryOptions({ projectId, environmentId }),
47+
environmentMetadataQueryOptions({ projectId, environmentId }),
4848
],
4949
});
5050

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import {
2+
Badge,
3+
Button,
4+
Card,
5+
CardContent,
6+
CardDescription,
7+
CardHeader,
8+
CardTitle,
9+
H4,
10+
} from "@rivet-gg/components";
11+
import { BillingUsageProgress } from "./billing-usage-progress";
12+
import { useBilling } from "./billing-context";
13+
import { BillingHeader } from "./billing-header";
14+
import { BillingPortalButton } from "./billing-portal-button";
15+
import {
16+
BillingPlanDescription,
17+
BillingPlanLead,
18+
BillingPlanPrice,
19+
ProjectBillingPlanLabel,
20+
} from "./billing-plan-badge";
21+
import { Link } from "@tanstack/react-router";
22+
import { faArrowRight, Icon } from "@rivet-gg/icons";
23+
24+
export function ActorsBilling() {
25+
const {
26+
credits: { max, used, overage },
27+
activePlan,
28+
plan,
29+
group,
30+
subscription,
31+
} = useBilling();
32+
33+
return (
34+
<div className="max-w-4xl w-full mx-auto px-4">
35+
<BillingHeader
36+
actions={
37+
<>
38+
<BillingPortalButton
39+
variant="outline"
40+
intent="payment_method_update"
41+
>
42+
Manage Billing
43+
</BillingPortalButton>
44+
<BillingPortalButton variant="outline" intent="general">
45+
View past invoices
46+
</BillingPortalButton>
47+
</>
48+
}
49+
/>
50+
51+
<Card className="bg-transparent mb-4">
52+
<CardHeader>
53+
<CardTitle className="flex items-center">
54+
<div className="flex-1 flex items-center">
55+
<ProjectBillingPlanLabel plan={plan} /> Plan{" "}
56+
{activePlan === plan ? (
57+
<Badge className="ml-2">Current Plan</Badge>
58+
) : null}
59+
{activePlan !== plan ? (
60+
<Badge className="ml-2" variant="secondary">
61+
Changes on{" "}
62+
{subscription?.periodEndTs.toLocaleDateString(
63+
undefined,
64+
{ dateStyle: "short" },
65+
)}{" "}
66+
to{" "}
67+
<ProjectBillingPlanLabel
68+
plan={activePlan}
69+
/>{" "}
70+
Plan
71+
</Badge>
72+
) : null}
73+
</div>
74+
<Button
75+
asChild
76+
variant="ghost"
77+
className="text-muted-foreground"
78+
endIcon={<Icon icon={faArrowRight} />}
79+
>
80+
<Link to="." search={{ modal: "manage-plan" }}>
81+
View Plans
82+
</Link>
83+
</Button>
84+
</CardTitle>
85+
<CardDescription>
86+
<BillingPlanPrice plan={plan} />{" "}
87+
<BillingPlanLead plan={plan} />
88+
</CardDescription>
89+
</CardHeader>
90+
<CardContent className="flex justify-end items-end gap-4">
91+
<div className="flex-1">
92+
<p>
93+
<BillingPlanDescription plan={plan} />
94+
</p>
95+
<p>
96+
Need something custom?{" "}
97+
<a
98+
href="https://rivet.gg/sales"
99+
rel="noopener noreferrer"
100+
target="_blank"
101+
className="text-primary"
102+
>
103+
Contact Sales
104+
</a>
105+
</p>
106+
</div>
107+
{plan === activePlan ? (
108+
<Button asChild>
109+
<Link to="." search={{ modal: "manage-plan" }}>
110+
Upgrade
111+
</Link>
112+
</Button>
113+
) : (
114+
<Button asChild>
115+
<Link to="." search={{ modal: "manage-plan" }}>
116+
Change Plan
117+
</Link>
118+
</Button>
119+
)}
120+
</CardContent>
121+
</Card>
122+
123+
<Card className="bg-transparent">
124+
<CardHeader>
125+
<CardTitle className="flex items-center">
126+
<span>Usage</span>
127+
{subscription ? (
128+
<Badge
129+
className="ml-2 font-normal gap-0.5"
130+
variant="outline"
131+
>
132+
<b>
133+
{subscription.periodStartTs.toLocaleDateString(
134+
undefined,
135+
{ dateStyle: "long" },
136+
)}
137+
</b>{" "}
138+
-{" "}
139+
<b>
140+
{" "}
141+
{subscription.periodEndTs.toLocaleDateString(
142+
undefined,
143+
{ dateStyle: "long" },
144+
)}
145+
</b>{" "}
146+
</Badge>
147+
) : null}
148+
</CardTitle>
149+
</CardHeader>
150+
<CardContent>
151+
<H4 className="font-normal">Actors</H4>
152+
<BillingUsageProgress
153+
max={max}
154+
used={used}
155+
overage={overage}
156+
plan={activePlan}
157+
/>
158+
</CardContent>
159+
</Card>
160+
</div>
161+
);
162+
}

0 commit comments

Comments
 (0)