create-velox-app 0.6.29 → 0.6.51

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 (223) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/GUIDE.md +230 -0
  3. package/dist/cli.d.ts +0 -1
  4. package/dist/cli.js +1 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +14 -5
  7. package/dist/templates/auth.d.ts +0 -1
  8. package/dist/templates/auth.js +10 -1
  9. package/dist/templates/compiler.d.ts +0 -1
  10. package/dist/templates/compiler.js +0 -1
  11. package/dist/templates/index.d.ts +0 -1
  12. package/dist/templates/index.js +30 -2
  13. package/dist/templates/placeholders.d.ts +6 -1
  14. package/dist/templates/placeholders.js +55 -1
  15. package/dist/templates/rsc-auth.d.ts +12 -0
  16. package/dist/templates/rsc-auth.js +208 -0
  17. package/dist/templates/rsc.d.ts +0 -1
  18. package/dist/templates/rsc.js +40 -2
  19. package/dist/templates/shared/css-generator.d.ts +26 -0
  20. package/dist/templates/shared/css-generator.js +553 -0
  21. package/dist/templates/shared/index.d.ts +3 -1
  22. package/dist/templates/shared/index.js +3 -1
  23. package/dist/templates/shared/root.d.ts +0 -1
  24. package/dist/templates/shared/root.js +11 -2
  25. package/dist/templates/shared/rsc-styles.d.ts +54 -0
  26. package/dist/templates/shared/rsc-styles.js +68 -0
  27. package/dist/templates/shared/theme.d.ts +133 -0
  28. package/dist/templates/shared/theme.js +141 -0
  29. package/dist/templates/shared/web-base.d.ts +0 -1
  30. package/dist/templates/shared/web-base.js +0 -1
  31. package/dist/templates/shared/web-styles.d.ts +0 -1
  32. package/dist/templates/shared/web-styles.js +0 -1
  33. package/dist/templates/shared.d.ts +0 -1
  34. package/dist/templates/shared.js +0 -1
  35. package/dist/templates/spa.d.ts +0 -1
  36. package/dist/templates/spa.js +10 -1
  37. package/dist/templates/trpc.d.ts +0 -1
  38. package/dist/templates/trpc.js +10 -1
  39. package/dist/templates/types.d.ts +2 -2
  40. package/dist/templates/types.js +6 -1
  41. package/package.json +6 -3
  42. package/src/templates/source/api/config/database.ts +13 -32
  43. package/src/templates/source/api/docker-compose.yml +21 -0
  44. package/src/templates/source/root/CLAUDE.auth.md +6 -0
  45. package/src/templates/source/root/CLAUDE.default.md +6 -0
  46. package/src/templates/source/root/package.json +6 -6
  47. package/src/templates/source/rsc/CLAUDE.md +56 -2
  48. package/src/templates/source/rsc/app/actions/posts.ts +1 -1
  49. package/src/templates/source/rsc/app/actions/users.ts +111 -20
  50. package/src/templates/source/rsc/app/layouts/dashboard.tsx +21 -16
  51. package/src/templates/source/rsc/app/layouts/marketing.tsx +34 -0
  52. package/src/templates/source/rsc/app/layouts/minimal-content.tsx +21 -0
  53. package/src/templates/source/rsc/app/layouts/minimal.tsx +86 -5
  54. package/src/templates/source/rsc/app/layouts/root.tsx +148 -44
  55. package/src/templates/source/rsc/docker-compose.yml +21 -0
  56. package/src/templates/source/rsc/package.json +3 -3
  57. package/src/templates/source/rsc/src/api/database.ts +13 -32
  58. package/src/templates/source/rsc/src/api/handler.ts +1 -1
  59. package/src/templates/source/rsc/src/entry.client.tsx +65 -18
  60. package/src/templates/source/rsc-auth/CLAUDE.md +230 -0
  61. package/src/templates/source/rsc-auth/app/actions/auth.ts +112 -0
  62. package/src/templates/source/rsc-auth/app/actions/users.ts +289 -0
  63. package/src/templates/source/rsc-auth/app/layouts/dashboard.tsx +132 -0
  64. package/src/templates/source/rsc-auth/app/layouts/marketing.tsx +59 -0
  65. package/src/templates/source/rsc-auth/app/layouts/minimal-content.tsx +21 -0
  66. package/src/templates/source/rsc-auth/app/layouts/minimal.tsx +111 -0
  67. package/src/templates/source/rsc-auth/app/layouts/root.tsx +355 -0
  68. package/src/templates/source/rsc-auth/app/pages/_not-found.tsx +15 -0
  69. package/src/templates/source/rsc-auth/app/pages/auth/login.tsx +198 -0
  70. package/src/templates/source/rsc-auth/app/pages/auth/register.tsx +225 -0
  71. package/src/templates/source/rsc-auth/app/pages/dashboard/index.tsx +267 -0
  72. package/src/templates/source/rsc-auth/app/pages/index.tsx +83 -0
  73. package/src/templates/source/rsc-auth/app/pages/users.tsx +47 -0
  74. package/src/templates/source/rsc-auth/app.config.ts +12 -0
  75. package/src/templates/source/rsc-auth/docker-compose.yml +21 -0
  76. package/src/templates/source/rsc-auth/env.example +11 -0
  77. package/src/templates/source/rsc-auth/gitignore +34 -0
  78. package/src/templates/source/rsc-auth/package.json +44 -0
  79. package/src/templates/source/rsc-auth/prisma/schema.prisma +23 -0
  80. package/src/templates/source/rsc-auth/prisma.config.ts +22 -0
  81. package/src/templates/source/rsc-auth/public/favicon.svg +4 -0
  82. package/src/templates/source/rsc-auth/src/api/database.ts +129 -0
  83. package/src/templates/source/rsc-auth/src/api/handler.ts +85 -0
  84. package/src/templates/source/rsc-auth/src/api/procedures/auth.ts +262 -0
  85. package/src/templates/source/rsc-auth/src/api/procedures/health.ts +48 -0
  86. package/src/templates/source/rsc-auth/src/api/procedures/users.ts +87 -0
  87. package/src/templates/source/rsc-auth/src/api/schemas/auth.ts +79 -0
  88. package/src/templates/source/rsc-auth/src/api/schemas/user.ts +38 -0
  89. package/src/templates/source/rsc-auth/src/api/utils/auth.ts +157 -0
  90. package/src/templates/source/rsc-auth/src/entry.client.tsx +63 -0
  91. package/src/templates/source/rsc-auth/src/entry.server.tsx +262 -0
  92. package/src/templates/source/rsc-auth/tsconfig.json +24 -0
  93. package/src/templates/source/shared/scripts/check-client-imports.sh +75 -0
  94. package/dist/cli.d.ts.map +0 -1
  95. package/dist/cli.js.map +0 -1
  96. package/dist/index.d.ts.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/templates/auth.d.ts.map +0 -1
  99. package/dist/templates/auth.js.map +0 -1
  100. package/dist/templates/compiler.d.ts.map +0 -1
  101. package/dist/templates/compiler.js.map +0 -1
  102. package/dist/templates/default.d.ts +0 -12
  103. package/dist/templates/default.d.ts.map +0 -1
  104. package/dist/templates/default.js +0 -85
  105. package/dist/templates/default.js.map +0 -1
  106. package/dist/templates/fullstack.d.ts +0 -15
  107. package/dist/templates/fullstack.d.ts.map +0 -1
  108. package/dist/templates/fullstack.js +0 -110
  109. package/dist/templates/fullstack.js.map +0 -1
  110. package/dist/templates/index.d.ts.map +0 -1
  111. package/dist/templates/index.js.map +0 -1
  112. package/dist/templates/placeholders.d.ts.map +0 -1
  113. package/dist/templates/placeholders.js.map +0 -1
  114. package/dist/templates/rsc.d.ts.map +0 -1
  115. package/dist/templates/rsc.js.map +0 -1
  116. package/dist/templates/shared/index.d.ts.map +0 -1
  117. package/dist/templates/shared/index.js.map +0 -1
  118. package/dist/templates/shared/root.d.ts.map +0 -1
  119. package/dist/templates/shared/root.js.map +0 -1
  120. package/dist/templates/shared/web-base.d.ts.map +0 -1
  121. package/dist/templates/shared/web-base.js.map +0 -1
  122. package/dist/templates/shared/web-styles.d.ts.map +0 -1
  123. package/dist/templates/shared/web-styles.js.map +0 -1
  124. package/dist/templates/shared.d.ts.map +0 -1
  125. package/dist/templates/shared.js.map +0 -1
  126. package/dist/templates/source/api/config/app.d.ts +0 -13
  127. package/dist/templates/source/api/config/app.d.ts.map +0 -1
  128. package/dist/templates/source/api/config/app.js +0 -14
  129. package/dist/templates/source/api/config/app.js.map +0 -1
  130. package/dist/templates/source/api/config/auth.d.ts +0 -34
  131. package/dist/templates/source/api/config/auth.d.ts.map +0 -1
  132. package/dist/templates/source/api/config/auth.js +0 -165
  133. package/dist/templates/source/api/config/auth.js.map +0 -1
  134. package/dist/templates/source/api/config/index.auth.d.ts +0 -6
  135. package/dist/templates/source/api/config/index.auth.d.ts.map +0 -1
  136. package/dist/templates/source/api/config/index.auth.js +0 -6
  137. package/dist/templates/source/api/config/index.auth.js.map +0 -1
  138. package/dist/templates/source/api/config/index.default.d.ts +0 -5
  139. package/dist/templates/source/api/config/index.default.d.ts.map +0 -1
  140. package/dist/templates/source/api/config/index.default.js +0 -5
  141. package/dist/templates/source/api/config/index.default.js.map +0 -1
  142. package/dist/templates/source/api/database/index.d.ts +0 -9
  143. package/dist/templates/source/api/database/index.d.ts.map +0 -1
  144. package/dist/templates/source/api/database/index.js +0 -18
  145. package/dist/templates/source/api/database/index.js.map +0 -1
  146. package/dist/templates/source/api/index.auth.d.ts +0 -5
  147. package/dist/templates/source/api/index.auth.d.ts.map +0 -1
  148. package/dist/templates/source/api/index.auth.js +0 -59
  149. package/dist/templates/source/api/index.auth.js.map +0 -1
  150. package/dist/templates/source/api/index.default.d.ts +0 -5
  151. package/dist/templates/source/api/index.default.d.ts.map +0 -1
  152. package/dist/templates/source/api/index.default.js +0 -56
  153. package/dist/templates/source/api/index.default.js.map +0 -1
  154. package/dist/templates/source/api/prisma.config.d.ts +0 -9
  155. package/dist/templates/source/api/prisma.config.d.ts.map +0 -1
  156. package/dist/templates/source/api/prisma.config.js +0 -15
  157. package/dist/templates/source/api/prisma.config.js.map +0 -1
  158. package/dist/templates/source/api/procedures/auth.d.ts +0 -14
  159. package/dist/templates/source/api/procedures/auth.d.ts.map +0 -1
  160. package/dist/templates/source/api/procedures/auth.js +0 -221
  161. package/dist/templates/source/api/procedures/auth.js.map +0 -1
  162. package/dist/templates/source/api/procedures/health.d.ts +0 -5
  163. package/dist/templates/source/api/procedures/health.d.ts.map +0 -1
  164. package/dist/templates/source/api/procedures/health.js +0 -21
  165. package/dist/templates/source/api/procedures/health.js.map +0 -1
  166. package/dist/templates/source/api/procedures/index.auth.d.ts +0 -7
  167. package/dist/templates/source/api/procedures/index.auth.d.ts.map +0 -1
  168. package/dist/templates/source/api/procedures/index.auth.js +0 -7
  169. package/dist/templates/source/api/procedures/index.auth.js.map +0 -1
  170. package/dist/templates/source/api/procedures/index.default.d.ts +0 -6
  171. package/dist/templates/source/api/procedures/index.default.d.ts.map +0 -1
  172. package/dist/templates/source/api/procedures/index.default.js +0 -6
  173. package/dist/templates/source/api/procedures/index.default.js.map +0 -1
  174. package/dist/templates/source/api/procedures/users.auth.d.ts +0 -7
  175. package/dist/templates/source/api/procedures/users.auth.d.ts.map +0 -1
  176. package/dist/templates/source/api/procedures/users.auth.js +0 -111
  177. package/dist/templates/source/api/procedures/users.auth.js.map +0 -1
  178. package/dist/templates/source/api/procedures/users.default.d.ts +0 -5
  179. package/dist/templates/source/api/procedures/users.default.d.ts.map +0 -1
  180. package/dist/templates/source/api/procedures/users.default.js +0 -86
  181. package/dist/templates/source/api/procedures/users.default.js.map +0 -1
  182. package/dist/templates/source/api/schemas/index.d.ts +0 -5
  183. package/dist/templates/source/api/schemas/index.d.ts.map +0 -1
  184. package/dist/templates/source/api/schemas/index.js +0 -5
  185. package/dist/templates/source/api/schemas/index.js.map +0 -1
  186. package/dist/templates/source/api/schemas/user.d.ts +0 -11
  187. package/dist/templates/source/api/schemas/user.d.ts.map +0 -1
  188. package/dist/templates/source/api/schemas/user.js +0 -20
  189. package/dist/templates/source/api/schemas/user.js.map +0 -1
  190. package/dist/templates/source/api/tsup.config.d.ts +0 -3
  191. package/dist/templates/source/api/tsup.config.d.ts.map +0 -1
  192. package/dist/templates/source/api/tsup.config.js +0 -10
  193. package/dist/templates/source/api/tsup.config.js.map +0 -1
  194. package/dist/templates/source/web/main.d.ts +0 -9
  195. package/dist/templates/source/web/main.d.ts.map +0 -1
  196. package/dist/templates/source/web/main.js +0 -27
  197. package/dist/templates/source/web/main.js.map +0 -1
  198. package/dist/templates/source/web/routes/__root.d.ts +0 -2
  199. package/dist/templates/source/web/routes/__root.d.ts.map +0 -1
  200. package/dist/templates/source/web/routes/__root.js +0 -28
  201. package/dist/templates/source/web/routes/__root.js.map +0 -1
  202. package/dist/templates/source/web/routes/about.d.ts +0 -2
  203. package/dist/templates/source/web/routes/about.d.ts.map +0 -1
  204. package/dist/templates/source/web/routes/about.js +0 -33
  205. package/dist/templates/source/web/routes/about.js.map +0 -1
  206. package/dist/templates/source/web/routes/index.auth.d.ts +0 -2
  207. package/dist/templates/source/web/routes/index.auth.d.ts.map +0 -1
  208. package/dist/templates/source/web/routes/index.auth.js +0 -159
  209. package/dist/templates/source/web/routes/index.auth.js.map +0 -1
  210. package/dist/templates/source/web/routes/index.default.d.ts +0 -2
  211. package/dist/templates/source/web/routes/index.default.d.ts.map +0 -1
  212. package/dist/templates/source/web/routes/index.default.js +0 -60
  213. package/dist/templates/source/web/routes/index.default.js.map +0 -1
  214. package/dist/templates/source/web/vite.config.d.ts +0 -3
  215. package/dist/templates/source/web/vite.config.d.ts.map +0 -1
  216. package/dist/templates/source/web/vite.config.js +0 -22
  217. package/dist/templates/source/web/vite.config.js.map +0 -1
  218. package/dist/templates/spa.d.ts.map +0 -1
  219. package/dist/templates/spa.js.map +0 -1
  220. package/dist/templates/trpc.d.ts.map +0 -1
  221. package/dist/templates/trpc.js.map +0 -1
  222. package/dist/templates/types.d.ts.map +0 -1
  223. package/dist/templates/types.js.map +0 -1
