Skip to content

Commit e159786

Browse files
committed
refactor(cloud): polish
1 parent 6f6fc84 commit e159786

File tree

19 files changed

+345
-208
lines changed

19 files changed

+345
-208
lines changed

frontend/src/app/actor-builds-list.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
// @ts-expect-error
3-
faActorsBorderless,
4-
Icon,
5-
} from "@rivet-gg/icons";
1+
import { faActorsBorderless, Icon } from "@rivet-gg/icons";
62
import { useInfiniteQuery } from "@tanstack/react-query";
73
import { Link, useNavigate } from "@tanstack/react-router";
84
import { Fragment } from "react";
@@ -23,7 +19,7 @@ export function ActorBuildsList() {
2319
<div className="flex flex-col gap-[1px]">
2420
{data?.length === 0 ? (
2521
<p className="text-xs text-muted-foreground ms-1">
26-
No instances found.
22+
Connect RivetKit to see instances.
2723
</p>
2824
) : null}
2925
{data?.map((build) => (
@@ -84,3 +80,19 @@ export function ActorBuildsList() {
8480
</div>
8581
);
8682
}
83+
84+
export function ActorBuildsListSkeleton() {
85+
return (
86+
<div className="h-full">
87+
<div className="flex flex-col gap-[1px]">
88+
{Array(RECORDS_PER_PAGE)
89+
.fill(null)
90+
.map((_, i) => (
91+
<Fragment key={i}>
92+
<Skeleton className="w-full h-6 my-1" />
93+
</Fragment>
94+
))}
95+
</div>
96+
</div>
97+
);
98+
}

frontend/src/app/context-switcher.tsx

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import { VisibilitySensor } from "@/components/visibility-sensor";
2424
export function ContextSwitcher() {
2525
const [isOpen, setIsOpen] = useState(false);
2626

27+
const match = useContextSwitchMatch();
28+
29+
if (!match) {
30+
return null;
31+
}
32+
2733
return (
2834
<>
2935
<Popover open={isOpen} onOpenChange={setIsOpen}>
@@ -47,45 +53,69 @@ export function ContextSwitcher() {
4753
);
4854
}
4955

50-
function Breadcrumbs() {
56+
const useContextSwitchMatch = ():
57+
| {
58+
project: string;
59+
namespace: string;
60+
organization: string;
61+
}
62+
| { organization: string; project: string }
63+
| false => {
5164
const match = useMatchRoute();
5265

5366
const matchNamespace = match({
5467
to: "/orgs/$organization/projects/$project/ns/$namespace",
5568
fuzzy: true,
5669
});
70+
5771
if (matchNamespace) {
72+
return matchNamespace;
73+
}
74+
75+
const matchProject = match({
76+
to: "/orgs/$organization/projects/$project",
77+
fuzzy: true,
78+
});
79+
80+
if (matchProject) {
81+
return matchProject;
82+
}
83+
84+
return false;
85+
};
86+
87+
function Breadcrumbs() {
88+
const match = useContextSwitchMatch();
89+
90+
if (match && "project" in match && "namespace" in match) {
5891
return (
5992
<div className="flex flex-col items-center min-w-0 w-full">
6093
<div className="text-left text-xs text-muted-foreground min-w-0 flex w-full">
6194
<ProjectBreadcrumb
62-
project={matchNamespace.project}
95+
project={match.project}
6396
className="truncate min-w-0 max-w-full block h-4"
6497
/>
6598
</div>
6699
<div className="min-w-0 w-full">
67100
<NamespaceBreadcrumb
68101
className="text-left truncate block"
69-
namespace={matchNamespace.namespace}
70-
project={matchNamespace.project}
102+
namespace={match.namespace}
103+
project={match.project}
71104
/>
72105
</div>
73106
</div>
74107
);
75108
}
76109

77-
const matchProject = match({
78-
to: "/orgs/$organization/projects/$project",
79-
fuzzy: true,
80-
});
81-
82-
if (matchProject) {
110+
if (match && "project" in match) {
83111
return (
84112
<>
85-
<ProjectBreadcrumb project={matchProject.project} />
113+
<ProjectBreadcrumb project={match.project} />
86114
</>
87115
);
88116
}
117+
118+
return null;
89119
}
90120

91121
function ProjectBreadcrumb({
@@ -102,7 +132,7 @@ function ProjectBreadcrumb({
102132
return <Skeleton className={cn("h-5 w-32", className)} />;
103133
}
104134

105-
return <span className={className}>{data?.name}</span>;
135+
return <span className={className}>{data?.displayName}</span>;
106136
}
107137

108138
function NamespaceBreadcrumb({
@@ -124,7 +154,7 @@ function NamespaceBreadcrumb({
124154
return <Skeleton className="h-5 w-32" />;
125155
}
126156

127-
return <span className={className}>{data?.name}</span>;
157+
return <span className={className}>{data?.displayName}</span>;
128158
}
129159

130160
function Content({ onClose }: { onClose?: () => void }) {

frontend/src/app/data-providers/engine-data-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import {
88
} from "@/components/actors";
99
import { engineEnv } from "@/lib/env";
1010
import { convertStringToId } from "@/lib/utils";
11+
import { noThrow, shouldRetryAllExpect403 } from "@/queries/utils";
1112
import {
1213
ActorQueryOptionsSchema,
1314
createDefaultGlobalContext,
1415
type DefaultDataProvider,
1516
RECORDS_PER_PAGE,
1617
} from "./default-data-provider";
17-
import { noThrow, shouldRetryAllExpect403 } from "./utils";
1818

1919
export type CreateNamespace = {
2020
displayName: string;

frontend/src/app/dialogs/billing-frame.tsx

Lines changed: 119 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Rivet } from "@rivet-gg/cloud";
12
import {
23
useMutation,
34
useQuery,
@@ -101,61 +102,135 @@ export default function BillingFrameContent() {
101102
</BillingDetailsButton>
102103
</div>
103104
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 mt-4">
104-
<CommunityPlan
105-
current={
106-
billing?.activePlan === "free" ||
107-
!billing?.activePlan
108-
}
105+
{[
106+
[Rivet.BillingPlan.Free, CommunityPlan],
107+
[Rivet.BillingPlan.Pro, ProPlan],
108+
[Rivet.BillingPlan.Team, TeamPlan],
109+
].map(([plan, PlanComponent]) => {
110+
const config = getConfig(plan, billing);
111+
return (
112+
<PlanComponent
113+
key={plan}
114+
{...config}
115+
buttonProps={{
116+
...config.buttonProps,
117+
disabled:
118+
config.buttonProps.disabled ||
119+
isPending,
120+
isLoading:
121+
variables?.__from === plan && isPending,
122+
onClick: () => {
123+
if (billing.futurePlan === plan) {
124+
return mutate({
125+
plan: Rivet.BillingPlan.Free,
126+
__from: plan,
127+
});
128+
}
129+
mutate({ plan, __from: plan });
130+
},
131+
}}
132+
/>
133+
);
134+
})}
135+
<EnterprisePlan
109136
buttonProps={{
110-
isLoading: isPending,
111-
onClick: () => mutate({ plan: "community" }),
112-
disabled:
113-
billing?.activePlan === "free" ||
114-
!billing?.activePlan ||
115-
!billing?.canChangePlan,
116-
}}
117-
/>
118-
<ProPlan
119-
current={billing?.activePlan === "pro"}
120-
buttonProps={{
121-
isLoading: isPending,
122137
onClick: () => {
123-
if (billing?.activePlan === "pro") {
124-
return mutate({ plan: "free" });
125-
}
126-
return mutate({ plan: "pro" });
138+
window.open(
139+
"https://www.rivet.dev/sales",
140+
"_blank",
141+
);
127142
},
128-
disabled: !billing?.canChangePlan,
129-
...(billing?.activePlan === "pro" &&
130-
billing?.futurePlan !== "free"
131-
? { children: "Cancel" }
132-
: {}),
133143
}}
134144
/>
135-
<TeamPlan
136-
current={billing?.activePlan === "team"}
137-
buttonProps={{
138-
isLoading: isPending,
139-
onClick: () => {
140-
if (billing?.activePlan === "team") {
141-
return mutate({ plan: "free" });
142-
}
143-
return mutate({ plan: "team" });
144-
},
145-
disabled: !billing?.canChangePlan,
146-
...(billing?.activePlan === "team" &&
147-
billing?.futurePlan !== "free"
148-
? { children: "Cancel" }
149-
: {}),
150-
}}
151-
/>
152-
<EnterprisePlan />
153145
</div>
154146
</Frame.Content>
155147
</>
156148
);
157149
}
158150

151+
function isCurrent(
152+
plan: Rivet.BillingPlan,
153+
data: Rivet.BillingDetailsResponse.Billing,
154+
) {
155+
return (
156+
plan === data.activePlan ||
157+
(plan === Rivet.BillingPlan.Free && !data.activePlan)
158+
);
159+
}
160+
161+
function getConfig(
162+
plan: Rivet.BillingPlan,
163+
billing: Rivet.BillingDetailsResponse.Billing | undefined,
164+
) {
165+
return {
166+
current: isCurrent(plan, billing),
167+
buttonProps: {
168+
children: buttonText(plan, billing),
169+
variant: buttonVariant(plan, billing),
170+
disabled: !billing?.canChangePlan || buttonDisabled(plan, billing),
171+
},
172+
};
173+
}
174+
175+
function buttonVariant(
176+
plan: Rivet.BillingPlan,
177+
data: Rivet.BillingDetailsResponse.Billing,
178+
) {
179+
if (plan === data.activePlan && data.futurePlan !== data.activePlan)
180+
return "default";
181+
if (plan === data.futurePlan && data.futurePlan !== data.activePlan)
182+
return "secondary";
183+
184+
if (comparePlans(plan, data.futurePlan) > 0) return "default";
185+
return "secondary";
186+
}
187+
188+
function buttonDisabled(
189+
plan: Rivet.BillingPlan,
190+
data: Rivet.BillingDetailsResponse.Billing,
191+
) {
192+
return plan === data.futurePlan && data.futurePlan !== data.activePlan;
193+
}
194+
195+
function buttonText(
196+
plan: Rivet.BillingPlan,
197+
data: Rivet.BillingDetailsResponse.Billing,
198+
) {
199+
if (plan === data.activePlan && data.futurePlan !== data.activePlan)
200+
return <>Resubscribe</>;
201+
if (plan === data.futurePlan && data.futurePlan !== data.activePlan)
202+
return (
203+
<>
204+
Downgrades on{" "}
205+
{new Date(data.currentPeriodEnd).toLocaleDateString(undefined, {
206+
month: "short",
207+
day: "numeric",
208+
})}
209+
</>
210+
);
211+
if (plan === data.activePlan) return "Cancel";
212+
return comparePlans(plan, data.futurePlan) > 0 ? "Upgrade" : "Downgrade";
213+
}
214+
215+
export function comparePlans(
216+
a: Rivet.BillingPlan,
217+
b: Rivet.BillingPlan,
218+
): number {
219+
const plans = [
220+
Rivet.BillingPlan.Free,
221+
Rivet.BillingPlan.Pro,
222+
Rivet.BillingPlan.Team,
223+
Rivet.BillingPlan.Enterprise,
224+
];
225+
226+
const tierA = plans.indexOf(a);
227+
const tierB = plans.indexOf(b);
228+
229+
if (tierA > tierB) return 1;
230+
if (tierA < tierB) return -1;
231+
return 0;
232+
}
233+
159234
function CurrentPlan({ plan }: { plan?: string }) {
160235
if (!plan || plan === "free") return <>Free</>;
161236
if (plan === "pro") return <>Hobby</>;

frontend/src/app/dialogs/create-project-frame.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,16 @@ export default function CreateProjectFrameContent() {
3939
onSubmit={async (values) => {
4040
await mutateAsync({
4141
displayName: values.name,
42-
nameId: values.slug || convertStringToId(values.name),
4342
});
4443
}}
45-
defaultValues={{ name: "", slug: "" }}
44+
defaultValues={{ name: "" }}
4645
>
4746
<Frame.Header>
4847
<Frame.Title>Create Project</Frame.Title>
4948
</Frame.Header>
5049
<Frame.Content>
5150
<Flex gap="4" direction="col">
5251
<CreateProjectForm.Name />
53-
<CreateProjectForm.Slug />
5452
</Flex>
5553
</Frame.Content>
5654
<Frame.Footer>

0 commit comments

Comments
 (0)