vibecarbon 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 (495) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +188 -0
  3. package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
  4. package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
  5. package/carbon/.claude/settings.json +29 -0
  6. package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
  7. package/carbon/.dockerignore +57 -0
  8. package/carbon/.env.example +219 -0
  9. package/carbon/.github/copilot-instructions.md +3 -0
  10. package/carbon/.github/workflows/deploy.yml +346 -0
  11. package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
  12. package/carbon/.windsurfrules +74 -0
  13. package/carbon/AGENTS.md +422 -0
  14. package/carbon/CLAUDE.md +3 -0
  15. package/carbon/DEVELOPMENT.md +187 -0
  16. package/carbon/Dockerfile +98 -0
  17. package/carbon/LICENSE +21 -0
  18. package/carbon/PRODUCTION.md +364 -0
  19. package/carbon/README.md +193 -0
  20. package/carbon/TESTING.md +138 -0
  21. package/carbon/backup/Dockerfile +75 -0
  22. package/carbon/backup/backup.sh +95 -0
  23. package/carbon/backup/compose-backup.sh +140 -0
  24. package/carbon/biome.json +83 -0
  25. package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
  26. package/carbon/cloud-init/k3s/master-init.sh +215 -0
  27. package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
  28. package/carbon/cloud-init/k3s/worker-init.sh +147 -0
  29. package/carbon/components.json +24 -0
  30. package/carbon/content/blog/authentication-guide.mdx +34 -0
  31. package/carbon/content/blog/getting-started.mdx +41 -0
  32. package/carbon/content/changelog/v0-1-0.mdx +40 -0
  33. package/carbon/content/docs/analytics.mdx +90 -0
  34. package/carbon/content/docs/authentication.mdx +231 -0
  35. package/carbon/content/docs/background-jobs.mdx +116 -0
  36. package/carbon/content/docs/cli.mdx +630 -0
  37. package/carbon/content/docs/database.mdx +236 -0
  38. package/carbon/content/docs/deployment.mdx +227 -0
  39. package/carbon/content/docs/development.mdx +238 -0
  40. package/carbon/content/docs/environments.mdx +84 -0
  41. package/carbon/content/docs/getting-started.mdx +112 -0
  42. package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
  43. package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
  44. package/carbon/content/docs/optional-services.mdx +160 -0
  45. package/carbon/db/Dockerfile +23 -0
  46. package/carbon/docker-compose.dev-init.yml +5 -0
  47. package/carbon/docker-compose.metabase.override.yml +14 -0
  48. package/carbon/docker-compose.metabase.prod.yml +28 -0
  49. package/carbon/docker-compose.metabase.yml +84 -0
  50. package/carbon/docker-compose.n8n.override.yml +14 -0
  51. package/carbon/docker-compose.n8n.prod.yml +31 -0
  52. package/carbon/docker-compose.n8n.yml +97 -0
  53. package/carbon/docker-compose.observability.override.yml +18 -0
  54. package/carbon/docker-compose.observability.prod.yml +28 -0
  55. package/carbon/docker-compose.observability.yml +125 -0
  56. package/carbon/docker-compose.override.yml +28 -0
  57. package/carbon/docker-compose.prod.yml +294 -0
  58. package/carbon/docker-compose.yml +508 -0
  59. package/carbon/docker-entrypoint.sh +12 -0
  60. package/carbon/ha/activate-standby.sh +52 -0
  61. package/carbon/ha/primary-init.sql +36 -0
  62. package/carbon/ha/standby-init.sh +74 -0
  63. package/carbon/k8s/LICENSE +99 -0
  64. package/carbon/k8s/README.md +272 -0
  65. package/carbon/k8s/base/app/deployment.yaml +130 -0
  66. package/carbon/k8s/base/app/hpa.yaml +44 -0
  67. package/carbon/k8s/base/app/kustomization.yaml +10 -0
  68. package/carbon/k8s/base/app/network-policy.yaml +124 -0
  69. package/carbon/k8s/base/app/pdb.yaml +14 -0
  70. package/carbon/k8s/base/app/rbac.yaml +36 -0
  71. package/carbon/k8s/base/app/service.yaml +16 -0
  72. package/carbon/k8s/base/backup/cronjob.yaml +120 -0
  73. package/carbon/k8s/base/backup/kustomization.yaml +7 -0
  74. package/carbon/k8s/base/backup/network-policy.yaml +31 -0
  75. package/carbon/k8s/base/backup/rbac.yaml +7 -0
  76. package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
  77. package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
  78. package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
  79. package/carbon/k8s/base/config/configmap.yaml +48 -0
  80. package/carbon/k8s/base/config/kustomization.yaml +8 -0
  81. package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
  82. package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
  83. package/carbon/k8s/base/kustomization.yaml +43 -0
  84. package/carbon/k8s/base/namespace.yaml +7 -0
  85. package/carbon/k8s/base/network-policies.yaml +95 -0
  86. package/carbon/k8s/base/registry/kustomization.yaml +5 -0
  87. package/carbon/k8s/base/registry/local-registry.yaml +193 -0
  88. package/carbon/k8s/base/traefik/certificate.yaml +21 -0
  89. package/carbon/k8s/base/traefik/configmap.yaml +13 -0
  90. package/carbon/k8s/base/traefik/deployment.yaml +165 -0
  91. package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
  92. package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
  93. package/carbon/k8s/base/traefik/middleware.yaml +109 -0
  94. package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
  95. package/carbon/k8s/base/traefik/service.yaml +38 -0
  96. package/carbon/k8s/flux/README.md +49 -0
  97. package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
  98. package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
  99. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
  100. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
  101. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
  102. package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
  103. package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
  104. package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
  105. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
  106. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
  107. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
  108. package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
  109. package/carbon/k8s/infra/kustomization.yaml +21 -0
  110. package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
  111. package/carbon/k8s/overlays/local/configmap.yaml +23 -0
  112. package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
  113. package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
  114. package/carbon/k8s/overlays/local/namespace.yaml +8 -0
  115. package/carbon/k8s/overlays/local/secrets.yaml +32 -0
  116. package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
  117. package/carbon/k8s/test-local.sh +318 -0
  118. package/carbon/k8s/values/supabase.values.yaml +133 -0
  119. package/carbon/package.json +154 -0
  120. package/carbon/runtime.Dockerfile +17 -0
  121. package/carbon/scripts/_dev-jwt.mjs +45 -0
  122. package/carbon/scripts/check-rls.ts +53 -0
  123. package/carbon/scripts/dev-init.js +191 -0
  124. package/carbon/scripts/dev.js +191 -0
  125. package/carbon/scripts/docker-down.js +63 -0
  126. package/carbon/scripts/docker-logs.js +59 -0
  127. package/carbon/scripts/docker-up.js +222 -0
  128. package/carbon/scripts/generate-dev-configs.sh +131 -0
  129. package/carbon/scripts/generate-rss.ts +116 -0
  130. package/carbon/scripts/generate-sitemap.ts +102 -0
  131. package/carbon/scripts/k8s-apply.js +71 -0
  132. package/carbon/scripts/k8s-delete.js +75 -0
  133. package/carbon/scripts/lib/manifest.js +176 -0
  134. package/carbon/scripts/secret-scan.mjs +278 -0
  135. package/carbon/scripts/validate-dev-configs.sh +101 -0
  136. package/carbon/src/client/App.tsx +202 -0
  137. package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
  138. package/carbon/src/client/assets/hyperformant-light.svg +29 -0
  139. package/carbon/src/client/assets/logos/aider.svg +1 -0
  140. package/carbon/src/client/assets/logos/antigravity.svg +1 -0
  141. package/carbon/src/client/assets/logos/bolt.svg +1 -0
  142. package/carbon/src/client/assets/logos/claude-code.svg +1 -0
  143. package/carbon/src/client/assets/logos/copilot.svg +1 -0
  144. package/carbon/src/client/assets/logos/cursor.svg +1 -0
  145. package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
  146. package/carbon/src/client/assets/logos/lovable.svg +1 -0
  147. package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
  148. package/carbon/src/client/assets/logos/v0.svg +1 -0
  149. package/carbon/src/client/assets/logos/windsurf.svg +1 -0
  150. package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
  151. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
  152. package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
  153. package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
  154. package/carbon/src/client/components/AppSidebar.tsx +760 -0
  155. package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
  156. package/carbon/src/client/components/CTAFooter.tsx +59 -0
  157. package/carbon/src/client/components/ComparisonSection.tsx +132 -0
  158. package/carbon/src/client/components/ContentPanel.tsx +66 -0
  159. package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
  160. package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
  161. package/carbon/src/client/components/FAQSection.tsx +76 -0
  162. package/carbon/src/client/components/FileUpload.tsx +210 -0
  163. package/carbon/src/client/components/HeaderActions.tsx +17 -0
  164. package/carbon/src/client/components/Hero.tsx +608 -0
  165. package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
  166. package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
  167. package/carbon/src/client/components/Logo.tsx +87 -0
  168. package/carbon/src/client/components/LogoStrip.tsx +76 -0
  169. package/carbon/src/client/components/MetricsStrip.tsx +48 -0
  170. package/carbon/src/client/components/Nav.tsx +195 -0
  171. package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
  172. package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
  173. package/carbon/src/client/components/PageHeader.tsx +24 -0
  174. package/carbon/src/client/components/PlanGate.tsx +36 -0
  175. package/carbon/src/client/components/PricingSection.tsx +371 -0
  176. package/carbon/src/client/components/SEO.tsx +34 -0
  177. package/carbon/src/client/components/SmoothScroll.tsx +45 -0
  178. package/carbon/src/client/components/TechStackSection.tsx +165 -0
  179. package/carbon/src/client/components/WorkflowSection.tsx +604 -0
  180. package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
  181. package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
  182. package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
  183. package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
  184. package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
  185. package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
  186. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
  187. package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
  188. package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
  189. package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
  190. package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
  191. package/carbon/src/client/components/effects/index.ts +2 -0
  192. package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
  193. package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
  194. package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
  195. package/carbon/src/client/components/scroll/index.ts +2 -0
  196. package/carbon/src/client/components/ui/accordion.tsx +71 -0
  197. package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
  198. package/carbon/src/client/components/ui/alert.tsx +73 -0
  199. package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
  200. package/carbon/src/client/components/ui/avatar.tsx +91 -0
  201. package/carbon/src/client/components/ui/badge.tsx +50 -0
  202. package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
  203. package/carbon/src/client/components/ui/button-group.tsx +78 -0
  204. package/carbon/src/client/components/ui/button.tsx +120 -0
  205. package/carbon/src/client/components/ui/calendar.tsx +182 -0
  206. package/carbon/src/client/components/ui/card.tsx +85 -0
  207. package/carbon/src/client/components/ui/carousel.tsx +227 -0
  208. package/carbon/src/client/components/ui/chart.tsx +357 -0
  209. package/carbon/src/client/components/ui/checkbox.tsx +27 -0
  210. package/carbon/src/client/components/ui/collapsible.tsx +15 -0
  211. package/carbon/src/client/components/ui/command.tsx +178 -0
  212. package/carbon/src/client/components/ui/context-menu.tsx +233 -0
  213. package/carbon/src/client/components/ui/dialog.tsx +132 -0
  214. package/carbon/src/client/components/ui/drawer.tsx +118 -0
  215. package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
  216. package/carbon/src/client/components/ui/empty.tsx +94 -0
  217. package/carbon/src/client/components/ui/field.tsx +226 -0
  218. package/carbon/src/client/components/ui/hover-card.tsx +44 -0
  219. package/carbon/src/client/components/ui/input-group.tsx +146 -0
  220. package/carbon/src/client/components/ui/input-otp.tsx +83 -0
  221. package/carbon/src/client/components/ui/input.tsx +20 -0
  222. package/carbon/src/client/components/ui/item.tsx +188 -0
  223. package/carbon/src/client/components/ui/kbd.tsx +26 -0
  224. package/carbon/src/client/components/ui/label.tsx +21 -0
  225. package/carbon/src/client/components/ui/menubar.tsx +250 -0
  226. package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
  227. package/carbon/src/client/components/ui/pagination.tsx +102 -0
  228. package/carbon/src/client/components/ui/popover.tsx +75 -0
  229. package/carbon/src/client/components/ui/progress.tsx +66 -0
  230. package/carbon/src/client/components/ui/radio-group.tsx +36 -0
  231. package/carbon/src/client/components/ui/resizable.tsx +46 -0
  232. package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
  233. package/carbon/src/client/components/ui/select.tsx +186 -0
  234. package/carbon/src/client/components/ui/separator.tsx +21 -0
  235. package/carbon/src/client/components/ui/sheet.tsx +124 -0
  236. package/carbon/src/client/components/ui/sidebar.tsx +702 -0
  237. package/carbon/src/client/components/ui/skeleton.tsx +13 -0
  238. package/carbon/src/client/components/ui/slider.tsx +57 -0
  239. package/carbon/src/client/components/ui/sonner.tsx +45 -0
  240. package/carbon/src/client/components/ui/spinner.tsx +15 -0
  241. package/carbon/src/client/components/ui/switch.tsx +30 -0
  242. package/carbon/src/client/components/ui/table.tsx +89 -0
  243. package/carbon/src/client/components/ui/tabs.tsx +73 -0
  244. package/carbon/src/client/components/ui/textarea.tsx +18 -0
  245. package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
  246. package/carbon/src/client/components/ui/toggle.tsx +44 -0
  247. package/carbon/src/client/components/ui/tooltip.tsx +56 -0
  248. package/carbon/src/client/hooks/api/index.ts +14 -0
  249. package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
  250. package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
  251. package/carbon/src/client/hooks/use-mobile.ts +21 -0
  252. package/carbon/src/client/hooks/useMousePosition.ts +91 -0
  253. package/carbon/src/client/hooks/useNotifications.tsx +124 -0
  254. package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
  255. package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
  256. package/carbon/src/client/hooks/usePackageManager.ts +69 -0
  257. package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
  258. package/carbon/src/client/hooks/useRunningServices.ts +114 -0
  259. package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
  260. package/carbon/src/client/index.css +467 -0
  261. package/carbon/src/client/index.html +56 -0
  262. package/carbon/src/client/lib/admin-services.ts +151 -0
  263. package/carbon/src/client/lib/api.ts +32 -0
  264. package/carbon/src/client/lib/blog.ts +35 -0
  265. package/carbon/src/client/lib/changelog.ts +33 -0
  266. package/carbon/src/client/lib/docs-search.ts +101 -0
  267. package/carbon/src/client/lib/docs.ts +37 -0
  268. package/carbon/src/client/lib/i18n.ts +32 -0
  269. package/carbon/src/client/lib/supabase.ts +72 -0
  270. package/carbon/src/client/lib/tailwind-colors.ts +357 -0
  271. package/carbon/src/client/lib/theme.ts +117 -0
  272. package/carbon/src/client/lib/utils.ts +22 -0
  273. package/carbon/src/client/locales/de.json +529 -0
  274. package/carbon/src/client/locales/en.json +461 -0
  275. package/carbon/src/client/locales/es.json +529 -0
  276. package/carbon/src/client/locales/fr.json +529 -0
  277. package/carbon/src/client/locales/pt.json +529 -0
  278. package/carbon/src/client/main.tsx +56 -0
  279. package/carbon/src/client/mdx.d.ts +13 -0
  280. package/carbon/src/client/pages/ApiDocs.tsx +76 -0
  281. package/carbon/src/client/pages/AuthCallback.tsx +34 -0
  282. package/carbon/src/client/pages/Blog.tsx +167 -0
  283. package/carbon/src/client/pages/Changelog.tsx +171 -0
  284. package/carbon/src/client/pages/Charts.tsx +388 -0
  285. package/carbon/src/client/pages/Checkout.tsx +227 -0
  286. package/carbon/src/client/pages/Contact.tsx +174 -0
  287. package/carbon/src/client/pages/Dashboard.tsx +368 -0
  288. package/carbon/src/client/pages/Docs.tsx +372 -0
  289. package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
  290. package/carbon/src/client/pages/Home.tsx +187 -0
  291. package/carbon/src/client/pages/Legal.tsx +100 -0
  292. package/carbon/src/client/pages/Login.tsx +408 -0
  293. package/carbon/src/client/pages/MFAVerify.tsx +156 -0
  294. package/carbon/src/client/pages/NotFound.tsx +21 -0
  295. package/carbon/src/client/pages/Onboarding.tsx +246 -0
  296. package/carbon/src/client/pages/ResetPassword.tsx +200 -0
  297. package/carbon/src/client/pages/UIComponents.tsx +390 -0
  298. package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
  299. package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
  300. package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
  301. package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
  302. package/carbon/src/client/pages/admin/Logs.tsx +18 -0
  303. package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
  304. package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
  305. package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
  306. package/carbon/src/client/pages/admin/Settings.tsx +314 -0
  307. package/carbon/src/client/pages/admin/Theme.tsx +465 -0
  308. package/carbon/src/client/pages/admin/Users.tsx +365 -0
  309. package/carbon/src/client/pages/api-docs.css +156 -0
  310. package/carbon/src/client/pages/organizations/Details.tsx +200 -0
  311. package/carbon/src/client/pages/organizations/Members.tsx +402 -0
  312. package/carbon/src/client/pages/settings/Billing.tsx +473 -0
  313. package/carbon/src/client/pages/settings/Profile.tsx +160 -0
  314. package/carbon/src/client/pages/settings/Security.tsx +341 -0
  315. package/carbon/src/client/public/favicon.svg +19 -0
  316. package/carbon/src/client/public/robots.txt +8 -0
  317. package/carbon/src/server/billing/index.ts +104 -0
  318. package/carbon/src/server/billing/provider.ts +81 -0
  319. package/carbon/src/server/billing/providers/paddle.ts +314 -0
  320. package/carbon/src/server/billing/providers/polar.ts +325 -0
  321. package/carbon/src/server/billing/providers/stripe.ts +233 -0
  322. package/carbon/src/server/emails/templates.ts +116 -0
  323. package/carbon/src/server/index.ts +554 -0
  324. package/carbon/src/server/lib/auth.ts +6 -0
  325. package/carbon/src/server/lib/email.ts +64 -0
  326. package/carbon/src/server/lib/env.ts +112 -0
  327. package/carbon/src/server/lib/errors.ts +21 -0
  328. package/carbon/src/server/lib/logger.ts +17 -0
  329. package/carbon/src/server/lib/rate-limiter.ts +288 -0
  330. package/carbon/src/server/lib/request.ts +34 -0
  331. package/carbon/src/server/lib/stripe.ts +42 -0
  332. package/carbon/src/server/lib/supabase.ts +65 -0
  333. package/carbon/src/server/middleware/requirePlan.ts +80 -0
  334. package/carbon/src/server/routes/_internal/services-status.ts +958 -0
  335. package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
  336. package/carbon/src/server/routes/health.ts +48 -0
  337. package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
  338. package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
  339. package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
  340. package/carbon/src/server/routes/v1/auth.ts +390 -0
  341. package/carbon/src/server/routes/v1/billing.ts +718 -0
  342. package/carbon/src/server/routes/v1/contact.ts +93 -0
  343. package/carbon/src/server/routes/v1/index.ts +1333 -0
  344. package/carbon/src/server/routes/v1/newsletter.ts +181 -0
  345. package/carbon/src/server/routes/v1/performance.ts +157 -0
  346. package/carbon/src/server/routes/v1/stats.ts +170 -0
  347. package/carbon/src/server/routes/v1/theme.ts +106 -0
  348. package/carbon/src/server/routes/webhooks/billing.ts +376 -0
  349. package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
  350. package/carbon/src/server/types.ts +11 -0
  351. package/carbon/src/shared/pricing.ts +155 -0
  352. package/carbon/src/shared/types.ts +338 -0
  353. package/carbon/supabase/migrations/00001_init.sql +717 -0
  354. package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
  355. package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
  356. package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
  357. package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
  358. package/carbon/supabase/seed.sql +16 -0
  359. package/carbon/tests/_helpers/app.ts +45 -0
  360. package/carbon/tests/_helpers/env.ts +37 -0
  361. package/carbon/tests/_helpers/factories.ts +69 -0
  362. package/carbon/tests/_helpers/jwt.ts +23 -0
  363. package/carbon/tests/_helpers/setup-integration.ts +20 -0
  364. package/carbon/tests/_helpers/setup-rtl.ts +12 -0
  365. package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
  366. package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
  367. package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
  368. package/carbon/tests/integration/server/routes/health.test.ts +61 -0
  369. package/carbon/tests/structural/i18n-parity.test.ts +42 -0
  370. package/carbon/tests/unit/client/utils.test.ts +49 -0
  371. package/carbon/tests/unit/shared/pricing.test.ts +93 -0
  372. package/carbon/tsconfig.json +27 -0
  373. package/carbon/tsconfig.server.json +27 -0
  374. package/carbon/tsconfig.test.json +9 -0
  375. package/carbon/vite.config.ts +110 -0
  376. package/carbon/vitest.config.ts +74 -0
  377. package/carbon/volumes/db/jwt.sql +57 -0
  378. package/carbon/volumes/db/metabase-init.sh +29 -0
  379. package/carbon/volumes/db/n8n-init.sh +25 -0
  380. package/carbon/volumes/db/realtime.sql +33 -0
  381. package/carbon/volumes/db/roles.sql +93 -0
  382. package/carbon/volumes/db/set-passwords.sh +12 -0
  383. package/carbon/volumes/db/super-admin.dev.sql +113 -0
  384. package/carbon/volumes/db/super-admin.generated.sql +113 -0
  385. package/carbon/volumes/db/super-admin.sql +114 -0
  386. package/carbon/volumes/grafana/dashboards/logs.json +179 -0
  387. package/carbon/volumes/grafana/dashboards/overview.json +523 -0
  388. package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
  389. package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
  390. package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
  391. package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
  392. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
  393. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
  394. package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
  395. package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
  396. package/carbon/volumes/kong/kong.yml +208 -0
  397. package/carbon/volumes/loki/loki-config.yml +58 -0
  398. package/carbon/volumes/n8n/hooks.js +131 -0
  399. package/carbon/volumes/n8n/scripts/setup.sh +66 -0
  400. package/carbon/volumes/prometheus/prometheus.yml +43 -0
  401. package/carbon/volumes/promtail/promtail-config.yml +64 -0
  402. package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
  403. package/carbon/volumes/traefik/middlewares.yml +95 -0
  404. package/carbon/volumes/traefik/vite-dev.yml +20 -0
  405. package/package.json +95 -0
  406. package/src/access.js +354 -0
  407. package/src/activate.js +187 -0
  408. package/src/add.js +718 -0
  409. package/src/backup.js +786 -0
  410. package/src/cli.js +350 -0
  411. package/src/configure.js +967 -0
  412. package/src/console.js +155 -0
  413. package/src/create.js +1499 -0
  414. package/src/deploy.js +311 -0
  415. package/src/destroy.js +2033 -0
  416. package/src/diagnose.js +735 -0
  417. package/src/down.js +80 -0
  418. package/src/failover.js +1032 -0
  419. package/src/lib/backup-s3.js +179 -0
  420. package/src/lib/build.js +33 -0
  421. package/src/lib/checksum.js +28 -0
  422. package/src/lib/ci-setup.js +666 -0
  423. package/src/lib/cli/help.js +129 -0
  424. package/src/lib/cli/parse-flags.js +160 -0
  425. package/src/lib/cli/select-action.js +65 -0
  426. package/src/lib/cli/select-environment.js +108 -0
  427. package/src/lib/cli/tty-guard.js +75 -0
  428. package/src/lib/cloudflare.js +447 -0
  429. package/src/lib/colors.js +52 -0
  430. package/src/lib/command.js +361 -0
  431. package/src/lib/config.js +359 -0
  432. package/src/lib/cost.js +103 -0
  433. package/src/lib/deploy/bundle.js +231 -0
  434. package/src/lib/deploy/compose/acme-verify.js +121 -0
  435. package/src/lib/deploy/compose/build-args.js +78 -0
  436. package/src/lib/deploy/compose/ha.js +1874 -0
  437. package/src/lib/deploy/compose/index.js +1294 -0
  438. package/src/lib/deploy/compose/reconcile.js +74 -0
  439. package/src/lib/deploy/github.js +382 -0
  440. package/src/lib/deploy/image.js +191 -0
  441. package/src/lib/deploy/index.js +119 -0
  442. package/src/lib/deploy/k8s/LICENSE +99 -0
  443. package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
  444. package/src/lib/deploy/k8s/ha/index.js +1000 -0
  445. package/src/lib/deploy/k8s/index.js +62 -0
  446. package/src/lib/deploy/k8s/k3s.js +2515 -0
  447. package/src/lib/deploy/orchestrator.js +1401 -0
  448. package/src/lib/deploy/prompts.js +545 -0
  449. package/src/lib/deploy/remote-build.js +130 -0
  450. package/src/lib/deploy/state.js +120 -0
  451. package/src/lib/deploy/utils.js +328 -0
  452. package/src/lib/deploy-logger.js +93 -0
  453. package/src/lib/dns-propagation.js +48 -0
  454. package/src/lib/environment.js +58 -0
  455. package/src/lib/fetch-retry.js +103 -0
  456. package/src/lib/github-environments.js +335 -0
  457. package/src/lib/hetzner-dns.js +377 -0
  458. package/src/lib/hetzner-guided-setup.js +275 -0
  459. package/src/lib/host-keys.js +37 -0
  460. package/src/lib/iac/cloud-init.js +52 -0
  461. package/src/lib/iac/index.js +325 -0
  462. package/src/lib/iac/programs/hetzner-compose.js +130 -0
  463. package/src/lib/iac/programs/hetzner-k8s.js +320 -0
  464. package/src/lib/kubectl.js +81 -0
  465. package/src/lib/licensing/index.js +259 -0
  466. package/src/lib/licensing/tiers.js +181 -0
  467. package/src/lib/licensing/validator.js +171 -0
  468. package/src/lib/merge-package-json.js +90 -0
  469. package/src/lib/operator-ip.js +381 -0
  470. package/src/lib/package-manager.js +111 -0
  471. package/src/lib/perf.js +71 -0
  472. package/src/lib/project-guard.js +39 -0
  473. package/src/lib/project.js +334 -0
  474. package/src/lib/providers/base.js +276 -0
  475. package/src/lib/providers/hetzner-s3.js +656 -0
  476. package/src/lib/providers/hetzner.js +755 -0
  477. package/src/lib/providers/index.js +164 -0
  478. package/src/lib/s3.js +510 -0
  479. package/src/lib/secret-scan.js +583 -0
  480. package/src/lib/secrets.js +63 -0
  481. package/src/lib/server-types.js +195 -0
  482. package/src/lib/shell.js +91 -0
  483. package/src/lib/ssh.js +241 -0
  484. package/src/lib/tracker.js +242 -0
  485. package/src/lib/upgrade-policy.js +170 -0
  486. package/src/lib/validators.js +105 -0
  487. package/src/lib/version.js +5 -0
  488. package/src/remove.js +292 -0
  489. package/src/reset.js +97 -0
  490. package/src/restore.js +871 -0
  491. package/src/scale.js +1734 -0
  492. package/src/shell.js +222 -0
  493. package/src/status.js +981 -0
  494. package/src/up.js +264 -0
  495. package/src/upgrade.js +721 -0