@@ -44,15 +44,69 @@ __PROJECT_NAME__/
44
44
 
45
45
  ### Server Actions
46
46
  - Defined in `app/actions/` with `'use server'` directive
47
- - Type-safe with Zod validation via `createAction()`
47
+ - Type-safe with Zod validation
48
48
  - Can be called directly from client components
49
49
 
50
+ ### Validated Actions (Recommended)
51
+ Use `validated()` for secure server actions with built-in protection:
52
+ ```typescript
53
+ // app/actions/users.ts
54
+ 'use server';
55
+ import { validated, validatedMutation, validatedQuery } from '@veloxts/web/server';
56
+ import { z } from 'zod';
57
+
58
+ // Public query (no auth required)
59
+ export const searchUsers = validatedQuery(
60
+ z.object({ query: z.string().optional() }),
61
+ async (input) => {
62
+ return db.user.findMany({ where: { name: { contains: input.query } } });
63
+ }
64
+ );
65
+
66
+ // Protected mutation (requires auth by default)
67
+ export const updateUser = validatedMutation(
68
+ z.object({ id: z.string(), name: z.string() }),
69
+ async (input, ctx) => {
70
+ // ctx.user is typed and available
71
+ return db.user.update({ where: { id: input.id }, data: input });
72
+ }
73
+ );
74
+
75
+ // Custom security options
76
+ export const createUser = validated(
77
+ CreateUserSchema,
78
+ async (input) => { /* ... */ },
79
+ {
80
+ rateLimit: { maxRequests: 10, windowMs: 60_000 },
81
+ maxInputSize: 10 * 1024,
82
+ }
83
+ );
84
+
85
+ // With role-based authorization
86
+ export const deleteUser = validated(
87
+ DeleteUserSchema,
88
+ async (input) => { /* ... */ },
89
+ {
90
+ requireAuth: true,
91
+ requireRoles: ['admin'],
92
+ }
93
+ );
94
+ ```
95
+
96
+ Security features:
97
+ - **Input validation** - Zod schema validation
98
+ - **Input sanitization** - Prototype pollution prevention
99
+ - **Input size limits** - DoS protection (default 1MB)
100
+ - **Rate limiting** - Sliding window per IP
101
+ - **Authentication** - Optional via `requireAuth: true`
102
+ - **Authorization** - Custom callbacks via `authorize`
103
+
50
104
  ### Procedure Bridge Pattern
