Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/components/SidebarStageBackdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function SidebarStageBackdrop({ variant }: { variant: SidebarStageBackdro
);
}

export function StageBackdropArt({ variant }: { variant: SidebarStageBackdropVariant }) {
function StageBackdropArt({ variant }: { variant: SidebarStageBackdropVariant }) {
return variant === "nightly" ? <NightlySkyArt /> : <DevBlueprintArt />;
}

Expand Down
37 changes: 0 additions & 37 deletions apps/web/src/components/auth/AuthSurfaceShell.tsx

This file was deleted.

22 changes: 8 additions & 14 deletions apps/web/src/components/auth/ConnectAgentSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { RadioGroup } from "../ui/radio-group";
import { Spinner } from "../ui/spinner";
import { AuthSurfaceShell } from "./AuthSurfaceShell";
import { StandalonePage, StandalonePageHeader } from "../ui/standalone-page";

export const accessConfig: Record<
AuthMcpClientAccess,
Expand Down Expand Up @@ -138,24 +138,24 @@ export function ConnectAgentSurface() {

if (loaded.status === "loading") {
return (
<AuthSurfaceShell>
<StandalonePage>
<ConnectAgentHeading
title="Checking the sign-in request"
description="One moment while this environment verifies the agent's request."
/>
<Spinner className="mt-6" size="lg" tone="muted" />
</AuthSurfaceShell>
</StandalonePage>
);
}

if (loaded.status === "invalid") {
return (
<AuthSurfaceShell>
<StandalonePage>
<ConnectAgentHeading title="This sign-in cannot continue" description={loaded.message} />
<p className="mt-4 text-sm text-muted-foreground">
Close this page and start the sign-in again from your agent.
</p>
</AuthSurfaceShell>
</StandalonePage>
);
}

Expand All @@ -164,7 +164,7 @@ export function ConnectAgentSurface() {
const canApprove = pending === null && (oneClick || pairingCode.trim().length > 0);

return (
<AuthSurfaceShell>
<StandalonePage>
<ConnectAgentHeading
title={`Connect ${details.clientName}`}
description={
Expand Down Expand Up @@ -259,7 +259,7 @@ export function ConnectAgentSurface() {
</Button>
</div>
</form>
</AuthSurfaceShell>
</StandalonePage>
);
}

Expand All @@ -270,13 +270,7 @@ function ConnectAgentHeading({
readonly title: string;
readonly description: ReactNode;
}) {
return (
<>
<p className="text-3xs font-semibold tracking-widest text-primary uppercase">Agent sign-in</p>
<h1 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">{title}</h1>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{description}</p>
</>
);
return <StandalonePageHeader context="Agent sign-in" title={title} description={description} />;
}

function AccessOption({
Expand Down
10 changes: 3 additions & 7 deletions apps/web/src/components/auth/PairingRouteSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { AuthSessionState } from "@t3tools/contracts";
import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
import React, { startTransition, useEffect, useEffectEvent, useRef, useState } from "react";

import { APP_DISPLAY_NAME } from "../../branding";
import { connectPairing } from "../../connection/onboarding";
import {
peekPairingTokenFromUrl,
Expand All @@ -18,9 +17,8 @@ import { useAtomCommand } from "../../state/use-atom-command";

export function PairingPendingSurface() {
return (
<StandalonePage tone="pairing">
<StandalonePage>
<StandalonePageHeader
eyebrow={APP_DISPLAY_NAME}
title="Pairing with this environment"
description="Validating the pairing link and preparing your session."
/>
Expand Down Expand Up @@ -84,9 +82,8 @@ export function PairingRouteSurface({
}, [autoPairToken]);

return (
<StandalonePage tone="pairing">
<StandalonePage>
<StandalonePageHeader
eyebrow={APP_DISPLAY_NAME}
title="Pair with this environment"
description={describeAuthGate(auth.bootstrapMethods)}
/>
Expand Down Expand Up @@ -208,9 +205,8 @@ export function HostedPairingRouteSurface() {
}, []);

return (
<StandalonePage tone="pairing">
<StandalonePage>
<StandalonePageHeader
eyebrow={APP_DISPLAY_NAME}
title={
status === "paired"
? "Backend paired"
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/components/clerk/clerkAppearance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import type { ClerkProviderProps } from "@clerk/react";

// Scoped to auth flows: UserProfile reuses formButtonPrimary for destructive confirmations.
const authFlowElements = {
formButtonPrimary:
"border-primary bg-primary bg-none text-primary-foreground shadow-xs shadow-primary/24 hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
};

export const clerkAppearance = {
// index.css orders this layer below components and utilities so the element classes below win.
cssLayerName: "clerk",
options: { shimmer: false },
variables: {
borderRadius: "0.5rem",
// Clerk reuses its primary color for filled buttons and bare links.
colorPrimary: "var(--update-foreground)",
colorPrimaryForeground: "var(--card)",
Expand All @@ -19,10 +29,20 @@ export const clerkAppearance = {
colorRing: "var(--ring)",
},
elements: {
modalBackdrop: "dialog-backdrop",
cardBox: "dialog-glass rounded-2xl border",
card: "border-0 bg-transparent shadow-none",
footer: "bg-transparent bg-none",
header: "items-start text-start",
headerTitle: "text-xl font-semibold",
headerSubtitle: "text-sm text-muted-foreground",
buttonArrowIcon: "hidden",
formFieldErrorText: { color: "var(--error-foreground)" },
formFieldWarningText: { color: "var(--warning-foreground)" },
formFieldSuccessText: { color: "var(--success-foreground)" },
otpCodeFieldErrorText: { color: "var(--error-foreground)" },
otpCodeFieldSuccessText: { color: "var(--success-foreground)" },
},
signIn: { elements: authFlowElements },
signUp: { elements: authFlowElements },
} satisfies NonNullable<ClerkProviderProps["appearance"]>;
38 changes: 9 additions & 29 deletions apps/web/src/components/cloud/ConnectCliAuthSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,12 @@ import {
connectCliSignInRedirectUrl,
} from "../../cloud/connectCliAuth";
import { isElectron } from "../../env";
import { AuthSurfaceShell } from "../auth/AuthSurfaceShell";
import { StandalonePage, StandalonePageHeader } from "../ui/standalone-page";
import { resolveClerkSignInProps } from "../clerk/authRedirect";
import { Button } from "../ui/button";

function ConnectCliAuthMessage({
eyebrow,
title,
description,
}: {
readonly eyebrow?: string;
readonly title: string;
readonly description: string;
}) {
return (
<>
{eyebrow ? (
<p className="text-3xs font-semibold tracking-widest text-primary uppercase">{eyebrow}</p>
) : null}
<h1 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">{title}</h1>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{description}</p>
</>
);
}

const invalidLinkMessage = {
eyebrow: "Authorization request",
context: "Authorization request",
title: "This connect link is incomplete",
description:
"The link is missing its authorization request. Re-run `t3 connect` in your terminal and open the freshly printed URL.",
Expand Down Expand Up @@ -79,16 +59,16 @@ export function ConnectCliAuthorizeSurface() {

if (!request) {
return (
<AuthSurfaceShell>
<ConnectCliAuthMessage {...invalidLinkMessage} />
</AuthSurfaceShell>
<StandalonePage>
<StandalonePageHeader {...invalidLinkMessage} />
</StandalonePage>
);
}

return (
<AuthSurfaceShell>
<ConnectCliAuthMessage
eyebrow="Browser authorization"
<StandalonePage>
<StandalonePageHeader
context="Browser authorization"
title="Connecting your terminal"
description={
isSignedIn
Expand All @@ -103,6 +83,6 @@ export function ConnectCliAuthorizeSurface() {
</Button>
</div>
) : null}
</AuthSurfaceShell>
</StandalonePage>
);
}
13 changes: 4 additions & 9 deletions apps/web/src/components/promptLinks/HostedPromptLinkLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useLocation } from "@tanstack/react-router";
import * as Schema from "effect/Schema";
import { type ReactNode, useEffect, useRef, useState } from "react";

import { APP_DISPLAY_NAME, DESKTOP_DOWNLOAD_URL } from "~/branding";
import { DESKTOP_DOWNLOAD_URL } from "~/branding";
import { useLocalStorage } from "~/hooks/useLocalStorage";
import { isHostedStaticApp } from "~/hostedPairing";
import { useEnvironments } from "~/state/environments";
Expand Down Expand Up @@ -112,21 +112,16 @@ function HostedPromptLinkLanding({

if (!summary.ok) {
return (
<StandalonePage tone="error">
<StandalonePageHeader
eyebrow={APP_DISPLAY_NAME}
title="Could not open the link"
description={summary.error}
/>
<StandalonePage>
<StandalonePageHeader title="Could not open the link" description={summary.error} />
</StandalonePage>
);
}

const { prompt, details } = summary.value;
return (
<StandalonePage tone="brand">
<StandalonePage>
<StandalonePageHeader
eyebrow={APP_DISPLAY_NAME}
title="Open this prompt"
description="It opens as a draft. Nothing runs until you press Send."
/>
Expand Down
62 changes: 15 additions & 47 deletions apps/web/src/components/ui/standalone-page.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,36 @@
import type { ReactNode } from "react";

const backdropClassNames = {
pairing:
"absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-emerald-500)_14%,transparent),transparent)]",
error:
"absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-red-500)_16%,transparent),transparent)]",
brand:
"absolute inset-x-0 top-0 h-72 bg-[radial-gradient(48rem_20rem_at_top,color-mix(in_srgb,var(--color-blue-500)_12%,transparent),transparent)]",
};
import { T3Wordmark } from "../T3Wordmark";

export function StandalonePage({
tone,
masthead,
children,
}: {
readonly tone: keyof typeof backdropClassNames;
readonly masthead?: ReactNode;
readonly children: ReactNode;
}) {
export function StandalonePage({ children }: { readonly children: ReactNode }) {
return (
// The page owns scrolling because root cannot scroll; safe centering keeps oversized cards top-aligned.
<div className="relative h-full overflow-hidden bg-background text-foreground">
<div className="pointer-events-none absolute inset-0 opacity-80" aria-hidden>
<div className={backdropClassNames[tone]} />
{tone === "pairing" ? (
<div className="absolute inset-y-0 left-0 w-72 bg-[radial-gradient(28rem_18rem_at_left,color-mix(in_srgb,var(--color-sky-500)_10%,transparent),transparent)]" />
) : null}
<div
className={
tone === "brand"
? "absolute inset-0 bg-[linear-gradient(145deg,color-mix(in_srgb,var(--background)_94%,var(--color-black))_0%,var(--background)_62%)]"
: "absolute inset-0 bg-[linear-gradient(145deg,color-mix(in_srgb,var(--background)_90%,var(--color-black))_0%,var(--background)_55%)]"
}
/>
</div>

<div className="relative flex h-full items-center-safe justify-center overflow-y-auto scrollbar-gutter-both px-4 py-10 sm:px-6">
<section
className={`relative w-full max-w-xl rounded-2xl border border-border/80 shadow-2xl shadow-black/20 backdrop-blur-md ${tone === "brand" ? "overflow-hidden bg-card/94" : "bg-card/90"}`}
>
{masthead}
<div className="p-6 sm:p-8">{children}</div>
</section>
</div>
<div className="flex h-full items-center-safe justify-center overflow-y-auto scrollbar-gutter-both bg-background px-4 py-10 text-foreground sm:px-6">
<section className="dialog-glass w-full max-w-xl rounded-2xl border p-6">{children}</section>
</div>
);
}

export function StandalonePageHeader({
eyebrow,
context,
title,
description,
}: {
readonly eyebrow: ReactNode;
readonly context?: ReactNode;
readonly title: ReactNode;
readonly description: ReactNode;
}) {
return (
<>
<p className="text-[11px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
{eyebrow}
</p>
<h1 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{title}</h1>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{description}</p>
<div className="flex items-baseline justify-between gap-3 text-muted-foreground">
<span className="flex items-baseline gap-1.5" role="img" aria-label="T3 Code">
<T3Wordmark className="h-3 w-auto shrink-0 text-foreground" aria-hidden />
<span className="text-lg font-medium tracking-tight">Code</span>
</span>
{context ? <span className="text-sm">{context}</span> : null}
</div>
<h1 className="mt-6 text-xl leading-tight font-semibold">{title}</h1>
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
</>
);
}
2 changes: 2 additions & 0 deletions apps/web/src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* Clerk injects its component styles into the clerk layer (clerkAppearance.cssLayerName). */
@layer theme, base, clerk, components, utilities;
@import "tailwindcss";

@custom-variant dark (&:is(.dark, .dark *));
Expand Down
Loading
Loading