create-blitzpack 0.1.0 → 0.1.2

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 +92 -94
  2. package/package.json +5 -6
  3. package/template/.dockerignore +0 -59
  4. package/template/.github/workflows/ci.yml +0 -157
  5. package/template/.husky/pre-commit +0 -1
  6. package/template/.husky/pre-push +0 -1
  7. package/template/.lintstagedrc.cjs +0 -4
  8. package/template/.nvmrc +0 -1
  9. package/template/.prettierrc +0 -9
  10. package/template/.vscode/settings.json +0 -13
  11. package/template/CLAUDE.md +0 -175
  12. package/template/CONTRIBUTING.md +0 -32
  13. package/template/Dockerfile +0 -90
  14. package/template/GETTING_STARTED.md +0 -35
  15. package/template/LICENSE +0 -21
  16. package/template/README.md +0 -116
  17. package/template/apps/api/.dockerignore +0 -51
  18. package/template/apps/api/.env.local.example +0 -62
  19. package/template/apps/api/emails/account-deleted-email.tsx +0 -69
  20. package/template/apps/api/emails/components/email-layout.tsx +0 -154
  21. package/template/apps/api/emails/config.ts +0 -22
  22. package/template/apps/api/emails/password-changed-email.tsx +0 -88
  23. package/template/apps/api/emails/password-reset-email.tsx +0 -86
  24. package/template/apps/api/emails/verification-email.tsx +0 -85
  25. package/template/apps/api/emails/welcome-email.tsx +0 -70
  26. package/template/apps/api/package.json +0 -84
  27. package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +0 -13
  28. package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +0 -67
  29. package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +0 -5
  30. package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +0 -7
  31. package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +0 -2
  32. package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +0 -26
  33. package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +0 -2
  34. package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +0 -10
  35. package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +0 -29
  36. package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +0 -8
  37. package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +0 -11
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +0 -3
  39. package/template/apps/api/prisma/schema.prisma +0 -116
  40. package/template/apps/api/prisma/seed.ts +0 -159
  41. package/template/apps/api/prisma.config.ts +0 -14
  42. package/template/apps/api/src/app.ts +0 -377
  43. package/template/apps/api/src/common/logger.service.ts +0 -227
  44. package/template/apps/api/src/config/env.ts +0 -60
  45. package/template/apps/api/src/config/rate-limit.ts +0 -29
  46. package/template/apps/api/src/hooks/auth.ts +0 -122
  47. package/template/apps/api/src/plugins/auth.ts +0 -198
  48. package/template/apps/api/src/plugins/database.ts +0 -45
  49. package/template/apps/api/src/plugins/logger.ts +0 -33
  50. package/template/apps/api/src/plugins/multipart.ts +0 -16
  51. package/template/apps/api/src/plugins/scalar.ts +0 -20
  52. package/template/apps/api/src/plugins/schedule.ts +0 -52
  53. package/template/apps/api/src/plugins/services.ts +0 -66
  54. package/template/apps/api/src/plugins/swagger.ts +0 -56
  55. package/template/apps/api/src/routes/accounts.ts +0 -91
  56. package/template/apps/api/src/routes/admin-sessions.ts +0 -92
  57. package/template/apps/api/src/routes/metrics.ts +0 -71
  58. package/template/apps/api/src/routes/password.ts +0 -46
  59. package/template/apps/api/src/routes/sessions.ts +0 -53
  60. package/template/apps/api/src/routes/stats.ts +0 -38
  61. package/template/apps/api/src/routes/uploads-serve.ts +0 -27
  62. package/template/apps/api/src/routes/uploads.ts +0 -154
  63. package/template/apps/api/src/routes/users.ts +0 -114
  64. package/template/apps/api/src/routes/verification.ts +0 -90
  65. package/template/apps/api/src/server.ts +0 -34
  66. package/template/apps/api/src/services/accounts.service.ts +0 -125
  67. package/template/apps/api/src/services/authorization.service.ts +0 -162
  68. package/template/apps/api/src/services/email.service.ts +0 -170
  69. package/template/apps/api/src/services/file-storage.service.ts +0 -267
  70. package/template/apps/api/src/services/metrics.service.ts +0 -175
  71. package/template/apps/api/src/services/password.service.ts +0 -56
  72. package/template/apps/api/src/services/sessions.service.spec.ts +0 -134
  73. package/template/apps/api/src/services/sessions.service.ts +0 -276
  74. package/template/apps/api/src/services/stats.service.ts +0 -273
  75. package/template/apps/api/src/services/uploads.service.ts +0 -163
  76. package/template/apps/api/src/services/users.service.spec.ts +0 -249
  77. package/template/apps/api/src/services/users.service.ts +0 -198
  78. package/template/apps/api/src/utils/file-validation.ts +0 -108
  79. package/template/apps/api/start.sh +0 -33
  80. package/template/apps/api/test/helpers/fastify-app.ts +0 -24
  81. package/template/apps/api/test/helpers/mock-authorization.ts +0 -16
  82. package/template/apps/api/test/helpers/mock-logger.ts +0 -28
  83. package/template/apps/api/test/helpers/mock-prisma.ts +0 -30
  84. package/template/apps/api/test/helpers/test-db.ts +0 -125
  85. package/template/apps/api/test/integration/auth-flow.integration.spec.ts +0 -449
  86. package/template/apps/api/test/integration/password.integration.spec.ts +0 -427
  87. package/template/apps/api/test/integration/rate-limit.integration.spec.ts +0 -51
  88. package/template/apps/api/test/integration/sessions.integration.spec.ts +0 -445
  89. package/template/apps/api/test/integration/users.integration.spec.ts +0 -211
  90. package/template/apps/api/test/setup.ts +0 -31
  91. package/template/apps/api/tsconfig.json +0 -26
  92. package/template/apps/api/vitest.config.ts +0 -35
  93. package/template/apps/web/.env.local.example +0 -11
  94. package/template/apps/web/components.json +0 -24
  95. package/template/apps/web/next.config.ts +0 -22
  96. package/template/apps/web/package.json +0 -56
  97. package/template/apps/web/postcss.config.js +0 -5
  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 +0 -3
  101. package/template/apps/web/src/app/(admin)/admin/layout.tsx +0 -222
  102. package/template/apps/web/src/app/(admin)/admin/page.tsx +0 -157
  103. package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +0 -18
  104. package/template/apps/web/src/app/(admin)/admin/users/page.tsx +0 -20
  105. package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +0 -177
  106. package/template/apps/web/src/app/(auth)/login/page.tsx +0 -159
  107. package/template/apps/web/src/app/(auth)/reset-password/page.tsx +0 -245
  108. package/template/apps/web/src/app/(auth)/signup/page.tsx +0 -153
  109. package/template/apps/web/src/app/dashboard/change-password/page.tsx +0 -255
  110. package/template/apps/web/src/app/dashboard/page.tsx +0 -296
  111. package/template/apps/web/src/app/error.tsx +0 -32
  112. package/template/apps/web/src/app/examples/file-upload/page.tsx +0 -200
  113. package/template/apps/web/src/app/favicon.ico +0 -0
  114. package/template/apps/web/src/app/global-error.tsx +0 -96
  115. package/template/apps/web/src/app/globals.css +0 -22
  116. package/template/apps/web/src/app/icon.png +0 -0
  117. package/template/apps/web/src/app/layout.tsx +0 -34
  118. package/template/apps/web/src/app/not-found.tsx +0 -28
  119. package/template/apps/web/src/app/page.tsx +0 -192
  120. package/template/apps/web/src/components/admin/activity-feed.tsx +0 -101
  121. package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +0 -114
  122. package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +0 -124
  123. package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +0 -511
  124. package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +0 -102
  125. package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +0 -90
  126. package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +0 -108
  127. package/template/apps/web/src/components/admin/health-indicator.tsx +0 -175
  128. package/template/apps/web/src/components/admin/refresh-control.tsx +0 -90
  129. package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +0 -79
  130. package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +0 -74
  131. package/template/apps/web/src/components/admin/sessions-management-table.tsx +0 -372
  132. package/template/apps/web/src/components/admin/stat-card.tsx +0 -137
  133. package/template/apps/web/src/components/admin/user-create-dialog.tsx +0 -152
  134. package/template/apps/web/src/components/admin/user-delete-dialog.tsx +0 -73
  135. package/template/apps/web/src/components/admin/user-edit-dialog.tsx +0 -170
  136. package/template/apps/web/src/components/admin/users-management-table.tsx +0 -285
  137. package/template/apps/web/src/components/auth/email-verification-banner.tsx +0 -85
  138. package/template/apps/web/src/components/auth/github-button.tsx +0 -40
  139. package/template/apps/web/src/components/auth/google-button.tsx +0 -54
  140. package/template/apps/web/src/components/auth/protected-route.tsx +0 -66
  141. package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +0 -31
  142. package/template/apps/web/src/components/auth/with-auth.tsx +0 -30
  143. package/template/apps/web/src/components/error/error-card.tsx +0 -47
  144. package/template/apps/web/src/components/error/forbidden.tsx +0 -25
  145. package/template/apps/web/src/components/landing/command-block.tsx +0 -64
  146. package/template/apps/web/src/components/landing/feature-card.tsx +0 -60
  147. package/template/apps/web/src/components/landing/included-feature-card.tsx +0 -63
  148. package/template/apps/web/src/components/landing/logo.tsx +0 -41
  149. package/template/apps/web/src/components/landing/tech-badge.tsx +0 -11
  150. package/template/apps/web/src/components/layout/auth-nav.tsx +0 -58
  151. package/template/apps/web/src/components/layout/footer.tsx +0 -3
  152. package/template/apps/web/src/config/landing-data.ts +0 -152
  153. package/template/apps/web/src/config/site.ts +0 -5
  154. package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +0 -181
  155. package/template/apps/web/src/hooks/api/use-admin-sessions.ts +0 -75
  156. package/template/apps/web/src/hooks/api/use-admin-stats.ts +0 -33
  157. package/template/apps/web/src/hooks/api/use-sessions.ts +0 -52
  158. package/template/apps/web/src/hooks/api/use-uploads.ts +0 -156
  159. package/template/apps/web/src/hooks/api/use-users.ts +0 -149
  160. package/template/apps/web/src/hooks/use-mobile.ts +0 -21
  161. package/template/apps/web/src/hooks/use-realtime-metrics.ts +0 -120
  162. package/template/apps/web/src/lib/__tests__/utils.test.ts +0 -29
  163. package/template/apps/web/src/lib/api.ts +0 -151
  164. package/template/apps/web/src/lib/auth.ts +0 -13
  165. package/template/apps/web/src/lib/env.ts +0 -52
  166. package/template/apps/web/src/lib/form-utils.ts +0 -11
  167. package/template/apps/web/src/lib/utils.ts +0 -1
  168. package/template/apps/web/src/providers.tsx +0 -34
  169. package/template/apps/web/src/store/atoms.ts +0 -15
  170. package/template/apps/web/src/test/helpers/test-utils.tsx +0 -44
  171. package/template/apps/web/src/test/setup.ts +0 -8
  172. package/template/apps/web/tailwind.config.ts +0 -5
  173. package/template/apps/web/tsconfig.json +0 -26
  174. package/template/apps/web/vitest.config.ts +0 -32
  175. package/template/assets/logo-512.png +0 -0
  176. package/template/assets/logo.svg +0 -4
  177. package/template/docker-compose.prod.yml +0 -66
  178. package/template/docker-compose.yml +0 -36
  179. package/template/eslint.config.ts +0 -119
  180. package/template/package.json +0 -77
  181. package/template/packages/tailwind-config/package.json +0 -9
  182. package/template/packages/tailwind-config/theme.css +0 -179
  183. package/template/packages/types/package.json +0 -29
  184. package/template/packages/types/src/__tests__/schemas.test.ts +0 -255
  185. package/template/packages/types/src/api-response.ts +0 -53
  186. package/template/packages/types/src/health-check.ts +0 -11
  187. package/template/packages/types/src/pagination.ts +0 -41
  188. package/template/packages/types/src/role.ts +0 -5
  189. package/template/packages/types/src/session.ts +0 -48
  190. package/template/packages/types/src/stats.ts +0 -113
  191. package/template/packages/types/src/upload.ts +0 -51
  192. package/template/packages/types/src/user.ts +0 -36
  193. package/template/packages/types/tsconfig.json +0 -5
  194. package/template/packages/types/vitest.config.ts +0 -21
  195. package/template/packages/ui/components.json +0 -21
  196. package/template/packages/ui/package.json +0 -108
  197. package/template/packages/ui/src/__tests__/button.test.tsx +0 -70
  198. package/template/packages/ui/src/alert-dialog.tsx +0 -141
  199. package/template/packages/ui/src/alert.tsx +0 -66
  200. package/template/packages/ui/src/animated-theme-toggler.tsx +0 -167
  201. package/template/packages/ui/src/avatar.tsx +0 -53
  202. package/template/packages/ui/src/badge.tsx +0 -36
  203. package/template/packages/ui/src/button.tsx +0 -84
  204. package/template/packages/ui/src/card.tsx +0 -92
  205. package/template/packages/ui/src/checkbox.tsx +0 -32
  206. package/template/packages/ui/src/data-table/data-table-column-header.tsx +0 -68
  207. package/template/packages/ui/src/data-table/data-table-pagination.tsx +0 -99
  208. package/template/packages/ui/src/data-table/data-table-toolbar.tsx +0 -55
  209. package/template/packages/ui/src/data-table/data-table-view-options.tsx +0 -63
  210. package/template/packages/ui/src/data-table/data-table.tsx +0 -167
  211. package/template/packages/ui/src/dialog.tsx +0 -143
  212. package/template/packages/ui/src/dropdown-menu.tsx +0 -257
  213. package/template/packages/ui/src/empty-state.tsx +0 -52
  214. package/template/packages/ui/src/file-upload-input.tsx +0 -202
  215. package/template/packages/ui/src/form.tsx +0 -168
  216. package/template/packages/ui/src/hooks/use-mobile.ts +0 -19
  217. package/template/packages/ui/src/icons/brand-icons.tsx +0 -16
  218. package/template/packages/ui/src/input.tsx +0 -21
  219. package/template/packages/ui/src/label.tsx +0 -24
  220. package/template/packages/ui/src/lib/utils.ts +0 -6
  221. package/template/packages/ui/src/password-input.tsx +0 -102
  222. package/template/packages/ui/src/popover.tsx +0 -48
  223. package/template/packages/ui/src/radio-group.tsx +0 -45
  224. package/template/packages/ui/src/scroll-area.tsx +0 -58
  225. package/template/packages/ui/src/select.tsx +0 -187
  226. package/template/packages/ui/src/separator.tsx +0 -28
  227. package/template/packages/ui/src/sheet.tsx +0 -139
  228. package/template/packages/ui/src/sidebar.tsx +0 -726
  229. package/template/packages/ui/src/skeleton-variants.tsx +0 -87
  230. package/template/packages/ui/src/skeleton.tsx +0 -13
  231. package/template/packages/ui/src/slider.tsx +0 -63
  232. package/template/packages/ui/src/sonner.tsx +0 -25
  233. package/template/packages/ui/src/spinner.tsx +0 -16
  234. package/template/packages/ui/src/switch.tsx +0 -31
  235. package/template/packages/ui/src/table.tsx +0 -116
  236. package/template/packages/ui/src/tabs.tsx +0 -66
  237. package/template/packages/ui/src/textarea.tsx +0 -18
  238. package/template/packages/ui/src/tooltip.tsx +0 -61
  239. package/template/packages/ui/src/user-avatar.tsx +0 -97
  240. package/template/packages/ui/test-config.js +0 -3
  241. package/template/packages/ui/tsconfig.json +0 -12
  242. package/template/packages/ui/turbo.json +0 -18
  243. package/template/packages/ui/vitest.config.ts +0 -17
  244. package/template/packages/ui/vitest.setup.ts +0 -1
  245. package/template/packages/utils/package.json +0 -23
  246. package/template/packages/utils/src/__tests__/utils.test.ts +0 -223
  247. package/template/packages/utils/src/array.ts +0 -18
  248. package/template/packages/utils/src/async.ts +0 -3
  249. package/template/packages/utils/src/date.ts +0 -77
  250. package/template/packages/utils/src/errors.ts +0 -73
  251. package/template/packages/utils/src/number.ts +0 -11
  252. package/template/packages/utils/src/string.ts +0 -13
  253. package/template/packages/utils/tsconfig.json +0 -5
  254. package/template/packages/utils/vitest.config.ts +0 -21
  255. package/template/pnpm-workspace.yaml +0 -4
  256. package/template/tsconfig.base.json +0 -32
  257. package/template/turbo.json +0 -133
  258. package/template/vitest.shared.ts +0 -26
  259. package/template/vitest.workspace.ts +0 -9
