better-ts-stack 2.0.0 → 2.0.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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/args.js +2 -2
  4. package/dist/builder/buildPlanner.js +71 -0
  5. package/dist/builder/configGenerator.js +9 -7
  6. package/dist/builder/dependencyInstaller.js +0 -1
  7. package/dist/builder/fileProcessor.js +47 -44
  8. package/dist/builder/gitInitializer.js +20 -1
  9. package/dist/builder/index.js +19 -38
  10. package/dist/builder/moduleSelector.js +0 -1
  11. package/dist/builder/templateContext.js +0 -22
  12. package/dist/index.js +4 -3
  13. package/dist/modules/registry.js +4 -6
  14. package/dist/output/nextSteps.js +0 -1
  15. package/dist/prompts/index.js +0 -1
  16. package/dist/prompts/stack.js +4 -1
  17. package/dist/types/index.js +7 -3
  18. package/dist/ui/index.js +0 -1
  19. package/dist/ui/messages.js +25 -1
  20. package/dist/ui/prompts.js +0 -1
  21. package/dist/ui/theme.js +0 -1
  22. package/dist/validators/index.js +10 -41
  23. package/package.json +2 -1
  24. package/templates/backend/express/.prettierrc +2 -2
  25. package/templates/backend/express/config.json +1 -5
  26. package/templates/backend/express/eslint.config.mjs +40 -40
  27. package/templates/backend/express/gitignore +36 -36
  28. package/templates/backend/express/src/{index.ts → index.ts.hbs} +28 -29
  29. package/templates/backend/express/src/routes/health.ts +12 -12
  30. package/templates/backend/express/tsconfig.json +25 -25
  31. package/templates/backend/nestjs/.prettierrc +16 -16
  32. package/templates/backend/nestjs/config.json +1 -4
  33. package/templates/backend/nestjs/eslint.config.mjs +40 -40
  34. package/templates/backend/nestjs/gitignore +36 -36
  35. package/templates/backend/nestjs/nest-cli.json +9 -9
  36. package/templates/backend/nestjs/src/app.module.ts.hbs +34 -34
  37. package/templates/backend/nestjs/src/health/health.controller.ts +12 -12
  38. package/templates/backend/nestjs/src/main.ts +23 -23
  39. package/templates/backend/nestjs/tsconfig.json +25 -25
  40. package/templates/frontend/nextjs/components.json +19 -19
  41. package/templates/frontend/nextjs/config.json +1 -2
  42. package/templates/frontend/nextjs/gitignore +42 -42
  43. package/templates/frontend/nextjs/lib/utils.ts +6 -6
  44. package/templates/frontend/nextjs/proxy.ts.hbs +24 -24
  45. package/templates/frontend/nextjs/tsconfig.json +34 -34
  46. package/templates/frontend/tanstack/config.json +1 -4
  47. package/templates/frontend/tanstack/src/routeTree.gen.ts +68 -68
  48. package/templates/frontend/tanstack/src/styles/app.css +85 -85
  49. package/templates/modules/auth/express/config.json +1 -8
  50. package/templates/modules/auth/express/src/controllers/authController.ts.hbs +116 -116
  51. package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +26 -26
  52. package/templates/modules/auth/express/src/services/userStore.ts.hbs +117 -117
  53. package/templates/modules/auth/nestjs/config.json +1 -5
  54. package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -46
  55. package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -36
  56. package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -22
  57. package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -35
  58. package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -70
  59. package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -27
  60. package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -144
  61. package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -16
  62. package/templates/modules/auth/nextjs/app/dashboard/page.tsx +96 -96
  63. package/templates/modules/auth/nextjs/app/sign-in/page.tsx +35 -35
  64. package/templates/modules/auth/nextjs/app/sign-up/page.tsx +35 -35
  65. package/templates/modules/auth/nextjs/components/auth/sign-in-form.tsx +132 -132
  66. package/templates/modules/auth/nextjs/components/auth/sign-out-button.tsx +50 -50
  67. package/templates/modules/auth/nextjs/components/auth/sign-up-form.tsx +152 -152
  68. package/templates/modules/auth/nextjs/config.json +2 -15
  69. package/templates/modules/auth/nextjs/lib/auth-schema.ts +39 -39
  70. package/templates/modules/auth/nextjs/lib/auth.ts.hbs +37 -37
  71. package/templates/modules/auth/tanstack/config.json +2 -16
  72. package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +4 -4
  73. package/templates/modules/auth/tanstack/src/routeTree.gen.ts +140 -140
  74. package/templates/modules/docker/.dockerignore +14 -14
  75. package/templates/modules/docker/Dockerfile.hbs +1 -3
  76. package/templates/modules/docker/config.json +1 -5
  77. package/templates/modules/docker/docker-compose.yml.hbs +9 -9
  78. package/templates/modules/drizzle/express/config.json +1 -2
  79. package/templates/modules/drizzle/express/drizzle.config.ts +13 -13
  80. package/templates/modules/drizzle/express/src/lib/db.ts +13 -13
  81. package/templates/modules/drizzle/express/src/lib/schema.ts +10 -10
  82. package/templates/modules/drizzle/nestjs/config.json +1 -2
  83. package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -13
  84. package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -30
  85. package/templates/modules/drizzle/nestjs/src/database/schema.ts +10 -10
  86. package/templates/modules/drizzle/nextjs/config.json +1 -4
  87. package/templates/modules/drizzle/nextjs/lib/db.ts +13 -13
  88. package/templates/modules/drizzle/nextjs/lib/schema.ts.hbs +84 -84
  89. package/templates/modules/drizzle/tanstack/config.json +5 -5
  90. package/templates/modules/mongoose/express/config.json +1 -2
  91. package/templates/modules/mongoose/nestjs/config.json +1 -2
  92. package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -21
  93. package/templates/modules/mongoose/nextjs/config.json +1 -2
  94. package/templates/modules/mongoose/nextjs/lib/db.ts +36 -36
  95. package/templates/modules/mongoose/tanstack/config.json +1 -2
  96. package/templates/modules/mongoose/tanstack/src/lib/db.ts +37 -37
  97. package/templates/modules/prisma/express/config.json +0 -1
  98. package/templates/modules/prisma/express/prisma/schema.prisma +1 -1
  99. package/templates/modules/prisma/express/prisma.config.ts +12 -12
  100. package/templates/modules/prisma/express/src/lib/prisma.ts +24 -24
  101. package/templates/modules/prisma/nestjs/config.json +0 -1
  102. package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -23
  103. package/templates/modules/prisma/nestjs/prisma.config.ts +12 -12
  104. package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -11
  105. package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -17
  106. package/templates/modules/prisma/nextjs/config.json +0 -1
  107. package/templates/modules/prisma/nextjs/prisma/schema.prisma.hbs +91 -91
  108. package/templates/modules/prisma/tanstack/config.json +0 -1
  109. package/dist/args.d.ts +0 -12
  110. package/dist/args.d.ts.map +0 -1
  111. package/dist/args.js.map +0 -1
  112. package/dist/builder/configGenerator.d.ts +0 -7
  113. package/dist/builder/configGenerator.d.ts.map +0 -1
  114. package/dist/builder/configGenerator.js.map +0 -1
  115. package/dist/builder/dependencyInstaller.d.ts +0 -3
  116. package/dist/builder/dependencyInstaller.d.ts.map +0 -1
  117. package/dist/builder/dependencyInstaller.js.map +0 -1
  118. package/dist/builder/fileProcessor.d.ts +0 -6
  119. package/dist/builder/fileProcessor.d.ts.map +0 -1
  120. package/dist/builder/fileProcessor.js.map +0 -1
  121. package/dist/builder/gitInitializer.d.ts +0 -2
  122. package/dist/builder/gitInitializer.d.ts.map +0 -1
  123. package/dist/builder/gitInitializer.js.map +0 -1
  124. package/dist/builder/index.d.ts +0 -3
  125. package/dist/builder/index.d.ts.map +0 -1
  126. package/dist/builder/index.js.map +0 -1
  127. package/dist/builder/moduleSelector.d.ts +0 -6
  128. package/dist/builder/moduleSelector.d.ts.map +0 -1
  129. package/dist/builder/moduleSelector.js.map +0 -1
  130. package/dist/builder/templateContext.d.ts +0 -6
  131. package/dist/builder/templateContext.d.ts.map +0 -1
  132. package/dist/builder/templateContext.js.map +0 -1
  133. package/dist/index.d.ts +0 -2
  134. package/dist/index.d.ts.map +0 -1
  135. package/dist/index.js.map +0 -1
  136. package/dist/modules/registry.d.ts +0 -6
  137. package/dist/modules/registry.d.ts.map +0 -1
  138. package/dist/modules/registry.js.map +0 -1
  139. package/dist/output/nextSteps.d.ts +0 -3
  140. package/dist/output/nextSteps.d.ts.map +0 -1
  141. package/dist/output/nextSteps.js.map +0 -1
  142. package/dist/prompts/index.d.ts +0 -4
  143. package/dist/prompts/index.d.ts.map +0 -1
  144. package/dist/prompts/index.js.map +0 -1
  145. package/dist/prompts/stack.d.ts +0 -4
  146. package/dist/prompts/stack.d.ts.map +0 -1
  147. package/dist/prompts/stack.js.map +0 -1
  148. package/dist/types/index.d.ts +0 -137
  149. package/dist/types/index.d.ts.map +0 -1
  150. package/dist/types/index.js.map +0 -1
  151. package/dist/ui/index.d.ts +0 -3
  152. package/dist/ui/index.d.ts.map +0 -1
  153. package/dist/ui/index.js.map +0 -1
  154. package/dist/ui/messages.d.ts +0 -9
  155. package/dist/ui/messages.d.ts.map +0 -1
  156. package/dist/ui/messages.js.map +0 -1
  157. package/dist/ui/prompts.d.ts +0 -20
  158. package/dist/ui/prompts.d.ts.map +0 -1
  159. package/dist/ui/prompts.js.map +0 -1
  160. package/dist/ui/theme.d.ts +0 -9
  161. package/dist/ui/theme.d.ts.map +0 -1
  162. package/dist/ui/theme.js.map +0 -1
  163. package/dist/validators/index.d.ts +0 -6
  164. package/dist/validators/index.d.ts.map +0 -1
  165. package/dist/validators/index.js.map +0 -1
