better-ts-stack 1.0.8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/builder/configGenerator.d.ts.map +1 -1
  2. package/dist/builder/configGenerator.js +4 -1
  3. package/dist/builder/configGenerator.js.map +1 -1
  4. package/dist/builder/moduleSelector.d.ts.map +1 -1
  5. package/dist/builder/moduleSelector.js +1 -1
  6. package/dist/builder/moduleSelector.js.map +1 -1
  7. package/dist/builder/templateContext.d.ts +1 -1
  8. package/dist/builder/templateContext.d.ts.map +1 -1
  9. package/dist/builder/templateContext.js +1 -1
  10. package/dist/builder/templateContext.js.map +1 -1
  11. package/dist/prompts/stack.d.ts.map +1 -1
  12. package/dist/prompts/stack.js +16 -11
  13. package/dist/prompts/stack.js.map +1 -1
  14. package/dist/types/index.d.ts +8 -3
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/index.js +6 -1
  17. package/dist/types/index.js.map +1 -1
  18. package/package.json +1 -1
  19. package/templates/frontend/tanstack/components.json +18 -0
  20. package/templates/frontend/tanstack/config.json +49 -0
  21. package/templates/frontend/tanstack/eslint.config.mjs +23 -0
  22. package/templates/frontend/tanstack/gitignore +38 -0
  23. package/templates/frontend/tanstack/public/favicon.svg +7 -0
  24. package/templates/frontend/tanstack/src/components/ui/button.tsx +51 -0
  25. package/templates/frontend/tanstack/src/components/ui/card.tsx +60 -0
  26. package/templates/frontend/tanstack/src/components/ui/field.tsx +67 -0
  27. package/templates/frontend/tanstack/src/components/ui/input.tsx +18 -0
  28. package/templates/frontend/tanstack/src/lib/utils.ts +6 -0
  29. package/templates/frontend/tanstack/src/routeTree.gen.ts +68 -0
  30. package/templates/frontend/tanstack/src/router.tsx +32 -0
  31. package/templates/frontend/tanstack/src/routes/__root.tsx +70 -0
  32. package/templates/frontend/tanstack/src/routes/index.tsx.hbs +102 -0
  33. package/templates/frontend/tanstack/src/styles/app.css +85 -0
  34. package/templates/frontend/tanstack/tsconfig.json +22 -0
  35. package/templates/frontend/tanstack/vite.config.ts +14 -0
  36. package/templates/modules/auth/tanstack/config.json +33 -0
  37. package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +135 -0
  38. package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +49 -0
  39. package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +153 -0
  40. package/templates/modules/auth/tanstack/src/lib/auth-client.ts +3 -0
  41. package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +12 -0
  42. package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +39 -0
  43. package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +38 -0
  44. package/templates/modules/auth/tanstack/src/routeTree.gen.ts +140 -0
  45. package/templates/modules/auth/tanstack/src/routes/api/auth/$.ts +16 -0
  46. package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +111 -0
  47. package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +37 -0
  48. package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +37 -0
  49. package/templates/modules/docker/Dockerfile.hbs +125 -80
  50. package/templates/modules/drizzle/tanstack/config.json +26 -0
  51. package/templates/modules/drizzle/tanstack/drizzle.config.ts +13 -0
  52. package/templates/modules/drizzle/tanstack/src/lib/db.ts +13 -0
  53. package/templates/modules/drizzle/tanstack/src/lib/schema.ts.hbs +84 -0
  54. package/templates/modules/prisma/express/config.json +1 -0
  55. package/templates/modules/prisma/nextjs/config.json +1 -0
  56. package/templates/modules/prisma/tanstack/config.json +29 -0
  57. package/templates/modules/prisma/tanstack/prisma/schema.prisma.hbs +91 -0
  58. package/templates/modules/prisma/tanstack/prisma.config.ts +12 -0
  59. package/templates/modules/prisma/tanstack/src/lib/prisma.ts +18 -0