@@ -1,511 +0,0 @@
1
- 'use client';
2
-
3
- import type { RealtimeMetricsPoint } from '@repo/packages-types/stats';
4
- import { cn } from '@repo/packages-ui/lib/utils';
5
- import { Skeleton } from '@repo/packages-ui/skeleton';
6
- import { formatTime } from '@repo/packages-utils/date';
7
- import { AnimatePresence, motion } from 'framer-motion';
8
- import {
9
- Activity,
10
- AlertTriangle,
11
- Cpu,
12
- HardDrive,
13
- Pause,
14
- Timer,
15
- Wifi,
16
- WifiOff,
17
- } from 'lucide-react';
18
- import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
19
- import {
20
- Area,
21
- AreaChart,
22
- CartesianGrid,
23
- ReferenceDot,
24
- ResponsiveContainer,
25
- Tooltip,
26
- XAxis,
27
- YAxis,
28
- } from 'recharts';
29
-
30
- import { useRealtimeMetrics } from '@/hooks/use-realtime-metrics';
31
-
32
- type MetricType = 'memory' | 'cpu' | 'requests' | 'responseTime' | 'errorRate';
33
-
34
- interface MetricConfig {
35
- key: MetricType;
36
- label: string;
37
- color: string;
38
- gradientId: string;
39
- icon: typeof Cpu;
40
- unit: string;
41
- getValue: (point: RealtimeMetricsPoint) => number;
42
- format: (value: number, point?: RealtimeMetricsPoint) => string;
43
- }
44
-
45
- const METRICS: MetricConfig[] = [
46
- {
47
- key: 'memory',
48
- label: 'Memory',
49
- color: '#8b5cf6',
50
- gradientId: 'memoryGradient',
51
- icon: HardDrive,
52
- unit: '%',
53
- getValue: (p) => p.memory.usedPercent,
54
- format: (v, p) =>
55
- p
56
- ? `${v.toFixed(1)}% (${p.memory.heapUsedMB.toFixed(0)} MB)`
57
- : `${v.toFixed(1)}%`,
58
- },
59
- {
60
- key: 'cpu',
61
- label: 'CPU',
62
- color: '#f59e0b',
63
- gradientId: 'cpuGradient',
64
- icon: Cpu,
65
- unit: '%',
66
- getValue: (p) => p.cpu.percentage,
67
- format: (v) => `${v.toFixed(1)}%`,
68
- },
69
- {
70
- key: 'requests',
71
- label: 'Req/s',
72
- color: '#10b981',
73
- gradientId: 'requestsGradient',
74
- icon: Activity,
75
- unit: '/s',
76
- getValue: (p) => p.requests.perSecond,
77
- format: (v) => `${v.toFixed(1)}/s`,
78
- },
79
- {
80
- key: 'responseTime',
81
- label: 'Latency',
82
- color: '#3b82f6',
83
- gradientId: 'responseTimeGradient',
84
- icon: Timer,
85
- unit: 'ms',
86
- getValue: (p) => p.requests.avgResponseTimeMs,
87
- format: (v) => `${v.toFixed(0)}ms`,
88
- },
89
- {
90
- key: 'errorRate',
91
- label: 'Error Rate',
92
- color: '#ef4444',
93
- gradientId: 'errorRateGradient',
94
- icon: AlertTriangle,
95
- unit: '%',
96
- getValue: (p) => p.errors.rate,
97
- format: (v) => `${v.toFixed(2)}%`,
98
- },
99
- ];
100
-
101
- interface MetricTabProps {
102
- config: MetricConfig;
103
- isActive: boolean;
104
- onClick: () => void;
105
- }
106
-
107
- function MetricTab({ config, isActive, onClick }: MetricTabProps) {
108
- const Icon = config.icon;
109
-
110
- return (
111
- <button
112
- onClick={onClick}
113
- className={cn(
114
- 'relative flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors',
115
- isActive
116
- ? 'text-foreground'
117
- : 'text-muted-foreground hover:text-foreground'
118
- )}
119
- >
120
- <Icon className="size-3.5" />
121
- <span>{config.label}</span>
122
- {isActive && (
123
- <motion.div
124
- layoutId="activeMetricTab"
125
- className="bg-foreground absolute inset-x-0 -bottom-px h-0.5"
126
- transition={{ type: 'spring', stiffness: 500, damping: 30 }}
127
- />
128
- )}
129
- </button>
130
- );
131
- }
132
-
133
- interface CustomTooltipProps {
134
- active?: boolean;
135
- payload?: Array<{ value: number; payload: RealtimeMetricsPoint }>;
136
- activeMetric: MetricConfig;
137
- }
138
-
139
- function CustomTooltip({ active, payload, activeMetric }: CustomTooltipProps) {
140
- if (!active || !payload || payload.length === 0) return null;
141
-
142
- const point = payload[0].payload;
143
- const Icon = activeMetric.icon;
144
-
145
- return (
146
- <motion.div
147
- initial={{ opacity: 0, y: 5 }}
148
- animate={{ opacity: 1, y: 0 }}
149
- className="rounded-lg border border-zinc-200 bg-white/95 px-3 py-2 shadow-lg backdrop-blur dark:border-zinc-700 dark:bg-zinc-800/95"
150
- >
151
- <p className="mb-1 text-[10px] text-zinc-500 dark:text-zinc-400">
152
- {formatTime(point.timestamp)}
153
- </p>
154
- <div className="flex items-center gap-2">
155
- <Icon className="size-4" style={{ color: activeMetric.color }} />
156
- <span className="font-semibold" style={{ color: activeMetric.color }}>
157
- {activeMetric.format(activeMetric.getValue(point), point)}
158
- </span>
159
- </div>
160
- </motion.div>
161
- );
162
- }
163
-
164
- export function RealtimeMetricsChart() {
165
- const { data, isConnected, error, reconnect } = useRealtimeMetrics();
166
- const [activeMetric, setActiveMetric] = useState<MetricType>('memory');
167
- const [frozenData, setFrozenData] = useState<RealtimeMetricsPoint[] | null>(
168
- null
169
- );
170
- const dataRef = useRef(data);
171
-
172
- useLayoutEffect(() => {
173
- dataRef.current = data;
174
- }, [data]);
175
-
176
- const handleMouseEnter = useCallback(() => {
177
- const currentData = dataRef.current;
178
- if (currentData.length > 0) {
179
- setFrozenData(structuredClone(currentData));
180
- }
181
- }, []);
182
-
183
- const handleMouseLeave = useCallback(() => {
184
- setFrozenData(null);
185
- }, []);
186
-
187
- const isPaused = frozenData !== null;
188
- const displayData = frozenData ?? data;
189
-
190
- const metricConfig = useMemo(
191
- () => METRICS.find((m) => m.key === activeMetric)!,
192
- [activeMetric]
193
- );
194
-
195
- const chartData = useMemo(() => {
196
- return displayData.map((point) => ({
197
- ...point,
198
- value: metricConfig.getValue(point),
199
- }));
200
- }, [displayData, metricConfig]);
201
-
202
- const displayedLatestPoint = displayData[displayData.length - 1];
203
-
204
- const maxValue = useMemo(() => {
205
- if (chartData.length === 0) return 100;
206
- const max = Math.max(...chartData.map((d) => d.value));
207
- return Math.ceil(max * 1.2) || 100;
208
- }, [chartData]);
209
-
210
- const stats = useMemo(() => {
211
- if (chartData.length === 0) return null;
212
- const values = chartData.map((d) => d.value);
213
- return {
214
- min: Math.min(...values),
215
- max: Math.max(...values),
216
- avg: values.reduce((a, b) => a + b, 0) / values.length,
217
- };
218
- }, [chartData]);
219
-
220
- return (
221
- <div className="bg-card col-span-2 rounded-xl border p-5">
222
- <div className="mb-4 flex items-center justify-between">
223
- <div className="flex items-center gap-3">
224
- <div className="relative">
225
- <div
226
- className={cn(
227
- 'rounded-lg p-2',
228
- isConnected ? 'bg-emerald-500/10' : 'bg-rose-500/10'
229
- )}
230
- >
231
- {isConnected ? (
232
- <Wifi className="size-5 text-emerald-500" />
233
- ) : (
234
- <WifiOff className="size-5 text-rose-500" />
235
- )}
236
- </div>
237
- <AnimatePresence>
238
- {isConnected && (
239
- <motion.span
240
- initial={{ scale: 0 }}
241
- animate={{ scale: 1 }}
242
- exit={{ scale: 0 }}
243
- className="absolute -right-0.5 -top-0.5 flex size-2.5"
244
- >
245
- <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
246
- <span className="relative inline-flex size-2.5 rounded-full bg-emerald-500" />
247
- </motion.span>
248
- )}
249
- </AnimatePresence>
250
- </div>
251
- <div>
252
- <h3 className="text-sm font-semibold">Real-time Metrics</h3>
253
- <p className="text-muted-foreground text-xs">
254
- {isConnected ? (
255
- 'Live system monitoring'
256
- ) : (
257
- <button
258
- onClick={reconnect}
259
- className="text-rose-500 hover:underline"
260
- >
261
- {error?.message || 'Disconnected. Click to reconnect'}
262
- </button>
263
- )}
264
- </p>
265
- </div>
266
- </div>
267
-
268
- {displayedLatestPoint && (
269
- <div className="text-right">
270
- <p className="text-muted-foreground text-[10px]">
271
- {isPaused ? 'Paused at' : 'Last update'}
272
- </p>
273
- <p className="font-mono text-xs tabular-nums">
274
- {formatTime(displayedLatestPoint.timestamp)}
275
- </p>
276
- </div>
277
- )}
278
- </div>
279
-
280
- <div className="mb-4 flex items-center border-b">
281
- {METRICS.map((metric) => (
282
- <MetricTab
283
- key={metric.key}
284
- config={metric}
285
- isActive={activeMetric === metric.key}
286
- onClick={() => setActiveMetric(metric.key)}
287
- />
288
- ))}
289
- <div className="ml-auto flex items-center gap-3 pb-1.5 text-xs">
290
- {isPaused && (
291
- <span className="flex items-center gap-1 text-amber-500">
292
- <Pause className="size-3" />
293
- Paused
294
- </span>
295
- )}
296
- {displayedLatestPoint && (
297
- <div className="flex items-center gap-1.5">
298
- <span className="text-muted-foreground">
299
- {metricConfig.label}:
300
- </span>
301
- <span className="font-semibold tabular-nums">
302
- {metricConfig.format(
303
- metricConfig.getValue(displayedLatestPoint),
304
- displayedLatestPoint
305
- )}
306
- </span>
307
- </div>
308
- )}
309
- </div>
310
- </div>
311
-
312
- <div
313
- role="img"
314
- aria-label={`Real-time ${metricConfig.label} chart`}
315
- className="h-[280px]"
316
- onMouseEnter={handleMouseEnter}
317
- onMouseLeave={handleMouseLeave}
318
- >
319
- <ResponsiveContainer width="100%" height="100%">
320
- <AreaChart
321
- data={chartData}
322
- margin={{ top: 10, right: 10, left: -10, bottom: 0 }}
323
- >
324
- <defs>
325
- {METRICS.map((metric) => (
326
- <linearGradient
327
- key={metric.gradientId}
328
- id={metric.gradientId}
329
- x1="0"
330
- y1="0"
331
- x2="0"
332
- y2="1"
333
- >
334
- <stop
335
- offset="5%"
336
- stopColor={metric.color}
337
- stopOpacity={0.3}
338
- />
339
- <stop offset="95%" stopColor={metric.color} stopOpacity={0} />
340
- </linearGradient>
341
- ))}
342
- </defs>
343
- <CartesianGrid
344
- strokeDasharray="3 3"
345
- vertical={false}
346
- className="stroke-zinc-200 dark:stroke-zinc-700"
347
- />
348
- <XAxis
349
- dataKey="timestamp"
350
- tickFormatter={formatTime}
351
- tick={{ fontSize: 10 }}
352
- tickLine={false}
353
- axisLine={false}
354
- interval="preserveStartEnd"
355
- minTickGap={50}
356
- className="text-zinc-500 dark:text-zinc-400"
357
- />
358
- <YAxis
359
- domain={[0, maxValue]}
360
- tick={{ fontSize: 10 }}
361
- tickLine={false}
362
- axisLine={false}
363
- width={40}
364
- tickFormatter={(v) =>
365
- `${v}${metricConfig.unit === '%' ? '%' : ''}`
366
- }
367
- className="text-zinc-500 dark:text-zinc-400"
368
- />
369
- <Tooltip
370
- content={<CustomTooltip activeMetric={metricConfig} />}
371
- cursor={{
372
- stroke: metricConfig.color,
373
- strokeWidth: 1,
374
- strokeDasharray: '5 5',
375
- }}
376
- />
377
- <Area
378
- type="monotone"
379
- dataKey="value"
380
- stroke={metricConfig.color}
381
- strokeWidth={2}
382
- fill={`url(#${metricConfig.gradientId})`}
383
- isAnimationActive={false}
384
- />
385
- {displayedLatestPoint && (
386
- <ReferenceDot
387
- x={displayedLatestPoint.timestamp}
388
- y={metricConfig.getValue(displayedLatestPoint)}
389
- r={0}
390
- shape={(props) => {
391
- const { cx, cy } = props as { cx: number; cy: number };
392
- return (
393
- <g>
394
- {!isPaused && (
395
- <circle
396
- cx={cx}
397
- cy={cy}
398
- r={4}
399
- fill={metricConfig.color}
400
- opacity={0.4}
401
- >
402
- <animate
403
- attributeName="r"
404
- from="4"
405
- to="12"
406
- dur="1.5s"
407
- repeatCount="indefinite"
408
- />
409
- <animate
410
- attributeName="opacity"
411
- from="0.4"
412
- to="0"
413
- dur="1.5s"
414
- repeatCount="indefinite"
415
- />
416
- </circle>
417
- )}
418
- <circle
419
- cx={cx}
420
- cy={cy}
421
- r={4}
422
- fill={metricConfig.color}
423
- stroke="white"
424
- strokeWidth={2}
425
- />
426
- </g>
427
- );
428
- }}
429
- />
430
- )}
431
- </AreaChart>
432
- </ResponsiveContainer>
433
- </div>
434
-
435
- <div className="mt-4 flex items-center justify-between border-t pt-3">
436
- <div className="flex items-center gap-4 text-xs">
437
- <div className="flex items-center gap-1.5">
438
- <div
439
- className="size-2 rounded-full"
440
- style={{ backgroundColor: metricConfig.color }}
441
- />
442
- <span className="text-muted-foreground">{metricConfig.label}</span>
443
- </div>
444
- {stats && (
445
- <div className="text-muted-foreground flex items-center gap-3">
446
- <span>
447
- Min:{' '}
448
- <span className="text-foreground tabular-nums">
449
- {metricConfig.format(stats.min)}
450
- </span>
451
- </span>
452
- <span>
453
- Avg:{' '}
454
- <span className="text-foreground tabular-nums">
455
- {metricConfig.format(stats.avg)}
456
- </span>
457
- </span>
458
- <span>
459
- Max:{' '}
460
- <span className="text-foreground tabular-nums">
461
- {metricConfig.format(stats.max)}
462
- </span>
463
- </span>
464
- </div>
465
- )}
466
- </div>
467
- <div className="flex items-center gap-1.5">
468
- <span
469
- className={cn(
470
- 'size-2 rounded-full',
471
- isConnected ? 'bg-emerald-500' : 'bg-rose-500'
472
- )}
473
- />
474
- <span className="text-muted-foreground text-xs">
475
- {isConnected ? 'Live' : 'Offline'}
476
- </span>
477
- </div>
478
- </div>
479
- </div>
480
- );
481
- }
482
-
483
- export function RealtimeMetricsChartSkeleton() {
484
- return (
485
- <div className="bg-card col-span-2 rounded-xl border p-5">
486
- <div className="mb-4 flex items-center justify-between">
487
- <div className="flex items-center gap-3">
488
- <Skeleton className="size-9 rounded-lg" />
489
- <div className="space-y-1">
490
- <Skeleton className="h-4 w-32" />
491
- <Skeleton className="h-3 w-28" />
492
- </div>
493
- </div>
494
- <div className="space-y-1 text-right">
495
- <Skeleton className="ml-auto h-3 w-16" />
496
- <Skeleton className="ml-auto h-3 w-20" />
497
- </div>
498
- </div>
499
- <div className="mb-4 flex items-center gap-4 border-b pb-1.5">
500
- {Array.from({ length: 5 }).map((_, i) => (
501
- <Skeleton key={i} className="h-4 w-16" />
502
- ))}
503
- </div>
504
- <Skeleton className="h-[280px] w-full rounded-lg" />
505
- <div className="mt-4 flex items-center justify-between border-t pt-3">
506
- <Skeleton className="h-3 w-40" />
507
- <Skeleton className="h-3 w-16" />
508
- </div>
509
- </div>
510
- );
511
- }
@@ -1,102 +0,0 @@
1
- 'use client';
2
-
3
- import type { RoleDistributionItem } from '@repo/packages-types/stats';
4
- import { Skeleton } from '@repo/packages-ui/skeleton';
5
- import {
6
- Cell,
7
- Legend,
8
- Pie,
9
- PieChart,
10
- ResponsiveContainer,
11
- Tooltip,
12
- } from 'recharts';
13
-
14
- import { SimpleChartTooltip } from './chart-tooltip';
15
-
16
- interface RoleDistributionChartProps {
17
- data: RoleDistributionItem[];
18
- }
19
-
20
- const ROLE_COLORS: Record<string, string> = {
21
- user: '#3b82f6',
22
- admin: '#f59e0b',
23
- super_admin: '#ef4444',
24
- };
25
-
26
- const ROLE_LABELS: Record<string, string> = {
27
- user: 'Users',
28
- admin: 'Admins',
29
- super_admin: 'Super Admins',
30
- };
31
-
32
- export function RoleDistributionChart({ data }: RoleDistributionChartProps) {
33
- const chartData = data.map((item) => ({
34
- ...item,
35
- name: ROLE_LABELS[item.role] || item.role,
36
- color: ROLE_COLORS[item.role] || '#94a3b8',
37
- }));
38
-
39
- const total = data.reduce((sum, item) => sum + item.count, 0);
40
-
41
- return (
42
- <div className="bg-card rounded-xl border p-5">
43
- <div className="mb-4">
44
- <h3 className="text-sm font-semibold">Role Distribution</h3>
45
- <p className="text-muted-foreground text-xs">
46
- Users by permission level
47
- </p>
48
- </div>
49
- <div className="h-[200px]">
50
- <ResponsiveContainer width="100%" height="100%">
51
- <PieChart>
52
- <Pie
53
- data={chartData}
54
- cx="50%"
55
- cy="50%"
56
- innerRadius={50}
57
- outerRadius={75}
58
- paddingAngle={2}
59
- dataKey="count"
60
- nameKey="name"
61
- >
62
- {chartData.map((entry, index) => (
63
- <Cell key={`cell-${index}`} fill={entry.color} />
64
- ))}
65
- </Pie>
66
- <Tooltip
67
- content={
68
- <SimpleChartTooltip
69
- valueFormatter={(value: number) => [
70
- `${value} (${((value / total) * 100).toFixed(1)}%)`,
71
- 'Count',
72
- ]}
73
- />
74
- }
75
- />
76
- <Legend
77
- verticalAlign="bottom"
78
- height={36}
79
- formatter={(value) => (
80
- <span className="text-foreground text-xs">{value}</span>
81
- )}
82
- />
83
- </PieChart>
84
- </ResponsiveContainer>
85
- </div>
86
- </div>
87
- );
88
- }
89
-
90
- export function RoleDistributionChartSkeleton() {
91
- return (
92
- <div className="bg-card rounded-xl border p-5">
93
- <div className="mb-4 space-y-1">
94
- <Skeleton className="h-4 w-28" />
95
- <Skeleton className="h-3 w-36" />
96
- </div>
97
- <div className="flex h-[200px] items-center justify-center">
98
- <Skeleton className="size-[150px] rounded-full" />
99
- </div>
100
- </div>
101
- );
102
- }
@@ -1,90 +0,0 @@
1
- 'use client';
2
-
3
- import type { SessionActivityPoint } from '@repo/packages-types/stats';
4
- import { Skeleton } from '@repo/packages-ui/skeleton';
5
- import {
6
- Bar,
7
- BarChart,
8
- CartesianGrid,
9
- ResponsiveContainer,
10
- Tooltip,
11
- XAxis,
12
- YAxis,
13
- } from 'recharts';
14
-
15
- import { ChartTooltip } from './chart-tooltip';
16
-
17
- interface SessionActivityChartProps {
18
- data: SessionActivityPoint[];
19
- }
20
-
21
- function formatDate(dateStr: string) {
22
- const date = new Date(dateStr);
23
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
24
- }
25
-
26
- export function SessionActivityChart({ data }: SessionActivityChartProps) {
27
- return (
28
- <div className="bg-card rounded-xl border p-5">
29
- <div className="mb-4">
30
- <h3 className="text-sm font-semibold">Session Activity</h3>
31
- <p className="text-muted-foreground text-xs">
32
- Login sessions created per day (30 days)
33
- </p>
34
- </div>
35
- <div className="h-[240px]">
36
- <ResponsiveContainer width="100%" height="100%">
37
- <BarChart
38
- data={data}
39
- margin={{ top: 5, right: 5, left: -20, bottom: 0 }}
40
- >
41
- <CartesianGrid
42
- strokeDasharray="3 3"
43
- vertical={false}
44
- className="stroke-zinc-200 dark:stroke-zinc-700"
45
- />
46
- <XAxis
47
- dataKey="date"
48
- tickFormatter={formatDate}
49
- tick={{ fontSize: 11 }}
50
- tickLine={false}
51
- axisLine={false}
52
- interval="preserveStartEnd"
53
- className="text-zinc-500 dark:text-zinc-400"
54
- />
55
- <YAxis
56
- tick={{ fontSize: 11 }}
57
- tickLine={false}
58
- axisLine={false}
59
- allowDecimals={false}
60
- className="text-zinc-500 dark:text-zinc-400"
61
- />
62
- <Tooltip
63
- content={<ChartTooltip labelFormatter={formatDate} />}
64
- cursor={{ fill: 'rgba(99, 102, 241, 0.08)' }}
65
- />
66
- <Bar
67
- dataKey="count"
68
- name="Sessions"
69
- fill="#3b82f6"
70
- radius={[4, 4, 0, 0]}
71
- maxBarSize={32}
72
- />
73
- </BarChart>
74
- </ResponsiveContainer>
75
- </div>
76
- </div>
77
- );
78
- }
79
-
80
- export function SessionActivityChartSkeleton() {
81
- return (
82
- <div className="bg-card rounded-xl border p-5">
83
- <div className="mb-4 space-y-1">
84
- <Skeleton className="h-4 w-28" />
85
- <Skeleton className="h-3 w-44" />
86
- </div>
87
- <Skeleton className="h-[240px] w-full" />
88
- </div>
89
- );
90
- }