@@ -0,0 +1,390 @@
1
+ import { Minus, MoreVertical, Plus } from 'lucide-react';
2
+ import { useState } from 'react';
3
+ import { ContentPanel } from '../components/ContentPanel';
4
+ import { PageHeader } from '../components/PageHeader';
5
+ import { Badge } from '../components/ui/badge';
6
+ import { Button } from '../components/ui/button';
7
+ import { ButtonGroup } from '../components/ui/button-group';
8
+ import {
9
+ Card,
10
+ CardAction,
11
+ CardContent,
12
+ CardDescription,
13
+ CardFooter,
14
+ CardHeader,
15
+ CardTitle,
16
+ } from '../components/ui/card';
17
+ import { Checkbox } from '../components/ui/checkbox';
18
+ import {
19
+ DropdownMenu,
20
+ DropdownMenuContent,
21
+ DropdownMenuItem,
22
+ DropdownMenuTrigger,
23
+ } from '../components/ui/dropdown-menu';
24
+ import { Input } from '../components/ui/input';
25
+ import { Label } from '../components/ui/label';
26
+ import { RadioGroup, RadioGroupItem } from '../components/ui/radio-group';
27
+ import {
28
+ Select,
29
+ SelectContent,
30
+ SelectItem,
31
+ SelectTrigger,
32
+ SelectValue,
33
+ } from '../components/ui/select';
34
+ import { Separator } from '../components/ui/separator';
35
+ import { Slider } from '../components/ui/slider';
36
+ import { Switch } from '../components/ui/switch';
37
+ import { Textarea } from '../components/ui/textarea';
38
+
39
+ export default function UIComponents() {
40
+ const [gpuCount, setGpuCount] = useState(8);
41
+ const [priceRange, setPriceRange] = useState([200, 800]);
42
+
43
+ return (
44
+ <>
45
+ <PageHeader title="UI Components" />
46
+
47
+ <ContentPanel variant="full">
48
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
49
+ {/* Card Example */}
50
+ <div className="flex flex-col gap-1">
51
+ <p className="text-sm text-muted-foreground px-1.5 py-2">Card</p>
52
+ <Card className="relative flex-1 pt-0">
53
+ {/* Color overlay */}
54
+ <div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
55
+ {/* Image */}
56
+ <img
57
+ alt="Abstract green texture"
58
+ className="relative z-20 aspect-video w-full object-cover brightness-[0.6] grayscale"
59
+ src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=800&auto=format&fit=crop"
60
+ />
61
+ <CardHeader>
62
+ <CardTitle>Observability Plus is replacing Monitoring</CardTitle>
63
+ <CardDescription>
64
+ Switch to the improved way to explore your data, with natural language. Monitoring
65
+ will no longer be available on the Pro plan in November, 2025
66
+ </CardDescription>
67
+ </CardHeader>
68
+ <CardFooter>
69
+ <Button>
70
+ Create Query <Plus data-icon="inline-end" />
71
+ </Button>
72
+ <Badge variant="secondary" className="ml-auto">
73
+ Warning
74
+ </Badge>
75
+ </CardFooter>
76
+ </Card>
77
+ </div>
78
+
79
+ {/* Form Example */}
80
+ <div className="flex flex-col gap-1">
81
+ <p className="text-sm text-muted-foreground px-1.5 py-2">Form</p>
82
+ <Card className="flex-1">
83
+ <CardHeader>
84
+ <CardTitle>User Information</CardTitle>
85
+ <CardDescription>Please fill in your details below</CardDescription>
86
+ <CardAction>
87
+ <DropdownMenu>
88
+ <DropdownMenuTrigger render={<Button variant="ghost" size="icon" />}>
89
+ <MoreVertical />
90
+ <span className="sr-only">More options</span>
91
+ </DropdownMenuTrigger>
92
+ <DropdownMenuContent align="end">
93
+ <DropdownMenuItem>Edit</DropdownMenuItem>
94
+ <DropdownMenuItem>Duplicate</DropdownMenuItem>
95
+ <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
96
+ </DropdownMenuContent>
97
+ </DropdownMenu>
98
+ </CardAction>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <form className="flex flex-col gap-4">
102
+ <div className="grid grid-cols-2 gap-4">
103
+ <div className="flex flex-col gap-2">
104
+ <Label htmlFor="name">Name</Label>
105
+ <Input id="name" placeholder="Enter your name" required />
106
+ </div>
107
+ <div className="flex flex-col gap-2">
108
+ <Label htmlFor="role">Role</Label>
109
+ <Select>
110
+ <SelectTrigger id="role" className="w-full">
111
+ <SelectValue placeholder="Select a role" />
112
+ </SelectTrigger>
113
+ <SelectContent>
114
+ <SelectItem value="admin">Admin</SelectItem>
115
+ <SelectItem value="member">Member</SelectItem>
116
+ <SelectItem value="viewer">Viewer</SelectItem>
117
+ </SelectContent>
118
+ </Select>
119
+ </div>
120
+ </div>
121
+ <div className="flex flex-col gap-2">
122
+ <Label htmlFor="framework">Framework</Label>
123
+ <Select>
124
+ <SelectTrigger id="framework" className="w-full">
125
+ <SelectValue placeholder="Select a framework" />
126
+ </SelectTrigger>
127
+ <SelectContent>
128
+ <SelectItem value="react">React</SelectItem>
129
+ <SelectItem value="vue">Vue</SelectItem>
130
+ <SelectItem value="svelte">Svelte</SelectItem>
131
+ <SelectItem value="angular">Angular</SelectItem>
132
+ </SelectContent>
133
+ </Select>
134
+ </div>
135
+ <div className="flex flex-col gap-2">
136
+ <Label htmlFor="comments">Comments</Label>
137
+ <Textarea id="comments" placeholder="Add any additional comments" />
138
+ </div>
139
+ <div className="flex gap-2">
140
+ <Button type="submit">Submit</Button>
141
+ <Button type="button" variant="outline">
142
+ Cancel
143
+ </Button>
144
+ </div>
145
+ </form>
146
+ </CardContent>
147
+ </Card>
148
+ </div>
149
+
150
+ {/* Fields Example */}
151
+ <div className="flex flex-col gap-1">
152
+ <p className="text-sm text-muted-foreground px-1.5 py-2">Fields</p>
153
+ <div className="flex min-w-0 flex-1 items-start rounded-[var(--radius-card)] border border-dashed p-4 sm:p-6">
154
+ <fieldset className="flex w-full max-w-md flex-col gap-4">
155
+ {/* Radio Group Section */}
156
+ <div className="flex flex-col gap-2">
157
+ <legend className="text-base font-medium">Compute Environment</legend>
158
+ <p className="text-sm text-muted-foreground">
159
+ Select the compute environment for your cluster.
160
+ </p>
161
+ <RadioGroup defaultValue="kubernetes" className="mt-2 gap-3">
162
+ <label
163
+ htmlFor="kubernetes"
164
+ className="flex cursor-pointer items-center justify-between gap-4 rounded-xl border border-input p-4 has-[[data-checked]]:border-ring has-[[data-checked]]:bg-accent"
165
+ >
166
+ <div className="flex flex-col gap-1">
167
+ <span className="font-medium">Kubernetes</span>
168
+ <p className="text-sm text-muted-foreground">
169
+ Run GPU workloads on a K8s configured cluster. This is the default.
170
+ </p>
171
+ </div>
172
+ <RadioGroupItem value="kubernetes" id="kubernetes" />
173
+ </label>
174
+ <label
175
+ htmlFor="vm"
176
+ className="flex cursor-pointer items-center justify-between gap-4 rounded-xl border border-input p-4 has-[[data-checked]]:border-ring has-[[data-checked]]:bg-accent"
177
+ >
178
+ <div className="flex flex-col gap-1">
179
+ <span className="font-medium">Virtual Machine</span>
180
+ <p className="text-sm text-muted-foreground">
181
+ Access a VM configured cluster to run workloads. (Coming soon)
182
+ </p>
183
+ </div>
184
+ <RadioGroupItem value="vm" id="vm" />
185
+ </label>
186
+ </RadioGroup>
187
+ </div>
188
+
189
+ <Separator />
190
+
191
+ {/* Number Input with Button Group */}
192
+ <div className="flex items-start justify-between gap-4">
193
+ <div className="flex flex-col gap-1">
194
+ <Label htmlFor="gpu-count">Number of GPUs</Label>
195
+ <p className="text-sm text-muted-foreground">You can add more later.</p>
196
+ </div>
197
+ <ButtonGroup>
198
+ <Input
199
+ id="gpu-count"
200
+ type="text"
201
+ value={gpuCount}
202
+ onChange={(e) => setGpuCount(Number(e.target.value) || 0)}
203
+ className="w-14 text-center"
204
+ maxLength={3}
205
+ />
206
+ <Button
207
+ type="button"
208
+ variant="outline"
209
+ size="icon"
210
+ onClick={() => setGpuCount(Math.max(0, gpuCount - 1))}
211
+ aria-label="Decrement"
212
+ >
213
+ <Minus />
214
+ </Button>
215
+ <Button
216
+ type="button"
217
+ variant="outline"
218
+ size="icon"
219
+ onClick={() => setGpuCount(gpuCount + 1)}
220
+ aria-label="Increment"
221
+ >
222
+ <Plus />
223
+ </Button>
224
+ </ButtonGroup>
225
+ </div>
226
+
227
+ <Separator />
228
+
229
+ {/* Switch */}
230
+ <div className="flex items-start justify-between gap-4">
231
+ <div className="flex flex-col gap-1">
232
+ <Label htmlFor="tinting">Wallpaper Tinting</Label>
233
+ <p className="text-sm text-muted-foreground">
234
+ Allow the wallpaper to be tinted.
235
+ </p>
236
+ </div>
237
+ <Switch id="tinting" defaultChecked />
238
+ </div>
239
+
240
+ <Separator />
241
+
242
+ {/* Checkbox */}
243
+ <label
244
+ htmlFor="terms"
245
+ className="flex cursor-pointer items-center gap-3 rounded-xl border border-input p-4 has-[[data-checked]]:border-ring has-[[data-checked]]:bg-accent"
246
+ >
247
+ <Checkbox id="terms" defaultChecked />
248
+ <span className="font-normal">I agree to the terms and conditions</span>
249
+ </label>
250
+
251
+ {/* Slider */}
252
+ <div className="flex flex-col gap-2">
253
+ <Label>Price Range</Label>
254
+ <p className="text-sm text-muted-foreground">
255
+ Set your budget range ($
256
+ <span className="font-medium tabular-nums">{priceRange[0]}</span> -{' '}
257
+ <span className="font-medium tabular-nums">{priceRange[1]}</span>).
258
+ </p>
259
+ <Slider
260
+ value={priceRange}
261
+ onValueChange={(value) => {
262
+ if (Array.isArray(value)) {
263
+ setPriceRange(value as number[]);
264
+ }
265
+ }}
266
+ min={0}
267
+ max={1000}
268
+ step={10}
269
+ className="mt-2"
270
+ />
271
+ </div>
272
+
273
+ {/* Buttons */}
274
+ <div className="flex gap-2 pt-2">
275
+ <Button type="submit">Submit</Button>
276
+ <Button type="button" variant="outline">
277
+ Cancel
278
+ </Button>
279
+ </div>
280
+ </fieldset>
281
+ </div>
282
+ </div>
283
+
284
+ {/* Complex Form Example */}
285
+ <div className="flex flex-col gap-1">
286
+ <p className="text-sm text-muted-foreground px-1.5 py-2">Complex Form</p>
287
+ <Card className="flex-1">
288
+ <CardHeader>
289
+ <CardTitle>Payment Method</CardTitle>
290
+ <CardDescription>All transactions are secure and encrypted</CardDescription>
291
+ </CardHeader>
292
+ <CardContent>
293
+ <form className="flex flex-col gap-4">
294
+ {/* Card Details */}
295
+ <div className="flex flex-col gap-4">
296
+ <div className="flex flex-col gap-2">
297
+ <Label htmlFor="card-name">Name on Card</Label>
298
+ <Input id="card-name" placeholder="John Doe" required />
299
+ </div>
300
+ <div className="grid grid-cols-3 gap-4">
301
+ <div className="col-span-2 flex flex-col gap-2">
302
+ <Label htmlFor="card-number">Card Number</Label>
303
+ <Input id="card-number" placeholder="1234 5678 9012 3456" required />
304
+ <p className="text-sm text-muted-foreground">Enter your 16-digit number.</p>
305
+ </div>
306
+ <div className="col-span-1 flex flex-col gap-2">
307
+ <Label htmlFor="cvv">CVV</Label>
308
+ <Input id="cvv" placeholder="123" required />
309
+ </div>
310
+ </div>
311
+ <div className="grid grid-cols-2 gap-4">
312
+ <div className="flex flex-col gap-2">
313
+ <Label htmlFor="exp-month">Month</Label>
314
+ <Select>
315
+ <SelectTrigger id="exp-month" className="w-full">
316
+ <SelectValue placeholder="MM" />
317
+ </SelectTrigger>
318
+ <SelectContent>
319
+ {Array.from({ length: 12 }, (_, i) => {
320
+ const month = String(i + 1).padStart(2, '0');
321
+ return (
322
+ <SelectItem key={month} value={month}>
323
+ {month}
324
+ </SelectItem>
325
+ );
326
+ })}
327
+ </SelectContent>
328
+ </Select>
329
+ </div>
330
+ <div className="flex flex-col gap-2">
331
+ <Label htmlFor="exp-year">Year</Label>
332
+ <Select>
333
+ <SelectTrigger id="exp-year" className="w-full">
334
+ <SelectValue placeholder="YYYY" />
335
+ </SelectTrigger>
336
+ <SelectContent>
337
+ {Array.from({ length: 10 }, (_, i) => {
338
+ const year = String(2024 + i);
339
+ return (
340
+ <SelectItem key={year} value={year}>
341
+ {year}
342
+ </SelectItem>
343
+ );
344
+ })}
345
+ </SelectContent>
346
+ </Select>
347
+ </div>
348
+ </div>
349
+ </div>
350
+
351
+ <Separator />
352
+
353
+ {/* Billing Address */}
354
+ <fieldset className="flex flex-col gap-2">
355
+ <legend className="text-base font-medium">Billing Address</legend>
356
+ <p className="text-sm text-muted-foreground">
357
+ The billing address associated with your payment.
358
+ </p>
359
+ <div className="flex items-center gap-3 mt-2">
360
+ <Checkbox id="same-as-shipping" defaultChecked />
361
+ <Label htmlFor="same-as-shipping" className="font-normal">
362
+ Same as shipping address
363
+ </Label>
364
+ </div>
365
+ </fieldset>
366
+
367
+ <Separator />
368
+
369
+ {/* Comments */}
370
+ <div className="flex flex-col gap-2">
371
+ <Label htmlFor="payment-comments">Comments</Label>
372
+ <Textarea id="payment-comments" placeholder="Add any additional comments" />
373
+ </div>
374
+
375
+ {/* Buttons */}
376
+ <div className="flex gap-2">
377
+ <Button type="submit">Submit</Button>
378
+ <Button type="button" variant="outline">
379
+ Cancel
380
+ </Button>
381
+ </div>
382
+ </form>
383
+ </CardContent>
384
+ </Card>
385
+ </div>
386
+ </div>
387
+ </ContentPanel>
388
+ </>
389
+ );
390
+ }
@@ -0,0 +1,220 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ import { Archive, Eye, Loader2, Mail, MessageSquare, Trash2 } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { toast } from 'sonner';
5
+ import { PageHeader } from '@/components/PageHeader';
6
+ import { Badge } from '@/components/ui/badge';
7
+ import { Button } from '@/components/ui/button';
8
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@/components/ui/dialog';
16
+ import { getAuthHeaders } from '@/lib/api';
17
+ import { ContentPanel } from '../../components/ContentPanel';
18
+
19
+ interface ContactSubmission {
20
+ id: string;
21
+ name: string;
22
+ email: string;
23
+ subject: string;
24
+ message: string;
25
+ status: string;
26
+ created_at: string;
27
+ }
28
+
29
+ const queryKey = ['admin', 'contact-submissions'];
30
+
31
+ function StatusBadge({ status }: { status: string }) {
32
+ const variants: Record<string, 'default' | 'secondary' | 'outline' | 'destructive'> = {
33
+ unread: 'default',
34
+ read: 'secondary',
35
+ replied: 'outline',
36
+ archived: 'outline',
37
+ };
38
+ return <Badge variant={variants[status] || 'outline'}>{status}</Badge>;
39
+ }
40
+
41
+ export default function AdminContactSubmissions() {
42
+ const queryClient = useQueryClient();
43
+ const [selectedSubmission, setSelectedSubmission] = useState<ContactSubmission | null>(null);
44
+
45
+ const { data, isLoading } = useQuery<{
46
+ submissions: ContactSubmission[];
47
+ total: number;
48
+ }>({
49
+ queryKey,
50
+ queryFn: async () => {
51
+ const headers = await getAuthHeaders();
52
+ const response = await fetch('/api/v1/admin/contact?limit=50', { headers });
53
+ if (!response.ok) throw new Error('Failed to fetch submissions');
54
+ return response.json();
55
+ },
56
+ });
57
+
58
+ const updateMutation = useMutation({
59
+ mutationFn: async ({ id, status }: { id: string; status: string }) => {
60
+ const headers = await getAuthHeaders();
61
+ const response = await fetch(`/api/v1/admin/contact/${id}`, {
62
+ method: 'PATCH',
63
+ headers,
64
+ body: JSON.stringify({ status }),
65
+ });
66
+ if (!response.ok) throw new Error('Failed to update');
67
+ },
68
+ onSuccess: () => {
69
+ queryClient.invalidateQueries({ queryKey });
70
+ },
71
+ });
72
+
73
+ const deleteMutation = useMutation({
74
+ mutationFn: async (id: string) => {
75
+ const headers = await getAuthHeaders();
76
+ const response = await fetch(`/api/v1/admin/contact/${id}`, {
77
+ method: 'DELETE',
78
+ headers,
79
+ });
80
+ if (!response.ok) throw new Error('Failed to delete');
81
+ },
82
+ onSuccess: () => {
83
+ queryClient.invalidateQueries({ queryKey });
84
+ setSelectedSubmission(null);
85
+ toast.success('Submission deleted');
86
+ },
87
+ });
88
+
89
+ const submissions = data?.submissions ?? [];
90
+
91
+ return (
92
+ <>
93
+ <PageHeader
94
+ title="Contact Submissions"
95
+ description={`${data?.total ?? 0} total submissions`}
96
+ />
97
+
98
+ <ContentPanel variant="default">
99
+ <Card>
100
+ <CardHeader>
101
+ <CardTitle className="flex items-center gap-2">
102
+ <MessageSquare className="size-5" />
103
+ Submissions
104
+ </CardTitle>
105
+ <CardDescription>Contact form submissions from your website</CardDescription>
106
+ </CardHeader>
107
+ <CardContent>
108
+ {isLoading ? (
109
+ <div className="flex items-center justify-center py-8">
110
+ <Loader2 className="size-6 animate-spin text-muted-foreground" />
111
+ </div>
112
+ ) : submissions.length === 0 ? (
113
+ <p className="text-sm text-muted-foreground text-center py-8">
114
+ No contact submissions yet.
115
+ </p>
116
+ ) : (
117
+ <div className="space-y-2">
118
+ {submissions.map((submission) => (
119
+ <div
120
+ key={submission.id}
121
+ className="flex items-center justify-between rounded-lg border p-3 hover:bg-muted/50 transition-colors cursor-pointer"
122
+ onClick={() => {
123
+ setSelectedSubmission(submission);
124
+ if (submission.status === 'unread') {
125
+ updateMutation.mutate({ id: submission.id, status: 'read' });
126
+ }
127
+ }}
128
+ onKeyDown={(e) => {
129
+ if (e.key === 'Enter') setSelectedSubmission(submission);
130
+ }}
131
+ role="button"
132
+ tabIndex={0}
133
+ >
134
+ <div className="space-y-1 min-w-0 flex-1">
135
+ <div className="flex items-center gap-2">
136
+ <span
137
+ className={`font-medium text-sm ${submission.status === 'unread' ? 'text-foreground' : 'text-muted-foreground'}`}
138
+ >
139
+ {submission.subject}
140
+ </span>
141
+ <StatusBadge status={submission.status} />
142
+ </div>
143
+ <div className="text-xs text-muted-foreground">
144
+ {submission.name} ({submission.email}) &middot;{' '}
145
+ {new Date(submission.created_at).toLocaleDateString()}
146
+ </div>
147
+ </div>
148
+ <div className="flex items-center gap-1 ml-2">
149
+ <Button
150
+ variant="ghost"
151
+ size="icon-sm"
152
+ onClick={(e) => {
153
+ e.stopPropagation();
154
+ updateMutation.mutate({ id: submission.id, status: 'archived' });
155
+ }}
156
+ >
157
+ <Archive className="size-3.5" />
158
+ </Button>
159
+ <Button
160
+ variant="ghost"
161
+ size="icon-sm"
162
+ onClick={(e) => {
163
+ e.stopPropagation();
164
+ deleteMutation.mutate(submission.id);
165
+ }}
166
+ >
167
+ <Trash2 className="size-3.5" />
168
+ </Button>
169
+ </div>
170
+ </div>
171
+ ))}
172
+ </div>
173
+ )}
174
+ </CardContent>
175
+ </Card>
176
+ </ContentPanel>
177
+
178
+ {/* Submission detail dialog */}
179
+ <Dialog open={!!selectedSubmission} onOpenChange={() => setSelectedSubmission(null)}>
180
+ <DialogContent className="max-w-lg">
181
+ <DialogHeader>
182
+ <DialogTitle>{selectedSubmission?.subject}</DialogTitle>
183
+ <DialogDescription>
184
+ From {selectedSubmission?.name} ({selectedSubmission?.email}) &middot;{' '}
185
+ {selectedSubmission && new Date(selectedSubmission.created_at).toLocaleString()}
186
+ </DialogDescription>
187
+ </DialogHeader>
188
+ <div className="space-y-4">
189
+ <p className="text-sm whitespace-pre-wrap">{selectedSubmission?.message}</p>
190
+ <div className="flex items-center gap-2">
191
+ <Button variant="outline" size="sm" asChild>
192
+ <a
193
+ href={`mailto:${selectedSubmission?.email}?subject=Re: ${selectedSubmission?.subject}`}
194
+ >
195
+ <Mail className="size-3.5 mr-1" />
196
+ Reply via Email
197
+ </a>
198
+ </Button>
199
+ {selectedSubmission?.status !== 'replied' && (
200
+ <Button
201
+ variant="outline"
202
+ size="sm"
203
+ onClick={() => {
204
+ if (selectedSubmission) {
205
+ updateMutation.mutate({ id: selectedSubmission.id, status: 'replied' });
206
+ setSelectedSubmission({ ...selectedSubmission, status: 'replied' });
207
+ }
208
+ }}
209
+ >
210
+ <Eye className="size-3.5 mr-1" />
211
+ Mark as Replied
212
+ </Button>
213
+ )}
214
+ </div>
215
+ </div>
216
+ </DialogContent>
217
+ </Dialog>
218
+ </>
219
+ );
220
+ }
@@ -0,0 +1,24 @@
1
+ import { PerformanceMetrics } from '@/components/admin/PerformanceMetrics';
2
+ import { ServicesStatus } from '@/components/admin/ServicesStatus';
3
+ import { PageHeader } from '@/components/PageHeader';
4
+ import { ContentPanel } from '../../components/ContentPanel';
5
+
6
+ export default function AdminDashboard() {
7
+ return (
8
+ <>
9
+ <PageHeader title="Overview" description="Infrastructure and service status" />
10
+
11
+ <ContentPanel variant="full">
12
+ <div className="grid gap-6 lg:grid-cols-2">
13
+ {/* Left Column - Service Status */}
14
+ <div>
15
+ <ServicesStatus />
16
+ </div>
17
+
18
+ {/* Right Column - Performance Metrics */}
19
+ <PerformanceMetrics />
20
+ </div>
21
+ </ContentPanel>
22
+ </>
23
+ );
24
+ }