@@ -0,0 +1,153 @@
1
+ "use client";
2
+
3
+ import { Link, useNavigate } from "@tanstack/react-router";
4
+ import { useState, useTransition } from "react";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { LoaderCircle } from "lucide-react";
7
+ import { useForm } from "react-hook-form";
8
+
9
+ import { authClient } from "~/lib/auth-client";
10
+ import {
11
+ getAuthErrorMessage,
12
+ signUpSchema,
13
+ type SignUpValues,
14
+ } from "~/lib/auth-schema";
15
+ import { Button } from "~/components/ui/button";
16
+ import {
17
+ Card,
18
+ CardContent,
19
+ CardDescription,
20
+ CardFooter,
21
+ CardHeader,
22
+ CardTitle,
23
+ } from "~/components/ui/card";
24
+ import {
25
+ Field,
26
+ FieldDescription,
27
+ FieldError,
28
+ FieldGroup,
29
+ FieldLabel,
30
+ } from "~/components/ui/field";
31
+ import { Input } from "~/components/ui/input";
32
+
33
+ export function SignUpForm() {
34
+ const navigate = useNavigate();
35
+ const [isPending, startTransition] = useTransition();
36
+ const [authError, setAuthError] = useState<string | null>(null);
37
+ const form = useForm<SignUpValues>({
38
+ resolver: zodResolver(signUpSchema),
39
+ defaultValues: {
40
+ name: "",
41
+ email: "",
42
+ password: "",
43
+ },
44
+ });
45
+
46
+ const onSubmit = form.handleSubmit((values) => {
47
+ setAuthError(null);
48
+
49
+ startTransition(async () => {
50
+ const result = await authClient.signUp.email({
51
+ name: values.name,
52
+ email: values.email,
53
+ password: values.password,
54
+ });
55
+
56
+ if (result.error) {
57
+ setAuthError(getAuthErrorMessage(result.error));
58
+ return;
59
+ }
60
+
61
+ navigate({ to: "/dashboard" });
62
+ });
63
+ });
64
+
65
+ return (
66
+ <Card className="border-white/80 bg-white/90 shadow-2xl shadow-slate-200/70 backdrop-blur">
67
+ <CardHeader className="space-y-2">
68
+ <CardTitle>Create your account</CardTitle>
69
+ <CardDescription>
70
+ Start with a simple email and password sign-up flow powered by Better
71
+ Auth.
72
+ </CardDescription>
73
+ </CardHeader>
74
+ <CardContent>
75
+ <form className="space-y-6" onSubmit={onSubmit} noValidate>
76
+ <FieldGroup>
77
+ <Field data-invalid={Boolean(form.formState.errors.name)}>
78
+ <FieldLabel htmlFor="name">Full name</FieldLabel>
79
+ <Input
80
+ id="name"
81
+ autoComplete="name"
82
+ aria-invalid={Boolean(form.formState.errors.name)}
83
+ placeholder="Ada Lovelace"
84
+ {...form.register("name")}
85
+ />
86
+ <FieldError errors={[form.formState.errors.name]} />
87
+ </Field>
88
+
89
+ <Field data-invalid={Boolean(form.formState.errors.email)}>
90
+ <FieldLabel htmlFor="email">Email</FieldLabel>
91
+ <Input
92
+ id="email"
93
+ type="email"
94
+ autoComplete="email"
95
+ aria-invalid={Boolean(form.formState.errors.email)}
96
+ placeholder="you@example.com"
97
+ {...form.register("email")}
98
+ />
99
+ <FieldDescription>
100
+ This email will be used for future sign-ins.
101
+ </FieldDescription>
102
+ <FieldError errors={[form.formState.errors.email]} />
103
+ </Field>
104
+
105
+ <Field data-invalid={Boolean(form.formState.errors.password)}>
106
+ <FieldLabel htmlFor="password">Password</FieldLabel>
107
+ <Input
108
+ id="password"
109
+ type="password"
110
+ autoComplete="new-password"
111
+ aria-invalid={Boolean(form.formState.errors.password)}
112
+ placeholder="Create a strong password"
113
+ {...form.register("password")}
114
+ />
115
+ <FieldDescription>
116
+ Use at least 8 characters for the generated starter flow.
117
+ </FieldDescription>
118
+ <FieldError errors={[form.formState.errors.password]} />
119
+ </Field>
120
+ </FieldGroup>
121
+
122
+ {authError ? (
123
+ <div className="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
124
+ {authError}
125
+ </div>
126
+ ) : null}
127
+
128
+ <Button className="w-full" type="submit" disabled={isPending}>
129
+ {isPending ? (
130
+ <>
131
+ <LoaderCircle className="mr-2 size-4 animate-spin" />
132
+ Creating account
133
+ </>
134
+ ) : (
135
+ "Signup"
136
+ )}
137
+ </Button>
138
+ </form>
139
+ </CardContent>
140
+ <CardFooter className="justify-center">
141
+ <p className="text-sm text-slate-600">
142
+ Already have an account?{" "}
143
+ <Link
144
+ className="font-medium text-sky-700 hover:text-sky-800"
145
+ to="/sign-in"
146
+ >
147
+ Login
148
+ </Link>
149
+ </p>
150
+ </CardFooter>
151
+ </Card>
152
+ );
153
+ }
@@ -0,0 +1,3 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+
3
+ export const authClient = createAuthClient();
@@ -0,0 +1,12 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+ import { getRequestHeaders } from "@tanstack/react-start/server";
3
+
4
+ import { auth } from "./auth";
5
+
6
+ export const getSession = createServerFn({ method: "GET" }).handler(async () => {
7
+ const session = await auth.api.getSession({
8
+ headers: getRequestHeaders(),
9
+ });
10
+
11
+ return session;
12
+ });
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+
3
+ export const signInSchema = z.object({
4
+ email: z.string().email("Enter a valid email address."),
5
+ password: z.string().min(8, "Password must be at least 8 characters."),
6
+ });
7
+
8
+ export const signUpSchema = signInSchema.extend({
9
+ name: z
10
+ .string()
11
+ .trim()
12
+ .min(2, "Name must be at least 2 characters.")
13
+ .max(50, "Name must be 50 characters or fewer."),
14
+ });
15
+
16
+ export type SignInValues = z.infer<typeof signInSchema>;
17
+ export type SignUpValues = z.infer<typeof signUpSchema>;
18
+
19
+ export function getAuthErrorMessage(error: unknown) {
20
+ if (!error) {
21
+ return "Something went wrong. Please try again.";
22
+ }
23
+
24
+ if (typeof error === "string") {
25
+ return error;
26
+ }
27
+
28
+ if (typeof error === "object") {
29
+ if ("message" in error && typeof error.message === "string") {
30
+ return error.message;
31
+ }
32
+
33
+ if ("statusText" in error && typeof error.statusText === "string") {
34
+ return error.statusText;
35
+ }
36
+ }
37
+
38
+ return "Something went wrong. Please try again.";
39
+ }
@@ -0,0 +1,38 @@
1
+ import "dotenv/config";
2
+ import { betterAuth } from "better-auth";
3
+ import { tanstackStartCookies } from "better-auth/tanstack-start";
4
+ {{#if (eq database 'prisma')}}
5
+ import { prismaAdapter } from "better-auth/adapters/prisma";
6
+ import client from "./prisma";
7
+ {{/if}}
8
+ {{#if (eq database 'drizzle')}}
9
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
10
+ import { db } from "./db";
11
+ {{/if}}
12
+ {{#if (eq database 'mongoose')}}
13
+ import { mongodbAdapter } from "better-auth/adapters/mongodb";
14
+ import { MongoClient } from "mongodb";
15
+
16
+ const client = new MongoClient(process.env.MONGODB_URI!);
17
+ const db = client.db();
18
+ {{/if}}
19
+
20
+ export const auth = betterAuth({
21
+ {{#if (eq database 'prisma')}}
22
+ database: prismaAdapter(client, { provider: "postgresql" }),
23
+ {{/if}}
24
+ {{#if (eq database 'drizzle')}}
25
+ database: drizzleAdapter(db, { provider: "pg" }),
26
+ {{/if}}
27
+ {{#if (eq database 'mongoose')}}
28
+ // Transactions require a replica set. Create user/session/account/verification
29
+ // collections before the first sign-up; see the database setup documentation.
30
+ database: mongodbAdapter(db, { client }),
31
+ {{/if}}
32
+ emailAndPassword: {
33
+ enabled: true,
34
+ },
35
+ plugins: [tanstackStartCookies()],
36
+ secret: process.env.BETTER_AUTH_SECRET,
37
+ baseURL: process.env.BETTER_AUTH_URL,
38
+ });
@@ -0,0 +1,140 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from './routes/__root'
12
+ import { Route as IndexRouteImport } from './routes/index'
13
+ import { Route as DashboardRouteImport } from './routes/dashboard'
14
+ import { Route as SignInRouteImport } from './routes/sign-in'
15
+ import { Route as SignUpRouteImport } from './routes/sign-up'
16
+ import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
17
+
18
+ const IndexRoute = IndexRouteImport.update({
19
+ id: '/',
20
+ path: '/',
21
+ getParentRoute: () => rootRouteImport,
22
+ } as any)
23
+ const DashboardRoute = DashboardRouteImport.update({
24
+ id: '/dashboard',
25
+ path: '/dashboard',
26
+ getParentRoute: () => rootRouteImport,
27
+ } as any)
28
+ const SignInRoute = SignInRouteImport.update({
29
+ id: '/sign-in',
30
+ path: '/sign-in',
31
+ getParentRoute: () => rootRouteImport,
32
+ } as any)
33
+ const SignUpRoute = SignUpRouteImport.update({
34
+ id: '/sign-up',
35
+ path: '/sign-up',
36
+ getParentRoute: () => rootRouteImport,
37
+ } as any)
38
+ const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
39
+ id: '/api/auth/$',
40
+ path: '/api/auth/$',
41
+ getParentRoute: () => rootRouteImport,
42
+ } as any)
43
+
44
+ export interface FileRoutesByFullPath {
45
+ '/': typeof IndexRoute
46
+ '/dashboard': typeof DashboardRoute
47
+ '/sign-in': typeof SignInRoute
48
+ '/sign-up': typeof SignUpRoute
49
+ '/api/auth/$': typeof ApiAuthSplatRoute
50
+ }
51
+ export interface FileRoutesByTo {
52
+ '/': typeof IndexRoute
53
+ '/dashboard': typeof DashboardRoute
54
+ '/sign-in': typeof SignInRoute
55
+ '/sign-up': typeof SignUpRoute
56
+ '/api/auth/$': typeof ApiAuthSplatRoute
57
+ }
58
+ export interface FileRoutesById {
59
+ __root__: typeof rootRouteImport
60
+ '/': typeof IndexRoute
61
+ '/dashboard': typeof DashboardRoute
62
+ '/sign-in': typeof SignInRoute
63
+ '/sign-up': typeof SignUpRoute
64
+ '/api/auth/$': typeof ApiAuthSplatRoute
65
+ }
66
+ export interface FileRouteTypes {
67
+ fileRoutesByFullPath: FileRoutesByFullPath
68
+ fullPaths: '/' | '/dashboard' | '/sign-in' | '/sign-up' | '/api/auth/$'
69
+ fileRoutesByTo: FileRoutesByTo
70
+ to: '/' | '/dashboard' | '/sign-in' | '/sign-up' | '/api/auth/$'
71
+ id: '__root__' | '/' | '/dashboard' | '/sign-in' | '/sign-up' | '/api/auth/$'
72
+ fileRoutesById: FileRoutesById
73
+ }
74
+ export interface RootRouteChildren {
75
+ IndexRoute: typeof IndexRoute
76
+ DashboardRoute: typeof DashboardRoute
77
+ SignInRoute: typeof SignInRoute
78
+ SignUpRoute: typeof SignUpRoute
79
+ ApiAuthSplatRoute: typeof ApiAuthSplatRoute
80
+ }
81
+
82
+ declare module '@tanstack/react-router' {
83
+ interface FileRoutesByPath {
84
+ '/': {
85
+ id: '/'
86
+ path: '/'
87
+ fullPath: '/'
88
+ preLoaderRoute: typeof IndexRouteImport
89
+ parentRoute: typeof rootRouteImport
90
+ }
91
+ '/dashboard': {
92
+ id: '/dashboard'
93
+ path: '/dashboard'
94
+ fullPath: '/dashboard'
95
+ preLoaderRoute: typeof DashboardRouteImport
96
+ parentRoute: typeof rootRouteImport
97
+ }
98
+ '/sign-in': {
99
+ id: '/sign-in'
100
+ path: '/sign-in'
101
+ fullPath: '/sign-in'
102
+ preLoaderRoute: typeof SignInRouteImport
103
+ parentRoute: typeof rootRouteImport
104
+ }
105
+ '/sign-up': {
106
+ id: '/sign-up'
107
+ path: '/sign-up'
108
+ fullPath: '/sign-up'
109
+ preLoaderRoute: typeof SignUpRouteImport
110
+ parentRoute: typeof rootRouteImport
111
+ }
112
+ '/api/auth/$': {
113
+ id: '/api/auth/$'
114
+ path: '/api/auth/$'
115
+ fullPath: '/api/auth/$'
116
+ preLoaderRoute: typeof ApiAuthSplatRouteImport
117
+ parentRoute: typeof rootRouteImport
118
+ }
119
+ }
120
+ }
121
+
122
+ const rootRouteChildren: RootRouteChildren = {
123
+ IndexRoute: IndexRoute,
124
+ DashboardRoute: DashboardRoute,
125
+ SignInRoute: SignInRoute,
126
+ SignUpRoute: SignUpRoute,
127
+ ApiAuthSplatRoute: ApiAuthSplatRoute,
128
+ }
129
+ export const routeTree = rootRouteImport
130
+ ._addFileChildren(rootRouteChildren)
131
+ ._addFileTypes<FileRouteTypes>()
132
+
133
+ import type { getRouter } from './router.tsx'
134
+ import type { createStart } from '@tanstack/react-start'
135
+ declare module '@tanstack/react-start' {
136
+ interface Register {
137
+ ssr: true
138
+ router: Awaited<ReturnType<typeof getRouter>>
139
+ }
140
+ }
@@ -0,0 +1,16 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+
3
+ import { auth } from "~/lib/auth";
4
+
5
+ export const Route = createFileRoute("/api/auth/$")({
6
+ server: {
7
+ handlers: {
8
+ GET: async ({ request }: { request: Request }) => {
9
+ return await auth.handler(request);
10
+ },
11
+ POST: async ({ request }: { request: Request }) => {
12
+ return await auth.handler(request);
13
+ },
14
+ },
15
+ },
16
+ });
@@ -0,0 +1,111 @@
1
+ import { createFileRoute, redirect } from "@tanstack/react-router";
2
+ import { Link } from "@tanstack/react-router";
3
+ import { ArrowRight, ShieldCheck } from "lucide-react";
4
+
5
+ import { SignOutButton } from "~/components/auth/sign-out-button";
6
+ import { buttonVariants } from "~/components/ui/button";
7
+ import {
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from "~/components/ui/card";
14
+ import { getSession } from "~/lib/auth-functions";
15
+ import { cn } from "~/lib/utils";
16
+
17
+ export const Route = createFileRoute("/dashboard")({
18
+ beforeLoad: async () => {
19
+ const session = await getSession();
20
+
21
+ if (!session) {
22
+ throw redirect({ to: "/sign-in" });
23
+ }
24
+
25
+ return { user: session.user };
26
+ },
27
+ component: Dashboard,
28
+ });
29
+
30
+ function Dashboard() {
31
+ const user = Route.useRouteContext({ select: (ctx) => ctx.user });
32
+
33
+ return (
34
+ <main className="min-h-screen bg-[linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_100%)] px-6 py-16 sm:px-10">
35
+ <div className="mx-auto max-w-5xl space-y-8">
36
+ <header className="flex flex-col gap-6 rounded-[2rem] border border-white/70 bg-white/85 p-8 shadow-xl shadow-slate-200/60 backdrop-blur md:flex-row md:items-end md:justify-between">
37
+ <div className="space-y-3">
38
+ <span className="inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium uppercase tracking-[0.24em] text-emerald-700">
39
+ <ShieldCheck className="size-4" />
40
+ Authenticated session
41
+ </span>
42
+ <div className="space-y-1">
43
+ <h1 className="text-3xl font-semibold tracking-tight text-slate-950">
44
+ Welcome {user.name}
45
+ </h1>
46
+ <p className="text-sm text-slate-600">{user.email}</p>
47
+ </div>
48
+ </div>
49
+ <SignOutButton />
50
+ </header>
51
+
52
+ <section className="grid gap-5 md:grid-cols-2">
53
+ <Card>
54
+ <CardHeader>
55
+ <CardTitle>Protected route example</CardTitle>
56
+ <CardDescription>
57
+ This page verifies the session on the server before rendering any
58
+ content.
59
+ </CardDescription>
60
+ </CardHeader>
61
+ <CardContent className="space-y-4 text-sm text-slate-600">
62
+ <p>
63
+ The route guard calls{" "}
64
+ <code className="font-mono text-slate-900">getSession()</code> in
65
+ <code className="font-mono text-slate-900"> beforeLoad</code>,
66
+ which reads the cookie headers server-side via{" "}
67
+ <code className="font-mono text-slate-900">
68
+ auth.api.getSession
69
+ </code>{" "}
70
+ on every render and navigation.
71
+ </p>
72
+ <p>
73
+ That keeps route protection aligned with Better Auth&apos;s
74
+ TanStack Start guidance while staying easy to extend.
75
+ </p>
76
+ </CardContent>
77
+ </Card>
78
+
79
+ <Card>
80
+ <CardHeader>
81
+ <CardTitle>Suggested next steps</CardTitle>
82
+ <CardDescription>
83
+ Keep the generated auth flow, then customize the app around your
84
+ product.
85
+ </CardDescription>
86
+ </CardHeader>
87
+ <CardContent className="space-y-3 text-sm text-slate-600">
88
+ <p>
89
+ Replace the demo dashboard with your first authenticated page.
90
+ </p>
91
+ <p>Extend the schema with profile or onboarding fields.</p>
92
+ <p>
93
+ Add provider-based auth or email verification later if needed.
94
+ </p>
95
+ <Link
96
+ to="/"
97
+ className={cn(
98
+ buttonVariants({ variant: "outline" }),
99
+ "w-fit gap-2"
100
+ )}
101
+ >
102
+ Back to home
103
+ <ArrowRight className="size-4" />
104
+ </Link>
105
+ </CardContent>
106
+ </Card>
107
+ </section>
108
+ </div>
109
+ </main>
110
+ );
111
+ }
@@ -0,0 +1,37 @@
1
+ import { createFileRoute, redirect } from "@tanstack/react-router";
2
+
3
+ import { SignInForm } from "~/components/auth/sign-in-form";
4
+ import { getSession } from "~/lib/auth-functions";
5
+
6
+ export const Route = createFileRoute("/sign-in")({
7
+ beforeLoad: async () => {
8
+ const session = await getSession();
9
+
10
+ if (session) {
11
+ throw redirect({ to: "/dashboard" });
12
+ }
13
+ },
14
+ component: SignInPage,
15
+ });
16
+
17
+ function SignInPage() {
18
+ return (
19
+ <main className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(14,165,233,0.16),_transparent_30%),linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_100%)] px-6 py-16 sm:px-10">
20
+ <div className="mx-auto flex min-h-[calc(100vh-8rem)] max-w-5xl items-center gap-12 lg:grid lg:grid-cols-[0.9fr_1.1fr]">
21
+ <section className="hidden space-y-5 lg:block">
22
+ <span className="inline-flex rounded-full border border-sky-200 bg-white/80 px-3 py-1 text-xs font-medium uppercase tracking-[0.24em] text-sky-700">
23
+ Better Auth
24
+ </span>
25
+ <h1 className="text-4xl font-semibold tracking-tight text-slate-950">
26
+ Welcome back.
27
+ </h1>
28
+ <p className="max-w-md text-base leading-7 text-slate-600">
29
+ This generated starter includes protected routes, session-aware page
30
+ guards, and reusable auth form primitives you can extend from here.
31
+ </p>
32
+ </section>
33
+ <SignInForm />
34
+ </div>
35
+ </main>
36
+ );
37
+ }
@@ -0,0 +1,37 @@
1
+ import { createFileRoute, redirect } from "@tanstack/react-router";
2
+
3
+ import { SignUpForm } from "~/components/auth/sign-up-form";
4
+ import { getSession } from "~/lib/auth-functions";
5
+
6
+ export const Route = createFileRoute("/sign-up")({
7
+ beforeLoad: async () => {
8
+ const session = await getSession();
9
+
10
+ if (session) {
11
+ throw redirect({ to: "/dashboard" });
12
+ }
13
+ },
14
+ component: SignUpPage,
15
+ });
16
+
17
+ function SignUpPage() {
18
+ return (
19
+ <main className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.18),_transparent_28%),linear-gradient(180deg,_#f8fafc_0%,_#ecfeff_100%)] px-6 py-16 sm:px-10">
20
+ <div className="mx-auto flex min-h-[calc(100vh-8rem)] max-w-5xl items-center gap-12 lg:grid lg:grid-cols-[0.9fr_1.1fr]">
21
+ <section className="hidden space-y-5 lg:block">
22
+ <span className="inline-flex rounded-full border border-sky-200 bg-white/80 px-3 py-1 text-xs font-medium uppercase tracking-[0.24em] text-sky-700">
23
+ Better Auth
24
+ </span>
25
+ <h1 className="text-4xl font-semibold tracking-tight text-slate-950">
26
+ Create your account.
27
+ </h1>
28
+ <p className="max-w-md text-base leading-7 text-slate-600">
29
+ Start with email and password today, then layer in profile data,
30
+ onboarding, and provider-based auth when your product needs it.
31
+ </p>
32
+ </section>
33
+ <SignUpForm />
34
+ </div>
35
+ </main>
36
+ );
37
+ }