create-tigra 1.1.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 (243) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -87
  3. package/bin/create-tigra.js +259 -308
  4. package/package.json +49 -41
  5. package/template/_claude/QUICK_REFERENCE.md +193 -0
  6. package/template/_claude/README.md +53 -0
  7. package/template/_claude/commands/create-client.md +881 -0
  8. package/template/_claude/commands/create-server.md +383 -0
  9. package/template/_claude/rules/client/01-project-structure.md +133 -0
  10. package/template/_claude/rules/client/02-components-and-types.md +146 -0
  11. package/template/_claude/rules/client/03-data-and-state.md +156 -0
  12. package/template/_claude/rules/client/04-design-system.md +185 -0
  13. package/template/_claude/rules/client/05-security.md +55 -0
  14. package/template/_claude/rules/client/06-ux-checklist.md +81 -0
  15. package/template/_claude/rules/client/core.md +42 -0
  16. package/template/_claude/rules/global/core.md +77 -0
  17. package/template/_claude/rules/server/core.md +50 -0
  18. package/template/_claude/rules/server/database.md +124 -0
  19. package/template/_claude/rules/server/project-conventions.md +150 -0
  20. package/template/_claude/rules/server/response-handling.md +144 -0
  21. package/template/client/.env.example +5 -0
  22. package/template/client/README.md +36 -0
  23. package/template/client/components.json +23 -0
  24. package/template/client/eslint.config.mjs +18 -0
  25. package/template/client/next.config.ts +34 -0
  26. package/template/client/package.json +44 -0
  27. package/template/client/postcss.config.mjs +7 -0
  28. package/template/client/src/app/(auth)/layout.tsx +18 -0
  29. package/template/client/src/app/(auth)/login/page.tsx +13 -0
  30. package/template/client/src/app/(auth)/register/page.tsx +13 -0
  31. package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
  32. package/template/client/src/app/(main)/layout.tsx +11 -0
  33. package/template/client/src/app/error.tsx +27 -0
  34. package/template/client/src/app/favicon.ico +0 -0
  35. package/template/client/src/app/globals.css +145 -0
  36. package/template/client/src/app/layout.tsx +36 -0
  37. package/template/client/src/app/loading.tsx +11 -0
  38. package/template/client/src/app/not-found.tsx +23 -0
  39. package/template/client/src/app/page.tsx +45 -0
  40. package/template/client/src/app/providers.tsx +43 -0
  41. package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
  42. package/template/client/src/components/common/EmptyState.tsx +31 -0
  43. package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
  44. package/template/client/src/components/common/Pagination.tsx +55 -0
  45. package/template/client/src/components/layout/Footer.tsx +17 -0
  46. package/template/client/src/components/layout/Header.tsx +173 -0
  47. package/template/client/src/components/layout/MainLayout.tsx +18 -0
  48. package/template/client/src/components/ui/alert-dialog.tsx +196 -0
  49. package/template/client/src/components/ui/badge.tsx +48 -0
  50. package/template/client/src/components/ui/button.tsx +64 -0
  51. package/template/client/src/components/ui/card.tsx +92 -0
  52. package/template/client/src/components/ui/input.tsx +21 -0
  53. package/template/client/src/components/ui/label.tsx +24 -0
  54. package/template/client/src/components/ui/select.tsx +190 -0
  55. package/template/client/src/components/ui/skeleton.tsx +13 -0
  56. package/template/client/src/components/ui/table.tsx +116 -0
  57. package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
  58. package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
  59. package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
  60. package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
  61. package/template/client/src/features/auth/services/auth.service.ts +52 -0
  62. package/template/client/src/features/auth/store/authSlice.ts +38 -0
  63. package/template/client/src/features/auth/types/auth.types.ts +32 -0
  64. package/template/client/src/hooks/useDebounce.ts +14 -0
  65. package/template/client/src/hooks/useLocalStorage.ts +55 -0
  66. package/template/client/src/hooks/useMediaQuery.ts +27 -0
  67. package/template/client/src/lib/api/api.types.ts +34 -0
  68. package/template/client/src/lib/api/axios.config.ts +98 -0
  69. package/template/client/src/lib/constants/api-endpoints.ts +18 -0
  70. package/template/client/src/lib/constants/app.constants.ts +12 -0
  71. package/template/client/src/lib/constants/routes.ts +9 -0
  72. package/template/client/src/lib/utils/error.ts +32 -0
  73. package/template/client/src/lib/utils/format.ts +37 -0
  74. package/template/client/src/lib/utils/security.ts +34 -0
  75. package/template/client/src/lib/utils.ts +6 -0
  76. package/template/client/src/middleware.ts +57 -0
  77. package/template/client/src/store/hooks.ts +7 -0
  78. package/template/client/src/store/index.ts +12 -0
  79. package/template/client/src/types/index.ts +3 -0
  80. package/template/client/tsconfig.json +34 -0
  81. package/template/gitignore +34 -0
  82. package/template/server/.dockerignore +66 -0
  83. package/template/server/.env.example +96 -69
  84. package/template/server/.env.production.example +90 -0
  85. package/template/server/Dockerfile +94 -0
  86. package/template/server/docker-compose.yml +82 -111
  87. package/template/server/docs/logging.md +62 -0
  88. package/template/server/eslint.config.mjs +17 -0
  89. package/template/server/package.json +68 -81
  90. package/template/server/phpmyadmin-config.php +26 -0
  91. package/template/server/postman_collection.json +666 -0
  92. package/template/server/prisma/schema.prisma +77 -93
  93. package/template/server/prisma/seed.ts +46 -142
  94. package/template/server/scripts/flush-redis.ts +41 -0
  95. package/template/server/src/app.ts +243 -71
  96. package/template/server/src/config/env.ts +67 -94
  97. package/template/server/src/libs/auth.ts +88 -0
  98. package/template/server/src/libs/cleanup.ts +35 -0
  99. package/template/server/src/libs/cookies.ts +46 -0
  100. package/template/server/src/libs/logger.ts +33 -60
  101. package/template/server/src/libs/monitoring.ts +205 -0
  102. package/template/server/src/libs/password.ts +38 -0
  103. package/template/server/src/libs/prisma.ts +68 -0
  104. package/template/server/src/libs/redis.ts +60 -79
  105. package/template/server/src/libs/requestLogger.ts +66 -0
  106. package/template/server/src/libs/storage/file-storage.service.ts +211 -0
  107. package/template/server/src/libs/storage/file-validator.ts +97 -0
  108. package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
  109. package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
  110. package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
  111. package/template/server/src/modules/auth/auth.controller.ts +90 -141
  112. package/template/server/src/modules/auth/auth.repo.ts +120 -218
  113. package/template/server/src/modules/auth/auth.routes.ts +96 -83
  114. package/template/server/src/modules/auth/auth.schemas.ts +35 -137
  115. package/template/server/src/modules/auth/auth.service.ts +286 -329
  116. package/template/server/src/modules/auth/session.repo.ts +110 -0
  117. package/template/server/src/modules/users/users.controller.ts +120 -0
  118. package/template/server/src/modules/users/users.repo.ts +77 -0
  119. package/template/server/src/modules/users/users.routes.ts +89 -0
  120. package/template/server/src/modules/users/users.schemas.ts +21 -0
  121. package/template/server/src/modules/users/users.service.ts +169 -0
  122. package/template/server/src/server.ts +58 -139
  123. package/template/server/src/shared/errors/AppError.ts +21 -0
  124. package/template/server/src/shared/errors/errors.ts +43 -0
  125. package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
  126. package/template/server/src/shared/responses/successResponse.ts +17 -0
  127. package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
  128. package/template/server/src/shared/types/index.ts +26 -0
  129. package/template/server/src/test/setup.ts +74 -38
  130. package/template/server/tsconfig.json +27 -89
  131. package/template/server/uploads/avatars/.gitkeep +1 -0
  132. package/template/server/vitest.config.ts +43 -98
  133. package/template/.agent/rules/client/01-project-structure.md +0 -326
  134. package/template/.agent/rules/client/02-component-patterns.md +0 -249
  135. package/template/.agent/rules/client/03-typescript-rules.md +0 -226
  136. package/template/.agent/rules/client/04-state-management.md +0 -474
  137. package/template/.agent/rules/client/05-api-integration.md +0 -129
  138. package/template/.agent/rules/client/06-forms-validation.md +0 -129
  139. package/template/.agent/rules/client/07-common-patterns.md +0 -150
  140. package/template/.agent/rules/client/08-color-system.md +0 -93
  141. package/template/.agent/rules/client/09-security-rules.md +0 -97
  142. package/template/.agent/rules/client/10-testing-strategy.md +0 -370
  143. package/template/.agent/rules/global/ai-edit-safety.md +0 -38
  144. package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
  145. package/template/.agent/rules/server/02-general-rules.md +0 -111
  146. package/template/.agent/rules/server/03-migrations.md +0 -20
  147. package/template/.agent/rules/server/04-pagination.md +0 -130
  148. package/template/.agent/rules/server/05-project-conventions.md +0 -71
  149. package/template/.agent/rules/server/06-response-handling.md +0 -173
  150. package/template/.agent/rules/server/07-testing-strategy.md +0 -506
  151. package/template/.agent/rules/server/08-observability.md +0 -180
  152. package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
  153. package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
  154. package/template/.agent/rules/server/12-performance-optimization.md +0 -567
  155. package/template/.claude/rules/client-01-project-structure.md +0 -327
  156. package/template/.claude/rules/client-02-component-patterns.md +0 -250
  157. package/template/.claude/rules/client-03-typescript-rules.md +0 -227
  158. package/template/.claude/rules/client-04-state-management.md +0 -475
  159. package/template/.claude/rules/client-05-api-integration.md +0 -130
  160. package/template/.claude/rules/client-06-forms-validation.md +0 -130
  161. package/template/.claude/rules/client-07-common-patterns.md +0 -151
  162. package/template/.claude/rules/client-08-color-system.md +0 -94
  163. package/template/.claude/rules/client-09-security-rules.md +0 -98
  164. package/template/.claude/rules/client-10-testing-strategy.md +0 -371
  165. package/template/.claude/rules/global-ai-edit-safety.md +0 -39
  166. package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
  167. package/template/.claude/rules/server-02-general-rules.md +0 -112
  168. package/template/.claude/rules/server-03-migrations.md +0 -21
  169. package/template/.claude/rules/server-04-pagination.md +0 -131
  170. package/template/.claude/rules/server-05-project-conventions.md +0 -72
  171. package/template/.claude/rules/server-06-response-handling.md +0 -174
  172. package/template/.claude/rules/server-07-testing-strategy.md +0 -507
  173. package/template/.claude/rules/server-08-observability.md +0 -181
  174. package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
  175. package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
  176. package/template/.claude/rules/server-12-performance-optimization.md +0 -568
  177. package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
  178. package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
  179. package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
  180. package/template/.cursor/rules/client-04-state-management.mdc +0 -475
  181. package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
  182. package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
  183. package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
  184. package/template/.cursor/rules/client-08-color-system.mdc +0 -94
  185. package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
  186. package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
  187. package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
  188. package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
  189. package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
  190. package/template/.cursor/rules/server-03-migrations.mdc +0 -21
  191. package/template/.cursor/rules/server-04-pagination.mdc +0 -131
  192. package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
  193. package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
  194. package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
  195. package/template/.cursor/rules/server-08-observability.mdc +0 -181
  196. package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
  197. package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
  198. package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
  199. package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
  200. package/template/CLAUDE.md +0 -207
  201. package/template/server/.tsc-aliasrc.json +0 -13
  202. package/template/server/IMPORT_FIX_CHECKLIST.md +0 -98
  203. package/template/server/IMPORT_FIX_COMPLETE.md +0 -89
  204. package/template/server/README.md +0 -183
  205. package/template/server/REMAINING_IMPORT_FIXES.md +0 -150
  206. package/template/server/SECURITY.md +0 -190
  207. package/template/server/Tigra-API.postman_collection.json +0 -733
  208. package/template/server/biome.json +0 -42
  209. package/template/server/scripts/fix-all-imports.ps1 +0 -52
  210. package/template/server/scripts/fix-imports-reference.ps1 +0 -16
  211. package/template/server/scripts/fix-imports.mjs +0 -55
  212. package/template/server/scripts/setup-env.js +0 -50
  213. package/template/server/scripts/wait-for-db.js +0 -60
  214. package/template/server/src/hooks/request-timing.hook.ts +0 -26
  215. package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
  216. package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
  217. package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
  218. package/template/server/src/libs/db.ts +0 -76
  219. package/template/server/src/libs/error-handler.ts +0 -89
  220. package/template/server/src/libs/queue.ts +0 -79
  221. package/template/server/src/modules/admin/admin.controller.ts +0 -122
  222. package/template/server/src/modules/admin/admin.routes.ts +0 -62
  223. package/template/server/src/modules/admin/admin.schemas.ts +0 -35
  224. package/template/server/src/modules/admin/admin.service.ts +0 -167
  225. package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
  226. package/template/server/src/modules/auth/auth.service.test.ts +0 -119
  227. package/template/server/src/modules/auth/auth.types.ts +0 -97
  228. package/template/server/src/modules/resources/resources.controller.ts +0 -218
  229. package/template/server/src/modules/resources/resources.repo.ts +0 -253
  230. package/template/server/src/modules/resources/resources.routes.ts +0 -116
  231. package/template/server/src/modules/resources/resources.schemas.ts +0 -146
  232. package/template/server/src/modules/resources/resources.service.ts +0 -218
  233. package/template/server/src/modules/resources/resources.types.ts +0 -73
  234. package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
  235. package/template/server/src/plugins/security.plugin.ts +0 -21
  236. package/template/server/src/routes/health.routes.ts +0 -31
  237. package/template/server/src/types/fastify.d.ts +0 -36
  238. package/template/server/src/utils/errors.ts +0 -108
  239. package/template/server/src/utils/pagination.ts +0 -120
  240. package/template/server/src/utils/response.ts +0 -110
  241. package/template/server/src/workers/file.worker.ts +0 -106
  242. package/template/server/tsconfig.build.json +0 -30
  243. package/template/server/tsconfig.test.json +0 -22
