Skip to content
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions apps/webapp/app/components/BackgroundWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { type ReactNode } from "react";
import blurredDashboardBackgroundMenuTop from "~/assets/images/blurred-dashboard-background-menu-top.jpg";
import blurredDashboardBackgroundMenuBottom from "~/assets/images/blurred-dashboard-background-menu-bottom.jpg";
import blurredDashboardBackgroundTable from "~/assets/images/blurred-dashboard-background-table.jpg";

export function BackgroundWrapper({ children }: { children: ReactNode }) {
return (
<div className="relative h-full w-full overflow-hidden">
{/* Left menu top background - fixed width 260px, maintains aspect ratio */}
<div
className="absolute left-0 top-0 w-[260px] bg-contain bg-left-top bg-no-repeat"
style={{
backgroundImage: `url(${blurredDashboardBackgroundMenuTop})`,
aspectRatio: "auto",
height: "100vh",
backgroundSize: "260px auto",
}}
/>

{/* Left menu bottom background - fixed width 260px, maintains aspect ratio */}
<div
className="absolute bottom-0 left-0 w-[260px] bg-contain bg-left-bottom bg-no-repeat"
style={{
backgroundImage: `url(${blurredDashboardBackgroundMenuBottom})`,
aspectRatio: "auto",
height: "100vh",
backgroundSize: "260px auto",
}}
/>

{/* Right table background - fixed width 2000px, positioned next to menu */}
<div
className="absolute top-0 bg-left-top bg-no-repeat"
style={{
left: "260px",
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
width: "100%",
height: "100vh",
backgroundSize: "1200px auto",
backgroundColor: "#101214",
}}
/>

{/* Content layer */}
<div className="relative z-10 h-full w-full">{children}</div>
</div>
);
}
14 changes: 12 additions & 2 deletions apps/webapp/app/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { cn } from "~/utils/cn";

/** This container is used to surround the entire app, it correctly places the nav bar */
export function AppContainer({ children }: { children: React.ReactNode }) {
return <div className={cn("grid h-full w-full grid-rows-1 overflow-hidden")}>{children}</div>;
export function AppContainer({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("grid h-full w-full grid-rows-1 overflow-hidden", className)}>
{children}
</div>
);
}

export function MainBody({ children }: { children: React.ReactNode }) {
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/primitives/Spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export function ButtonSpinner() {
<Spinner
className="size-3"
color={{
foreground: "rgba(0, 0, 0, 1)",
background: "rgba(0, 0, 0, 0.25)",
background: "rgba(255, 255, 255, 0.4)",
foreground: "rgba(255, 255, 255)",
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default function Page() {
const emailFields = useFieldList(form.ref, emails);

return (
<MainCenteredContainer>
<MainCenteredContainer className="max-w-[26rem] rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
<div>
<FormTitle
LeadingIcon={<UserPlusIcon className="size-6 text-indigo-500" />}
Expand Down Expand Up @@ -203,7 +203,7 @@ export default function Page() {
</Button>
}
cancelButton={
<LinkButton to={organizationTeamPath(organization)} variant={"tertiary/small"}>
<LinkButton to={organizationTeamPath(organization)} variant={"secondary/small"}>
Cancel
</LinkButton>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { Form, useActionData, useNavigation } from "@remix-run/react";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { z } from "zod";
import { MainCenteredContainer } from "~/components/layout/AppLayout";
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Fieldset } from "~/components/primitives/Fieldset";
Expand All @@ -20,15 +21,14 @@ import { Label } from "~/components/primitives/Label";
import { ButtonSpinner } from "~/components/primitives/Spinner";
import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
import { useFeatures } from "~/hooks/useFeatures";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { createProject } from "~/models/project.server";
import { requireUserId } from "~/services/session.server";
import {
OrganizationParamsSchema,
organizationPath,
v3ProjectPath,
selectPlanPath,
v3ProjectPath,
} from "~/utils/pathBuilder";

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

return (
<MainCenteredContainer>
<div>
<FormTitle
LeadingIcon={<FolderIcon className="size-7 text-indigo-500" />}
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization.`}
/>
<Form method="post" {...form.props}>
{message && (
<Callout variant="success" className="mb-4">
{message}
</Callout>
)}
<Fieldset>
<InputGroup>
<Label htmlFor={projectName.id}>Project name</Label>
<Input
{...conform.input(projectName, { type: "text" })}
placeholder="Your project name"
icon={FolderIcon}
autoFocus
/>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{canCreateV3Projects ? (
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
)}
<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
disabled={isLoading}
TrailingIcon={isLoading ? ButtonSpinner : undefined}
>
{isLoading ? "Creating…" : "Create"}
</Button>
}
cancelButton={
organization.projectsCount > 0 ? (
<LinkButton to={organizationPath(organization)} variant={"tertiary/small"}>
Cancel
</LinkButton>
) : undefined
}
<AppContainer className="bg-charcoal-900">
<BackgroundWrapper>
<MainCenteredContainer className="max-w-[26rem] rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
<div>
<FormTitle
LeadingIcon={<FolderIcon className="size-7 text-indigo-500" />}
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization.`}
/>
</Fieldset>
</Form>
</div>
</MainCenteredContainer>
<Form method="post" {...form.props}>
{message && (
<Callout variant="success" className="mb-4">
{message}
</Callout>
)}
<Fieldset>
<InputGroup>
<Label htmlFor={projectName.id}>Project name</Label>
<Input
{...conform.input(projectName, { type: "text" })}
placeholder="Your project name"
icon={FolderIcon}
autoFocus
/>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{canCreateV3Projects ? (
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
)}
<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
disabled={isLoading}
TrailingIcon={isLoading ? ButtonSpinner : undefined}
>
{isLoading ? "Creating…" : "Create"}
</Button>
}
cancelButton={
organization.projectsCount > 0 ? (
<LinkButton to={organizationPath(organization)} variant={"secondary/small"}>
Cancel
</LinkButton>
) : undefined
}
/>
</Fieldset>
</Form>
</div>
</MainCenteredContainer>
</BackgroundWrapper>
</AppContainer>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { MainCenteredContainer } from "~/components/layout/AppLayout";
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
import { AppContainer } from "~/components/layout/AppLayout";
import { Header1 } from "~/components/primitives/Headers";
import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
Expand Down Expand Up @@ -48,16 +49,22 @@ export default function ChoosePlanPage() {
useTypedLoaderData<typeof loader>();

return (
<MainCenteredContainer className="flex max-w-[80rem] flex-col items-center gap-8 p-3">
<Header1 className="text-center">Subscribe for full access</Header1>
<PricingPlans
plans={plans}
subscription={v3Subscription}
organizationSlug={organizationSlug}
hasPromotedPlan
showGithubVerificationBadge
periodEnd={periodEnd}
/>
</MainCenteredContainer>
<AppContainer className="bg-charcoal-900">
<BackgroundWrapper>
<div className="mx-auto flex h-full w-full max-w-[80rem] flex-col items-center justify-center gap-8 p-3">
<Header1 className="text-center">Subscribe for full access</Header1>
<div className="w-full rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg">
<PricingPlans
plans={plans}
subscription={v3Subscription}
organizationSlug={organizationSlug}
hasPromotedPlan
showGithubVerificationBadge
periodEnd={periodEnd}
/>
</div>
</div>
</BackgroundWrapper>
</AppContainer>
);
}
Loading
Loading