import { useForm } from "@tanstack/react-form"; import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"; import { Link, Outlet, RouterProvider, createRootRoute, createRoute, createRouter, useNavigate } from "@tanstack/react-router"; import { StrictMode, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import { healthResponseSchema } from "@__TRESTLE_PROJECT_NAME__/contracts"; import { authClient } from "./auth-client"; import "./styles.css"; const apiOrigin = (import.meta.env.VITE_API_ORIGIN as string | undefined)?.replace(/\/$/u, "") ?? ""; const api = (path: string) => `${apiOrigin}${path}`; function Shell() { return
; } function Home() { const health = useQuery({ queryKey: ["health"], queryFn: async () => { const response = await fetch(api("/api/health")); return healthResponseSchema.parse(await response.json()); }}); return

TrestleJS

__TRESTLE_PROJECT_NAME__

React, TanStack, Better Auth, Hono, Drizzle, PostgreSQL, and Cloudflare—with the architectural boundaries already in place.

API: {health.isPending ? "checking" : health.data?.status ?? "unavailable"}
; } function Field(props: { label: string; type: string; value: string; onChange: (value: string) => void }) { return ; } function AuthForm({ mode }: { mode: "sign-in" | "sign-up" }) { const navigate = useNavigate(); const [error, setError] = useState(); const form = useForm({ defaultValues: { name: "", email: "", password: "" }, onSubmit: async ({ value }) => { setError(undefined); const result = mode === "sign-up" ? await authClient.signUp.email({ name: value.name, email: value.email, password: value.password }) : await authClient.signIn.email({ email: value.email, password: value.password }); if (result.error) { setError(result.error.message ?? "Authentication failed"); return; } await navigate({ to: mode === "sign-up" ? "/check-email" : "/dashboard" }); }, }); return

Account

{mode === "sign-up" ? "Create your account" : "Welcome back"}

{ event.preventDefault(); void form.handleSubmit(); }}> {mode === "sign-up" && {(field) => }} {(field) => } {(field) => } {error &&

{error}

} [state.canSubmit, state.isSubmitting] as const}>{([canSubmit, isSubmitting]) => } {mode === "sign-in" && Forgot your password?}
; } function CheckEmail() { const inbox = useQuery({ queryKey: ["captured-emails"], refetchInterval: 1000, queryFn: async () => { const response = await fetch(api("/api/dev/emails")); if (!response.ok) return { emails: [] as Array<{ id: string; to: string[]; subject: string; text: string }> }; return response.json() as Promise<{ emails: Array<{ id: string; to: string[]; subject: string; text: string }> }>; }}); return

Local email capture

Check your email

Verification, password-reset, and invitation messages appear here during local development.

{inbox.data?.emails.map((email) => { const link = email.text.match(/https?:\/\/\S+/u)?.[0]; return ; })}
; } function ForgotPassword() { const [email, setEmail] = useState(""); const [message, setMessage] = useState(); return

Account recovery

Reset your password

{ event.preventDefault(); const result = await authClient.requestPasswordReset({ email, redirectTo: `${window.location.origin}/reset-password` }); setMessage(result.error ? result.error.message ?? "Unable to request reset" : "If the account exists, a reset message has been sent."); }}> {message &&

{message}

}
; } function ResetPassword() { const [password, setPassword] = useState(""); const [message, setMessage] = useState(); const token = new URLSearchParams(window.location.search).get("token") ?? ""; return

Account recovery

Choose a new password

{ event.preventDefault(); const result = await authClient.resetPassword({ newPassword: password, token }); setMessage(result.error ? result.error.message ?? "Unable to reset password" : "Password updated. You can sign in now."); }}> {message &&

{message}

}
; } function AcceptInvitation() { const invitationId = new URLSearchParams(window.location.search).get("id") ?? ""; const [message, setMessage] = useState(); return

Organization invitation

Join the organization

{message &&

{message}

}
; } function Dashboard() { const navigate = useNavigate(); const { data: session, isPending } = authClient.useSession(); const [organizationName, setOrganizationName] = useState(""); const [message, setMessage] = useState(); const [inviteEmail, setInviteEmail] = useState(""); useEffect(() => { if (!isPending && !session) void navigate({ to: "/sign-in", replace: true }); }, [isPending, navigate, session]); if (isPending || !session) return

Loading your account…

; const createOrganization = async () => { setMessage(undefined); const slug = organizationName.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); const result = await authClient.organization.create({ name: organizationName, slug }); setMessage(result.error ? result.error.message ?? "Could not create organization" : `Created ${organizationName}`); if (!result.error) setOrganizationName(""); }; return

Protected route

Hello, {session.user.name}

Signed in as {session.user.email}

Create an organization

setOrganizationName(event.target.value)} />
{message &&

{message}

}

Invite a member

setInviteEmail(event.target.value)} />
; } function BillingSettings() { const subscription = useQuery({ queryKey: ["billing-subscription"], queryFn: async () => { const response = await fetch(api("/api/billing/subscription"), { credentials: "include" }); if (!response.ok) throw new Error("Select an organization before managing billing"); return response.json() as Promise<{ subscription: null | { plan: string; status: string; currentPeriodEnd?: string; cancelAtPeriodEnd: boolean; entitlements: string[] } }>; } }); const [message, setMessage] = useState(); const manage = async () => { const response = await fetch(api("/api/billing/portal"), { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify({ requestId: crypto.randomUUID() }) }); const result = await response.json() as { url?: string; error?: string }; if (result.url) window.location.assign(result.url); else setMessage(result.error ?? "Unable to open billing portal"); }; return

Settings

Billing

{subscription.isPending ?

Loading…

: subscription.error ?

{subscription.error.message}

: subscription.data?.subscription ?

Current plan: {subscription.data.subscription.plan}

Status: {subscription.data.subscription.status}

{subscription.data.subscription.cancelAtPeriodEnd ? "Cancels at period end" : "Renews automatically"}

:

No active subscription.

}{message &&

{message}

}
; } const rootRoute = createRootRoute({ component: Shell }); const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: "/", component: Home }); const signInRoute = createRoute({ getParentRoute: () => rootRoute, path: "/sign-in", component: () => }); const signUpRoute = createRoute({ getParentRoute: () => rootRoute, path: "/sign-up", component: () => }); const dashboardRoute = createRoute({ getParentRoute: () => rootRoute, path: "/dashboard", component: Dashboard }); const checkEmailRoute = createRoute({ getParentRoute: () => rootRoute, path: "/check-email", component: CheckEmail }); const forgotPasswordRoute = createRoute({ getParentRoute: () => rootRoute, path: "/forgot-password", component: ForgotPassword }); const resetPasswordRoute = createRoute({ getParentRoute: () => rootRoute, path: "/reset-password", component: ResetPassword }); const acceptInvitationRoute = createRoute({ getParentRoute: () => rootRoute, path: "/accept-invitation", component: AcceptInvitation }); const billingRoute = createRoute({ getParentRoute: () => rootRoute, path: "/settings/billing", component: BillingSettings }); const routeTree = rootRoute.addChildren([indexRoute, signInRoute, signUpRoute, dashboardRoute, checkEmailRoute, forgotPasswordRoute, resetPasswordRoute, acceptInvitationRoute, billingRoute]); const router = createRouter({ routeTree }); const queryClient = new QueryClient(); declare module "@tanstack/react-router" { interface Register { router: typeof router; } } const element = document.querySelector("#root"); if (!element) throw new Error("Missing #root element"); createRoot(element).render();