@@ -0,0 +1,881 @@
1
+ # Create Client Project
2
+
3
+ You are scaffolding a new Next.js App Router + TypeScript client project. The project name is: **$ARGUMENTS**
4
+
5
+ If no project name is provided, ask the user for one before proceeding.
6
+
7
+ ---
8
+
9
+ ## Instructions
10
+
11
+ Create a complete, production-ready Next.js client project following the architecture and rules defined in `.claude/rules/client/`. Read those rules before generating any code.
12
+
13
+ ### Step 1: Create Next.js Project
14
+
15
+ Run:
16
+ ```bash
17
+ npx create-next-app@latest $ARGUMENTS --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --no-turbopack
18
+ ```
19
+
20
+ Then install additional dependencies:
21
+ ```bash
22
+ # State & Data
23
+ npm install @reduxjs/toolkit react-redux @tanstack/react-query axios
24
+
25
+ # Dev tools
26
+ npm install -D @tanstack/react-query-devtools
27
+
28
+ # Forms & Validation
29
+ npm install react-hook-form @hookform/resolvers zod
30
+
31
+ # UI
32
+ npm install sonner next-themes lucide-react class-variance-authority clsx tailwind-merge tailwindcss-animate
33
+
34
+ # Init shadcn/ui
35
+ npx shadcn@latest init --defaults
36
+ ```
37
+
38
+ Then install core shadcn components:
39
+ ```bash
40
+ npx shadcn@latest add button card input label skeleton alert-dialog select table badge
41
+ ```
42
+
43
+ ---
44
+
45
+ ### Step 2: Project Structure
46
+
47
+ Create the following directory structure under `src/`:
48
+
49
+ ```
50
+ src/
51
+ ├── app/
52
+ │ ├── layout.tsx # Root layout
53
+ │ ├── page.tsx # Home page
54
+ │ ├── providers.tsx # Client providers (Redux, React Query, Theme)
55
+ │ ├── globals.css # Global styles with CSS variables
56
+ │ ├── loading.tsx # Global loading
57
+ │ ├── error.tsx # Global error boundary
58
+ │ ├── not-found.tsx # 404 page
59
+ │ ├── (auth)/
60
+ │ │ ├── layout.tsx # Auth layout (centered, minimal)
61
+ │ │ ├── login/
62
+ │ │ │ └── page.tsx # Login page
63
+ │ │ └── register/
64
+ │ │ └── page.tsx # Register page
65
+ │ └── (main)/
66
+ │ ├── layout.tsx # Main layout with Header/Footer
67
+ │ └── dashboard/
68
+ │ └── page.tsx # Dashboard page (protected)
69
+ ├── components/
70
+ │ ├── ui/ # shadcn/ui components (auto-generated)
71
+ │ ├── layout/
72
+ │ │ ├── Header.tsx
73
+ │ │ ├── Footer.tsx
74
+ │ │ └── MainLayout.tsx
75
+ │ └── common/
76
+ │ ├── LoadingSpinner.tsx
77
+ │ ├── EmptyState.tsx
78
+ │ ├── Pagination.tsx
79
+ │ └── ConfirmDialog.tsx
80
+ ├── features/
81
+ │ └── auth/
82
+ │ ├── components/
83
+ │ │ ├── LoginForm.tsx
84
+ │ │ └── RegisterForm.tsx
85
+ │ ├── hooks/
86
+ │ │ └── useAuth.ts
87
+ │ ├── services/
88
+ │ │ └── auth.service.ts
89
+ │ ├── store/
90
+ │ │ └── authSlice.ts
91
+ │ └── types/
92
+ │ └── auth.types.ts
93
+ ├── hooks/
94
+ │ ├── useDebounce.ts
95
+ │ ├── useLocalStorage.ts
96
+ │ └── useMediaQuery.ts
97
+ ├── lib/
98
+ │ ├── api/
99
+ │ │ ├── axios.config.ts
100
+ │ │ └── api.types.ts
101
+ │ ├── constants/
102
+ │ │ ├── routes.ts
103
+ │ │ ├── api-endpoints.ts
104
+ │ │ └── app.constants.ts
105
+ │ ├── utils/
106
+ │ │ ├── format.ts
107
+ │ │ ├── error.ts
108
+ │ │ └── security.ts
109
+ │ └── utils.ts # cn() helper
110
+ ├── store/
111
+ │ ├── index.ts
112
+ │ └── hooks.ts
113
+ ├── types/
114
+ │ └── index.ts
115
+ └── middleware.ts
116
+ ```
117
+
118
+ ---
119
+
120
+ ### Step 3: Configuration Files
121
+
122
+ #### `tailwind.config.ts`
123
+ Set up the full Tailwind config as specified in `04-design-system.md`:
124
+ - darkMode: "class"
125
+ - Content paths include src/features/**
126
+ - Extend colors with ALL semantic tokens: primary, secondary, destructive, muted, accent, popover, card, border, input, ring, brand (primary, secondary, accent), success, warning, info
127
+ - Container config: centered, 2rem padding, 1400px max
128
+ - Include tailwindcss-animate plugin
129
+
130
+ #### `src/app/globals.css`
131
+ Implement the FULL CSS variable system from `04-design-system.md`:
132
+ - `:root` with ALL light mode variables (background, foreground, primary, secondary, muted, accent, destructive, popover, card, border, input, ring, radius, brand colors, semantic colors: success, warning, info)
133
+ - `.dark` with ALL dark mode variables
134
+ - Base layer: `border-border` on all elements, `bg-background text-foreground` on body
135
+
136
+ #### `next.config.ts`
137
+ - Add security headers from `05-security.md`: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, X-XSS-Protection
138
+ - Configure images.remotePatterns if needed
139
+
140
+ #### `.env.example` and `.env.local`
141
+ ```env
142
+ NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api/v1
143
+ NEXT_PUBLIC_APP_NAME=$ARGUMENTS
144
+ ```
145
+
146
+ ---
147
+
148
+ ### Step 4: Core Library Files
149
+
150
+ #### `src/lib/utils.ts`
151
+ ```typescript
152
+ import { type ClassValue, clsx } from "clsx";
153
+ import { twMerge } from "tailwind-merge";
154
+
155
+ export function cn(...inputs: ClassValue[]): string {
156
+ return twMerge(clsx(inputs));
157
+ }
158
+ ```
159
+
160
+ #### `src/lib/api/api.types.ts`
161
+ Define ALL API response types from `02-components-and-types.md`:
162
+ - `ApiResponse<T>` — `{ success: true, message: string, data: T }`
163
+ - `PaginatedApiResponse<T>` — with items array and pagination object (page, limit, totalItems, totalPages, hasNextPage, hasPreviousPage)
164
+ - `ApiError` — `{ success: false, error: { code: string, message: string } }`
165
+ - `PaginationParams` — `{ page?: number, limit?: number }`
166
+
167
+ #### `src/lib/api/axios.config.ts`
168
+ Implement the FULL Axios config from `03-data-and-state.md`:
169
+ - Base URL from env
170
+ - Request interceptor: attach Bearer token from Redux store (client-side only with `typeof window` check)
171
+ - Response interceptor: handle 401 with token refresh flow, dispatch logout on failure, redirect to `/login`
172
+ - 30s timeout
173
+
174
+ #### `src/lib/constants/api-endpoints.ts`
175
+ ```typescript
176
+ export const API_ENDPOINTS = {
177
+ AUTH: {
178
+ REGISTER: '/auth/register',
179
+ LOGIN: '/auth/login',
180
+ LOGOUT: '/auth/logout',
181
+ REFRESH: '/auth/refresh',
182
+ ME: '/auth/me',
183
+ VERIFY_EMAIL: '/auth/verify-email',
184
+ RESEND_VERIFICATION: '/auth/resend-verification',
185
+ REQUEST_PASSWORD_RESET: '/auth/request-password-reset',
186
+ RESET_PASSWORD: '/auth/reset-password',
187
+ },
188
+ USERS: {
189
+ ME: '/users/me',
190
+ UPDATE_ME: '/users/me',
191
+ DELETE_ME: '/users/me',
192
+ },
193
+ } as const;
194
+ ```
195
+
196
+ #### `src/lib/constants/routes.ts`
197
+ ```typescript
198
+ export const ROUTES = {
199
+ HOME: '/',
200
+ LOGIN: '/login',
201
+ REGISTER: '/register',
202
+ VERIFY_EMAIL: '/verify-email',
203
+ RESET_PASSWORD: '/reset-password',
204
+ DASHBOARD: '/dashboard',
205
+ PROFILE: '/profile',
206
+ } as const;
207
+ ```
208
+
209
+ #### `src/lib/constants/app.constants.ts`
210
+ ```typescript
211
+ export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || '$ARGUMENTS';
212
+
213
+ export const PAGINATION = {
214
+ DEFAULT_PAGE: 1,
215
+ DEFAULT_LIMIT: 10,
216
+ MAX_LIMIT: 100,
217
+ } as const;
218
+
219
+ export const USER_ROLES = {
220
+ USER: 'USER',
221
+ COMPANY: 'COMPANY',
222
+ ADMIN: 'ADMIN',
223
+ GUIDE: 'GUIDE',
224
+ DRIVER: 'DRIVER',
225
+ } as const;
226
+ ```
227
+
228
+ #### `src/lib/utils/error.ts`
229
+ Implement the error utility from `03-data-and-state.md`:
230
+ ```typescript
231
+ import axios from 'axios';
232
+ import type { ApiError } from '@/lib/api/api.types';
233
+
234
+ export const getErrorMessage = (error: unknown): string => {
235
+ if (axios.isAxiosError(error)) {
236
+ const apiError = error.response?.data as ApiError;
237
+ if (apiError?.error?.message) {
238
+ return apiError.error.message;
239
+ }
240
+ if (error.code === 'ERR_NETWORK') {
241
+ return 'Network error. Check your connection.';
242
+ }
243
+ return error.message;
244
+ }
245
+ if (error instanceof Error) {
246
+ return error.message;
247
+ }
248
+ return 'An unexpected error occurred';
249
+ };
250
+
251
+ export const getErrorCode = (error: unknown): string | undefined => {
252
+ if (axios.isAxiosError(error)) {
253
+ return error.response?.data?.error?.code;
254
+ }
255
+ return undefined;
256
+ };
257
+
258
+ export const isErrorCode = (error: unknown, code: string): boolean => {
259
+ return getErrorCode(error) === code;
260
+ };
261
+ ```
262
+
263
+ #### `src/lib/utils/format.ts`
264
+ ```typescript
265
+ export const formatCurrency = (amount: number, currency = 'GEL'): string => {
266
+ return new Intl.NumberFormat('en-US', {
267
+ style: 'currency',
268
+ currency,
269
+ minimumFractionDigits: 0,
270
+ maximumFractionDigits: 2,
271
+ }).format(amount);
272
+ };
273
+
274
+ export const formatDate = (date: string | Date): string => {
275
+ return new Intl.DateTimeFormat('en-US', {
276
+ year: 'numeric',
277
+ month: 'long',
278
+ day: 'numeric',
279
+ }).format(new Date(date));
280
+ };
281
+
282
+ export const formatRelativeTime = (date: string | Date): string => {
283
+ const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
284
+ const now = new Date();
285
+ const then = new Date(date);
286
+ const diffInSeconds = (then.getTime() - now.getTime()) / 1000;
287
+
288
+ const units: { unit: Intl.RelativeTimeFormatUnit; seconds: number }[] = [
289
+ { unit: 'year', seconds: 31536000 },
290
+ { unit: 'month', seconds: 2592000 },
291
+ { unit: 'day', seconds: 86400 },
292
+ { unit: 'hour', seconds: 3600 },
293
+ { unit: 'minute', seconds: 60 },
294
+ ];
295
+
296
+ for (const { unit, seconds } of units) {
297
+ if (Math.abs(diffInSeconds) >= seconds) {
298
+ return rtf.format(Math.round(diffInSeconds / seconds), unit);
299
+ }
300
+ }
301
+ return rtf.format(Math.round(diffInSeconds), 'second');
302
+ };
303
+
304
+ export const truncate = (str: string, length: number): string => {
305
+ return str.length > length ? `${str.substring(0, length)}...` : str;
306
+ };
307
+ ```
308
+
309
+ #### `src/lib/utils/security.ts`
310
+ Implement from `05-security.md`:
311
+ ```typescript
312
+ export const isSafeUrl = (url: string): boolean => {
313
+ try {
314
+ const parsed = new URL(url);
315
+ return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
316
+ } catch {
317
+ return false;
318
+ }
319
+ };
320
+
321
+ export const sanitizeString = (input: string): string => {
322
+ return input.trim().replace(/[<>]/g, '').slice(0, 1000);
323
+ };
324
+
325
+ export const sanitizeEmail = (email: string): string => {
326
+ return email.toLowerCase().trim().slice(0, 255);
327
+ };
328
+
329
+ export const maskEmail = (email: string): string => {
330
+ const [local, domain] = email.split('@');
331
+ if (!local || !domain) return email;
332
+ return `${local[0]}***${local[local.length - 1]}@${domain}`;
333
+ };
334
+ ```
335
+
336
+ ---
337
+
338
+ ### Step 5: State Management
339
+
340
+ #### `src/store/index.ts`
341
+ Redux store setup from `03-data-and-state.md`:
342
+ - Configure store with auth reducer
343
+ - Load auth state from localStorage (with SSR guard: `typeof window !== 'undefined'`)
344
+ - Subscribe to save auth state to localStorage (with SSR guard)
345
+ - Export `RootState` and `AppDispatch` types
346
+
347
+ #### `src/store/hooks.ts`
348
+ Typed Redux hooks:
349
+ ```typescript
350
+ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
351
+ import type { RootState, AppDispatch } from './index';
352
+
353
+ export const useAppDispatch: () => AppDispatch = useDispatch;
354
+ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
355
+ ```
356
+
357
+ #### `src/features/auth/store/authSlice.ts`
358
+ Auth slice from `03-data-and-state.md`:
359
+ - State: `{ user: IUser | null, tokens: IAuthTokens | null, isAuthenticated: boolean }`
360
+ - Reducers: `setCredentials`, `updateTokens`, `logout`
361
+
362
+ #### `src/features/auth/types/auth.types.ts`
363
+ Auth types from `02-components-and-types.md`:
364
+ - `IUser` interface
365
+ - `UserRole` type (`'USER' | 'COMPANY' | 'ADMIN' | 'GUIDE' | 'DRIVER'`)
366
+ - `IAuthTokens` interface
367
+ - `ILoginRequest`, `IRegisterRequest` interfaces
368
+ - `IAuthState` interface
369
+
370
+ #### `src/features/auth/services/auth.service.ts`
371
+ Auth service class from `03-data-and-state.md`:
372
+ - `register`, `login`, `logout`, `refreshToken`, `getMe`, `verifyEmail`, `requestPasswordReset`, `resetPassword`
373
+ - Uses `apiClient` and `API_ENDPOINTS`
374
+ - Singleton export: `export const authService = new AuthService();`
375
+
376
+ #### `src/features/auth/hooks/useAuth.ts`
377
+ ```typescript
378
+ 'use client';
379
+
380
+ import { useAppDispatch, useAppSelector } from '@/store/hooks';
381
+ import { useMutation } from '@tanstack/react-query';
382
+ import { authService } from '../services/auth.service';
383
+ import { setCredentials, logout as logoutAction } from '../store/authSlice';
384
+ import { useRouter } from 'next/navigation';
385
+
386
+ export const useAuth = () => {
387
+ const dispatch = useAppDispatch();
388
+ const router = useRouter();
389
+ const { user, isAuthenticated, tokens } = useAppSelector((state) => state.auth);
390
+
391
+ const loginMutation = useMutation({
392
+ mutationFn: authService.login,
393
+ onSuccess: (data) => {
394
+ dispatch(setCredentials(data));
395
+ router.push('/dashboard');
396
+ },
397
+ });
398
+
399
+ const logout = async (): Promise<void> => {
400
+ try {
401
+ await authService.logout();
402
+ } finally {
403
+ dispatch(logoutAction());
404
+ if (typeof window !== 'undefined') {
405
+ localStorage.removeItem('auth');
406
+ sessionStorage.clear();
407
+ }
408
+ router.push('/login');
409
+ }
410
+ };
411
+
412
+ return {
413
+ user,
414
+ isAuthenticated,
415
+ login: loginMutation.mutate,
416
+ logout,
417
+ isLoggingIn: loginMutation.isPending,
418
+ };
419
+ };
420
+ ```
421
+
422
+ ---
423
+
424
+ ### Step 6: Providers & Layout
425
+
426
+ #### `src/app/providers.tsx`
427
+ ```typescript
428
+ 'use client';
429
+
430
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
431
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
432
+ import { Provider as ReduxProvider } from 'react-redux';
433
+ import { ThemeProvider } from 'next-themes';
434
+ import { Toaster } from 'sonner';
435
+ import { useState } from 'react';
436
+ import { store } from '@/store';
437
+
438
+ export function Providers({ children }: { children: React.ReactNode }): React.ReactElement {
439
+ const [queryClient] = useState(
440
+ () =>
441
+ new QueryClient({
442
+ defaultOptions: {
443
+ queries: {
444
+ staleTime: 5 * 60 * 1000,
445
+ gcTime: 10 * 60 * 1000,
446
+ refetchOnWindowFocus: false,
447
+ retry: 1,
448
+ },
449
+ },
450
+ })
451
+ );
452
+
453
+ return (
454
+ <ReduxProvider store={store}>
455
+ <QueryClientProvider client={queryClient}>
456
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
457
+ {children}
458
+ <Toaster position="top-right" richColors />
459
+ </ThemeProvider>
460
+ <ReactQueryDevtools initialIsOpen={false} />
461
+ </QueryClientProvider>
462
+ </ReduxProvider>
463
+ );
464
+ }
465
+ ```
466
+
467
+ #### `src/app/layout.tsx`
468
+ Root layout:
469
+ - Import and use `next/font` (Inter or Geist Sans)
470
+ - `<html lang="en" suppressHydrationWarning>`
471
+ - Wrap children in `<Providers>`
472
+ - Default metadata with app name
473
+
474
+ #### `src/app/(main)/layout.tsx`
475
+ Main layout with Header and Footer wrapping children in a flex column min-h-screen structure.
476
+
477
+ #### `src/app/(auth)/layout.tsx`
478
+ Auth layout: centered, minimal — flex items-center justify-center min-h-screen.
479
+
480
+ ---
481
+
482
+ ### Step 7: Common Components
483
+
484
+ #### `src/components/layout/Header.tsx`
485
+ Client component with:
486
+ - App name / logo link to home
487
+ - Navigation links
488
+ - Auth-aware: show Login/Register or user menu based on auth state
489
+ - Theme toggle button (Sun/Moon icons with `next-themes` `useTheme`)
490
+ - Responsive (mobile menu)
491
+
492
+ #### `src/components/layout/Footer.tsx`
493
+ Server component, simple footer with copyright.
494
+
495
+ #### `src/components/common/LoadingSpinner.tsx`
496
+ Using Lucide `Loader2` icon with spin animation. Accept `size` prop.
497
+
498
+ #### `src/components/common/EmptyState.tsx`
499
+ ```typescript
500
+ import { FileQuestion } from 'lucide-react';
501
+ import { Button } from '@/components/ui/button';
502
+ import Link from 'next/link';
503
+
504
+ interface EmptyStateProps {
505
+ title: string;
506
+ description: string;
507
+ actionLabel?: string;
508
+ actionHref?: string;
509
+ }
510
+
511
+ export const EmptyState = ({
512
+ title,
513
+ description,
514
+ actionLabel,
515
+ actionHref,
516
+ }: EmptyStateProps): React.ReactElement => (
517
+ <div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
518
+ <FileQuestion className="mb-4 h-16 w-16 text-muted-foreground" />
519
+ <h3 className="mb-2 text-xl font-semibold">{title}</h3>
520
+ <p className="mb-4 text-muted-foreground">{description}</p>
521
+ {actionLabel && actionHref && (
522
+ <Button asChild>
523
+ <Link href={actionHref}>{actionLabel}</Link>
524
+ </Button>
525
+ )}
526
+ </div>
527
+ );
528
+ ```
529
+
530
+ #### `src/components/common/Pagination.tsx`
531
+ ```typescript
532
+ 'use client';
533
+
534
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation';
535
+ import { Button } from '@/components/ui/button';
536
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
537
+ import { useCallback } from 'react';
538
+
539
+ interface PaginationProps {
540
+ page: number;
541
+ totalPages: number;
542
+ }
543
+
544
+ export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactElement => {
545
+ const router = useRouter();
546
+ const pathname = usePathname();
547
+ const searchParams = useSearchParams();
548
+
549
+ const createPageUrl = useCallback(
550
+ (pageNumber: number): string => {
551
+ const params = new URLSearchParams(searchParams.toString());
552
+ params.set('page', pageNumber.toString());
553
+ return `${pathname}?${params.toString()}`;
554
+ },
555
+ [pathname, searchParams]
556
+ );
557
+
558
+ return (
559
+ <div className="flex items-center justify-center gap-2">
560
+ <Button
561
+ variant="outline"
562
+ size="sm"
563
+ onClick={() => router.push(createPageUrl(page - 1))}
564
+ disabled={page <= 1}
565
+ aria-label="Previous page"
566
+ >
567
+ <ChevronLeft className="h-4 w-4" />
568
+ </Button>
569
+ <span className="text-sm text-muted-foreground">
570
+ Page {page} of {totalPages}
571
+ </span>
572
+ <Button
573
+ variant="outline"
574
+ size="sm"
575
+ onClick={() => router.push(createPageUrl(page + 1))}
576
+ disabled={page >= totalPages}
577
+ aria-label="Next page"
578
+ >
579
+ <ChevronRight className="h-4 w-4" />
580
+ </Button>
581
+ </div>
582
+ );
583
+ };
584
+ ```
585
+
586
+ #### `src/components/common/ConfirmDialog.tsx`
587
+ ```typescript
588
+ 'use client';
589
+
590
+ import {
591
+ AlertDialog,
592
+ AlertDialogAction,
593
+ AlertDialogCancel,
594
+ AlertDialogContent,
595
+ AlertDialogDescription,
596
+ AlertDialogFooter,
597
+ AlertDialogHeader,
598
+ AlertDialogTitle,
599
+ } from '@/components/ui/alert-dialog';
600
+
601
+ interface ConfirmDialogProps {
602
+ open: boolean;
603
+ onOpenChange: (open: boolean) => void;
604
+ onConfirm: () => void;
605
+ title: string;
606
+ description: string;
607
+ confirmLabel?: string;
608
+ isDestructive?: boolean;
609
+ }
610
+
611
+ export const ConfirmDialog = ({
612
+ open,
613
+ onOpenChange,
614
+ onConfirm,
615
+ title,
616
+ description,
617
+ confirmLabel = 'Confirm',
618
+ isDestructive = false,
619
+ }: ConfirmDialogProps): React.ReactElement => (
620
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
621
+ <AlertDialogContent>
622
+ <AlertDialogHeader>
623
+ <AlertDialogTitle>{title}</AlertDialogTitle>
624
+ <AlertDialogDescription>{description}</AlertDialogDescription>
625
+ </AlertDialogHeader>
626
+ <AlertDialogFooter>
627
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
628
+ <AlertDialogAction
629
+ onClick={onConfirm}
630
+ className={isDestructive ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90' : ''}
631
+ >
632
+ {confirmLabel}
633
+ </AlertDialogAction>
634
+ </AlertDialogFooter>
635
+ </AlertDialogContent>
636
+ </AlertDialog>
637
+ );
638
+ ```
639
+
640
+ ---
641
+
642
+ ### Step 8: Global Custom Hooks
643
+
644
+ #### `src/hooks/useDebounce.ts`
645
+ ```typescript
646
+ 'use client';
647
+
648
+ import { useState, useEffect } from 'react';
649
+
650
+ export const useDebounce = <T,>(value: T, delay = 500): T => {
651
+ const [debouncedValue, setDebouncedValue] = useState(value);
652
+
653
+ useEffect(() => {
654
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
655
+ return () => clearTimeout(timer);
656
+ }, [value, delay]);
657
+
658
+ return debouncedValue;
659
+ };
660
+ ```
661
+
662
+ #### `src/hooks/useLocalStorage.ts`
663
+ ```typescript
664
+ 'use client';
665
+
666
+ import { useState, useEffect } from 'react';
667
+
668
+ export const useLocalStorage = <T,>(key: string, initialValue: T) => {
669
+ const [storedValue, setStoredValue] = useState<T>(initialValue);
670
+ const [isHydrated, setIsHydrated] = useState(false);
671
+
672
+ useEffect(() => {
673
+ try {
674
+ const item = window.localStorage.getItem(key);
675
+ if (item) {
676
+ setStoredValue(JSON.parse(item));
677
+ }
678
+ } catch {
679
+ // Ignore errors
680
+ }
681
+ setIsHydrated(true);
682
+ }, [key]);
683
+
684
+ const setValue = (value: T | ((val: T) => T)): void => {
685
+ try {
686
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
687
+ setStoredValue(valueToStore);
688
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
689
+ } catch {
690
+ // Ignore write errors
691
+ }
692
+ };
693
+
694
+ return [storedValue, setValue, isHydrated] as const;
695
+ };
696
+ ```
697
+
698
+ #### `src/hooks/useMediaQuery.ts`
699
+ ```typescript
700
+ 'use client';
701
+
702
+ import { useState, useEffect } from 'react';
703
+
704
+ export const useMediaQuery = (query: string): boolean => {
705
+ const [matches, setMatches] = useState(false);
706
+
707
+ useEffect(() => {
708
+ const media = window.matchMedia(query);
709
+ setMatches(media.matches);
710
+
711
+ const listener = (e: MediaQueryListEvent): void => setMatches(e.matches);
712
+ media.addEventListener('change', listener);
713
+ return () => media.removeEventListener('change', listener);
714
+ }, [query]);
715
+
716
+ return matches;
717
+ };
718
+
719
+ export const useIsMobile = (): boolean => useMediaQuery('(max-width: 768px)');
720
+ export const useIsTablet = (): boolean => useMediaQuery('(max-width: 1024px)');
721
+ ```
722
+
723
+ ---
724
+
725
+ ### Step 9: Auth Pages
726
+
727
+ #### `src/app/(auth)/login/page.tsx`
728
+ Server component page that renders `LoginForm`.
729
+
730
+ #### `src/app/(auth)/register/page.tsx`
731
+ Server component page that renders `RegisterForm`.
732
+
733
+ #### `src/features/auth/components/LoginForm.tsx`
734
+ Client component:
735
+ - React Hook Form + Zod validation (email + password)
736
+ - Submit button with loading state ("Signing in..." / "Sign in")
737
+ - Link to register page
738
+ - Uses `useAuth` hook for login
739
+ - Field-level error messages below each field (red text + red border)
740
+
741
+ #### `src/features/auth/components/RegisterForm.tsx`
742
+ Client component:
743
+ - React Hook Form + Zod validation
744
+ - First name, last name, email, password, confirm password
745
+ - Password schema: min 8 chars, must contain uppercase, lowercase, and number
746
+ - Submit button with loading state
747
+ - Link to login page
748
+
749
+ ---
750
+
751
+ ### Step 10: Dashboard & Home Pages
752
+
753
+ #### `src/app/page.tsx`
754
+ Simple home page (Server Component). Welcome message with links to login/register or dashboard.
755
+
756
+ #### `src/app/(main)/dashboard/page.tsx`
757
+ Simple dashboard page placeholder (Server Component). Shows "Welcome to your dashboard."
758
+
759
+ #### `src/app/loading.tsx`
760
+ Global loading component with LoadingSpinner.
761
+
762
+ #### `src/app/error.tsx`
763
+ Client component global error boundary:
764
+ ```typescript
765
+ 'use client';
766
+
767
+ import { useEffect } from 'react';
768
+ import { Button } from '@/components/ui/button';
769
+ import { AlertCircle } from 'lucide-react';
770
+
771
+ export default function GlobalError({
772
+ error,
773
+ reset,
774
+ }: {
775
+ error: Error & { digest?: string };
776
+ reset: () => void;
777
+ }): React.ReactElement {
778
+ useEffect(() => {
779
+ // Log to error reporting service in production
780
+ }, [error]);
781
+
782
+ return (
783
+ <div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
784
+ <AlertCircle className="mb-4 h-12 w-12 text-destructive" />
785
+ <h2 className="mb-2 text-2xl font-bold">Something went wrong</h2>
786
+ <p className="mb-4 text-muted-foreground">{error.message}</p>
787
+ <Button onClick={reset}>Try again</Button>
788
+ </div>
789
+ );
790
+ }
791
+ ```
792
+
793
+ #### `src/app/not-found.tsx`
794
+ Simple 404 page with link to home.
795
+
796
+ ---
797
+
798
+ ### Step 11: Middleware
799
+
800
+ #### `src/middleware.ts`
801
+
802
+ > **Note**: The current auth system stores tokens in Redux + localStorage. Next.js middleware runs on the Edge and cannot access localStorage. This middleware checks for an `accessToken` cookie. For full protection, the login flow should also set an httpOnly cookie via a Server Action or API route. As a fallback, this middleware provides basic route protection — full auth verification happens client-side via the axios interceptor.
803
+
804
+ ```typescript
805
+ import { NextResponse } from 'next/server';
806
+ import type { NextRequest } from 'next/server';
807
+
808
+ const protectedPaths = ['/dashboard', '/profile', '/my-tours', '/admin'];
809
+ const authPaths = ['/login', '/register'];
810
+
811
+ export function middleware(request: NextRequest): NextResponse {
812
+ const { pathname } = request.nextUrl;
813
+ const token = request.cookies.get('accessToken')?.value;
814
+
815
+ const isProtectedPath = protectedPaths.some((path) =>
816
+ pathname.startsWith(path)
817
+ );
818
+
819
+ if (isProtectedPath && !token) {
820
+ const loginUrl = new URL('/login', request.url);
821
+ loginUrl.searchParams.set('from', pathname);
822
+ return NextResponse.redirect(loginUrl);
823
+ }
824
+
825
+ const isAuthPath = authPaths.some((path) => pathname.startsWith(path));
826
+
827
+ if (isAuthPath && token) {
828
+ return NextResponse.redirect(new URL('/dashboard', request.url));
829
+ }
830
+
831
+ return NextResponse.next();
832
+ }
833
+
834
+ export const config = {
835
+ matcher: [
836
+ '/dashboard/:path*',
837
+ '/profile/:path*',
838
+ '/my-tours/:path*',
839
+ '/admin/:path*',
840
+ '/login',
841
+ '/register',
842
+ ],
843
+ };
844
+ ```
845
+
846
+ ---
847
+
848
+ ### Step 12: Global Types
849
+
850
+ #### `src/types/index.ts`
851
+ Shared utility types:
852
+ ```typescript
853
+ export type Nullable<T> = T | null;
854
+ export type Optional<T> = T | undefined;
855
+ export type AsyncResult<T> = Promise<T>;
856
+ ```
857
+
858
+ ---
859
+
860
+ ### Step 13: Final Verification
861
+
862
+ After creating all files:
863
+ 1. Run `npm run dev` to verify the project starts without errors
864
+ 2. Check that the home page renders at `http://localhost:3001` (or the configured port)
865
+ 3. Verify no TypeScript errors
866
+
867
+ ---
868
+
869
+ ## IMPORTANT RULES
870
+
871
+ - Follow ALL rules from `.claude/rules/client/` and `.claude/rules/global/`
872
+ - Server Components by default — only add `'use client'` when hooks/state/events are needed
873
+ - Max 250 lines per component, max 5 props, max 3 JSX nesting levels
874
+ - No hardcoded colors — use Tailwind semantic tokens only (`bg-primary`, `text-foreground`)
875
+ - No inline styles — Tailwind only with `cn()` for conditional classes
876
+ - Import order: React/Next → third-party → UI → local → hooks → services → types → utils
877
+ - Forms: validate with Zod client-side AND server-side
878
+ - Never prefix secrets with `NEXT_PUBLIC_`
879
+ - Use `next/image` for images, `next/link` for navigation
880
+ - Pair background with foreground colors (`bg-primary text-primary-foreground`)
881
+ - All interactive elements need hover/active/focus-visible states