better-ts-stack 2.1.1 → 2.2.0
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.
- package/LICENSE +21 -21
- package/README.md +16 -8
- package/dist/args.js +7 -0
- package/dist/builder/buildPlanner.js +7 -1
- package/dist/builder/configGenerator.js +28 -3
- package/dist/builder/index.js +2 -1
- package/dist/builder/moduleSelector.js +6 -1
- package/dist/builder/readmeGenerator.js +8 -2
- package/dist/builder/templateContext.js +1 -0
- package/dist/output/nextSteps.js +2 -4
- package/dist/prompts/stack.js +6 -1
- package/dist/types/index.js +2 -0
- package/dist/ui/messages.js +2 -0
- package/package.json +71 -71
- package/templates/backend/express/.prettierignore +52 -52
- package/templates/backend/express/.prettierrc +16 -16
- package/templates/backend/express/gitignore +3 -1
- package/templates/backend/express/src/env.ts.hbs +13 -2
- package/templates/backend/express/src/index.ts.hbs +31 -2
- package/templates/backend/express/tsconfig.eslint.json +9 -9
- package/templates/backend/nestjs/.prettierignore +52 -52
- package/templates/backend/nestjs/gitignore +3 -1
- package/templates/backend/nestjs/src/app.module.ts.hbs +4 -1
- package/templates/backend/nestjs/src/env.ts.hbs +13 -2
- package/templates/backend/nestjs/src/health/health.controller.ts.hbs +7 -0
- package/templates/backend/nestjs/src/{main.ts → main.ts.hbs} +11 -0
- package/templates/backend/nestjs/tsconfig.eslint.json +9 -9
- package/templates/frontend/nextjs/app/globals.css +129 -89
- package/templates/frontend/nextjs/app/layout.tsx +36 -34
- package/templates/frontend/nextjs/app/page.tsx.hbs +63 -43
- package/templates/frontend/nextjs/components/ui/alert.tsx +75 -0
- package/templates/frontend/nextjs/components/ui/badge.tsx +51 -0
- package/templates/frontend/nextjs/components/ui/button.tsx +57 -51
- package/templates/frontend/nextjs/components/ui/card.tsx +102 -60
- package/templates/frontend/nextjs/components/ui/field.tsx +238 -67
- package/templates/frontend/nextjs/components/ui/input.tsx +19 -18
- package/templates/frontend/nextjs/components/ui/label.tsx +19 -0
- package/templates/frontend/nextjs/components/ui/separator.tsx +24 -0
- package/templates/frontend/nextjs/components/ui/skeleton.tsx +13 -0
- package/templates/frontend/nextjs/components/ui/spinner.tsx +10 -0
- package/templates/frontend/nextjs/components.json +11 -5
- package/templates/frontend/nextjs/config.json +36 -35
- package/templates/frontend/nextjs/eslint.config.mjs +11 -11
- package/templates/frontend/nextjs/lib/utils.ts +1 -6
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
- package/templates/frontend/nextjs/postcss.config.mjs +7 -7
- package/templates/frontend/nextjs/public/file.svg +1 -1
- package/templates/frontend/nextjs/public/globe.svg +1 -1
- package/templates/frontend/nextjs/public/window.svg +1 -1
- package/templates/frontend/tanstack/components.json +25 -18
- package/templates/frontend/tanstack/config.json +5 -2
- package/templates/frontend/tanstack/eslint.config.mjs +23 -23
- package/templates/frontend/tanstack/gitignore +39 -39
- package/templates/frontend/tanstack/public/favicon.svg +7 -7
- package/templates/frontend/tanstack/src/components/ui/alert.tsx +75 -0
- package/templates/frontend/tanstack/src/components/ui/badge.tsx +51 -0
- package/templates/frontend/tanstack/src/components/ui/button.tsx +57 -51
- package/templates/frontend/tanstack/src/components/ui/card.tsx +102 -60
- package/templates/frontend/tanstack/src/components/ui/field.tsx +236 -67
- package/templates/frontend/tanstack/src/components/ui/input.tsx +19 -18
- package/templates/frontend/tanstack/src/components/ui/label.tsx +17 -0
- package/templates/frontend/tanstack/src/components/ui/separator.tsx +24 -0
- package/templates/frontend/tanstack/src/components/ui/skeleton.tsx +13 -0
- package/templates/frontend/tanstack/src/components/ui/spinner.tsx +10 -0
- package/templates/frontend/tanstack/src/lib/utils.ts +1 -6
- package/templates/frontend/tanstack/src/router.tsx +31 -31
- package/templates/frontend/tanstack/src/routes/__root.tsx +69 -69
- package/templates/frontend/tanstack/src/routes/index.tsx.hbs +64 -45
- package/templates/frontend/tanstack/src/styles/app.css +100 -55
- package/templates/frontend/tanstack/tsconfig.json +22 -22
- package/templates/modules/agents/AGENTS.md.hbs +245 -0
- package/templates/modules/agents/CLAUDE.md +1 -0
- package/templates/modules/agents/config.json +10 -0
- package/templates/modules/auth/express/config.json +12 -11
- package/templates/modules/auth/express/src/lib/auth.ts.hbs +143 -0
- package/templates/modules/auth/express/src/lib/email.ts +37 -0
- package/templates/modules/auth/express/src/middleware/requireAuth.ts +29 -0
- package/templates/modules/auth/express/src/routes/me.ts +13 -0
- package/templates/modules/auth/nestjs/config.json +13 -11
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +64 -14
- package/templates/modules/auth/nestjs/src/auth/auth.ts.hbs +152 -0
- package/templates/modules/auth/nestjs/src/auth/email.ts +37 -0
- package/templates/modules/auth/nestjs/src/me/me.controller.ts +15 -0
- package/templates/modules/auth/nextjs/app/api/auth/[...all]/route.ts +4 -4
- package/templates/modules/auth/nextjs/app/dashboard/page.tsx +101 -45
- package/templates/modules/auth/nextjs/app/forgot-password/page.tsx +11 -0
- package/templates/modules/auth/nextjs/app/reset-password/page.tsx +17 -0
- package/templates/modules/auth/nextjs/app/sign-in/page.tsx +23 -17
- package/templates/modules/auth/nextjs/app/sign-up/page.tsx +25 -19
- package/templates/modules/auth/nextjs/components/auth/change-password-form.tsx +125 -0
- package/templates/modules/auth/nextjs/components/auth/email-verification-card.tsx +91 -0
- package/templates/modules/auth/nextjs/components/auth/forgot-password-form.tsx +133 -0
- package/templates/modules/auth/nextjs/components/auth/reset-password-form.tsx +155 -0
- package/templates/modules/auth/nextjs/components/auth/sign-in-form.tsx +46 -32
- package/templates/modules/auth/nextjs/components/auth/sign-out-button.tsx +6 -5
- package/templates/modules/auth/nextjs/components/auth/sign-up-form.tsx +36 -30
- package/templates/modules/auth/nextjs/components/auth/social-buttons.tsx +73 -0
- package/templates/modules/auth/nextjs/config.json +24 -18
- package/templates/modules/auth/nextjs/lib/auth-client.ts +3 -3
- package/templates/modules/auth/nextjs/lib/auth-schema.ts +27 -2
- package/templates/modules/auth/nextjs/lib/auth.ts.hbs +65 -0
- package/templates/modules/auth/nextjs/lib/email.ts +43 -0
- package/templates/modules/auth/nextjs/lib/session.ts +12 -0
- package/templates/modules/auth/tanstack/config.json +25 -19
- package/templates/modules/auth/tanstack/src/components/auth/change-password-form.tsx +123 -0
- package/templates/modules/auth/tanstack/src/components/auth/email-verification-card.tsx +89 -0
- package/templates/modules/auth/tanstack/src/components/auth/forgot-password-form.tsx +131 -0
- package/templates/modules/auth/tanstack/src/components/auth/reset-password-form.tsx +149 -0
- package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +44 -35
- package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +7 -6
- package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +37 -34
- package/templates/modules/auth/tanstack/src/components/auth/social-buttons.tsx +71 -0
- package/templates/modules/auth/tanstack/src/lib/auth-client.ts +2 -2
- package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +14 -8
- package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +27 -2
- package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +98 -38
- package/templates/modules/auth/tanstack/src/lib/email.ts +43 -0
- package/templates/modules/auth/tanstack/src/routeTree.gen.ts +61 -3
- package/templates/modules/auth/tanstack/src/routes/api/auth/$.ts +15 -15
- package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +119 -111
- package/templates/modules/auth/tanstack/src/routes/forgot-password.tsx +17 -0
- package/templates/modules/auth/tanstack/src/routes/reset-password.tsx +25 -0
- package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +44 -37
- package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +44 -37
- package/templates/modules/docker/.dockerignore +2 -0
- package/templates/modules/docker/config.json +15 -15
- package/templates/modules/drizzle/express/config.json +26 -26
- package/templates/modules/drizzle/express/src/lib/schema.ts.hbs +90 -0
- package/templates/modules/drizzle/nestjs/config.json +26 -26
- package/templates/modules/drizzle/nestjs/src/database/schema.ts.hbs +90 -0
- package/templates/modules/drizzle/nextjs/config.json +26 -26
- package/templates/modules/drizzle/nextjs/drizzle.config.ts +13 -13
- package/templates/modules/drizzle/tanstack/config.json +26 -26
- package/templates/modules/drizzle/tanstack/drizzle.config.ts +12 -12
- package/templates/modules/drizzle/tanstack/src/lib/db.ts +13 -13
- package/templates/modules/mongoose/express/config.json +15 -15
- package/templates/modules/mongoose/express/src/lib/db.ts +43 -43
- package/templates/modules/mongoose/express/src/models/User.ts +48 -48
- package/templates/modules/mongoose/nestjs/config.json +16 -15
- package/templates/modules/mongoose/nextjs/config.json +15 -15
- package/templates/modules/mongoose/nextjs/models/User.ts +48 -48
- package/templates/modules/mongoose/tanstack/config.json +16 -16
- package/templates/modules/mongoose/tanstack/src/models/User.ts +48 -48
- package/templates/modules/prisma/express/config.json +27 -27
- package/templates/modules/prisma/express/prisma/schema.prisma.hbs +92 -0
- package/templates/modules/prisma/nestjs/config.json +27 -27
- package/templates/modules/prisma/nestjs/prisma/schema.prisma.hbs +91 -0
- package/templates/modules/prisma/nextjs/config.json +28 -28
- package/templates/modules/prisma/nextjs/lib/prisma.ts +18 -18
- package/templates/modules/prisma/nextjs/prisma.config.ts +14 -14
- package/templates/modules/prisma/tanstack/config.json +28 -28
- package/templates/modules/prisma/tanstack/prisma/schema.prisma.hbs +91 -91
- package/templates/modules/prisma/tanstack/prisma.config.ts +14 -14
- package/templates/modules/prisma/tanstack/src/lib/prisma.ts +17 -17
- package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +0 -8
- package/templates/modules/vscode/.vscode/extensions.json.hbs +29 -0
- package/templates/modules/vscode/.vscode/settings.json.hbs +22 -0
- package/templates/modules/vscode/config.json +10 -0
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +0 -116
- package/templates/modules/auth/express/src/lib/jwt.ts.hbs +0 -27
- package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +0 -25
- package/templates/modules/auth/express/src/routes/auth.ts.hbs +0 -27
- package/templates/modules/auth/express/src/services/userStore.ts.hbs +0 -117
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts.hbs +0 -61
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +0 -36
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +0 -35
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +0 -70
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +0 -27
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +0 -144
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +0 -16
- package/templates/modules/drizzle/express/src/lib/schema.ts +0 -12
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +0 -12
- package/templates/modules/prisma/express/prisma/schema.prisma +0 -24
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +0 -23
|
@@ -1,38 +1,98 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import { betterAuth } from "better-auth";
|
|
3
|
-
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
{{
|
|
8
|
-
{
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
{{
|
|
12
|
-
{
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { betterAuth } from "better-auth";
|
|
3
|
+
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
|
4
|
+
|
|
5
|
+
import type { SocialProvider } from "./auth-schema";
|
|
6
|
+
import { sendEmail } from "./email";
|
|
7
|
+
{{#if (eq database 'prisma')}}
|
|
8
|
+
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
9
|
+
import client from "./prisma";
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if (eq database 'drizzle')}}
|
|
12
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
13
|
+
import { db } from "./db";
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{#if (eq database 'mongoose')}}
|
|
16
|
+
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
17
|
+
import { MongoClient } from "mongodb";
|
|
18
|
+
|
|
19
|
+
const client = new MongoClient(process.env.MONGODB_URI!);
|
|
20
|
+
const db = client.db();
|
|
21
|
+
{{/if}}
|
|
22
|
+
|
|
23
|
+
// Reports a failed email instead of failing the request: the sign-up or reset
|
|
24
|
+
// already happened, and the user can ask for the email again
|
|
25
|
+
function deliver(email: Parameters<typeof sendEmail>[0]): void {
|
|
26
|
+
sendEmail(email).catch((error: unknown) => {
|
|
27
|
+
console.error(`Could not send "${email.subject}" to ${email.to}:`, error);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const auth = betterAuth({
|
|
32
|
+
{{#if (eq database 'prisma')}}
|
|
33
|
+
database: prismaAdapter(client, { provider: "postgresql" }),
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if (eq database 'drizzle')}}
|
|
36
|
+
database: drizzleAdapter(db, { provider: "pg" }),
|
|
37
|
+
{{/if}}
|
|
38
|
+
{{#if (eq database 'mongoose')}}
|
|
39
|
+
// Transactions require a replica set. Create user/session/account/verification
|
|
40
|
+
// collections before the first sign-up; see the database setup documentation.
|
|
41
|
+
database: mongodbAdapter(db, { client }),
|
|
42
|
+
{{/if}}
|
|
43
|
+
emailAndPassword: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
// Set to true to block sign-in until the email address is verified
|
|
46
|
+
requireEmailVerification: false,
|
|
47
|
+
// A reset signs the user out everywhere
|
|
48
|
+
revokeSessionsOnPasswordReset: true,
|
|
49
|
+
sendResetPassword: async ({ user, url }) => {
|
|
50
|
+
deliver({
|
|
51
|
+
to: user.email,
|
|
52
|
+
subject: "Reset your password",
|
|
53
|
+
text: `Open this link to choose a new password:
|
|
54
|
+
${url}`,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
emailVerification: {
|
|
59
|
+
sendOnSignUp: true,
|
|
60
|
+
autoSignInAfterVerification: true,
|
|
61
|
+
sendVerificationEmail: async ({ user, url }) => {
|
|
62
|
+
deliver({
|
|
63
|
+
to: user.email,
|
|
64
|
+
subject: "Verify your email address",
|
|
65
|
+
text: `Open this link to verify your email address:
|
|
66
|
+
${url}`,
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
// Each provider is turned on once its client ID and secret are set
|
|
71
|
+
socialProviders: {
|
|
72
|
+
...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
|
|
73
|
+
? {
|
|
74
|
+
github: {
|
|
75
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
76
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
: {}),
|
|
80
|
+
...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
|
81
|
+
? {
|
|
82
|
+
google: {
|
|
83
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
84
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
: {}),
|
|
88
|
+
},
|
|
89
|
+
plugins: [tanstackStartCookies()],
|
|
90
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
91
|
+
baseURL: process.env.BETTER_AUTH_URL,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// The providers turned on above; the sign-in and sign-up pages show a button
|
|
95
|
+
// for each
|
|
96
|
+
export const socialProviders = Object.keys(
|
|
97
|
+
auth.options.socialProviders
|
|
98
|
+
) as SocialProvider[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Sends through Resend when RESEND_API_KEY is set. Without it the email is
|
|
2
|
+
// printed to the server log, so verification and reset links can be copied
|
|
3
|
+
// from the terminal during development
|
|
4
|
+
export async function sendEmail({
|
|
5
|
+
to,
|
|
6
|
+
subject,
|
|
7
|
+
text,
|
|
8
|
+
}: {
|
|
9
|
+
to: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
text: string;
|
|
12
|
+
}): Promise<void> {
|
|
13
|
+
const apiKey = process.env.RESEND_API_KEY;
|
|
14
|
+
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
console.log(`
|
|
17
|
+
--- Email to ${to}: ${subject}
|
|
18
|
+
${text}
|
|
19
|
+
---
|
|
20
|
+
`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const response = await fetch("https://api.resend.com/emails", {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
from: process.env.EMAIL_FROM || "onboarding@resend.dev",
|
|
32
|
+
to,
|
|
33
|
+
subject,
|
|
34
|
+
text,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Resend rejected the email (${response.status}): ${await response.text()}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { Route as rootRouteImport } from './routes/__root'
|
|
12
12
|
import { Route as IndexRouteImport } from './routes/index'
|
|
13
13
|
import { Route as DashboardRouteImport } from './routes/dashboard'
|
|
14
|
+
import { Route as ForgotPasswordRouteImport } from './routes/forgot-password'
|
|
15
|
+
import { Route as ResetPasswordRouteImport } from './routes/reset-password'
|
|
14
16
|
import { Route as SignInRouteImport } from './routes/sign-in'
|
|
15
17
|
import { Route as SignUpRouteImport } from './routes/sign-up'
|
|
16
18
|
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
|
@@ -25,6 +27,16 @@ const DashboardRoute = DashboardRouteImport.update({
|
|
|
25
27
|
path: '/dashboard',
|
|
26
28
|
getParentRoute: () => rootRouteImport,
|
|
27
29
|
} as any)
|
|
30
|
+
const ForgotPasswordRoute = ForgotPasswordRouteImport.update({
|
|
31
|
+
id: '/forgot-password',
|
|
32
|
+
path: '/forgot-password',
|
|
33
|
+
getParentRoute: () => rootRouteImport,
|
|
34
|
+
} as any)
|
|
35
|
+
const ResetPasswordRoute = ResetPasswordRouteImport.update({
|
|
36
|
+
id: '/reset-password',
|
|
37
|
+
path: '/reset-password',
|
|
38
|
+
getParentRoute: () => rootRouteImport,
|
|
39
|
+
} as any)
|
|
28
40
|
const SignInRoute = SignInRouteImport.update({
|
|
29
41
|
id: '/sign-in',
|
|
30
42
|
path: '/sign-in',
|
|
@@ -44,6 +56,8 @@ const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
|
|
44
56
|
export interface FileRoutesByFullPath {
|
|
45
57
|
'/': typeof IndexRoute
|
|
46
58
|
'/dashboard': typeof DashboardRoute
|
|
59
|
+
'/forgot-password': typeof ForgotPasswordRoute
|
|
60
|
+
'/reset-password': typeof ResetPasswordRoute
|
|
47
61
|
'/sign-in': typeof SignInRoute
|
|
48
62
|
'/sign-up': typeof SignUpRoute
|
|
49
63
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
@@ -51,6 +65,8 @@ export interface FileRoutesByFullPath {
|
|
|
51
65
|
export interface FileRoutesByTo {
|
|
52
66
|
'/': typeof IndexRoute
|
|
53
67
|
'/dashboard': typeof DashboardRoute
|
|
68
|
+
'/forgot-password': typeof ForgotPasswordRoute
|
|
69
|
+
'/reset-password': typeof ResetPasswordRoute
|
|
54
70
|
'/sign-in': typeof SignInRoute
|
|
55
71
|
'/sign-up': typeof SignUpRoute
|
|
56
72
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
@@ -59,21 +75,47 @@ export interface FileRoutesById {
|
|
|
59
75
|
__root__: typeof rootRouteImport
|
|
60
76
|
'/': typeof IndexRoute
|
|
61
77
|
'/dashboard': typeof DashboardRoute
|
|
78
|
+
'/forgot-password': typeof ForgotPasswordRoute
|
|
79
|
+
'/reset-password': typeof ResetPasswordRoute
|
|
62
80
|
'/sign-in': typeof SignInRoute
|
|
63
81
|
'/sign-up': typeof SignUpRoute
|
|
64
82
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
65
83
|
}
|
|
66
84
|
export interface FileRouteTypes {
|
|
67
85
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
68
|
-
fullPaths:
|
|
86
|
+
fullPaths:
|
|
87
|
+
| '/'
|
|
88
|
+
| '/dashboard'
|
|
89
|
+
| '/forgot-password'
|
|
90
|
+
| '/reset-password'
|
|
91
|
+
| '/sign-in'
|
|
92
|
+
| '/sign-up'
|
|
93
|
+
| '/api/auth/$'
|
|
69
94
|
fileRoutesByTo: FileRoutesByTo
|
|
70
|
-
to:
|
|
71
|
-
|
|
95
|
+
to:
|
|
96
|
+
| '/'
|
|
97
|
+
| '/dashboard'
|
|
98
|
+
| '/forgot-password'
|
|
99
|
+
| '/reset-password'
|
|
100
|
+
| '/sign-in'
|
|
101
|
+
| '/sign-up'
|
|
102
|
+
| '/api/auth/$'
|
|
103
|
+
id:
|
|
104
|
+
| '__root__'
|
|
105
|
+
| '/'
|
|
106
|
+
| '/dashboard'
|
|
107
|
+
| '/forgot-password'
|
|
108
|
+
| '/reset-password'
|
|
109
|
+
| '/sign-in'
|
|
110
|
+
| '/sign-up'
|
|
111
|
+
| '/api/auth/$'
|
|
72
112
|
fileRoutesById: FileRoutesById
|
|
73
113
|
}
|
|
74
114
|
export interface RootRouteChildren {
|
|
75
115
|
IndexRoute: typeof IndexRoute
|
|
76
116
|
DashboardRoute: typeof DashboardRoute
|
|
117
|
+
ForgotPasswordRoute: typeof ForgotPasswordRoute
|
|
118
|
+
ResetPasswordRoute: typeof ResetPasswordRoute
|
|
77
119
|
SignInRoute: typeof SignInRoute
|
|
78
120
|
SignUpRoute: typeof SignUpRoute
|
|
79
121
|
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
@@ -95,6 +137,20 @@ declare module '@tanstack/react-router' {
|
|
|
95
137
|
preLoaderRoute: typeof DashboardRouteImport
|
|
96
138
|
parentRoute: typeof rootRouteImport
|
|
97
139
|
}
|
|
140
|
+
'/forgot-password': {
|
|
141
|
+
id: '/forgot-password'
|
|
142
|
+
path: '/forgot-password'
|
|
143
|
+
fullPath: '/forgot-password'
|
|
144
|
+
preLoaderRoute: typeof ForgotPasswordRouteImport
|
|
145
|
+
parentRoute: typeof rootRouteImport
|
|
146
|
+
}
|
|
147
|
+
'/reset-password': {
|
|
148
|
+
id: '/reset-password'
|
|
149
|
+
path: '/reset-password'
|
|
150
|
+
fullPath: '/reset-password'
|
|
151
|
+
preLoaderRoute: typeof ResetPasswordRouteImport
|
|
152
|
+
parentRoute: typeof rootRouteImport
|
|
153
|
+
}
|
|
98
154
|
'/sign-in': {
|
|
99
155
|
id: '/sign-in'
|
|
100
156
|
path: '/sign-in'
|
|
@@ -122,6 +178,8 @@ declare module '@tanstack/react-router' {
|
|
|
122
178
|
const rootRouteChildren: RootRouteChildren = {
|
|
123
179
|
IndexRoute: IndexRoute,
|
|
124
180
|
DashboardRoute: DashboardRoute,
|
|
181
|
+
ForgotPasswordRoute: ForgotPasswordRoute,
|
|
182
|
+
ResetPasswordRoute: ResetPasswordRoute,
|
|
125
183
|
SignInRoute: SignInRoute,
|
|
126
184
|
SignUpRoute: SignUpRoute,
|
|
127
185
|
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
|
@@ -1,16 +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
|
-
},
|
|
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
16
|
});
|
|
@@ -1,111 +1,119 @@
|
|
|
1
|
-
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
import { createFileRoute, Link, redirect } from "@tanstack/react-router";
|
|
2
|
+
import { ArrowRight, ShieldCheck } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { ChangePasswordForm } from "~/components/auth/change-password-form";
|
|
5
|
+
import { EmailVerificationCard } from "~/components/auth/email-verification-card";
|
|
6
|
+
import { SignOutButton } from "~/components/auth/sign-out-button";
|
|
7
|
+
import { Badge } from "~/components/ui/badge";
|
|
8
|
+
import { Button } from "~/components/ui/button";
|
|
9
|
+
import {
|
|
10
|
+
Card,
|
|
11
|
+
CardContent,
|
|
12
|
+
CardDescription,
|
|
13
|
+
CardFooter,
|
|
14
|
+
CardHeader,
|
|
15
|
+
CardTitle,
|
|
16
|
+
} from "~/components/ui/card";
|
|
17
|
+
import { getSession } from "~/lib/auth-functions";
|
|
18
|
+
|
|
19
|
+
export const Route = createFileRoute("/dashboard")({
|
|
20
|
+
beforeLoad: async () => {
|
|
21
|
+
const session = await getSession();
|
|
22
|
+
|
|
23
|
+
if (!session) {
|
|
24
|
+
throw redirect({ to: "/sign-in" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { user: session.user };
|
|
28
|
+
},
|
|
29
|
+
component: Dashboard,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function Dashboard() {
|
|
33
|
+
const user = Route.useRouteContext({ select: (ctx) => ctx.user });
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<main className="min-h-svh bg-muted px-6 py-16 sm:px-10">
|
|
37
|
+
<div className="mx-auto flex max-w-5xl flex-col gap-8">
|
|
38
|
+
<header className="flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
|
|
39
|
+
<div className="flex flex-col gap-3">
|
|
40
|
+
<Badge variant="secondary">
|
|
41
|
+
<ShieldCheck data-icon="inline-start" />
|
|
42
|
+
Authenticated session
|
|
43
|
+
</Badge>
|
|
44
|
+
<div className="flex flex-col gap-1">
|
|
45
|
+
<h1 className="text-3xl font-semibold tracking-tight">
|
|
46
|
+
Welcome {user.name}
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="text-sm text-muted-foreground">{user.email}</p>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<SignOutButton />
|
|
52
|
+
</header>
|
|
53
|
+
|
|
54
|
+
<section className="grid gap-5 md:grid-cols-2">
|
|
55
|
+
<EmailVerificationCard
|
|
56
|
+
email={user.email}
|
|
57
|
+
emailVerified={user.emailVerified}
|
|
58
|
+
/>
|
|
59
|
+
<ChangePasswordForm />
|
|
60
|
+
<Card>
|
|
61
|
+
<CardHeader>
|
|
62
|
+
<CardTitle>Protected route example</CardTitle>
|
|
63
|
+
<CardDescription>
|
|
64
|
+
This page verifies the session on the server before rendering
|
|
65
|
+
any content.
|
|
66
|
+
</CardDescription>
|
|
67
|
+
</CardHeader>
|
|
68
|
+
<CardContent className="flex flex-col gap-4 text-muted-foreground">
|
|
69
|
+
<p>
|
|
70
|
+
The route guard calls{" "}
|
|
71
|
+
<code className="font-mono text-foreground">getSession()</code>{" "}
|
|
72
|
+
in <code className="font-mono text-foreground">beforeLoad</code>,
|
|
73
|
+
which reads the cookie headers server-side via{" "}
|
|
74
|
+
<code className="font-mono text-foreground">
|
|
75
|
+
auth.api.getSession
|
|
76
|
+
</code>{" "}
|
|
77
|
+
on every render and navigation.
|
|
78
|
+
</p>
|
|
79
|
+
<p>
|
|
80
|
+
That keeps route protection aligned with Better Auth's
|
|
81
|
+
TanStack Start guidance while staying easy to extend.
|
|
82
|
+
</p>
|
|
83
|
+
</CardContent>
|
|
84
|
+
</Card>
|
|
85
|
+
|
|
86
|
+
<Card>
|
|
87
|
+
<CardHeader>
|
|
88
|
+
<CardTitle>Suggested next steps</CardTitle>
|
|
89
|
+
<CardDescription>
|
|
90
|
+
Keep the generated auth flow, then customize the app around your
|
|
91
|
+
product.
|
|
92
|
+
</CardDescription>
|
|
93
|
+
</CardHeader>
|
|
94
|
+
<CardContent className="flex flex-col gap-3 text-muted-foreground">
|
|
95
|
+
<p>
|
|
96
|
+
Replace the demo dashboard with your first authenticated page.
|
|
97
|
+
</p>
|
|
98
|
+
<p>Extend the schema with profile or onboarding fields.</p>
|
|
99
|
+
<p>
|
|
100
|
+
Add a GitHub or Google client ID and secret to .env to turn on
|
|
101
|
+
social sign-in, and RESEND_API_KEY to send real emails.
|
|
102
|
+
</p>
|
|
103
|
+
</CardContent>
|
|
104
|
+
<CardFooter>
|
|
105
|
+
<Button
|
|
106
|
+
variant="outline"
|
|
107
|
+
nativeButton={false}
|
|
108
|
+
render={<Link to="/" />}
|
|
109
|
+
>
|
|
110
|
+
Back to home
|
|
111
|
+
<ArrowRight data-icon="inline-end" />
|
|
112
|
+
</Button>
|
|
113
|
+
</CardFooter>
|
|
114
|
+
</Card>
|
|
115
|
+
</section>
|
|
116
|
+
</div>
|
|
117
|
+
</main>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
import { ForgotPasswordForm } from "~/components/auth/forgot-password-form";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/forgot-password")({
|
|
6
|
+
component: ForgotPasswordPage,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function ForgotPasswordPage() {
|
|
10
|
+
return (
|
|
11
|
+
<main className="flex min-h-svh items-center justify-center bg-muted p-6 md:p-10">
|
|
12
|
+
<div className="w-full max-w-md">
|
|
13
|
+
<ForgotPasswordForm />
|
|
14
|
+
</div>
|
|
15
|
+
</main>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
import { ResetPasswordForm } from "~/components/auth/reset-password-form";
|
|
4
|
+
|
|
5
|
+
// The link in the reset email lands here with ?token=..., or with ?error=...
|
|
6
|
+
// when the token is invalid or has expired
|
|
7
|
+
export const Route = createFileRoute("/reset-password")({
|
|
8
|
+
validateSearch: (search: Record<string, unknown>) => ({
|
|
9
|
+
token: typeof search.token === "string" ? search.token : undefined,
|
|
10
|
+
error: typeof search.error === "string" ? search.error : undefined,
|
|
11
|
+
}),
|
|
12
|
+
component: ResetPasswordPage,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function ResetPasswordPage() {
|
|
16
|
+
const { token, error } = Route.useSearch();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<main className="flex min-h-svh items-center justify-center bg-muted p-6 md:p-10">
|
|
20
|
+
<div className="w-full max-w-md">
|
|
21
|
+
<ResetPasswordForm token={error ? null : (token ?? null)} />
|
|
22
|
+
</div>
|
|
23
|
+
</main>
|
|
24
|
+
);
|
|
25
|
+
}
|