51
105
  Server actions can bridge to API procedures for code reuse:
52
106
  ```typescript
53
107
  // app/actions/posts.ts
54
108
  'use server';
55
- import { action } from '@veloxts/web';
109
+ import { action } from '@veloxts/web/server';
56
110
  import { postProcedures } from '@/api/procedures/posts';
57
111
 
58
112
  export const createPost = action.fromProcedure(
@@ -35,7 +35,7 @@
35
35
  * ```
36
36
  */
37
37
 
38
- import { action } from '@veloxts/web';
38
+ import { action } from '@veloxts/web/server';
39
39
 
40
40
  import { postProcedures } from '@/api/procedures/posts';
41
41
 
@@ -3,9 +3,17 @@
3
3
  /**
4
4
  * User Server Actions
5
5
  *
6
- * Type-safe server actions for user operations using the VeloxTS action() helper.
6
+ * Type-safe server actions with built-in security using VeloxTS validated() helper.
7
7
  * These can be called directly from client components with full type inference.
8
8
  *
9
+ * The validated() helper provides:
10
+ * - Input validation via Zod schemas
11
+ * - Input size limits (DoS protection)
12
+ * - Input sanitization (prototype pollution prevention)
13
+ * - Rate limiting (optional)
14
+ * - Authentication checks (optional)
15
+ * - Authorization via custom callbacks (optional)
16
+ *
9
17
  * @example
10
18
  * ```tsx
11
19
  * // In a client component
@@ -19,7 +27,7 @@
19
27
  * ```
20
28
  */
