create-blitzpack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/index.js +452 -0
  2. package/package.json +57 -0
  3. package/template/.dockerignore +59 -0
  4. package/template/.github/workflows/ci.yml +157 -0
  5. package/template/.husky/pre-commit +1 -0
  6. package/template/.husky/pre-push +1 -0
  7. package/template/.lintstagedrc.cjs +4 -0
  8. package/template/.nvmrc +1 -0
  9. package/template/.prettierrc +9 -0
  10. package/template/.vscode/settings.json +13 -0
  11. package/template/CLAUDE.md +175 -0
  12. package/template/CONTRIBUTING.md +32 -0
  13. package/template/Dockerfile +90 -0
  14. package/template/GETTING_STARTED.md +35 -0
  15. package/template/LICENSE +21 -0
  16. package/template/README.md +116 -0
  17. package/template/apps/api/.dockerignore +51 -0
  18. package/template/apps/api/.env.local.example +62 -0
  19. package/template/apps/api/emails/account-deleted-email.tsx +69 -0
  20. package/template/apps/api/emails/components/email-layout.tsx +154 -0
  21. package/template/apps/api/emails/config.ts +22 -0
  22. package/template/apps/api/emails/password-changed-email.tsx +88 -0
  23. package/template/apps/api/emails/password-reset-email.tsx +86 -0
  24. package/template/apps/api/emails/verification-email.tsx +85 -0
  25. package/template/apps/api/emails/welcome-email.tsx +70 -0
  26. package/template/apps/api/package.json +84 -0
  27. package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +13 -0
  28. package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +67 -0
  29. package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +5 -0
  30. package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +7 -0
  31. package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +2 -0
  32. package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +26 -0
  33. package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +2 -0
  34. package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +10 -0
  35. package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +29 -0
  36. package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +8 -0
  37. package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +11 -0
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  39. package/template/apps/api/prisma/schema.prisma +116 -0
  40. package/template/apps/api/prisma/seed.ts +159 -0
  41. package/template/apps/api/prisma.config.ts +14 -0
  42. package/template/apps/api/src/app.ts +377 -0
  43. package/template/apps/api/src/common/logger.service.ts +227 -0
  44. package/template/apps/api/src/config/env.ts +60 -0
  45. package/template/apps/api/src/config/rate-limit.ts +29 -0
  46. package/template/apps/api/src/hooks/auth.ts +122 -0
  47. package/template/apps/api/src/plugins/auth.ts +198 -0
  48. package/template/apps/api/src/plugins/database.ts +45 -0
  49. package/template/apps/api/src/plugins/logger.ts +33 -0
  50. package/template/apps/api/src/plugins/multipart.ts +16 -0
  51. package/template/apps/api/src/plugins/scalar.ts +20 -0
  52. package/template/apps/api/src/plugins/schedule.ts +52 -0
  53. package/template/apps/api/src/plugins/services.ts +66 -0
  54. package/template/apps/api/src/plugins/swagger.ts +56 -0
  55. package/template/apps/api/src/routes/accounts.ts +91 -0
  56. package/template/apps/api/src/routes/admin-sessions.ts +92 -0
  57. package/template/apps/api/src/routes/metrics.ts +71 -0
  58. package/template/apps/api/src/routes/password.ts +46 -0
  59. package/template/apps/api/src/routes/sessions.ts +53 -0
  60. package/template/apps/api/src/routes/stats.ts +38 -0
  61. package/template/apps/api/src/routes/uploads-serve.ts +27 -0
  62. package/template/apps/api/src/routes/uploads.ts +154 -0
  63. package/template/apps/api/src/routes/users.ts +114 -0
  64. package/template/apps/api/src/routes/verification.ts +90 -0
  65. package/template/apps/api/src/server.ts +34 -0
  66. package/template/apps/api/src/services/accounts.service.ts +125 -0
  67. package/template/apps/api/src/services/authorization.service.ts +162 -0
  68. package/template/apps/api/src/services/email.service.ts +170 -0
  69. package/template/apps/api/src/services/file-storage.service.ts +267 -0
  70. package/template/apps/api/src/services/metrics.service.ts +175 -0
  71. package/template/apps/api/src/services/password.service.ts +56 -0
  72. package/template/apps/api/src/services/sessions.service.spec.ts +134 -0
  73. package/template/apps/api/src/services/sessions.service.ts +276 -0
  74. package/template/apps/api/src/services/stats.service.ts +273 -0
  75. package/template/apps/api/src/services/uploads.service.ts +163 -0
  76. package/template/apps/api/src/services/users.service.spec.ts +249 -0
  77. package/template/apps/api/src/services/users.service.ts +198 -0
  78. package/template/apps/api/src/utils/file-validation.ts +108 -0
  79. package/template/apps/api/start.sh +33 -0
  80. package/template/apps/api/test/helpers/fastify-app.ts +24 -0
  81. package/template/apps/api/test/helpers/mock-authorization.ts +16 -0
  82. package/template/apps/api/test/helpers/mock-logger.ts +28 -0
  83. package/template/apps/api/test/helpers/mock-prisma.ts +30 -0
  84. package/template/apps/api/test/helpers/test-db.ts +125 -0
  85. package/template/apps/api/test/integration/auth-flow.integration.spec.ts +449 -0
  86. package/template/apps/api/test/integration/password.integration.spec.ts +427 -0
  87. package/template/apps/api/test/integration/rate-limit.integration.spec.ts +51 -0
  88. package/template/apps/api/test/integration/sessions.integration.spec.ts +445 -0
  89. package/template/apps/api/test/integration/users.integration.spec.ts +211 -0
  90. package/template/apps/api/test/setup.ts +31 -0
  91. package/template/apps/api/tsconfig.json +26 -0
  92. package/template/apps/api/vitest.config.ts +35 -0
  93. package/template/apps/web/.env.local.example +11 -0
  94. package/template/apps/web/components.json +24 -0
  95. package/template/apps/web/next.config.ts +22 -0
  96. package/template/apps/web/package.json +56 -0
  97. package/template/apps/web/postcss.config.js +5 -0
  98. package/template/apps/web/public/apple-icon.png +0 -0
  99. package/template/apps/web/public/icon.png +0 -0
  100. package/template/apps/web/public/robots.txt +3 -0
  101. package/template/apps/web/src/app/(admin)/admin/layout.tsx +222 -0
  102. package/template/apps/web/src/app/(admin)/admin/page.tsx +157 -0
  103. package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +18 -0
  104. package/template/apps/web/src/app/(admin)/admin/users/page.tsx +20 -0
  105. package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +177 -0
  106. package/template/apps/web/src/app/(auth)/login/page.tsx +159 -0
  107. package/template/apps/web/src/app/(auth)/reset-password/page.tsx +245 -0
  108. package/template/apps/web/src/app/(auth)/signup/page.tsx +153 -0
  109. package/template/apps/web/src/app/dashboard/change-password/page.tsx +255 -0
  110. package/template/apps/web/src/app/dashboard/page.tsx +296 -0
  111. package/template/apps/web/src/app/error.tsx +32 -0
  112. package/template/apps/web/src/app/examples/file-upload/page.tsx +200 -0
  113. package/template/apps/web/src/app/favicon.ico +0 -0
  114. package/template/apps/web/src/app/global-error.tsx +96 -0
  115. package/template/apps/web/src/app/globals.css +22 -0
  116. package/template/apps/web/src/app/icon.png +0 -0
  117. package/template/apps/web/src/app/layout.tsx +34 -0
  118. package/template/apps/web/src/app/not-found.tsx +28 -0
  119. package/template/apps/web/src/app/page.tsx +192 -0
  120. package/template/apps/web/src/components/admin/activity-feed.tsx +101 -0
  121. package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +114 -0
  122. package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +124 -0
  123. package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +511 -0
  124. package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +102 -0
  125. package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +90 -0
  126. package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +108 -0
  127. package/template/apps/web/src/components/admin/health-indicator.tsx +175 -0
  128. package/template/apps/web/src/components/admin/refresh-control.tsx +90 -0
  129. package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +79 -0
  130. package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +74 -0
  131. package/template/apps/web/src/components/admin/sessions-management-table.tsx +372 -0
  132. package/template/apps/web/src/components/admin/stat-card.tsx +137 -0
  133. package/template/apps/web/src/components/admin/user-create-dialog.tsx +152 -0
  134. package/template/apps/web/src/components/admin/user-delete-dialog.tsx +73 -0
  135. package/template/apps/web/src/components/admin/user-edit-dialog.tsx +170 -0
  136. package/template/apps/web/src/components/admin/users-management-table.tsx +285 -0
  137. package/template/apps/web/src/components/auth/email-verification-banner.tsx +85 -0
  138. package/template/apps/web/src/components/auth/github-button.tsx +40 -0
  139. package/template/apps/web/src/components/auth/google-button.tsx +54 -0
  140. package/template/apps/web/src/components/auth/protected-route.tsx +66 -0
  141. package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +31 -0
  142. package/template/apps/web/src/components/auth/with-auth.tsx +30 -0
  143. package/template/apps/web/src/components/error/error-card.tsx +47 -0
  144. package/template/apps/web/src/components/error/forbidden.tsx +25 -0
  145. package/template/apps/web/src/components/landing/command-block.tsx +64 -0
  146. package/template/apps/web/src/components/landing/feature-card.tsx +60 -0
  147. package/template/apps/web/src/components/landing/included-feature-card.tsx +63 -0
  148. package/template/apps/web/src/components/landing/logo.tsx +41 -0
  149. package/template/apps/web/src/components/landing/tech-badge.tsx +11 -0
  150. package/template/apps/web/src/components/layout/auth-nav.tsx +58 -0
  151. package/template/apps/web/src/components/layout/footer.tsx +3 -0
  152. package/template/apps/web/src/config/landing-data.ts +152 -0
  153. package/template/apps/web/src/config/site.ts +5 -0
  154. package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +181 -0
  155. package/template/apps/web/src/hooks/api/use-admin-sessions.ts +75 -0
  156. package/template/apps/web/src/hooks/api/use-admin-stats.ts +33 -0
  157. package/template/apps/web/src/hooks/api/use-sessions.ts +52 -0
  158. package/template/apps/web/src/hooks/api/use-uploads.ts +156 -0
  159. package/template/apps/web/src/hooks/api/use-users.ts +149 -0
  160. package/template/apps/web/src/hooks/use-mobile.ts +21 -0
  161. package/template/apps/web/src/hooks/use-realtime-metrics.ts +120 -0
  162. package/template/apps/web/src/lib/__tests__/utils.test.ts +29 -0
  163. package/template/apps/web/src/lib/api.ts +151 -0
  164. package/template/apps/web/src/lib/auth.ts +13 -0
  165. package/template/apps/web/src/lib/env.ts +52 -0
  166. package/template/apps/web/src/lib/form-utils.ts +11 -0
  167. package/template/apps/web/src/lib/utils.ts +1 -0
  168. package/template/apps/web/src/providers.tsx +34 -0
  169. package/template/apps/web/src/store/atoms.ts +15 -0
  170. package/template/apps/web/src/test/helpers/test-utils.tsx +44 -0
  171. package/template/apps/web/src/test/setup.ts +8 -0
  172. package/template/apps/web/tailwind.config.ts +5 -0
  173. package/template/apps/web/tsconfig.json +26 -0
  174. package/template/apps/web/vitest.config.ts +32 -0
  175. package/template/assets/logo-512.png +0 -0
  176. package/template/assets/logo.svg +4 -0
  177. package/template/docker-compose.prod.yml +66 -0
  178. package/template/docker-compose.yml +36 -0
  179. package/template/eslint.config.ts +119 -0
  180. package/template/package.json +77 -0
  181. package/template/packages/tailwind-config/package.json +9 -0
  182. package/template/packages/tailwind-config/theme.css +179 -0
  183. package/template/packages/types/package.json +29 -0
  184. package/template/packages/types/src/__tests__/schemas.test.ts +255 -0
  185. package/template/packages/types/src/api-response.ts +53 -0
  186. package/template/packages/types/src/health-check.ts +11 -0
  187. package/template/packages/types/src/pagination.ts +41 -0
  188. package/template/packages/types/src/role.ts +5 -0
  189. package/template/packages/types/src/session.ts +48 -0
  190. package/template/packages/types/src/stats.ts +113 -0
  191. package/template/packages/types/src/upload.ts +51 -0
  192. package/template/packages/types/src/user.ts +36 -0
  193. package/template/packages/types/tsconfig.json +5 -0
  194. package/template/packages/types/vitest.config.ts +21 -0
  195. package/template/packages/ui/components.json +21 -0
  196. package/template/packages/ui/package.json +108 -0
  197. package/template/packages/ui/src/__tests__/button.test.tsx +70 -0
  198. package/template/packages/ui/src/alert-dialog.tsx +141 -0
  199. package/template/packages/ui/src/alert.tsx +66 -0
  200. package/template/packages/ui/src/animated-theme-toggler.tsx +167 -0
  201. package/template/packages/ui/src/avatar.tsx +53 -0
  202. package/template/packages/ui/src/badge.tsx +36 -0
  203. package/template/packages/ui/src/button.tsx +84 -0
  204. package/template/packages/ui/src/card.tsx +92 -0
  205. package/template/packages/ui/src/checkbox.tsx +32 -0
  206. package/template/packages/ui/src/data-table/data-table-column-header.tsx +68 -0
  207. package/template/packages/ui/src/data-table/data-table-pagination.tsx +99 -0
  208. package/template/packages/ui/src/data-table/data-table-toolbar.tsx +55 -0
  209. package/template/packages/ui/src/data-table/data-table-view-options.tsx +63 -0
  210. package/template/packages/ui/src/data-table/data-table.tsx +167 -0
  211. package/template/packages/ui/src/dialog.tsx +143 -0
  212. package/template/packages/ui/src/dropdown-menu.tsx +257 -0
  213. package/template/packages/ui/src/empty-state.tsx +52 -0
  214. package/template/packages/ui/src/file-upload-input.tsx +202 -0
  215. package/template/packages/ui/src/form.tsx +168 -0
  216. package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
  217. package/template/packages/ui/src/icons/brand-icons.tsx +16 -0
  218. package/template/packages/ui/src/input.tsx +21 -0
  219. package/template/packages/ui/src/label.tsx +24 -0
  220. package/template/packages/ui/src/lib/utils.ts +6 -0
  221. package/template/packages/ui/src/password-input.tsx +102 -0
  222. package/template/packages/ui/src/popover.tsx +48 -0
  223. package/template/packages/ui/src/radio-group.tsx +45 -0
  224. package/template/packages/ui/src/scroll-area.tsx +58 -0
  225. package/template/packages/ui/src/select.tsx +187 -0
  226. package/template/packages/ui/src/separator.tsx +28 -0
  227. package/template/packages/ui/src/sheet.tsx +139 -0
  228. package/template/packages/ui/src/sidebar.tsx +726 -0
  229. package/template/packages/ui/src/skeleton-variants.tsx +87 -0
  230. package/template/packages/ui/src/skeleton.tsx +13 -0
  231. package/template/packages/ui/src/slider.tsx +63 -0
  232. package/template/packages/ui/src/sonner.tsx +25 -0
  233. package/template/packages/ui/src/spinner.tsx +16 -0
  234. package/template/packages/ui/src/switch.tsx +31 -0
  235. package/template/packages/ui/src/table.tsx +116 -0
  236. package/template/packages/ui/src/tabs.tsx +66 -0
  237. package/template/packages/ui/src/textarea.tsx +18 -0
  238. package/template/packages/ui/src/tooltip.tsx +61 -0
  239. package/template/packages/ui/src/user-avatar.tsx +97 -0
  240. package/template/packages/ui/test-config.js +3 -0
  241. package/template/packages/ui/tsconfig.json +12 -0
  242. package/template/packages/ui/turbo.json +18 -0
  243. package/template/packages/ui/vitest.config.ts +17 -0
  244. package/template/packages/ui/vitest.setup.ts +1 -0
  245. package/template/packages/utils/package.json +23 -0
  246. package/template/packages/utils/src/__tests__/utils.test.ts +223 -0
  247. package/template/packages/utils/src/array.ts +18 -0
  248. package/template/packages/utils/src/async.ts +3 -0
  249. package/template/packages/utils/src/date.ts +77 -0
  250. package/template/packages/utils/src/errors.ts +73 -0
  251. package/template/packages/utils/src/number.ts +11 -0
  252. package/template/packages/utils/src/string.ts +13 -0
  253. package/template/packages/utils/tsconfig.json +5 -0
  254. package/template/packages/utils/vitest.config.ts +21 -0
  255. package/template/pnpm-workspace.yaml +4 -0
  256. package/template/tsconfig.base.json +32 -0
  257. package/template/turbo.json +133 -0
  258. package/template/vitest.shared.ts +26 -0
  259. package/template/vitest.workspace.ts +9 -0
