Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
236 changes: 195 additions & 41 deletions docs-mintlify/guides/getting-started/setup.mdx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs-mintlify/snippets/home-prompt-island.jsx

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions examples/tanstack-start-demo/src/components/auth-demo-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { UserAvatar, useStackApp } from "@stackframe/tanstack-start";
import type { CurrentUser } from "@stackframe/tanstack-start";

type AuthDemoCardProps = {
title: string,
eyebrow: string,
description: string,
user: CurrentUser | null,
code: string,
};

export function AuthDemoCard(props: AuthDemoCardProps) {
const app = useStackApp();
const userLabel = props.user?.displayName ?? props.user?.primaryEmail ?? props.user?.id;

return (
<section className="grid w-full gap-6">
<div className="rounded-lg border border-zinc-200 bg-white p-8 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<p className="mb-2 text-sm font-medium text-zinc-500 dark:text-zinc-400">{props.eyebrow}</p>
<h1 className="text-3xl font-semibold tracking-tight">{props.title}</h1>
<p className="mt-4 max-w-2xl text-zinc-600 dark:text-zinc-300">{props.description}</p>

<div className="mt-8 rounded-lg border border-zinc-200 p-5 dark:border-zinc-800">
{props.user ? (
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
<UserAvatar user={props.user} size={64} />
<div className="min-w-0">
<p className="text-sm text-zinc-500 dark:text-zinc-400">Resolved Stack Auth user</p>
<p className="truncate text-xl font-semibold">{userLabel}</p>
<p className="mt-1 break-all font-mono text-sm text-zinc-500 dark:text-zinc-400">{props.user.id}</p>
</div>
</div>
) : (
<div>
<p className="text-lg font-semibold">No signed-in user</p>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
This route rendered the signed-out branch from Stack Auth.
</p>
<div className="mt-4 flex flex-wrap gap-3">
<button className="rounded-md bg-zinc-950 px-4 py-2 text-sm font-medium text-white transition-colors hover:transition-none dark:bg-white dark:text-zinc-950" onClick={() => runAsynchronouslyWithAlert(app.redirectToSignIn())}>
Sign in
</button>
<button className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 hover:transition-none dark:border-zinc-700 dark:hover:bg-zinc-800" onClick={() => runAsynchronouslyWithAlert(app.redirectToSignUp())}>
Sign up
</button>
</div>
</div>
)}
</div>
</div>

<div className="rounded-lg border border-zinc-200 bg-zinc-950 p-5 text-zinc-100 shadow-sm dark:border-zinc-800">
<div className="mb-3 flex items-center justify-between gap-3">
<h2 className="text-sm font-semibold uppercase text-zinc-400">Usage snippet</h2>
</div>
<pre className="overflow-x-auto text-sm leading-6"><code>{props.code}</code></pre>
</div>
</section>
);
}
18 changes: 7 additions & 11 deletions examples/tanstack-start-demo/src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Link } from "@tanstack/react-router";
import { UserButton } from "@stackframe/tanstack-start";
import { useEffect, useState } from "react";

export function Header() {
return (
Expand All @@ -11,23 +10,20 @@ export function Header() {
<Link to="/" className="font-semibold tracking-tight">
Stack TanStack Demo
</Link>
<Link to="/ssr" className="text-sm text-zinc-600 hover:text-zinc-950 hover:transition-none dark:text-zinc-300 dark:hover:text-white">
SSR
</Link>
<Link to="/client" className="text-sm text-zinc-600 hover:text-zinc-950 hover:transition-none dark:text-zinc-300 dark:hover:text-white">
Client
</Link>
<Link to="/protected" className="text-sm text-zinc-600 hover:text-zinc-950 hover:transition-none dark:text-zinc-300 dark:hover:text-white">
Protected
</Link>
</nav>
<ClientMountedUserButton />
<UserButton />
</div>
</header>
<div className="h-14" />
</>
);
}

function ClientMountedUserButton() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);

return isMounted ? <UserButton /> : <div className="h-9 w-9" />;
}
42 changes: 39 additions & 3 deletions examples/tanstack-start-demo/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as SsrRouteImport } from './routes/ssr'
import { Route as ProtectedRouteImport } from './routes/protected'
import { Route as ClientRouteImport } from './routes/client'
import { Route as IndexRouteImport } from './routes/index'
import { Route as HandlerSplatRouteImport } from './routes/handler/$'