21
29
 
22
- import { action } from '@veloxts/web';
30
+ import { validated, validatedMutation, validatedQuery } from '@veloxts/web/server';
23
31
  import { z } from 'zod';
24
32
 
25
33
  import { db } from '@/api/database';
@@ -36,6 +44,15 @@ const CreateUserSchema = z.object({
36
44
  email: z.string().email('Invalid email address'),
37
45
  });
38
46
 
47
+ /**
48
+ * Schema for updating a user
49
+ */
50
+ const UpdateUserSchema = z.object({
51
+ id: z.string().min(1, 'User ID is required'),
52
+ name: z.string().min(1).max(100).optional(),
53
+ email: z.string().email().optional(),
54
+ });
55
+
39
56
  /**
40
57
  * Schema for deleting a user
41
58
  */
@@ -43,24 +60,90 @@ const DeleteUserSchema = z.object({
43
60
  id: z.string().min(1, 'User ID is required'),
44
61
  });
45
62
 
63
+ /**
64
+ * Schema for searching users
65
+ */
66
+ const SearchUsersSchema = z.object({
67
+ query: z.string().max(100).optional(),
68
+ limit: z.number().min(1).max(100).default(10),
69
+ });
70
+
71
+ // ============================================================================
72
+ // Public Actions (no authentication required)
73
+ // ============================================================================
74
+
75
+ /**
76
+ * Search users - public query action
77
+ *
78
+ * Uses validatedQuery() which allows unauthenticated access by default.
79
+ * Includes input sanitization and validation.
80
+ */
81
+ export const searchUsers = validatedQuery(SearchUsersSchema, async (input) => {
82
+ const users = await db.user.findMany({
83
+ where: input.query
84
+ ? {
85
+ OR: [{ name: { contains: input.query } }, { email: { contains: input.query } }],
86
+ }
87
+ : undefined,
88
+ take: input.limit,
89
+ orderBy: { createdAt: 'desc' },
90
+ });
91
+
92
+ return users;
93
+ });
94
+
46
95
  // ============================================================================
