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,27 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { AlertCircle } from 'lucide-react';
5
+
6
+ import { Button } from '@/components/ui/button';
7
+
8
+ export default function GlobalError({
9
+ error,
10
+ reset,
11
+ }: {
12
+ error: Error & { digest?: string };
13
+ reset: () => void;
14
+ }): React.ReactElement {
15
+ return (
16
+ <div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
17
+ <AlertCircle className="mb-4 h-12 w-12 text-destructive" />
18
+ <h2 className="mb-2 text-2xl font-bold">Something went wrong</h2>
19
+ <p className="mb-4 text-muted-foreground">
20
+ {process.env.NODE_ENV === 'development'
21
+ ? error.message
22
+ : 'An unexpected error occurred. Please try again.'}
23
+ </p>
24
+ <Button onClick={reset}>Try again</Button>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,145 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --font-sans: var(--font-geist-sans);
11
+ --font-mono: var(--font-geist-mono);
12
+ --color-sidebar-ring: var(--sidebar-ring);
13
+ --color-sidebar-border: var(--sidebar-border);
14
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
15
+ --color-sidebar-accent: var(--sidebar-accent);
16
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
17
+ --color-sidebar-primary: var(--sidebar-primary);
18
+ --color-sidebar-foreground: var(--sidebar-foreground);
19
+ --color-sidebar: var(--sidebar);
20
+ --color-chart-5: var(--chart-5);
21
+ --color-chart-4: var(--chart-4);
22
+ --color-chart-3: var(--chart-3);
23
+ --color-chart-2: var(--chart-2);
24
+ --color-chart-1: var(--chart-1);
25
+ --color-ring: var(--ring);
26
+ --color-input: var(--input);
27
+ --color-border: var(--border);
28
+ --color-destructive: var(--destructive);
29
+ --color-accent-foreground: var(--accent-foreground);
30
+ --color-accent: var(--accent);
31
+ --color-muted-foreground: var(--muted-foreground);
32
+ --color-muted: var(--muted);
33
+ --color-secondary-foreground: var(--secondary-foreground);
34
+ --color-secondary: var(--secondary);
35
+ --color-primary-foreground: var(--primary-foreground);
36
+ --color-primary: var(--primary);
37
+ --color-popover-foreground: var(--popover-foreground);
38
+ --color-popover: var(--popover);
39
+ --color-card-foreground: var(--card-foreground);
40
+ --color-card: var(--card);
41
+ --color-success: var(--success);
42
+ --color-success-foreground: var(--success-foreground);
43
+ --color-warning: var(--warning);
44
+ --color-warning-foreground: var(--warning-foreground);
45
+ --color-info: var(--info);
46
+ --color-info-foreground: var(--info-foreground);
47
+ --radius-sm: calc(var(--radius) - 4px);
48
+ --radius-md: calc(var(--radius) - 2px);
49
+ --radius-lg: var(--radius);
50
+ --radius-xl: calc(var(--radius) + 4px);
51
+ --radius-2xl: calc(var(--radius) + 8px);
52
+ --radius-3xl: calc(var(--radius) + 12px);
53
+ --radius-4xl: calc(var(--radius) + 16px);
54
+ }
55
+
56
+ :root {
57
+ --radius: 0.625rem;
58
+ --background: oklch(1 0 0);
59
+ --foreground: oklch(0.145 0 0);
60
+ --card: oklch(1 0 0);
61
+ --card-foreground: oklch(0.145 0 0);
62
+ --popover: oklch(1 0 0);
63
+ --popover-foreground: oklch(0.145 0 0);
64
+ --primary: oklch(0.45 0.2 260);
65
+ --primary-foreground: oklch(0.985 0 0);
66
+ --secondary: oklch(0.97 0 0);
67
+ --secondary-foreground: oklch(0.205 0 0);
68
+ --muted: oklch(0.97 0 0);
69
+ --muted-foreground: oklch(0.556 0 0);
70
+ --accent: oklch(0.97 0 0);
71
+ --accent-foreground: oklch(0.205 0 0);
72
+ --destructive: oklch(0.577 0.245 27.325);
73
+ --border: oklch(0.922 0 0);
74
+ --input: oklch(0.922 0 0);
75
+ --ring: oklch(0.45 0.2 260);
76
+ --success: oklch(0.52 0.17 155);
77
+ --success-foreground: oklch(1 0 0);
78
+ --warning: oklch(0.75 0.18 75);
79
+ --warning-foreground: oklch(0.2 0 0);
80
+ --info: oklch(0.55 0.15 240);
81
+ --info-foreground: oklch(1 0 0);
82
+ --chart-1: oklch(0.646 0.222 41.116);
83
+ --chart-2: oklch(0.6 0.118 184.704);
84
+ --chart-3: oklch(0.398 0.07 227.392);
85
+ --chart-4: oklch(0.828 0.189 84.429);
86
+ --chart-5: oklch(0.769 0.188 70.08);
87
+ --sidebar: oklch(0.985 0 0);
88
+ --sidebar-foreground: oklch(0.145 0 0);
89
+ --sidebar-primary: oklch(0.205 0 0);
90
+ --sidebar-primary-foreground: oklch(0.985 0 0);
91
+ --sidebar-accent: oklch(0.97 0 0);
92
+ --sidebar-accent-foreground: oklch(0.205 0 0);
93
+ --sidebar-border: oklch(0.922 0 0);
94
+ --sidebar-ring: oklch(0.708 0 0);
95
+ }
96
+
97
+ .dark {
98
+ --background: oklch(0.145 0 0);
99
+ --foreground: oklch(0.985 0 0);
100
+ --card: oklch(0.205 0 0);
101
+ --card-foreground: oklch(0.985 0 0);
102
+ --popover: oklch(0.205 0 0);
103
+ --popover-foreground: oklch(0.985 0 0);
104
+ --primary: oklch(0.6 0.2 260);
105
+ --primary-foreground: oklch(0.145 0 0);
106
+ --secondary: oklch(0.269 0 0);
107
+ --secondary-foreground: oklch(0.985 0 0);
108
+ --muted: oklch(0.269 0 0);
109
+ --muted-foreground: oklch(0.708 0 0);
110
+ --accent: oklch(0.269 0 0);
111
+ --accent-foreground: oklch(0.985 0 0);
112
+ --destructive: oklch(0.704 0.191 22.216);
113
+ --border: oklch(1 0 0 / 10%);
114
+ --input: oklch(1 0 0 / 15%);
115
+ --ring: oklch(0.6 0.2 260);
116
+ --success: oklch(0.6 0.17 155);
117
+ --success-foreground: oklch(1 0 0);
118
+ --warning: oklch(0.8 0.18 75);
119
+ --warning-foreground: oklch(0.2 0 0);
120
+ --info: oklch(0.65 0.15 240);
121
+ --info-foreground: oklch(1 0 0);
122
+ --chart-1: oklch(0.488 0.243 264.376);
123
+ --chart-2: oklch(0.696 0.17 162.48);
124
+ --chart-3: oklch(0.769 0.188 70.08);
125
+ --chart-4: oklch(0.627 0.265 303.9);
126
+ --chart-5: oklch(0.645 0.246 16.439);
127
+ --sidebar: oklch(0.205 0 0);
128
+ --sidebar-foreground: oklch(0.985 0 0);
129
+ --sidebar-primary: oklch(0.488 0.243 264.376);
130
+ --sidebar-primary-foreground: oklch(0.985 0 0);
131
+ --sidebar-accent: oklch(0.269 0 0);
132
+ --sidebar-accent-foreground: oklch(0.985 0 0);
133
+ --sidebar-border: oklch(1 0 0 / 10%);
134
+ --sidebar-ring: oklch(0.556 0 0);
135
+ }
136
+
137
+ @layer base {
138
+ * {
139
+ @apply border-border outline-ring/50;
140
+ }
141
+ body {
142
+ @apply bg-background text-foreground;
143
+ font-feature-settings: "rlig" 1, "calt" 1;
144
+ }
145
+ }
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from 'next';
2
+ import type React from 'react';
3
+ import { Geist, Geist_Mono } from 'next/font/google';
4
+
5
+ import { Providers } from './providers';
6
+ import { APP_NAME } from '@/lib/constants/app.constants';
7
+ import './globals.css';
8
+
9
+ const geistSans = Geist({
10
+ variable: '--font-geist-sans',
11
+ subsets: ['latin'],
12
+ });
13
+
14
+ const geistMono = Geist_Mono({
15
+ variable: '--font-geist-mono',
16
+ subsets: ['latin'],
17
+ });
18
+
19
+ export const metadata: Metadata = {
20
+ title: APP_NAME,
21
+ description: 'A full-stack application built with Next.js and Fastify',
22
+ };
23
+
24
+ export default function RootLayout({
25
+ children,
26
+ }: Readonly<{
27
+ children: React.ReactNode;
28
+ }>): React.ReactElement {
29
+ return (
30
+ <html lang="en" suppressHydrationWarning>
31
+ <body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
32
+ <Providers>{children}</Providers>
33
+ </body>
34
+ </html>
35
+ );
36
+ }
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ import { LoadingSpinner } from '@/components/common/LoadingSpinner';
4
+
5
+ export default function Loading(): React.ReactElement {
6
+ return (
7
+ <div className="flex min-h-dvh items-center justify-center">
8
+ <LoadingSpinner size="lg" />
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,23 @@
1
+ import type React from 'react';
2
+
3
+ import Link from 'next/link';
4
+
5
+ import { FileQuestion } from 'lucide-react';
6
+
7
+ import { Button } from '@/components/ui/button';
8
+ import { ROUTES } from '@/lib/constants/routes';
9
+
10
+ export default function NotFound(): React.ReactElement {
11
+ return (
12
+ <div className="flex min-h-dvh flex-col items-center justify-center p-8 text-center">
13
+ <FileQuestion className="mb-4 h-16 w-16 text-muted-foreground" />
14
+ <h1 className="mb-2 text-3xl font-bold">Page not found</h1>
15
+ <p className="mb-6 text-muted-foreground">
16
+ The page you&apos;re looking for doesn&apos;t exist or has been moved.
17
+ </p>
18
+ <Button asChild>
19
+ <Link href={ROUTES.HOME}>Go home</Link>
20
+ </Button>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { LogOut } from 'lucide-react';
5
+
6
+ import { Button } from '@/components/ui/button';
7
+ import { Skeleton } from '@/components/ui/skeleton';
8
+ import { useAppSelector } from '@/store/hooks';
9
+ import { useAuth } from '@/features/auth/hooks/useAuth';
10
+ import { APP_NAME } from '@/lib/constants/app.constants';
11
+
12
+ export default function WelcomePage(): React.ReactElement {
13
+ const { user } = useAppSelector((state) => state.auth);
14
+ const { logout, isLoggingOut } = useAuth();
15
+
16
+ return (
17
+ <div className="flex min-h-dvh flex-col">
18
+ <header className="flex items-center justify-between px-6 py-4">
19
+ <span className="text-lg font-semibold tracking-tight text-foreground">
20
+ {APP_NAME}
21
+ </span>
22
+ <Button
23
+ variant="ghost"
24
+ size="sm"
25
+ onClick={logout}
26
+ disabled={isLoggingOut}
27
+ className="text-muted-foreground transition-colors duration-150 hover:text-foreground"
28
+ >
29
+ <LogOut className="mr-2 h-4 w-4" />
30
+ Sign out
31
+ </Button>
32
+ </header>
33
+
34
+ <main className="flex flex-1 items-center justify-center">
35
+ {user ? (
36
+ <h1 className="text-3xl font-light tracking-tight text-foreground">
37
+ Welcome, {user.firstName}
38
+ </h1>
39
+ ) : (
40
+ <Skeleton className="h-9 w-64" />
41
+ )}
42
+ </main>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { useState } from 'react';
5
+
6
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
8
+ import { Provider as ReduxProvider } from 'react-redux';
9
+ import { ThemeProvider } from 'next-themes';
10
+ import { Toaster } from 'sonner';
11
+
12
+ import { store } from '@/store';
13
+ import { AuthInitializer } from '@/features/auth/components/AuthInitializer';
14
+
15
+ export function Providers({ children }: { children: React.ReactNode }): React.ReactElement {
16
+ const [queryClient] = useState(
17
+ () =>
18
+ new QueryClient({
19
+ defaultOptions: {
20
+ queries: {
21
+ staleTime: 5 * 60 * 1000,
22
+ gcTime: 10 * 60 * 1000,
23
+ refetchOnWindowFocus: false,
24
+ retry: 1,
25
+ },
26
+ },
27
+ })
28
+ );
29
+
30
+ return (
31
+ <ReduxProvider store={store}>
32
+ <QueryClientProvider client={queryClient}>
33
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
34
+ <AuthInitializer>
35
+ {children}
36
+ </AuthInitializer>
37
+ <Toaster position="top-right" richColors />
38
+ </ThemeProvider>
39
+ {process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
40
+ </QueryClientProvider>
41
+ </ReduxProvider>
42
+ );
43
+ }
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+
5
+ import {
6
+ AlertDialog,
7
+ AlertDialogAction,
8
+ AlertDialogCancel,
9
+ AlertDialogContent,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogHeader,
13
+ AlertDialogTitle,
14
+ } from '@/components/ui/alert-dialog';
15
+
16
+ interface ConfirmDialogProps {
17
+ open: boolean;
18
+ onOpenChange: (open: boolean) => void;
19
+ onConfirm: () => void;
20
+ title: string;
21
+ description: string;
22
+ confirmLabel?: string;
23
+ isDestructive?: boolean;
24
+ }
25
+
26
+ export const ConfirmDialog = ({
27
+ open,
28
+ onOpenChange,
29
+ onConfirm,
30
+ title,
31
+ description,
32
+ confirmLabel = 'Confirm',
33
+ isDestructive = false,
34
+ }: ConfirmDialogProps): React.ReactElement => (
35
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
36
+ <AlertDialogContent>
37
+ <AlertDialogHeader>
38
+ <AlertDialogTitle>{title}</AlertDialogTitle>
39
+ <AlertDialogDescription>{description}</AlertDialogDescription>
40
+ </AlertDialogHeader>
41
+ <AlertDialogFooter>
42
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
43
+ <AlertDialogAction
44
+ onClick={onConfirm}
45
+ className={
46
+ isDestructive
47
+ ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
48
+ : ''
49
+ }
50
+ >
51
+ {confirmLabel}
52
+ </AlertDialogAction>
53
+ </AlertDialogFooter>
54
+ </AlertDialogContent>
55
+ </AlertDialog>
56
+ );
@@ -0,0 +1,31 @@
1
+ import type React from 'react';
2
+
3
+ import Link from 'next/link';
4
+ import { FileQuestion } from 'lucide-react';
5
+
6
+ import { Button } from '@/components/ui/button';
7
+
8
+ interface EmptyStateProps {
9
+ title: string;
10
+ description: string;
11
+ actionLabel?: string;
12
+ actionHref?: string;
13
+ }
14
+
15
+ export const EmptyState = ({
16
+ title,
17
+ description,
18
+ actionLabel,
19
+ actionHref,
20
+ }: EmptyStateProps): React.ReactElement => (
21
+ <div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
22
+ <FileQuestion className="mb-4 h-16 w-16 text-muted-foreground" />
23
+ <h3 className="mb-2 text-xl font-semibold">{title}</h3>
24
+ <p className="mb-4 text-muted-foreground">{description}</p>
25
+ {actionLabel && actionHref && (
26
+ <Button asChild>
27
+ <Link href={actionHref}>{actionLabel}</Link>
28
+ </Button>
29
+ )}
30
+ </div>
31
+ );
@@ -0,0 +1,30 @@
1
+ import type React from 'react';
2
+ import { Loader2 } from 'lucide-react';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ type SpinnerSize = 'sm' | 'md' | 'lg';
7
+
8
+ const sizeClasses: Record<SpinnerSize, string> = {
9
+ sm: 'h-4 w-4',
10
+ md: 'h-6 w-6',
11
+ lg: 'h-8 w-8',
12
+ };
13
+
14
+ interface LoadingSpinnerProps {
15
+ size?: SpinnerSize;
16
+ className?: string;
17
+ }
18
+
19
+ export const LoadingSpinner = ({
20
+ size = 'md',
21
+ className,
22
+ }: LoadingSpinnerProps): React.ReactElement => {
23
+ return (
24
+ <Loader2
25
+ role="status"
26
+ className={cn('motion-safe:animate-spin text-muted-foreground', sizeClasses[size], className)}
27
+ aria-label="Loading"
28
+ />
29
+ );
30
+ };
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { useCallback } from 'react';
5
+
6
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation';
7
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
8
+
9
+ import { Button } from '@/components/ui/button';
10
+
11
+ interface PaginationProps {
12
+ page: number;
13
+ totalPages: number;
14
+ }
15
+
16
+ export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactElement => {
17
+ const router = useRouter();
18
+ const pathname = usePathname();
19
+ const searchParams = useSearchParams();
20
+
21
+ const createPageUrl = useCallback(
22
+ (pageNumber: number): string => {
23
+ const params = new URLSearchParams(searchParams.toString());
24
+ params.set('page', pageNumber.toString());
25
+ return `${pathname}?${params.toString()}`;
26
+ },
27
+ [pathname, searchParams]
28
+ );
29
+
30
+ return (
31
+ <div className="flex items-center justify-center gap-2">
32
+ <Button
33
+ variant="outline"
34
+ size="sm"
35
+ onClick={() => router.push(createPageUrl(page - 1))}
36
+ disabled={page <= 1}
37
+ aria-label="Previous page"
38
+ >
39
+ <ChevronLeft className="h-4 w-4" />
40
+ </Button>
41
+ <span className="text-sm tabular-nums text-muted-foreground">
42
+ Page {page} of {totalPages}
43
+ </span>
44
+ <Button
45
+ variant="outline"
46
+ size="sm"
47
+ onClick={() => router.push(createPageUrl(page + 1))}
48
+ disabled={page >= totalPages}
49
+ aria-label="Next page"
50
+ >
51
+ <ChevronRight className="h-4 w-4" />
52
+ </Button>
53
+ </div>
54
+ );
55
+ };
@@ -0,0 +1,17 @@
1
+ import type React from 'react';
2
+
3
+ import { APP_NAME } from '@/lib/constants/app.constants';
4
+
5
+ export const Footer = (): React.ReactElement => {
6
+ const year = new Date().getFullYear();
7
+
8
+ return (
9
+ <footer className="border-t border-border/50 bg-background">
10
+ <div className="container mx-auto flex items-center justify-center px-4 py-6 md:px-6 lg:px-8">
11
+ <p className="text-sm text-muted-foreground">
12
+ &copy; {year} {APP_NAME}. All rights reserved.
13
+ </p>
14
+ </div>
15
+ </footer>
16
+ );
17
+ };