@@ -1,27 +1,27 @@
1
- import jwt, { type SignOptions } from "jsonwebtoken";
2
-
3
- export interface AuthTokenPayload {
4
- sub: string;
5
- email: string;
6
- name?: string | null;
7
- }
8
-
9
- const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
10
-
11
- function ensureSecret(): string {
12
- if (!JWT_SECRET) {
13
- throw new Error("JWT_SECRET is not set");
14
- }
15
- return JWT_SECRET;
16
- }
17
-
18
- export function signToken(payload: AuthTokenPayload): string {
19
- const secret = ensureSecret();
20
- const expiresIn = JWT_EXPIRES_IN || "1h";
21
- return jwt.sign(payload, secret, { expiresIn } as SignOptions);
22
- }
23
-
24
- export function verifyToken(token: string): AuthTokenPayload {
25
- const secret = ensureSecret();
26
- return jwt.verify(token, secret) as AuthTokenPayload;
27
- }
1
+ import jwt, { type SignOptions } from "jsonwebtoken";
2
+
3
+ export interface AuthTokenPayload {
4
+ sub: string;
5
+ email: string;
6
+ name?: string | null;
7
+ }
8
+
9
+ const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
10
+
11
+ function ensureSecret(): string {
12
+ if (!JWT_SECRET) {
13
+ throw new Error("JWT_SECRET is not set");
14
+ }
15
+ return JWT_SECRET;
16
+ }
17
+
18
+ export function signToken(payload: AuthTokenPayload): string {
19
+ const secret = ensureSecret();
20
+ const expiresIn = JWT_EXPIRES_IN || "1h";
21
+ return jwt.sign(payload, secret, { expiresIn } as SignOptions);
22
+ }
23
+
24
+ export function verifyToken(token: string): AuthTokenPayload {
25
+ const secret = ensureSecret();
26
+ return jwt.verify(token, secret) as AuthTokenPayload;
27
+ }
@@ -1,144 +1,144 @@
1
- {{#if (eq database 'drizzle')}}
2
- import { Inject, Injectable } from "@nestjs/common";
3
- import { eq } from "drizzle-orm";
4
-
5
- import { type Database, DRIZZLE } from "../database/database.module.js";
6
- import { users } from "../database/schema.js";
7
- {{/if}}
8
- {{#if (eq database 'prisma')}}
9
- import { Injectable } from "@nestjs/common";
10
-
11
- import { PrismaService } from "../prisma/prisma.service.js";
12
- {{/if}}
13
- {{#if (eq database 'mongoose')}}
14
- import { Injectable } from "@nestjs/common";
15
- import { InjectModel } from "@nestjs/mongoose";
16
- import type { Model } from "mongoose";
17
-
18
- import { User } from "../users/user.schema.js";
19
- {{/if}}
20
- {{#if (eq database 'none')}}
21
- import { Injectable } from "@nestjs/common";
22
- import { randomUUID } from "node:crypto";
23
- {{/if}}
24
-
25
- export interface AuthUser {
26
- id: string;
27
- email: string;
28
- name?: string | null;
29
- password: string;
30
- }
31
-
32
- export interface CreateUserInput {
33
- email: string;
34
- name?: string | null;
35
- password: string;
36
- }
37
-
38
- @Injectable()
39
- export class UsersService {
40
- {{#if (eq database 'drizzle')}}
41
- constructor(@Inject(DRIZZLE) private readonly db: Database) {}
42
-
43
- async findByEmail(email: string): Promise<AuthUser | null> {
44
- const [user] = await this.db
45
- .select()
46
- .from(users)
47
- .where(eq(users.email, email))
48
- .limit(1);
49
- return user ?? null;
50
- }
51
-
52
- async create(data: CreateUserInput): Promise<AuthUser> {
53
- const [user] = await this.db.insert(users).values(data).returning();
54
- return user;
55
- }
56
- {{/if}}
57
- {{#if (eq database 'prisma')}}
58
- constructor(private readonly prisma: PrismaService) {}
59
-
60
- findByEmail(email: string): Promise<AuthUser | null> {
61
- return this.prisma.user.findUnique({ where: { email } });
62
- }
63
-
64
- create(data: CreateUserInput): Promise<AuthUser> {
65
- return this.prisma.user.create({ data });
66
- }
67
- {{/if}}
68
- {{#if (eq database 'mongoose')}}
69
- constructor(
70
- @InjectModel(User.name) private readonly userModel: Model<User>
71
- ) {}
72
-
73
- async findByEmail(email: string): Promise<AuthUser | null> {
74
- const user = await this.userModel.findOne({ email }).exec();
75
- if (!user) return null;
76
- return {
77
- id: user._id.toString(),
78
- email: user.email,
79
- name: user.name,
80
- password: user.password,
81
- };
82
- }
83
-
84
- async create(data: CreateUserInput): Promise<AuthUser> {
85
- const user = await this.userModel.create(data);
86
- return {
87
- id: user._id.toString(),
88
- email: user.email,
89
- name: user.name,
90
- password: user.password,
91
- };
92
- }
93
- {{/if}}
94
- {{#if (eq database 'none')}}
95
- // In-memory store for trying the API out: users are lost on restart, so add
96
- // a database before going to production
97
- private readonly users: AuthUser[] = [];
98
-
99
- findByEmail(email: string): Promise<AuthUser | null> {
100
- const user = this.users.find((candidate) => candidate.email === email);
101
- return Promise.resolve(user ?? null);
102
- }
103
-
104
- create(data: CreateUserInput): Promise<AuthUser> {
105
- // Enforce unique emails like a database constraint would, so a sign-up that
106
- // raced past the findByEmail check is rejected instead of duplicated
107
- if (this.users.some((candidate) => candidate.email === data.email)) {
108
- return Promise.reject(
109
- Object.assign(new Error("User already exists"), {
110
- code: "DUPLICATE_EMAIL",
111
- })
112
- );
113
- }
114
-
115
- const user: AuthUser = { id: randomUUID(), ...data };
116
- this.users.push(user);
117
- return Promise.resolve(user);
118
- }
119
- {{/if}}
120
- }
121
-
122
- // Unique-constraint error codes: Prisma, PostgreSQL (Drizzle wraps it in `cause`),
123
- // MongoDB, and the in-memory store
124
- const DUPLICATE_KEY_CODES = new Set<unknown>([
125
- "P2002",
126
- "23505",
127
- 11000,
128
- "DUPLICATE_EMAIL",
129
- ]);
130
-
131
- // True when an insert failed because the email is already taken (e.g. concurrent sign-ups)
132
- export function isDuplicateEmailError(error: unknown): boolean {
133
- let current = error;
134
- for (
135
- let depth = 0;
136
- depth < 5 && current && typeof current === "object";
137
- depth++
138
- ) {
139
- if (DUPLICATE_KEY_CODES.has((current as { code?: unknown }).code))
140
- return true;
141
- current = (current as { cause?: unknown }).cause;
142
- }
143
- return false;
144
- }
1
+ {{#if (eq database 'drizzle')}}
2
+ import { Inject, Injectable } from "@nestjs/common";
3
+ import { eq } from "drizzle-orm";
4
+
5
+ import { type Database, DRIZZLE } from "../database/database.module.js";
6
+ import { users } from "../database/schema.js";
7
+ {{/if}}
8
+ {{#if (eq database 'prisma')}}
9
+ import { Injectable } from "@nestjs/common";
10
+
11
+ import { PrismaService } from "../prisma/prisma.service.js";
12
+ {{/if}}
13
+ {{#if (eq database 'mongoose')}}
14
+ import { Injectable } from "@nestjs/common";
15
+ import { InjectModel } from "@nestjs/mongoose";
16
+ import type { Model } from "mongoose";
17
+
18
+ import { User } from "../users/user.schema.js";
19
+ {{/if}}
20
+ {{#if (eq database 'none')}}
21
+ import { Injectable } from "@nestjs/common";
22
+ import { randomUUID } from "node:crypto";
23
+ {{/if}}
24
+
25
+ export interface AuthUser {
26
+ id: string;
27
+ email: string;
28
+ name?: string | null;
29
+ password: string;
30
+ }
31
+
32
+ export interface CreateUserInput {
33
+ email: string;
34
+ name?: string | null;
35
+ password: string;
36
+ }
37
+
38
+ @Injectable()
39
+ export class UsersService {
40
+ {{#if (eq database 'drizzle')}}
41
+ constructor(@Inject(DRIZZLE) private readonly db: Database) {}
42
+
43
+ async findByEmail(email: string): Promise<AuthUser | null> {
44
+ const [user] = await this.db
45
+ .select()
46
+ .from(users)
47
+ .where(eq(users.email, email))
48
+ .limit(1);
49
+ return user ?? null;
50
+ }
51
+
52
+ async create(data: CreateUserInput): Promise<AuthUser> {
53
+ const [user] = await this.db.insert(users).values(data).returning();
54
+ return user;
55
+ }
56
+ {{/if}}
57
+ {{#if (eq database 'prisma')}}
58
+ constructor(private readonly prisma: PrismaService) {}
59
+
60
+ findByEmail(email: string): Promise<AuthUser | null> {
61
+ return this.prisma.user.findUnique({ where: { email } });
62
+ }
63
+
64
+ create(data: CreateUserInput): Promise<AuthUser> {
65
+ return this.prisma.user.create({ data });
66
+ }
67
+ {{/if}}
68
+ {{#if (eq database 'mongoose')}}
69
+ constructor(
70
+ @InjectModel(User.name) private readonly userModel: Model<User>
71
+ ) {}
72
+
73
+ async findByEmail(email: string): Promise<AuthUser | null> {
74
+ const user = await this.userModel.findOne({ email }).exec();
75
+ if (!user) return null;
76
+ return {
77
+ id: user._id.toString(),
78
+ email: user.email,
79
+ name: user.name,
80
+ password: user.password,
81
+ };
82
+ }
83
+
84
+ async create(data: CreateUserInput): Promise<AuthUser> {
85
+ const user = await this.userModel.create(data);
86
+ return {
87
+ id: user._id.toString(),
88
+ email: user.email,
89
+ name: user.name,
90
+ password: user.password,
91
+ };
92
+ }
93
+ {{/if}}
94
+ {{#if (eq database 'none')}}
95
+ // In-memory store for trying the API out: users are lost on restart, so add
96
+ // a database before going to production
97
+ private readonly users: AuthUser[] = [];
98
+
99
+ findByEmail(email: string): Promise<AuthUser | null> {
100
+ const user = this.users.find((candidate) => candidate.email === email);
101
+ return Promise.resolve(user ?? null);
102
+ }
103
+
104
+ create(data: CreateUserInput): Promise<AuthUser> {
105
+ // Enforce unique emails like a database constraint would, so a sign-up that
106
+ // raced past the findByEmail check is rejected instead of duplicated
107
+ if (this.users.some((candidate) => candidate.email === data.email)) {
108
+ return Promise.reject(
109
+ Object.assign(new Error("User already exists"), {
110
+ code: "DUPLICATE_EMAIL",
111
+ })
112
+ );
113
+ }
114
+
115
+ const user: AuthUser = { id: randomUUID(), ...data };
116
+ this.users.push(user);
117
+ return Promise.resolve(user);
118
+ }
119
+ {{/if}}
120
+ }
121
+
122
+ // Unique-constraint error codes: Prisma, PostgreSQL (Drizzle wraps it in `cause`),
123
+ // MongoDB, and the in-memory store
124
+ const DUPLICATE_KEY_CODES = new Set<unknown>([
125
+ "P2002",
126
+ "23505",
127
+ 11000,
128
+ "DUPLICATE_EMAIL",
129
+ ]);
130
+
131
+ // True when an insert failed because the email is already taken (e.g. concurrent sign-ups)
132
+ export function isDuplicateEmailError(error: unknown): boolean {
133
+ let current = error;
134
+ for (
135
+ let depth = 0;
136
+ depth < 5 && current && typeof current === "object";
137
+ depth++
138
+ ) {
139
+ if (DUPLICATE_KEY_CODES.has((current as { code?: unknown }).code))
140
+ return true;
141
+ current = (current as { cause?: unknown }).cause;
142
+ }
143
+ return false;
144
+ }
@@ -1,16 +1,16 @@
1
- import { BadRequestException, type PipeTransform } from "@nestjs/common";
2
- import type { ZodType } from "zod";
3
-
4
- // Validates a request body against a Zod schema and returns the parsed value
5
- export class ZodValidationPipe<T> implements PipeTransform<unknown, T> {
6
- constructor(private readonly schema: ZodType<T>) {}
7
-
8
- transform(value: unknown): T {
9
- // The body is undefined when the request has none
10
- const result = this.schema.safeParse(value ?? {});
11
- if (!result.success) {
12
- throw new BadRequestException(result.error.issues[0].message);
13
- }
14
- return result.data;
15
- }
16
- }
1
+ import { BadRequestException, type PipeTransform } from "@nestjs/common";
2
+ import type { ZodType } from "zod";
3
+
4
+ // Validates a request body against a Zod schema and returns the parsed value
5
+ export class ZodValidationPipe<T> implements PipeTransform<unknown, T> {
6
+ constructor(private readonly schema: ZodType<T>) {}
7
+
8
+ transform(value: unknown): T {
9
+ // The body is undefined when the request has none
10
+ const result = this.schema.safeParse(value ?? {});
11
+ if (!result.success) {
12
+ throw new BadRequestException(result.error.issues[0].message);
13
+ }
14
+ return result.data;
15
+ }
16
+ }
@@ -1,96 +1,96 @@
1
- import Link from "next/link";
2
- import { headers } from "next/headers";
3
- import { redirect } from "next/navigation";
4
- import { ArrowRight, ShieldCheck } from "lucide-react";
5
-
6
- import { SignOutButton } from "@/components/auth/sign-out-button";
7
- import { buttonVariants } from "@/components/ui/button";
8
- import {
9
- Card,
10
- CardContent,
11
- CardDescription,
12
- CardHeader,
13
- CardTitle,
14
- } from "@/components/ui/card";
15
- import { auth } from "@/lib/auth";
16
- import { cn } from "@/lib/utils";
17
-
18
- export default async function DashboardPage() {
19
- const session = await auth.api.getSession({
20
- headers: await headers(),
21
- });
22
-
23
- if (!session) {
24
- redirect("/sign-in");
25
- }
26
-
27
- return (
28
- <main className="min-h-screen bg-[linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_100%)] px-6 py-16 sm:px-10">
29
- <div className="mx-auto max-w-5xl space-y-8">
30
- <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">
31
- <div className="space-y-3">
32
- <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">
33
- <ShieldCheck className="size-4" />
34
- Authenticated session
35
- </span>
36
- <div className="space-y-1">
37
- <h1 className="text-3xl font-semibold tracking-tight text-slate-950">
38
- Welcome {session.user.name}
39
- </h1>
40
- <p className="text-sm text-slate-600">{session.user.email}</p>
41
- </div>
42
- </div>
43
- <SignOutButton />
44
- </header>
45
-
46
- <section className="grid gap-5 md:grid-cols-2">
47
- <Card>
48
- <CardHeader>
49
- <CardTitle>Protected route example</CardTitle>
50
- <CardDescription>
51
- This page verifies the session on the server before rendering any
52
- content.
53
- </CardDescription>
54
- </CardHeader>
55
- <CardContent className="space-y-4 text-sm text-slate-600">
56
- <p>
57
- The generated proxy only performs an optimistic cookie check. The
58
- authoritative guard happens here with{" "}
59
- <code className="font-mono text-slate-900">
60
- auth.api.getSession({"{"} headers: await headers() {"}"})
61
- </code>
62
- .
63
- </p>
64
- <p>
65
- That keeps route protection aligned with Better Auth&apos;s
66
- Next.js guidance while staying easy to extend.
67
- </p>
68
- </CardContent>
69
- </Card>
70
-
71
- <Card>
72
- <CardHeader>
73
- <CardTitle>Suggested next steps</CardTitle>
74
- <CardDescription>
75
- Keep the generated auth flow, then customize the app around your
76
- product.
77
- </CardDescription>
78
- </CardHeader>
79
- <CardContent className="space-y-3 text-sm text-slate-600">
80
- <p>Replace the demo dashboard with your first authenticated page.</p>
81
- <p>Extend the schema with profile or onboarding fields.</p>
82
- <p>Add provider-based auth or email verification later if needed.</p>
83
- <Link
84
- href="/"
85
- className={cn(buttonVariants({ variant: "outline" }), "w-fit gap-2")}
86
- >
87
- Back to home
88
- <ArrowRight className="size-4" />
89
- </Link>
90
- </CardContent>
91
- </Card>
92
- </section>
93
- </div>
94
- </main>
95
- );
96
- }
1
+ import Link from "next/link";
2
+ import { headers } from "next/headers";
3
+ import { redirect } from "next/navigation";
4
+ import { ArrowRight, ShieldCheck } from "lucide-react";
5
+
6
+ import { SignOutButton } from "@/components/auth/sign-out-button";
7
+ import { buttonVariants } from "@/components/ui/button";
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardDescription,
12
+ CardHeader,
13
+ CardTitle,
14
+ } from "@/components/ui/card";
15
+ import { auth } from "@/lib/auth";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ export default async function DashboardPage() {
19
+ const session = await auth.api.getSession({
20
+ headers: await headers(),
21
+ });
22
+
23
+ if (!session) {
24
+ redirect("/sign-in");
25
+ }
26
+
27
+ return (
28
+ <main className="min-h-screen bg-[linear-gradient(180deg,_#f8fafc_0%,_#eef2ff_100%)] px-6 py-16 sm:px-10">
29
+ <div className="mx-auto max-w-5xl space-y-8">
30
+ <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">
31
+ <div className="space-y-3">
32
+ <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">
33
+ <ShieldCheck className="size-4" />
34
+ Authenticated session
35
+ </span>
36
+ <div className="space-y-1">
37
+ <h1 className="text-3xl font-semibold tracking-tight text-slate-950">
38
+ Welcome {session.user.name}
39
+ </h1>
40
+ <p className="text-sm text-slate-600">{session.user.email}</p>
41
+ </div>
42
+ </div>
43
+ <SignOutButton />
44
+ </header>
45
+
46
+ <section className="grid gap-5 md:grid-cols-2">
47
+ <Card>
48
+ <CardHeader>
49
+ <CardTitle>Protected route example</CardTitle>
50
+ <CardDescription>
51
+ This page verifies the session on the server before rendering any
52
+ content.
53
+ </CardDescription>
54
+ </CardHeader>
55
+ <CardContent className="space-y-4 text-sm text-slate-600">
56
+ <p>
57
+ The generated proxy only performs an optimistic cookie check. The
58
+ authoritative guard happens here with{" "}
59
+ <code className="font-mono text-slate-900">
60
+ auth.api.getSession({"{"} headers: await headers() {"}"})
61
+ </code>
62
+ .
63
+ </p>
64
+ <p>
65
+ That keeps route protection aligned with Better Auth&apos;s
66
+ Next.js guidance while staying easy to extend.
67
+ </p>
68
+ </CardContent>
69
+ </Card>
70
+
71
+ <Card>
72
+ <CardHeader>
73
+ <CardTitle>Suggested next steps</CardTitle>
74
+ <CardDescription>
75
+ Keep the generated auth flow, then customize the app around your
76
+ product.
77
+ </CardDescription>
78
+ </CardHeader>
79
+ <CardContent className="space-y-3 text-sm text-slate-600">
80
+ <p>Replace the demo dashboard with your first authenticated page.</p>
81
+ <p>Extend the schema with profile or onboarding fields.</p>
82
+ <p>Add provider-based auth or email verification later if needed.</p>
83
+ <Link
84
+ href="/"
85
+ className={cn(buttonVariants({ variant: "outline" }), "w-fit gap-2")}
86
+ >
87
+ Back to home
88
+ <ArrowRight className="size-4" />
89
+ </Link>
90
+ </CardContent>
91
+ </Card>
92
+ </section>
93
+ </div>
94
+ </main>
95
+ );
96
+ }
@@ -1,35 +1,35 @@
1
- import { headers } from "next/headers";
2
- import { redirect } from "next/navigation";
3
-
4
- import { SignInForm } from "@/components/auth/sign-in-form";
5
- import { auth } from "@/lib/auth";
6
-
7
- export default async function SignInPage() {
8
- const session = await auth.api.getSession({
9
- headers: await headers(),
10
- });
11
-
12
- if (session) {
13
- redirect("/dashboard");
14
- }
15
-
16
- return (
17
- <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">
18
- <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]">
19
- <section className="hidden space-y-5 lg:block">
20
- <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">
21
- Better Auth
22
- </span>
23
- <h1 className="text-4xl font-semibold tracking-tight text-slate-950">
24
- Welcome back.
25
- </h1>
26
- <p className="max-w-md text-base leading-7 text-slate-600">
27
- This generated starter includes protected routes, session-aware page
28
- guards, and reusable auth form primitives you can extend from here.
29
- </p>
30
- </section>
31
- <SignInForm />
32
- </div>
33
- </main>
34
- );
35
- }
1
+ import { headers } from "next/headers";
2
+ import { redirect } from "next/navigation";
3
+
4
+ import { SignInForm } from "@/components/auth/sign-in-form";
5
+ import { auth } from "@/lib/auth";
6
+
7
+ export default async function SignInPage() {
8
+ const session = await auth.api.getSession({
9
+ headers: await headers(),
10
+ });
11
+
12
+ if (session) {
13
+ redirect("/dashboard");
14
+ }
15
+
16
+ return (
17
+ <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">
18
+ <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]">
19
+ <section className="hidden space-y-5 lg:block">
20
+ <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">
21
+ Better Auth
22
+ </span>
23
+ <h1 className="text-4xl font-semibold tracking-tight text-slate-950">
24
+ Welcome back.
25
+ </h1>
26
+ <p className="max-w-md text-base leading-7 text-slate-600">
27
+ This generated starter includes protected routes, session-aware page
28
+ guards, and reusable auth form primitives you can extend from here.
29
+ </p>
30
+ </section>
31
+ <SignInForm />
32
+ </div>
33
+ </main>
34
+ );
35
+ }