47
- // Actions
96
+ // Mutations (with security options)
48
97
  // ============================================================================
49
98
 
50
99
  /**
51
100
  * Creates a new user.
52
101
  *
53
- * Input is automatically validated against CreateUserSchema.
54
- * Returns a discriminated union for type-safe error handling.
102
+ * Uses validated() with explicit options:
103
+ * - Rate limiting: max 10 requests per minute
104
+ * - Input size limit: 10KB (prevent large payload DoS)
105
+ * - Input sanitization: enabled by default
106
+ */
107
+ export const createUser = validated(
108
+ CreateUserSchema,
109
+ async (input) => {
110
+ const user = await db.user.create({
111
+ data: {
112
+ name: input.name.trim(),
113
+ email: input.email.toLowerCase().trim(),
114
+ },
115
+ });
116
+
117
+ return user;
118
+ },
119
+ {
120
+ // Rate limit: 10 requests per minute per IP
121
+ rateLimit: {
122
+ maxRequests: 10,
123
+ windowMs: 60_000,
124
+ },
125
+ // Limit input size to 10KB
126
+ maxInputSize: 10 * 1024,
127
+ }
128
+ );
129
+
130
+ /**
131
+ * Updates a user.
132
+ *
133
+ * Uses validatedMutation() which requires authentication by default.
134
+ * When requireAuth is true, ctx.user is available and typed.
135
+ *
136
+ * Note: In a real app, add authorization to verify the user owns this record.
55
137
  */
