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.
- package/LICENSE +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- 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}) ·{' '}
|
|
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}) ·{' '}
|
|
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
|
+
}
|