@@ -0,0 +1,35 @@
1
+ import tsconfigPaths from 'vite-tsconfig-paths';
2
+ import { defineConfig, mergeConfig } from 'vitest/config';
3
+
4
+ import { sharedConfig } from '../../vitest.shared';
5
+
6
+ export default mergeConfig(
7
+ sharedConfig,
8
+ defineConfig({
9
+ test: {
10
+ name: '@repo/api',
11
+ include: [
12
+ 'src/**/*.spec.ts', // Unit tests (with mocks)
13
+ 'test/**/*.spec.ts', // Unit tests
14
+ 'test/**/*.integration.spec.ts', // Integration tests (with real DB)
15
+ ],
16
+ globals: true,
17
+ root: './',
18
+ setupFiles: ['./test/setup.ts'],
19
+ // Run tests sequentially for integration tests (shared DB)
20
+ fileParallelism: false, // Disable file-level parallelism
21
+ sequence: {
22
+ concurrent: false, // Disable test-level parallelism
23
+ },
24
+ coverage: {
25
+ thresholds: {
26
+ lines: 80,
27
+ functions: 80,
28
+ branches: 75,
29
+ statements: 80,
30
+ },
31
+ },
32
+ },
33
+ plugins: [tsconfigPaths()],
34
+ })
35
+ );
@@ -0,0 +1,11 @@
1
+ NODE_ENV=development
2
+
3
+ # CRITICAL: Must include /api at the end (all API routes are under /api prefix)
4
+ # The auth client and regular API client both use this base URL
5
+ # Auth routes: {NEXT_PUBLIC_API_URL}/auth/*
6
+ # Other routes: {NEXT_PUBLIC_API_URL}/*
7
+ #
8
+ # Examples:
9
+ # Local: http://localhost:8080/api
10
+ # Production: https://your-railway-api.up.railway.app/api
11
+ NEXT_PUBLIC_API_URL=http://localhost:8080/api
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ },
21
+ "registries": {
22
+ "@magicui": "https://magicui.design/r/{name}.json"
23
+ }
24
+ }
@@ -0,0 +1,22 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ const nextConfig: NextConfig = {
4
+ transpilePackages: ['@repo/packages-types', '@repo/packages-utils'],
5
+ compiler: {
6
+ removeConsole: process.env.NODE_ENV === 'production',
7
+ },
8
+ images: {
9
+ remotePatterns: [
10
+ {
11
+ protocol: 'https',
12
+ hostname: 'lh3.googleusercontent.com',
13
+ },
14
+ {
15
+ protocol: 'https',
16
+ hostname: 'avatars.githubusercontent.com',
17
+ },
18
+ ],
19
+ },
20
+ };
21
+
22
+ export default nextConfig;
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@repo/web",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "typecheck": "tsc --noEmit",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint --fix .",
12
+ "test": "vitest run",
13
+ "test:unit": "vitest run",
14
+ "test:integration": "echo 'No integration tests for web'",
15
+ "test:watch": "vitest",
16
+ "test:coverage": "vitest run --coverage",
17
+ "vercel-build": "cd ../.. && turbo build --filter=@repo/web",
18
+ "vercel-ignore-build": "npx turbo-ignore --fallback=HEAD^1"
19
+ },
20
+ "dependencies": {
21
+ "@hookform/resolvers": "^5.2.2",
22
+ "@repo/packages-types": "workspace:*",
23
+ "@repo/packages-ui": "workspace:*",
24
+ "@repo/packages-utils": "workspace:*",
25
+ "@tanstack/react-query": "^5.90.2",
26
+ "@tanstack/react-table": "^8.21.3",
27
+ "better-auth": "^1.3.27",
28
+ "date-fns": "^4.1.0",
29
+ "framer-motion": "^12.23.24",
30
+ "jotai": "^2.15.0",
31
+ "lucide-react": "^0.545.0",
32
+ "next": "16.0.3",
33
+ "next-themes": "^0.4.6",
34
+ "react": "19.2.0",
35
+ "react-dom": "19.2.0",
36
+ "react-hook-form": "^7.65.0",
37
+ "recharts": "^3.5.0",
38
+ "sonner": "^2.0.7",
39
+ "zod": "^4.1.12"
40
+ },
41
+ "devDependencies": {
42
+ "@repo/tailwind-config": "workspace:*",
43
+ "@tailwindcss/postcss": "^4.1.14",
44
+ "@types/node": "^24.7.1",
45
+ "@types/react": "19.2.6",
46
+ "@types/react-dom": "19.2.3",
47
+ "@vitejs/plugin-react": "^5.0.4",
48
+ "eslint": "^9.18.0",
49
+ "eslint-config-next": "16.0.3",
50
+ "tailwindcss": "^4.1.14",
51
+ "tailwindcss-debug-screens": "^3.0.1",
52
+ "tw-animate-css": "^1.4.0",
53
+ "typescript": "^5.7.2",
54
+ "vitest": "^3.2.4"
55
+ }
56
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ };
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,222 @@
1
+ 'use client';
2
+
3
+ import { AnimatedThemeToggler } from '@repo/packages-ui/animated-theme-toggler';
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from '@repo/packages-ui/dropdown-menu';
10
+ import {
11
+ Sidebar,
12
+ SidebarContent,
13
+ SidebarFooter,
14
+ SidebarGroup,
15
+ SidebarGroupContent,
16
+ SidebarGroupLabel,
17
+ SidebarHeader,
18
+ SidebarInset,
19
+ SidebarMenu,
20
+ SidebarMenuButton,
21
+ SidebarMenuItem,
22
+ SidebarProvider,
23
+ SidebarTrigger,
24
+ } from '@repo/packages-ui/sidebar';
25
+ import { UserAvatar } from '@repo/packages-ui/user-avatar';
26
+ import { motion } from 'framer-motion';
27
+ import {
28
+ ChevronsUpDown,
29
+ KeyRound,
30
+ LogOut,
31
+ MonitorCheck,
32
+ ShieldCheck,
33
+ Users,
34
+ } from 'lucide-react';
35
+ import Link from 'next/link';
36
+ import { usePathname, useRouter } from 'next/navigation';
37
+ import React, { type ReactNode } from 'react';
38
+
39
+ import { ProtectedRoute } from '@/components/auth/protected-route';
40
+ import { authClient } from '@/lib/auth';
41
+
42
+ const adminNavItems = [
43
+ {
44
+ title: 'System Monitoring',
45
+ href: '/admin',
46
+ icon: MonitorCheck,
47
+ },
48
+ {
49
+ title: 'Users',
50
+ href: '/admin/users',
51
+ icon: Users,
52
+ },
53
+ {
54
+ title: 'Sessions',
55
+ href: '/admin/sessions',
56
+ icon: KeyRound,
57
+ },
58
+ ];
59
+
60
+ function AdminLayoutContent({ children }: { children: ReactNode }) {
61
+ const pathname = usePathname();
62
+ const router = useRouter();
63
+ const { data: session } = authClient.useSession();
64
+
65
+ const handleSignOut = async () => {
66
+ await authClient.signOut();
67
+ router.push('/');
68
+ };
69
+
70
+ return (
71
+ <SidebarProvider>
72
+ <Sidebar collapsible="icon">
73
+ <SidebarHeader>
74
+ <SidebarMenu>
75
+ <SidebarMenuItem>
76
+ <SidebarMenuButton
77
+ size="lg"
78
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
79
+ >
80
+ <div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
81
+ <ShieldCheck className="size-4" />
82
+ </div>
83
+ <div className="grid flex-1 text-left text-sm leading-tight">
84
+ <span className="truncate font-semibold">Admin Panel</span>
85
+ <span className="truncate text-xs">Manage App</span>
86
+ </div>
87
+ </SidebarMenuButton>
88
+ </SidebarMenuItem>
89
+ </SidebarMenu>
90
+ </SidebarHeader>
91
+ <SidebarContent>
92
+ <SidebarGroup>
93
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
94
+ <SidebarGroupContent>
95
+ <SidebarMenu>
96
+ {adminNavItems.map((item) => {
97
+ const Icon = item.icon;
98
+ const isActive = pathname === item.href;
99
+
100
+ return (
101
+ <SidebarMenuItem key={item.href}>
102
+ <SidebarMenuButton
103
+ asChild
104
+ isActive={isActive}
105
+ tooltip={item.title}
106
+ className="hover:bg-sidebar-accent/50 relative overflow-visible data-[active=true]:bg-transparent"
107
+ >
108
+ <Link href={item.href} className="relative z-20">
109
+ {isActive && (
110
+ <motion.div
111
+ layoutId="active-nav-item"
112
+ className="bg-sidebar-primary absolute inset-0 -z-10 rounded-md"
113
+ transition={{
114
+ type: 'spring',
115
+ stiffness: 300,
116
+ damping: 30,
117
+ }}
118
+ />
119
+ )}
120
+ <motion.span
121
+ animate={{
122
+ color: isActive
123
+ ? 'var(--sidebar-primary-foreground)'
124
+ : 'var(--sidebar-foreground)',
125
+ }}
126
+ transition={{
127
+ duration: 0.2,
128
+ delay: isActive ? 0.1 : 0,
129
+ }}
130
+ className="flex items-center justify-center"
131
+ >
132
+ <Icon className="size-4" />
133
+ </motion.span>
134
+ <motion.span
135
+ animate={{
136
+ color: isActive
137
+ ? 'var(--sidebar-primary-foreground)'
138
+ : 'var(--sidebar-foreground)',
139
+ }}
140
+ transition={{
141
+ duration: 0.2,
142
+ delay: isActive ? 0.1 : 0,
143
+ }}
144
+ className="truncate"
145
+ >
146
+ {item.title}
147
+ </motion.span>
148
+ </Link>
149
+ </SidebarMenuButton>
150
+ </SidebarMenuItem>
151
+ );
152
+ })}
153
+ </SidebarMenu>
154
+ </SidebarGroupContent>
155
+ </SidebarGroup>
156
+ </SidebarContent>
157
+ <SidebarFooter>
158
+ <SidebarMenu>
159
+ <SidebarMenuItem>
160
+ <DropdownMenu>
161
+ <DropdownMenuTrigger asChild>
162
+ <SidebarMenuButton
163
+ size="lg"
164
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
165
+ >
166
+ <UserAvatar
167
+ src={session?.user.image}
168
+ name={session?.user.name}
169
+ email={session?.user.email}
170
+ size="sm"
171
+ className="rounded-lg"
172
+ />
173
+ <div className="grid flex-1 text-left text-sm leading-tight">
174
+ <span className="truncate font-semibold">
175
+ {session?.user.name || 'Admin'}
176
+ </span>
177
+ <span className="truncate text-xs">
178
+ {session?.user.email}
179
+ </span>
180
+ </div>
181
+ <ChevronsUpDown className="ml-auto size-4" />
182
+ </SidebarMenuButton>
183
+ </DropdownMenuTrigger>
184
+ <DropdownMenuContent
185
+ className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
186
+ side="bottom"
187
+ align="end"
188
+ sideOffset={4}
189
+ >
190
+ <DropdownMenuItem onClick={handleSignOut}>
191
+ <LogOut className="mr-2 size-4" />
192
+ Log out
193
+ </DropdownMenuItem>
194
+ </DropdownMenuContent>
195
+ </DropdownMenu>
196
+ </SidebarMenuItem>
197
+ </SidebarMenu>
198
+ </SidebarFooter>
199
+ </Sidebar>
200
+ <SidebarInset>
201
+ <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
202
+ <SidebarTrigger className="-ml-1" />
203
+ <div className="flex-1" />
204
+ <AnimatedThemeToggler />
205
+ </header>
206
+ <div className="flex flex-1 flex-col gap-4 p-4 pt-0">{children}</div>
207
+ </SidebarInset>
208
+ </SidebarProvider>
209
+ );
210
+ }
211
+
212
+ export default function AdminLayout({
213
+ children,
214
+ }: {
215
+ children: React.ReactNode;
216
+ }) {
217
+ return (
218
+ <ProtectedRoute requiredRole="admin" redirectTo="/dashboard">
219
+ <AdminLayoutContent>{children}</AdminLayoutContent>
220
+ </ProtectedRoute>
221
+ );
222
+ }
@@ -0,0 +1,157 @@
1
+ 'use client';
2
+
3
+ import { Activity, CalendarDays, UserPlus, Users } from 'lucide-react';
4
+ import { useCallback, useState } from 'react';
5
+
6
+ import {
7
+ ActivityFeedSkeleton,
8
+ RecentSignupsFeed,
9
+ } from '@/components/admin/activity-feed';
10
+ import {
11
+ AuthBreakdownChart,
12
+ AuthBreakdownChartSkeleton,
13
+ } from '@/components/admin/charts/auth-breakdown-chart';
14
+ import {
15
+ RealtimeMetricsChart,
16
+ RealtimeMetricsChartSkeleton,
17
+ } from '@/components/admin/charts/realtime-metrics-chart';
18
+ import {
19
+ RoleDistributionChart,
20
+ RoleDistributionChartSkeleton,
21
+ } from '@/components/admin/charts/role-distribution-chart';
22
+ import {
23
+ SessionActivityChart,
24
+ SessionActivityChartSkeleton,
25
+ } from '@/components/admin/charts/session-activity-chart';
26
+ import {
27
+ UserGrowthChart,
28
+ UserGrowthChartSkeleton,
29
+ } from '@/components/admin/charts/user-growth-chart';
30
+ import {
31
+ HealthIndicator,
32
+ HealthIndicatorSkeleton,
33
+ } from '@/components/admin/health-indicator';
34
+ import { RefreshControl } from '@/components/admin/refresh-control';
35
+ import { StatCard, StatCardSkeleton } from '@/components/admin/stat-card';
36
+ import { useFetchSystemStats } from '@/hooks/api/use-admin-stats';
37
+
38
+ export default function AdminDashboardPage() {
39
+ const [refreshInterval, setRefreshInterval] = useState<number | null>(15000);
40
+ const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
41
+
42
+ const { data, isLoading, isFetching, refetch } = useFetchSystemStats({
43
+ refetchInterval: refreshInterval ?? false,
44
+ refetchOnWindowFocus: false,
45
+ });
46
+
47
+ const handleRefresh = useCallback(() => {
48
+ refetch().then(() => setLastUpdated(new Date()));
49
+ }, [refetch]);
50
+
51
+ if (data && !lastUpdated) {
52
+ setLastUpdated(new Date());
53
+ }
54
+
55
+ return (
56
+ <div className="container mx-auto space-y-6 px-6 py-8">
57
+ <div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
58
+ <div>
59
+ <h1 className="mb-2 text-3xl font-bold">System Monitoring</h1>
60
+ <p className="text-muted-foreground">
61
+ Real-time application metrics and health status
62
+ </p>
63
+ </div>
64
+ <RefreshControl
65
+ interval={refreshInterval}
66
+ onIntervalChange={setRefreshInterval}
67
+ onRefresh={handleRefresh}
68
+ isRefreshing={isFetching}
69
+ lastUpdated={lastUpdated}
70
+ />
71
+ </div>
72
+
73
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
74
+ {isLoading ? (
75
+ Array.from({ length: 4 }).map((_, i) => <StatCardSkeleton key={i} />)
76
+ ) : data ? (
77
+ <>
78
+ <StatCard
79
+ title="Total Users"
80
+ value={data.overview.totalUsers}
81
+ icon={Users}
82
+ accentColor="blue"
83
+ />
84
+ <StatCard
85
+ title="Active Sessions (24h)"
86
+ value={data.overview.activeSessionsLast24h}
87
+ icon={Activity}
88
+ accentColor="emerald"
89
+ />
90
+ <StatCard
91
+ title="New Today"
92
+ value={data.overview.newUsersToday}
93
+ icon={UserPlus}
94
+ accentColor="cyan"
95
+ />
96
+ <StatCard
97
+ title="New This Week"
98
+ value={data.overview.newUsersThisWeek}
99
+ icon={CalendarDays}
100
+ accentColor="amber"
101
+ />
102
+ </>
103
+ ) : null}
104
+ </div>
105
+
106
+ <div className="grid gap-6 lg:grid-cols-2">
107
+ {isLoading ? (
108
+ <RealtimeMetricsChartSkeleton />
109
+ ) : (
110
+ <RealtimeMetricsChart />
111
+ )}
112
+ </div>
113
+
114
+ <div className="grid gap-6 lg:grid-cols-2">
115
+ {isLoading ? (
116
+ <>
117
+ <UserGrowthChartSkeleton />
118
+ <SessionActivityChartSkeleton />
119
+ </>
120
+ ) : data ? (
121
+ <>
122
+ <UserGrowthChart data={data.userGrowth} />
123
+ <SessionActivityChart data={data.sessionActivity} />
124
+ </>
125
+ ) : null}
126
+ </div>
127
+
128
+ <div className="grid gap-6 lg:grid-cols-2">
129
+ {isLoading ? (
130
+ <>
131
+ <RoleDistributionChartSkeleton />
132
+ <AuthBreakdownChartSkeleton />
133
+ </>
134
+ ) : data ? (
135
+ <>
136
+ <RoleDistributionChart data={data.roleDistribution} />
137
+ <AuthBreakdownChart data={data.authBreakdown} />
138
+ </>
139
+ ) : null}
140
+ </div>
141
+
142
+ <div className="grid gap-6 lg:grid-cols-2">
143
+ {isLoading ? (
144
+ <>
145
+ <ActivityFeedSkeleton />
146
+ <HealthIndicatorSkeleton />
147
+ </>
148
+ ) : data ? (
149
+ <>
150
+ <RecentSignupsFeed signups={data.recentSignups} />
151
+ <HealthIndicator health={data.systemHealth} />
152
+ </>
153
+ ) : null}
154
+ </div>
155
+ </div>
156
+ );
157
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import { SessionsManagementTable } from '@/components/admin/sessions-management-table';
4
+
5
+ export default function AdminSessionsPage() {
6
+ return (
7
+ <div className="container mx-auto space-y-6 px-6 py-8">
8
+ <div className="mb-8">
9
+ <h1 className="mb-2 text-3xl font-bold">Session Management</h1>
10
+ <p className="text-muted-foreground">
11
+ Monitor and manage user sessions across all devices
12
+ </p>
13
+ </div>
14
+
15
+ <SessionsManagementTable />
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { UsersManagementTable } from '@/components/admin/users-management-table';
6
+
7
+ export default function AdminUsersPage() {
8
+ return (
9
+ <div className="container mx-auto px-6 py-8">
10
+ <div className="mb-8">
11
+ <h1 className="mb-2 text-3xl font-bold">User Management</h1>
12
+ <p className="text-muted-foreground">
13
+ Manage user accounts, roles, and permissions
14
+ </p>
15
+ </div>
16
+
17
+ <UsersManagementTable />
18
+ </div>
19
+ );
20
+ }