Skip to content
This repository was archived by the owner on Jan 31, 2025. It is now read-only.

Commit 6e04284

Browse files
committed
feat: new onboarding (#440)
1 parent 8db0f31 commit 6e04284

File tree

12 files changed

+96
-74
lines changed

12 files changed

+96
-74
lines changed

apps/hub/src/components/command-panel.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CommandLoading,
88
cn,
99
} from "@rivet-gg/components";
10+
import { useIsFetching } from "@tanstack/react-query";
1011
import { useMatchRoute } from "@tanstack/react-router";
1112
import {
1213
type KeyboardEventHandler,
@@ -25,6 +26,7 @@ import { EnvironmentCommandPanelPage } from "./command-panel/command-panel-page/
2526
import { GroupCommandPanelPage } from "./command-panel/command-panel-page/group-command-panel-page";
2627
import { IndexCommandPanelPage } from "./command-panel/command-panel-page/index-command-panel-page";
2728
import { ProjectCommandPanelPage } from "./command-panel/command-panel-page/project-command-panel-page";
29+
import { ShimmerLine } from "./shimmer-line";
2830

2931
export function CommandPanel() {
3032
const [isOpen, setOpen] = useState(false);
@@ -125,6 +127,11 @@ export function CommandPanel() {
125127
[pages.length, search],
126128
);
127129

130+
const isLoading =
131+
useIsFetching({
132+
predicate: (query) => !query.queryKey.includes("watch"),
133+
}) > 0;
134+
128135
return (
129136
<>
130137
<Button
@@ -143,6 +150,7 @@ export function CommandPanel() {
143150
<CommandDialog
144151
commandProps={{
145152
onKeyDown: handleKeyDown,
153+
shouldFilter: !isLoading,
146154
}}
147155
open={isOpen}
148156
onOpenChange={setOpen}
@@ -154,11 +162,13 @@ export function CommandPanel() {
154162
placeholder="Type a command or search..."
155163
/>
156164
<CommandPanelNavigationProvider
165+
isLoading={isLoading}
157166
onClose={handleClose}
158167
onChangePage={handlePageChange}
159168
>
160169
<CommandList>
161170
<Suspense fallback={<CommandLoading>Hang on…</CommandLoading>}>
171+
{isLoading ? <ShimmerLine className="-top-[1px]" /> : null}
162172
<CommandEmpty>No results found.</CommandEmpty>
163173
{!page ? <IndexCommandPanelPage /> : null}
164174
{page?.key === "group" ? (

apps/hub/src/components/command-panel/command-panel-navigation-provider.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ export function CommandPanelNavigationProvider({
5656
children,
5757
onClose,
5858
onChangePage,
59+
isLoading,
5960
}: {
6061
children: ReactNode;
6162
onClose: () => void;
6263
onChangePage: (page: CommandPanelPage) => void;
64+
isLoading: boolean;
6365
}) {
6466
const routerNavigate = useNavigate();
6567

@@ -73,9 +75,16 @@ export function CommandPanelNavigationProvider({
7375
[onClose, routerNavigate],
7476
);
7577

78+
const handleChangePage = (page: CommandPanelPage) => {
79+
if (isLoading) {
80+
return;
81+
}
82+
onChangePage(page);
83+
};
84+
7685
return (
7786
<CommandPanelNavigationContext.Provider
78-
value={{ changePage: onChangePage, close: onClose, navigate }}
87+
value={{ changePage: handleChangePage, close: onClose, navigate }}
7988
>
8089
{children}
8190
</CommandPanelNavigationContext.Provider>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function EnvironmentCommandPanelPage({
5151
},
5252
] = useSuspenseQueries({
5353
queries: [
54-
projectQueryOptions(projectNameId),
54+
projectQueryOptions(projectId),
5555
projectMetadataQueryOptions({ projectId, environmentId }),
5656
],
5757
});

apps/hub/src/components/error-component.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export const ErrorComponent = ({
6565
<Button
6666
onClick={() => {
6767
router.invalidate();
68+
queryClient.resetQueries();
69+
queryClient.invalidateQueries();
6870
reset?.();
6971
}}
7072
>

apps/hub/src/components/get-started.tsx

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ import {
1919
faChevronRight,
2020
faCircleNodes,
2121
faCode,
22-
faCog,
2322
faDiagramNext,
2423
faGamepadAlt,
25-
faHandWave,
2624
faSparkles,
27-
faToolbox,
2825
faUpRightAndDownLeftFromCenter,
2926
faWifiSlash,
3027
} from "@rivet-gg/icons";
@@ -70,7 +67,7 @@ export function GetStarted() {
7067
<Button
7168
onClick={() => {
7269
document
73-
.getElementById("getting-started")
70+
.getElementById("examples")
7471
?.scrollIntoView({ behavior: "smooth" });
7572
}}
7673
endIcon={<Icon icon={faChevronDoubleDown} />}
@@ -109,49 +106,7 @@ export function GetStarted() {
109106
</div>
110107
</CardContent>
111108
</Card>
112-
<Card
113-
id="getting-started"
114-
asChild
115-
className="max-w-xl w-full mx-auto my-6 scroll-mt-28"
116-
>
117-
<motion.div>
118-
<CardHeader>
119-
<CardTitle>Getting Started</CardTitle>
120-
<CardDescription>
121-
Learn how Rivet works and how you can customize it to suit your
122-
needs.
123-
</CardDescription>
124-
</CardHeader>
125-
<CardContent>
126-
<motion.div
127-
variants={containerVariants}
128-
initial="hidden"
129-
whileInView="show"
130-
className="grid md:grid-cols-3 gap-4"
131-
>
132-
<ExampleLink
133-
href="examples"
134-
title="Intro to Rivet"
135-
size="md"
136-
icon={faHandWave}
137-
/>
138-
<ExampleLink
139-
href="examples"
140-
title="Initial Setup"
141-
size="md"
142-
icon={faToolbox}
143-
/>
144-
<ExampleLink
145-
href="examples"
146-
title="Configuration"
147-
size="md"
148-
icon={faCog}
149-
/>
150-
</motion.div>
151-
</CardContent>
152-
</motion.div>
153-
</Card>
154-
<Card asChild className="max-w-xl w-full mx-auto my-6">
109+
<Card id="examples" asChild className="max-w-xl w-full mx-auto my-6">
155110
<motion.div>
156111
<CardHeader>
157112
<CardTitle>Examples</CardTitle>
@@ -165,6 +120,7 @@ export function GetStarted() {
165120
variants={containerVariants}
166121
initial="hidden"
167122
whileInView="show"
123+
viewport={{ once: true }}
168124
className="grid md:grid-cols-3 gap-4"
169125
>
170126
<ExampleLink
@@ -214,6 +170,7 @@ export function GetStarted() {
214170
variants={containerVariants}
215171
initial="hidden"
216172
whileInView="show"
173+
viewport={{ once: true }}
217174
className="grid md:grid-cols-2 gap-4"
218175
>
219176
<ExampleLink
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useRouterState } from "@tanstack/react-router";
2+
import { ShimmerLine } from "../shimmer-line";
23

34
export function HeaderRouteLoader() {
45
const isLoading = useRouterState({
@@ -8,9 +9,5 @@ export function HeaderRouteLoader() {
89
if (!isLoading) {
910
return null;
1011
}
11-
return (
12-
<div className="animate-in fade-in absolute inset-x-0 -bottom-1 w-full overflow-hidden">
13-
<div className="animate-bounce-x from-secondary/0 via-primary to-secondary/0 relative -bottom-px h-1 bg-gradient-to-r" />
14-
</div>
15-
);
12+
return <ShimmerLine className="-bottom-1" />;
1613
}

apps/hub/src/components/intro.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import {
1616
CardTitle,
1717
Skeleton,
1818
} from "@rivet-gg/components";
19+
import * as Sentry from "@sentry/react";
1920
import { useSuspenseQuery } from "@tanstack/react-query";
20-
import { useNavigate } from "@tanstack/react-router";
21+
import { Navigate, useNavigate } from "@tanstack/react-router";
2122
import { motion } from "framer-motion";
2223
import { Suspense, useState } from "react";
2324

@@ -27,7 +28,12 @@ enum Step {
2728
ChoosePlan = 2,
2829
}
2930

30-
export function Intro({ initialStep }: { initialStep?: Step }) {
31+
interface IntroProps {
32+
initialStep?: Step;
33+
initialProjectName?: string;
34+
}
35+
36+
export function Intro({ initialStep, initialProjectName }: IntroProps) {
3137
const { mutateAsync, data: createdGroupResponse } = useGroupCreateMutation();
3238
const { mutateAsync: createProject, data: projectCreationData } =
3339
useProjectCreateMutation();
@@ -38,14 +44,13 @@ export function Intro({ initialStep }: { initialStep?: Step }) {
3844
data
3945
.flatMap((team) => team.projects)
4046
.find((project) => project.gameId === projectCreationData?.gameId) ||
41-
// biome-ignore lint/style/noNonNullAssertion: at this point user should have at least one project
42-
data.find((team) => team.projects.length > 0)!.projects[0]!;
47+
data.find((team) => team.projects.length > 0)?.projects[0];
4348

4449
const [step, setStep] = useState<Step>(
4550
() => initialStep ?? (!project ? Step.CreateGroup : Step.CreateProject),
4651
);
4752

48-
const groupId = createdGroupResponse?.groupId || project.developer.groupId;
53+
const groupId = createdGroupResponse?.groupId || project?.developer.groupId;
4954

5055
const navigate = useNavigate();
5156

@@ -60,7 +65,10 @@ export function Intro({ initialStep }: { initialStep?: Step }) {
6065
exit={{ opacity: 0 }}
6166
>
6267
<GroupCreateProjectForm.Form
63-
defaultValues={{ slug: "", name: "" }}
68+
defaultValues={{
69+
slug: "",
70+
name: initialProjectName ?? "",
71+
}}
6472
onSubmit={async (values) => {
6573
await createProject({
6674
displayName: values.name,
@@ -80,6 +88,12 @@ export function Intro({ initialStep }: { initialStep?: Step }) {
8088
</CardHeader>
8189
<CardContent>
8290
<div className="grid grid-cols-[auto_auto_min-content] items-center gap-4 ">
91+
{initialProjectName ? (
92+
<GroupCreateProjectForm.SetValue
93+
name="name"
94+
value={initialProjectName}
95+
/>
96+
) : null}
8397
<GroupCreateProjectForm.Name className="contents space-y-0" />
8498
<GroupCreateProjectForm.Slug className="contents space-y-0" />
8599
<GroupCreateProjectForm.Submit
@@ -98,6 +112,15 @@ export function Intro({ initialStep }: { initialStep?: Step }) {
98112
}
99113

100114
if (step === Step.ChoosePlan) {
115+
if (!groupId || !project) {
116+
// At this point those values should be defined, if not, we should redirect to the home page
117+
// It's unlikely that this will happen, but it's better to be safe than sorry
118+
Sentry.captureMessage(
119+
"Group or project not defined in Intro component",
120+
"fatal",
121+
);
122+
return <Navigate to="/" replace />;
123+
}
101124
return (
102125
<Suspense
103126
fallback={
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { cn } from "@rivet-gg/components";
2+
3+
interface ShimmerLineProps {
4+
className?: string;
5+
}
6+
export const ShimmerLine = ({ className }: ShimmerLineProps) => {
7+
return (
8+
<div
9+
className={cn(
10+
"animate-in fade-in absolute inset-x-0 w-full overflow-hidden",
11+
className,
12+
)}
13+
>
14+
<div className="animate-bounce-x from-secondary/0 via-primary to-secondary/0 relative -bottom-px h-1 bg-gradient-to-r" />
15+
</div>
16+
);
17+
};

apps/hub/src/domains/project/forms/group-create-project-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export type SubmitHandler = (
4646
form: UseFormReturn<FormValues>,
4747
) => Promise<void>;
4848

49-
const { Form, Submit } = createSchemaForm(formSchema);
50-
export { Form, Submit };
49+
const { Form, Submit, SetValue } = createSchemaForm(formSchema);
50+
export { Form, Submit, SetValue };
5151

5252
export const Name = ({ className }: { className?: string }) => {
5353
const { control } = useFormContext<FormValues>();

apps/hub/src/routes/_authenticated/_layout/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { LayoutGroup, motion } from "framer-motion";
88
import { z } from "zod";
99

1010
function IndexRoute() {
11-
const { initialStep } = Route.useSearch();
11+
const { initialStep, project_name: projectName } = Route.useSearch();
1212
return (
1313
<>
1414
<OnboardingBackground />
@@ -34,7 +34,10 @@ function IndexRoute() {
3434
animate={{ opacity: 1, transition: { delay: 0.5 } }}
3535
>
3636
<LayoutGroup>
37-
<Intro initialStep={initialStep} />
37+
<Intro
38+
initialProjectName={projectName}
39+
initialStep={initialStep}
40+
/>
3841
</LayoutGroup>
3942
</motion.div>
4043
</div>
@@ -46,6 +49,7 @@ function IndexRoute() {
4649
const searchSchema = z.object({
4750
newbie: z.coerce.boolean().optional(),
4851
initialStep: z.coerce.number().optional(),
52+
project_name: z.coerce.string().optional(),
4953
});
5054

5155
export const Route = createFileRoute("/_authenticated/_layout/")({

0 commit comments

Comments
 (0)