Skip to content

Commit 51305b6

Browse files
authored
Onboarding dashboard background (#2346)
* Adds a background image dashboard wrapper * Dashboard background image * Adds a background image to the welcome onboarding page * Background is constructed of 3 images * Adds the background to the create org page * Adds a background to the choose plan page * Change the default button spinner color to white * Adds the background to the create new project page * Updates the invite team member page * Adds background image to received invite page
1 parent 9e41e83 commit 51305b6

File tree

12 files changed

+366
-285
lines changed

12 files changed

+366
-285
lines changed
2.34 KB
Loading
20.3 KB
Loading
151 KB
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { type ReactNode } from "react";
2+
import blurredDashboardBackgroundMenuTop from "~/assets/images/blurred-dashboard-background-menu-top.jpg";
3+
import blurredDashboardBackgroundMenuBottom from "~/assets/images/blurred-dashboard-background-menu-bottom.jpg";
4+
import blurredDashboardBackgroundTable from "~/assets/images/blurred-dashboard-background-table.jpg";
5+
6+
export function BackgroundWrapper({ children }: { children: ReactNode }) {
7+
return (
8+
<div className="relative h-full w-full overflow-hidden">
9+
{/* Left menu top background - fixed width 260px, maintains aspect ratio */}
10+
<div
11+
className="absolute left-0 top-0 w-[260px] bg-contain bg-left-top bg-no-repeat"
12+
style={{
13+
backgroundImage: `url(${blurredDashboardBackgroundMenuTop})`,
14+
aspectRatio: "auto",
15+
height: "100vh",
16+
backgroundSize: "260px auto",
17+
}}
18+
/>
19+
20+
{/* Left menu bottom background - fixed width 260px, maintains aspect ratio */}
21+
<div
22+
className="absolute bottom-0 left-0 w-[260px] bg-contain bg-left-bottom bg-no-repeat"
23+
style={{
24+
backgroundImage: `url(${blurredDashboardBackgroundMenuBottom})`,
25+
aspectRatio: "auto",
26+
height: "100vh",
27+
backgroundSize: "260px auto",
28+
}}
29+
/>
30+
31+
{/* Right table background - fixed width 2000px, positioned next to menu */}
32+
<div
33+
className="absolute top-0 bg-left-top bg-no-repeat"
34+
style={{
35+
left: "260px",
36+
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
37+
width: "100%",
38+
height: "100vh",
39+
backgroundSize: "1200px auto",
40+
backgroundColor: "#101214",
41+
}}
42+
/>
43+
44+
{/* Content layer */}
45+
<div className="relative z-10 h-full w-full">{children}</div>
46+
</div>
47+
);
48+
}

apps/webapp/app/components/layout/AppLayout.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import { cn } from "~/utils/cn";
22

33
/** This container is used to surround the entire app, it correctly places the nav bar */
4-
export function AppContainer({ children }: { children: React.ReactNode }) {
5-
return <div className={cn("grid h-full w-full grid-rows-1 overflow-hidden")}>{children}</div>;
4+
export function AppContainer({
5+
children,
6+
className,
7+
}: {
8+
children: React.ReactNode;
9+
className?: string;
10+
}) {
11+
return (
12+
<div className={cn("grid h-full w-full grid-rows-1 overflow-hidden", className)}>
13+
{children}
14+
</div>
15+
);
616
}
717

818
export function MainBody({ children }: { children: React.ReactNode }) {

apps/webapp/app/components/primitives/Spinner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export function ButtonSpinner() {
6666
<Spinner
6767
className="size-3"
6868
color={{
69-
foreground: "rgba(0, 0, 0, 1)",
70-
background: "rgba(0, 0, 0, 0.25)",
69+
background: "rgba(255, 255, 255, 0.4)",
70+
foreground: "rgba(255, 255, 255)",
7171
}}
7272
/>
7373
);

apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export default function Page() {
143143
const emailFields = useFieldList(form.ref, emails);
144144

145145
return (
146-
<MainCenteredContainer>
146+
<MainCenteredContainer className="max-w-[26rem] rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
147147
<div>
148148
<FormTitle
149149
LeadingIcon={<UserPlusIcon className="size-6 text-indigo-500" />}
@@ -203,7 +203,7 @@ export default function Page() {
203203
</Button>
204204
}
205205
cancelButton={
206-
<LinkButton to={organizationTeamPath(organization)} variant={"tertiary/small"}>
206+
<LinkButton to={organizationTeamPath(organization)} variant={"secondary/small"}>
207207
Cancel
208208
</LinkButton>
209209
}

apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { Form, useActionData, useNavigation } from "@remix-run/react";
77
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
88
import invariant from "tiny-invariant";
99
import { z } from "zod";
10-
import { MainCenteredContainer } from "~/components/layout/AppLayout";
10+
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
11+
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
1112
import { Button, LinkButton } from "~/components/primitives/Buttons";
1213
import { Callout } from "~/components/primitives/Callout";
1314
import { Fieldset } from "~/components/primitives/Fieldset";
@@ -20,15 +21,14 @@ import { Label } from "~/components/primitives/Label";
2021
import { ButtonSpinner } from "~/components/primitives/Spinner";
2122
import { prisma } from "~/db.server";
2223
import { featuresForRequest } from "~/features.server";
23-
import { useFeatures } from "~/hooks/useFeatures";
2424
import { redirectWithSuccessMessage } from "~/models/message.server";
2525
import { createProject } from "~/models/project.server";
2626
import { requireUserId } from "~/services/session.server";
2727
import {
2828
OrganizationParamsSchema,
2929
organizationPath,
30-
v3ProjectPath,
3130
selectPlanPath,
31+
v3ProjectPath,
3232
} from "~/utils/pathBuilder";
3333

3434
export async function loader({ params, request }: LoaderFunctionArgs) {
@@ -138,57 +138,61 @@ export default function Page() {
138138
const isLoading = navigation.state === "submitting" || navigation.state === "loading";
139139

140140
return (
141-
<MainCenteredContainer>
142-
<div>
143-
<FormTitle
144-
LeadingIcon={<FolderIcon className="size-7 text-indigo-500" />}
145-
title="Create a new project"
146-
description={`This will create a new project in your "${organization.title}" organization.`}
147-
/>
148-
<Form method="post" {...form.props}>
149-
{message && (
150-
<Callout variant="success" className="mb-4">
151-
{message}
152-
</Callout>
153-
)}
154-
<Fieldset>
155-
<InputGroup>
156-
<Label htmlFor={projectName.id}>Project name</Label>
157-
<Input
158-
{...conform.input(projectName, { type: "text" })}
159-
placeholder="Your project name"
160-
icon={FolderIcon}
161-
autoFocus
162-
/>
163-
<FormError id={projectName.errorId}>{projectName.error}</FormError>
164-
</InputGroup>
165-
{canCreateV3Projects ? (
166-
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
167-
) : (
168-
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
169-
)}
170-
<FormButtons
171-
confirmButton={
172-
<Button
173-
type="submit"
174-
variant={"primary/small"}
175-
disabled={isLoading}
176-
TrailingIcon={isLoading ? ButtonSpinner : undefined}
177-
>
178-
{isLoading ? "Creating…" : "Create"}
179-
</Button>
180-
}
181-
cancelButton={
182-
organization.projectsCount > 0 ? (
183-
<LinkButton to={organizationPath(organization)} variant={"tertiary/small"}>
184-
Cancel
185-
</LinkButton>
186-
) : undefined
187-
}
141+
<AppContainer className="bg-charcoal-900">
142+
<BackgroundWrapper>
143+
<MainCenteredContainer className="max-w-[26rem] rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
144+
<div>
145+
<FormTitle
146+
LeadingIcon={<FolderIcon className="size-7 text-indigo-500" />}
147+
title="Create a new project"
148+
description={`This will create a new project in your "${organization.title}" organization.`}
188149
/>
189-
</Fieldset>
190-
</Form>
191-
</div>
192-
</MainCenteredContainer>
150+
<Form method="post" {...form.props}>
151+
{message && (
152+
<Callout variant="success" className="mb-4">
153+
{message}
154+
</Callout>
155+
)}
156+
<Fieldset>
157+
<InputGroup>
158+
<Label htmlFor={projectName.id}>Project name</Label>
159+
<Input
160+
{...conform.input(projectName, { type: "text" })}
161+
placeholder="Your project name"
162+
icon={FolderIcon}
163+
autoFocus
164+
/>
165+
<FormError id={projectName.errorId}>{projectName.error}</FormError>
166+
</InputGroup>
167+
{canCreateV3Projects ? (
168+
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
169+
) : (
170+
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
171+
)}
172+
<FormButtons
173+
confirmButton={
174+
<Button
175+
type="submit"
176+
variant={"primary/small"}
177+
disabled={isLoading}
178+
TrailingIcon={isLoading ? ButtonSpinner : undefined}
179+
>
180+
{isLoading ? "Creating…" : "Create"}
181+
</Button>
182+
}
183+
cancelButton={
184+
organization.projectsCount > 0 ? (
185+
<LinkButton to={organizationPath(organization)} variant={"secondary/small"}>
186+
Cancel
187+
</LinkButton>
188+
) : undefined
189+
}
190+
/>
191+
</Fieldset>
192+
</Form>
193+
</div>
194+
</MainCenteredContainer>
195+
</BackgroundWrapper>
196+
</AppContainer>
193197
);
194198
}

apps/webapp/app/routes/_app.orgs.$organizationSlug_.select-plan/route.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
22
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
3-
import { MainCenteredContainer } from "~/components/layout/AppLayout";
3+
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
4+
import { AppContainer } from "~/components/layout/AppLayout";
45
import { Header1 } from "~/components/primitives/Headers";
56
import { prisma } from "~/db.server";
67
import { featuresForRequest } from "~/features.server";
@@ -48,16 +49,22 @@ export default function ChoosePlanPage() {
4849
useTypedLoaderData<typeof loader>();
4950

5051
return (
51-
<MainCenteredContainer className="flex max-w-[80rem] flex-col items-center gap-8 p-3">
52-
<Header1 className="text-center">Subscribe for full access</Header1>
53-
<PricingPlans
54-
plans={plans}
55-
subscription={v3Subscription}
56-
organizationSlug={organizationSlug}
57-
hasPromotedPlan
58-
showGithubVerificationBadge
59-
periodEnd={periodEnd}
60-
/>
61-
</MainCenteredContainer>
52+
<AppContainer className="bg-charcoal-900">
53+
<BackgroundWrapper>
54+
<div className="mx-auto flex h-full w-full max-w-[80rem] flex-col items-center justify-center gap-8 p-3">
55+
<Header1 className="text-center">Subscribe for full access</Header1>
56+
<div className="w-full rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
57+
<PricingPlans
58+
plans={plans}
59+
subscription={v3Subscription}
60+
organizationSlug={organizationSlug}
61+
hasPromotedPlan
62+
showGithubVerificationBadge
63+
periodEnd={periodEnd}
64+
/>
65+
</div>
66+
</div>
67+
</BackgroundWrapper>
68+
</AppContainer>
6269
);
6370
}

0 commit comments

Comments
 (0)