56
- export const createUser = action(CreateUserSchema, async (input) => {
57
- // Input is fully typed as { name: string; email: string }
58
- // Validation has already been performed by the action() helper
138
+ export const updateUser = validatedMutation(UpdateUserSchema, async (input, ctx) => {
139
+ // ctx.user is available because validatedMutation requires auth
140
+ console.log('User updating record:', ctx.user.id);
59
141
 
60
- const user = await db.user.create({
142
+ const user = await db.user.update({
143
+ where: { id: input.id },
61
144
  data: {
62
- name: input.name.trim(),
63
- email: input.email.toLowerCase().trim(),
145
+ ...(input.name && { name: input.name.trim() }),
146
+ ...(input.email && { email: input.email.toLowerCase().trim() }),
64
147
  },
65
148
  });
66
149
 
@@ -70,14 +153,22 @@ export const createUser = action(CreateUserSchema, async (input) => {
70
153
  /**
71
154
  * Deletes a user by ID.
72
155
  *
73
- * Input is automatically validated to ensure a valid ID is provided.
156
+ * Uses validated() with role-based authorization.
157
+ * Only admins can delete users.
74
158
  */
75
- export const deleteUser = action(DeleteUserSchema, async (input) => {
76
- // Input is fully typed as { id: string }
159
+ export const deleteUser = validated(
160
+ DeleteUserSchema,
161
+ async (input) => {
162
+ await db.user.delete({
163
+ where: { id: input.id },
164
+ });
77
165
 
78
- await db.user.delete({
79
- where: { id: input.id },
80
- });
81
-
82
- return { deleted: true };
83
- });
166
+ return { deleted: true };
167
+ },
168
+ {
169
+ // Require authentication
170
+ requireAuth: true,
171
+ // Role-based authorization (requires 'admin' role)
172
+ requireRoles: ['admin'],
173
+ }
174
+ );
@@ -24,12 +24,12 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
24
24
  }
25
25
 
26
26
  .dashboard-sidebar {
27
- width: 200px;
27
+ width: 240px;
28
28
  flex-shrink: 0;
29
- background: white;
29
+ background: #111;
30
30
  border-radius: 8px;
31
- padding: 1rem;
32
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
31
+ padding: 1.5rem;
32
+ border: 1px solid #222;
33
33
  height: fit-content;
34
34
  }
35
35
 
@@ -37,8 +37,10 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
37
37
  font-size: 0.75rem;
38
38
  text-transform: uppercase;
39
39
  color: #888;
40
- margin-bottom: 0.5rem;
41
- padding: 0 0.5rem;
40
+ margin-bottom: 0.75rem;
41
+ padding: 0 0.75rem;
42
+ font-weight: 600;
43
+ letter-spacing: 0.05em;
42
44
  }
43
45
 
44
46
  .sidebar-nav {
@@ -47,39 +49,42 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
47
49
 
48
50
  .sidebar-nav a {
49
51
  display: block;
50
- padding: 0.5rem;
51
- color: #1a1a2e;
52
+ padding: 0.75rem;
53
+ color: #ededed;
52
54
  text-decoration: none;
53
55
  border-radius: 4px;
54
56
  transition: background 0.2s;
55
57
  }
56
58
 
57
59
  .sidebar-nav a:hover {
58
- background: #f0f0f0;
60
+ background: #1a1a1a;
61
+ color: #00d9ff;
59
62
  }
60
63
 
61
64
  .sidebar-nav a.active {
62
- background: #6366f1;
63
- color: white;
65
+ background: #00d9ff;
66
+ color: #000;
64
67
  }
65
68
 
66
69
  .dashboard-content {
67
70
  flex: 1;
68
- background: white;
71
+ background: #111;
69
72
  border-radius: 8px;
70
73
  padding: 1.5rem;
71
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
74
+ border: 1px solid #222;
72
75
  }
73
76
 
74
77
  .dashboard-badge {
75
78
  display: inline-block;
76
- background: #6366f1;
77
- color: white;
79
+ background: #00d9ff;
80
+ color: #000;
78
81
  font-size: 0.625rem;
79
82
  text-transform: uppercase;
80
- padding: 0.25rem 0.5rem;
83
+ padding: 0.5rem 0.75rem;
81
84
  border-radius: 4px;
82
85
  margin-bottom: 1rem;
86
+ font-weight: 600;
87
+ letter-spacing: 0.05em;
83
88
  }
84
89
  `}</style>
85
90
 
@@ -15,6 +15,40 @@ interface MarketingLayoutProps {
15
15
  export default function MarketingLayout({ children }: MarketingLayoutProps) {
16
16
  return (
17
17
  <div className="marketing-layout">
18
+ <style>{`
19
+ .marketing-layout {
20
+ position: relative;
21
+ }
22
+
23
+ .marketing-banner {
24
+ background: #111;
25
+ border: 1px solid #222;
26
+ border-radius: 8px;
27
+ padding: 1rem 1.5rem;
28
+ margin-bottom: 1.5rem;
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 1rem;
32
+ }
33
+
34
+ .marketing-banner .badge {
35
+ display: inline-block;
36
+ background: #00d9ff;
37
+ color: #000;
38
+ font-size: 0.625rem;
39
+ text-transform: uppercase;
40
+ padding: 0.5rem 0.75rem;
41
+ border-radius: 4px;
42
+ font-weight: 600;
43
+ letter-spacing: 0.05em;
44
+ }
45
+
46
+ .marketing-banner span:not(.badge) {
47
+ color: #888;
48
+ font-size: 0.875rem;
49
+ }
50
+ `}</style>
51
+
18
52
  <div className="marketing-banner">
19
53
  <span className="badge">Marketing</span>
20
54
  <span>This page uses the marketing layout</span>
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Minimal Content Layout
3
+ *
4
+ * The content portion of the minimal layout, separate from the HTML document shell.
5
+ * This component is shared between server and client for proper hydration.
6
+ *
7
+ * Server renders: <div id="root"><MinimalContent><Page /></MinimalContent></div>
8
+ * Client hydrates: <MinimalContent><Page /></MinimalContent>
9
+ *
10
+ * This ensures the React tree matches on both sides, enabling proper hydration.
11
+ */
12
+
13
+ import type { ReactNode } from 'react';
14
+
15
+ interface MinimalContentProps {
16
+ children: ReactNode;
17
+ }
18
+
19
+ export default function MinimalContent({ children }: MinimalContentProps) {
20
+ return <div className="minimal-content">{children}</div>;
21
+ }
@@ -1,12 +1,23 @@
1
1
  /**
2
- * Minimal Layout
2
+ * Minimal Layout (Document Shell)
3
3
  *
4
4
  * A stripped-down layout for pages that need minimal chrome,
5
5
  * such as print-friendly pages, embedded widgets, or auth pages.
6
+ *
7
+ * This component provides the HTML document shell (server-only).
8
+ * The actual content wrapper is in MinimalContent (shared with client).
9
+ *
10
+ * Architecture for proper hydration:
11
+ * - Server renders: <MinimalLayout><Page /></MinimalLayout>
12
+ * Which produces: <html>...<div id="root"><MinimalContent><Page /></></div>...</html>
13
+ * - Client hydrates #root with: <MinimalContent><Page /></MinimalContent>
14
+ * This ensures the React tree matches exactly.
6
15
  */
7
16
 
8
17
  import type { ReactNode } from 'react';
9
18
 
19
+ import MinimalContent from './minimal-content.tsx';
20
+
10
21
  interface MinimalLayoutProps {
11
22
  children: ReactNode;
12
23
  params?: Record<string, string>;
@@ -19,11 +30,81 @@ export default function MinimalLayout({ children }: MinimalLayoutProps) {
19
30
  <meta charSet="utf-8" />
20
31
  <meta name="viewport" content="width=device-width, initial-scale=1" />
21
32
  <title>VeloxTS</title>
22
- <link rel="stylesheet" href="/_build/styles.css" />
33
+ <style>{`
34
+ /* Global Reset & Dark Mode Base */
35
+ *,
36
+ *::before,
37
+ *::after {
38
+ box-sizing: border-box;
39
+ margin: 0;
40
+ padding: 0;
41
+ font: inherit;
42
+ }
43
+
44
+ html {
45
+ font-size: 16px;
46
+ -webkit-font-smoothing: antialiased;
47
+ -moz-osx-font-smoothing: grayscale;
48
+ }
49
+
50
+ body {
51
+ background: #0a0a0a;
52
+ color: #ededed;
53
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
54
+ line-height: 1.6;
55
+ min-height: 100svh;
56
+ }
57
+
58
+ h1, h2, h3, h4, h5, h6 {
59
+ text-wrap: balance;
60
+ }
61
+
62
+ p, li, figcaption {
63
+ text-wrap: pretty;
64
+ }
65
+
66
+ a {
67
+ color: #00d9ff;
68
+ text-decoration: none;
69
+ transition: opacity 0.2s;
70
+ }
71
+
72
+ a:hover {
73
+ opacity: 0.8;
74
+ }
75
+
76
+ code {
77
+ font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Monaco, "Courier New", monospace;
78
+ background: #1a1a1a;
79
+ padding: 0.2em 0.4em;
80
+ border-radius: 4px;
81
+ font-size: 0.9em;
82
+ }
83
+
84
+ ::selection {
85
+ background: #00d9ff;
86
+ color: #000;
87
+ }
88
+
89
+ .minimal-body {
90
+ min-height: 100vh;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ padding: 2rem;
95
+ }
96
+
97
+ .minimal-content {
98
+ width: 100%;
99
+ max-width: 450px;
100
+ }
101
+ `}</style>
23
102
  </head>
24
- <body className="minimal-layout">
25
- {children}
26
- <script src="/_build/entry.client.js" type="module" />
103
+ <body className="minimal-body">
104
+ <div id="root">
105
+ <MinimalContent>{children}</MinimalContent>
106
+ </div>
107
+ <script src="/_build/src/entry.client.tsx" type="module" />
27
108
  </body>
28
109
  </html>
29
110
  );