const SsrRoute = SsrRouteImport.update({
id: '/ssr',
path: '/ssr',
getParentRoute: () => rootRouteImport,
} as any)
const ProtectedRoute = ProtectedRouteImport.update({
id: '/protected',
path: '/protected',
getParentRoute: () => rootRouteImport,
} as any)
const ClientRoute = ClientRouteImport.update({
id: '/client',
path: '/client',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
Expand All @@ -31,43 +43,65 @@ const HandlerSplatRoute = HandlerSplatRouteImport.update({

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/client': typeof ClientRoute
'/protected': typeof ProtectedRoute
'/ssr': typeof SsrRoute
'/handler/$': typeof HandlerSplatRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/client': typeof ClientRoute
'/protected': typeof ProtectedRoute
'/ssr': typeof SsrRoute
'/handler/$': typeof HandlerSplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/client': typeof ClientRoute
'/protected': typeof ProtectedRoute
'/ssr': typeof SsrRoute
'/handler/$': typeof HandlerSplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/protected' | '/handler/$'
fullPaths: '/' | '/client' | '/protected' | '/ssr' | '/handler/$'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/protected' | '/handler/$'
id: '__root__' | '/' | '/protected' | '/handler/$'
to: '/' | '/client' | '/protected' | '/ssr' | '/handler/$'
id: '__root__' | '/' | '/client' | '/protected' | '/ssr' | '/handler/$'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ClientRoute: typeof ClientRoute
ProtectedRoute: typeof ProtectedRoute
SsrRoute: typeof SsrRoute
HandlerSplatRoute: typeof HandlerSplatRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/ssr': {
id: '/ssr'
path: '/ssr'
fullPath: '/ssr'
preLoaderRoute: typeof SsrRouteImport
parentRoute: typeof rootRouteImport
}
'/protected': {
id: '/protected'
path: '/protected'
fullPath: '/protected'
preLoaderRoute: typeof ProtectedRouteImport
parentRoute: typeof rootRouteImport
}
'/client': {
id: '/client'
path: '/client'
fullPath: '/client'
preLoaderRoute: typeof ClientRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
Expand All @@ -87,7 +121,9 @@ declare module '@tanstack/react-router' {

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ClientRoute: ClientRoute,
ProtectedRoute: ProtectedRoute,
SsrRoute: SsrRoute,
HandlerSplatRoute: HandlerSplatRoute,
}
export const routeTree = rootRouteImport
Expand Down
51 changes: 43 additions & 8 deletions examples/tanstack-start-demo/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,50 @@ function RootComponent() {
return (
<StackProvider app={stackApp}>
<StackTheme>
<div className="min-h-screen bg-zinc-100 text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50">
<Header />
<main className="mx-auto flex min-h-[calc(100vh-3.5rem)] max-w-5xl px-4 py-8">
<Suspense fallback={null}>
<Outlet />
</Suspense>
</main>
</div>
<AppShell>
<Suspense fallback={<RouteLoadingState />}>
<Outlet />
</Suspense>
</AppShell>
</StackTheme>
</StackProvider>
);
}

function AppShell({ children }: { children: ReactNode }) {
return (
<div className="min-h-screen bg-zinc-100 text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50">
<Header />
<main className="mx-auto flex min-h-[calc(100vh-3.5rem)] max-w-5xl px-4 py-8">
{children}
</main>
</div>
);
}

function RouteLoadingState() {
return (
<section className="grid w-full place-items-center">
<div className="w-full max-w-2xl rounded-lg border border-zinc-200 bg-white p-8 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<div className="flex flex-col gap-5 sm:flex-row sm:items-center">
<div className="h-24 w-24 shrink-0 rounded-full bg-zinc-200 dark:bg-zinc-800" />
<div className="min-w-0 flex-1">
<div className="h-4 w-24 rounded bg-zinc-200 dark:bg-zinc-800" />
<div className="mt-3 h-9 w-full max-w-md rounded bg-zinc-200 dark:bg-zinc-800" />
</div>
</div>
<div className="mt-8 grid gap-3 text-sm">
<div className="grid gap-1 sm:grid-cols-[8rem_1fr]">
<div className="h-5 w-16 rounded bg-zinc-200 dark:bg-zinc-800" />
<div className="h-5 w-full rounded bg-zinc-200 dark:bg-zinc-800" />
</div>
<div className="grid gap-1 sm:grid-cols-[8rem_1fr]">
<div className="h-5 w-20 rounded bg-zinc-200 dark:bg-zinc-800" />
<div className="h-5 w-12 rounded bg-zinc-200 dark:bg-zinc-800" />
</div>
</div>
<div className="mt-8 h-9 w-20 rounded-md bg-zinc-200 dark:bg-zinc-800" />
</div>
</section>
);
}
38 changes: 38 additions & 0 deletions examples/tanstack-start-demo/src/routes/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useUser } from "@stackframe/tanstack-start";
import { createFileRoute } from "@tanstack/react-router";
import { AuthDemoCard } from "~/components/auth-demo-card";

export const Route = createFileRoute("/client")({
ssr: false,
component: ClientAuthDemoPage,
});

const clientSnippet = `import { useUser } from "@stackframe/tanstack-start";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/client")({
ssr: false,
component: ClientAuthDemoPage,
});

function ClientAuthDemoPage() {
// This route is skipped during SSR. The user is resolved
// in the browser from the client token store.
const user = useUser({ includeRestricted: true });

return <div>{user?.displayName ?? "Signed out"}</div>;
}`;

function ClientAuthDemoPage() {
const user = useUser({ includeRestricted: true });

return (
<AuthDemoCard
eyebrow="Client-only route"
title="Stack Auth user fetched in the browser"
description="This route opts out of SSR with ssr: false. The UI is rendered on the client, and Stack Auth resolves the current user from the browser token store."
user={user}
code={clientSnippet}
/>
);
}
16 changes: 15 additions & 1 deletion examples/tanstack-start-demo/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { UserAvatar, useStackApp, useUser } from "@stackframe/tanstack-start";
import { createFileRoute } from "@tanstack/react-router";
import { Link, createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
component: HomePage,
Expand All @@ -19,6 +19,14 @@ function HomePage() {
<p className="mt-4 text-zinc-600 dark:text-zinc-300">
This example uses <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-800">@stackframe/tanstack-start</code> with file-based routes and Stack Auth handler pages.
</p>
<div className="mt-6 flex flex-wrap gap-3">
<Link to="/ssr" className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 hover:transition-none dark:border-zinc-700 dark:hover:bg-zinc-800">
SSR demo
</Link>
<Link to="/client" className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 hover:transition-none dark:border-zinc-700 dark:hover:bg-zinc-800">
Client demo
</Link>
</div>
<div className="mt-6 flex flex-wrap gap-3">
<button className="rounded-md bg-zinc-950 px-4 py-2 text-sm font-medium text-white transition-colors hover:transition-none dark:bg-white dark:text-zinc-950" onClick={() => runAsynchronouslyWithAlert(app.redirectToSignIn())}>
Sign in
Expand Down Expand Up @@ -66,6 +74,12 @@ function HomePage() {
</dl>

<div className="mt-8 flex flex-wrap gap-3">
<Link to="/ssr" className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 hover:transition-none dark:border-zinc-700 dark:hover:bg-zinc-800">
SSR demo
</Link>
<Link to="/client" className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 hover:transition-none dark:border-zinc-700 dark:hover:bg-zinc-800">
Client demo
</Link>
<button className="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700 hover:transition-none" onClick={() => runAsynchronouslyWithAlert(app.redirectToSignOut())}>
Sign out
</button>
Expand Down
36 changes: 36 additions & 0 deletions examples/tanstack-start-demo/src/routes/ssr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useUser } from "@stackframe/tanstack-start";
import { createFileRoute } from "@tanstack/react-router";
import { AuthDemoCard } from "~/components/auth-demo-card";

export const Route = createFileRoute("/ssr")({
component: SsrAuthDemoPage,
});

const ssrSnippet = `import { useUser } from "@stackframe/tanstack-start";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/ssr")({
component: SsrAuthDemoPage,
});

function SsrAuthDemoPage() {
// This hook can suspend during SSR while Stack Auth reads
// the TanStack Start request cookies and fetches the user.
const user = useUser({ includeRestricted: true });

return <div>{user?.displayName ?? "Signed out"}</div>;
}`;

function SsrAuthDemoPage() {
const user = useUser({ includeRestricted: true });

return (
<AuthDemoCard
eyebrow="SSR route"
title="Stack Auth user fetched during server render"
description="This route keeps SSR enabled. The Stack Auth hook can resolve the current user from TanStack Start request cookies while React renders the route on the server."
user={user}
code={ssrSnippet}
/>
);
}
Loading
Loading