@wopr-network/platform-ui-core 1.0.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/.env.paperclip +18 -0
- package/.env.wopr +18 -0
- package/README.md +36 -0
- package/biome.json +52 -0
- package/next.config.ts +45 -0
- package/package.json +84 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/window.svg +1 -0
- package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
- package/src/__tests__/account-page-redirect.test.tsx +73 -0
- package/src/__tests__/account-switcher.test.tsx +85 -0
- package/src/__tests__/activity-page.test.tsx +176 -0
- package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
- package/src/__tests__/admin-api.test.ts +244 -0
- package/src/__tests__/admin-gpu-api.test.ts +188 -0
- package/src/__tests__/admin-guard.test.tsx +79 -0
- package/src/__tests__/admin-marketplace-api.test.ts +179 -0
- package/src/__tests__/admin-middleware.test.ts +157 -0
- package/src/__tests__/admin-tenant-table.test.tsx +95 -0
- package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
- package/src/__tests__/api-401-redirect.test.ts +78 -0
- package/src/__tests__/api-client.test.ts +316 -0
- package/src/__tests__/api-config.test.ts +89 -0
- package/src/__tests__/api-control-instance.test.ts +69 -0
- package/src/__tests__/api-fleet-resources.test.ts +52 -0
- package/src/__tests__/api-fleet-trpc.test.ts +252 -0
- package/src/__tests__/api-get-instance-config.test.ts +41 -0
- package/src/__tests__/api-null-guards.test.ts +244 -0
- package/src/__tests__/api-rename-instance.test.ts +60 -0
- package/src/__tests__/api-update-instance-config.test.ts +60 -0
- package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
- package/src/__tests__/auth-client.test.ts +87 -0
- package/src/__tests__/auth-password-reset.test.tsx +435 -0
- package/src/__tests__/auth-redirect.test.tsx +60 -0
- package/src/__tests__/auth.test.tsx +269 -0
- package/src/__tests__/auto-topup-card.test.tsx +257 -0
- package/src/__tests__/backups-tab.test.tsx +221 -0
- package/src/__tests__/billing-byok-callout.test.tsx +76 -0
- package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
- package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
- package/src/__tests__/billing.test.tsx +509 -0
- package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
- package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
- package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
- package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
- package/src/__tests__/bot-settings-data-control.test.ts +49 -0
- package/src/__tests__/bot-settings-restart.test.tsx +149 -0
- package/src/__tests__/bot-settings.test.tsx +678 -0
- package/src/__tests__/brand.test.ts +335 -0
- package/src/__tests__/buy-credits-panel.test.tsx +249 -0
- package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
- package/src/__tests__/capability-conflicts.test.ts +88 -0
- package/src/__tests__/capability-resolver.test.tsx +173 -0
- package/src/__tests__/changeset-detail.test.tsx +156 -0
- package/src/__tests__/channel-setup-logger.test.ts +22 -0
- package/src/__tests__/channel-setup-toast.test.tsx +60 -0
- package/src/__tests__/channel-wizard.test.tsx +505 -0
- package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
- package/src/__tests__/chat/chat-input.test.tsx +78 -0
- package/src/__tests__/chat/chat-message.test.tsx +45 -0
- package/src/__tests__/chat/chat-panel.test.tsx +111 -0
- package/src/__tests__/chat/chat-widget.test.tsx +82 -0
- package/src/__tests__/chat-store.test.ts +87 -0
- package/src/__tests__/command-center.test.tsx +246 -0
- package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
- package/src/__tests__/coupon-input.test.tsx +119 -0
- package/src/__tests__/create-instance.test.tsx +96 -0
- package/src/__tests__/create-org-wizard.test.tsx +200 -0
- package/src/__tests__/credit-balance.test.tsx +103 -0
- package/src/__tests__/credits.test.tsx +376 -0
- package/src/__tests__/csrf-middleware.test.ts +198 -0
- package/src/__tests__/degraded-state-banner.test.tsx +130 -0
- package/src/__tests__/dividend-calculator.test.tsx +20 -0
- package/src/__tests__/dividend-stats.test.tsx +64 -0
- package/src/__tests__/dividend.test.tsx +169 -0
- package/src/__tests__/dockerfile.test.ts +110 -0
- package/src/__tests__/email-verification-banner.test.tsx +64 -0
- package/src/__tests__/env-example.test.ts +25 -0
- package/src/__tests__/error-boundaries.test.tsx +64 -0
- package/src/__tests__/fetch-pricing.test.ts +121 -0
- package/src/__tests__/field-oauth.test.tsx +302 -0
- package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
- package/src/__tests__/fixtures/mock-manifests.ts +24 -0
- package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
- package/src/__tests__/fleet-health-update.test.tsx +83 -0
- package/src/__tests__/format-credit.test.ts +58 -0
- package/src/__tests__/gpu-dashboard.test.tsx +236 -0
- package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
- package/src/__tests__/instance-detail.test.tsx +571 -0
- package/src/__tests__/instance-list.test.tsx +230 -0
- package/src/__tests__/landing-hero.test.tsx +27 -0
- package/src/__tests__/landing-nav.test.tsx +24 -0
- package/src/__tests__/layout-snapshots.test.tsx +167 -0
- package/src/__tests__/logger.test.ts +54 -0
- package/src/__tests__/login-page-redirect.test.tsx +142 -0
- package/src/__tests__/manifest-validation.test.ts +126 -0
- package/src/__tests__/marketplace-admin.test.tsx +151 -0
- package/src/__tests__/marketplace.test.tsx +609 -0
- package/src/__tests__/merge-api-rates.test.ts +70 -0
- package/src/__tests__/middleware.test.ts +690 -0
- package/src/__tests__/network-page.test.tsx +100 -0
- package/src/__tests__/next-config-headers.test.ts +28 -0
- package/src/__tests__/not-found.test.tsx +26 -0
- package/src/__tests__/notifications.test.tsx +128 -0
- package/src/__tests__/oauth-buttons.test.tsx +101 -0
- package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
- package/src/__tests__/observability.test.tsx +541 -0
- package/src/__tests__/onboarding-data.test.ts +363 -0
- package/src/__tests__/onboarding-page.test.tsx +113 -0
- package/src/__tests__/onboarding-store.test.ts +121 -0
- package/src/__tests__/org-billing-api.test.tsx +70 -0
- package/src/__tests__/org-billing-null-guards.test.ts +64 -0
- package/src/__tests__/org-billing-page.test.tsx +124 -0
- package/src/__tests__/plugin-definition.test.ts +43 -0
- package/src/__tests__/plugin-install-flow.test.tsx +535 -0
- package/src/__tests__/plugin-registry.test.tsx +475 -0
- package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
- package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
- package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
- package/src/__tests__/plugin-tool-sync.test.ts +59 -0
- package/src/__tests__/portfolio-chart.test.tsx +24 -0
- package/src/__tests__/pricing.test.tsx +107 -0
- package/src/__tests__/promotion-form.test.tsx +180 -0
- package/src/__tests__/promotions-list.test.tsx +194 -0
- package/src/__tests__/provider-key-api.test.ts +134 -0
- package/src/__tests__/resend-verification-button.test.tsx +104 -0
- package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
- package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
- package/src/__tests__/settings.test.tsx +937 -0
- package/src/__tests__/setup-checklist.test.tsx +274 -0
- package/src/__tests__/setup.ts +82 -0
- package/src/__tests__/smoke.test.tsx +10 -0
- package/src/__tests__/snapshot-api.test.ts +104 -0
- package/src/__tests__/status-api.test.ts +46 -0
- package/src/__tests__/status-badge.test.tsx +33 -0
- package/src/__tests__/status-colors.test.ts +83 -0
- package/src/__tests__/status-page.test.tsx +86 -0
- package/src/__tests__/step-superpowers.test.tsx +218 -0
- package/src/__tests__/story-sections.test.tsx +24 -0
- package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
- package/src/__tests__/superpower-content.test.tsx +44 -0
- package/src/__tests__/suspension-banner.test.tsx +140 -0
- package/src/__tests__/tenant-context.test.tsx +146 -0
- package/src/__tests__/tenant-keys-api.test.ts +114 -0
- package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
- package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
- package/src/__tests__/terminal-sequence.test.tsx +28 -0
- package/src/__tests__/transaction-history.test.tsx +325 -0
- package/src/__tests__/trpc-types.test.ts +102 -0
- package/src/__tests__/use-capability-meta.test.ts +161 -0
- package/src/__tests__/use-chat.test.ts +616 -0
- package/src/__tests__/use-has-org.test.ts +44 -0
- package/src/__tests__/use-image-status.test.ts +77 -0
- package/src/__tests__/use-pagination-params.test.ts +88 -0
- package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
- package/src/__tests__/use-webmcp.test.ts +119 -0
- package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
- package/src/__tests__/validate-redirect-url.test.ts +61 -0
- package/src/__tests__/verify-page.test.tsx +140 -0
- package/src/__tests__/verify-redirect.test.tsx +41 -0
- package/src/__tests__/verify-result-banner.test.tsx +66 -0
- package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
- package/src/__tests__/webmcp-hook.test.tsx +72 -0
- package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
- package/src/__tests__/webmcp-register.test.ts +103 -0
- package/src/__tests__/webmcp-set-provider.test.ts +47 -0
- package/src/__tests__/webmcp-tools.test.ts +348 -0
- package/src/app/(auth)/error.tsx +72 -0
- package/src/app/(auth)/forgot-password/page.tsx +137 -0
- package/src/app/(auth)/layout.tsx +14 -0
- package/src/app/(auth)/loading.tsx +26 -0
- package/src/app/(auth)/login/page.tsx +188 -0
- package/src/app/(auth)/reset-password/page.tsx +169 -0
- package/src/app/(auth)/signup/page.tsx +309 -0
- package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
- package/src/app/(dashboard)/billing/error.tsx +72 -0
- package/src/app/(dashboard)/billing/layout.tsx +73 -0
- package/src/app/(dashboard)/billing/loading.tsx +41 -0
- package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
- package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
- package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
- package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
- package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
- package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
- package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
- package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
- package/src/app/(dashboard)/changesets/error.tsx +72 -0
- package/src/app/(dashboard)/changesets/page.tsx +10 -0
- package/src/app/(dashboard)/chat/page.tsx +74 -0
- package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
- package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
- package/src/app/(dashboard)/dashboard/page.tsx +13 -0
- package/src/app/(dashboard)/error.tsx +72 -0
- package/src/app/(dashboard)/layout.tsx +113 -0
- package/src/app/(dashboard)/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
- package/src/app/(dashboard)/marketplace/error.tsx +72 -0
- package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/page.tsx +268 -0
- package/src/app/(dashboard)/not-found.tsx +46 -0
- package/src/app/(dashboard)/onboarding/page.tsx +267 -0
- package/src/app/(dashboard)/settings/account/page.tsx +132 -0
- package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
- package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
- package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
- package/src/app/(dashboard)/settings/error.tsx +72 -0
- package/src/app/(dashboard)/settings/layout.tsx +114 -0
- package/src/app/(dashboard)/settings/loading.tsx +31 -0
- package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
- package/src/app/(dashboard)/settings/org/page.tsx +617 -0
- package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
- package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
- package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
- package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
- package/src/app/admin/accounting/loading.tsx +32 -0
- package/src/app/admin/accounting/page.tsx +5 -0
- package/src/app/admin/affiliates/loading.tsx +32 -0
- package/src/app/admin/affiliates/page.tsx +5 -0
- package/src/app/admin/audit/loading.tsx +32 -0
- package/src/app/admin/audit/page.tsx +5 -0
- package/src/app/admin/billing-health/loading.tsx +17 -0
- package/src/app/admin/billing-health/page.tsx +10 -0
- package/src/app/admin/compliance/page.tsx +5 -0
- package/src/app/admin/error.tsx +72 -0
- package/src/app/admin/gpu/loading.tsx +38 -0
- package/src/app/admin/gpu/page.tsx +5 -0
- package/src/app/admin/incidents/page.tsx +10 -0
- package/src/app/admin/inference/loading.tsx +32 -0
- package/src/app/admin/inference/page.tsx +5 -0
- package/src/app/admin/layout.tsx +44 -0
- package/src/app/admin/loading.tsx +32 -0
- package/src/app/admin/marketplace/loading.tsx +32 -0
- package/src/app/admin/marketplace/page.tsx +5 -0
- package/src/app/admin/migrations/loading.tsx +22 -0
- package/src/app/admin/migrations/page.tsx +5 -0
- package/src/app/admin/onboarding/loading.tsx +18 -0
- package/src/app/admin/onboarding/page.tsx +5 -0
- package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
- package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
- package/src/app/admin/promotions/[id]/loading.tsx +15 -0
- package/src/app/admin/promotions/[id]/page.tsx +311 -0
- package/src/app/admin/promotions/loading.tsx +21 -0
- package/src/app/admin/promotions/new/loading.tsx +16 -0
- package/src/app/admin/promotions/new/page.tsx +12 -0
- package/src/app/admin/promotions/page.tsx +266 -0
- package/src/app/admin/rate-overrides/loading.tsx +17 -0
- package/src/app/admin/rate-overrides/page.tsx +290 -0
- package/src/app/admin/roles/loading.tsx +27 -0
- package/src/app/admin/roles/page.tsx +5 -0
- package/src/app/admin/tenants/loading.tsx +32 -0
- package/src/app/admin/tenants/page.tsx +5 -0
- package/src/app/apple-icon.tsx +32 -0
- package/src/app/auth/callback/[provider]/page.tsx +104 -0
- package/src/app/auth/verify/page.tsx +224 -0
- package/src/app/channels/error.tsx +72 -0
- package/src/app/channels/loading.tsx +29 -0
- package/src/app/channels/page.tsx +262 -0
- package/src/app/channels/setup/[plugin]/page.tsx +136 -0
- package/src/app/error.tsx +72 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/fleet/error.tsx +72 -0
- package/src/app/fleet/health/page.tsx +9 -0
- package/src/app/fleet/layout.tsx +14 -0
- package/src/app/fleet/loading.tsx +33 -0
- package/src/app/fleet/page.tsx +5 -0
- package/src/app/global-error.tsx +96 -0
- package/src/app/globals.css +251 -0
- package/src/app/icon.svg +4 -0
- package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
- package/src/app/instances/[id]/page.tsx +10 -0
- package/src/app/instances/error.tsx +72 -0
- package/src/app/instances/instance-list-client.tsx +540 -0
- package/src/app/instances/loading.tsx +33 -0
- package/src/app/instances/new/create-instance-client.tsx +377 -0
- package/src/app/instances/new/page.tsx +9 -0
- package/src/app/instances/page.tsx +9 -0
- package/src/app/layout.tsx +83 -0
- package/src/app/not-found.tsx +38 -0
- package/src/app/og/route.tsx +50 -0
- package/src/app/page.tsx +39 -0
- package/src/app/plugins/error.tsx +72 -0
- package/src/app/plugins/layout.tsx +14 -0
- package/src/app/plugins/loading.tsx +30 -0
- package/src/app/plugins/page.tsx +555 -0
- package/src/app/pricing/error.tsx +72 -0
- package/src/app/pricing/loading.tsx +25 -0
- package/src/app/pricing/page.tsx +20 -0
- package/src/app/privacy/page.tsx +406 -0
- package/src/app/robots.ts +9 -0
- package/src/app/sitemap.ts +11 -0
- package/src/app/status/error.tsx +72 -0
- package/src/app/status/loading.tsx +21 -0
- package/src/app/status/page.tsx +20 -0
- package/src/app/terms/page.tsx +414 -0
- package/src/components/account-switcher.tsx +82 -0
- package/src/components/admin/accounting-dashboard.tsx +190 -0
- package/src/components/admin/admin-guard.tsx +36 -0
- package/src/components/admin/admin-nav.tsx +71 -0
- package/src/components/admin/affiliate-dashboard.tsx +564 -0
- package/src/components/admin/audit-log-table.tsx +336 -0
- package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
- package/src/components/admin/billing-health-dashboard.tsx +416 -0
- package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
- package/src/components/admin/bulk-actions-bar.tsx +80 -0
- package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
- package/src/components/admin/bulk-export-dialog.tsx +189 -0
- package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
- package/src/components/admin/bulk-grant-dialog.tsx +147 -0
- package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
- package/src/components/admin/bulk-preview-dialog.tsx +106 -0
- package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
- package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
- package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
- package/src/components/admin/bulk-select-all-banner.tsx +44 -0
- package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
- package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
- package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
- package/src/components/admin/bulk-undo-toast.tsx +121 -0
- package/src/components/admin/compliance-dashboard.tsx +1341 -0
- package/src/components/admin/gpu-dashboard.tsx +552 -0
- package/src/components/admin/grant-credits-dialog.tsx +121 -0
- package/src/components/admin/incident-dashboard.test.tsx +44 -0
- package/src/components/admin/incident-dashboard.tsx +717 -0
- package/src/components/admin/inference-dashboard.tsx +415 -0
- package/src/components/admin/marketplace-admin.tsx +765 -0
- package/src/components/admin/migrations-dashboard.tsx +404 -0
- package/src/components/admin/onboarding-dashboard.tsx +422 -0
- package/src/components/admin/promotions/promotion-form.tsx +440 -0
- package/src/components/admin/roles-dashboard.tsx +278 -0
- package/src/components/admin/suspend-dialog.tsx +98 -0
- package/src/components/admin/tenant-notes-panel.tsx +134 -0
- package/src/components/admin/tenant-row-actions.tsx +78 -0
- package/src/components/admin/tenant-table.tsx +339 -0
- package/src/components/auth/auth-error.tsx +22 -0
- package/src/components/auth/auth-redirect.tsx +18 -0
- package/src/components/auth/auth-shell.tsx +25 -0
- package/src/components/auth/email-verification-banner.tsx +25 -0
- package/src/components/auth/email-verification-result-banner.tsx +70 -0
- package/src/components/auth/resend-verification-button.tsx +94 -0
- package/src/components/auth/wopr-wordmark.tsx +19 -0
- package/src/components/billing/add-payment-method-dialog.tsx +267 -0
- package/src/components/billing/affiliate-dashboard.tsx +300 -0
- package/src/components/billing/auto-topup-card.tsx +432 -0
- package/src/components/billing/buy-credits-panel.tsx +180 -0
- package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
- package/src/components/billing/byok-callout.tsx +87 -0
- package/src/components/billing/coupon-input.tsx +86 -0
- package/src/components/billing/credit-balance.tsx +79 -0
- package/src/components/billing/degraded-state-banner.tsx +95 -0
- package/src/components/billing/dividend-banner.tsx +97 -0
- package/src/components/billing/dividend-eligibility.tsx +86 -0
- package/src/components/billing/dividend-pool-stats.tsx +86 -0
- package/src/components/billing/first-dividend-dialog.tsx +109 -0
- package/src/components/billing/low-balance-banner.tsx +50 -0
- package/src/components/billing/org-billing-page.tsx +360 -0
- package/src/components/billing/suspension-banner.tsx +53 -0
- package/src/components/billing/transaction-history.tsx +239 -0
- package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
- package/src/components/bot-settings/backups-tab.tsx +377 -0
- package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
- package/src/components/bot-settings/resources-tab.tsx +203 -0
- package/src/components/bot-settings/storage-tab.tsx +248 -0
- package/src/components/bot-settings/vps-info-panel.tsx +132 -0
- package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
- package/src/components/capability/CapabilityResolver.tsx +113 -0
- package/src/components/channel-wizard/field-interactive.tsx +48 -0
- package/src/components/channel-wizard/field-oauth.tsx +181 -0
- package/src/components/channel-wizard/field-paste.tsx +47 -0
- package/src/components/channel-wizard/field-qr.tsx +302 -0
- package/src/components/channel-wizard/index.ts +6 -0
- package/src/components/channel-wizard/step-renderer.tsx +103 -0
- package/src/components/channel-wizard/wizard.tsx +200 -0
- package/src/components/chat/ambient-dot.tsx +32 -0
- package/src/components/chat/chat-input.tsx +56 -0
- package/src/components/chat/chat-message.tsx +36 -0
- package/src/components/chat/chat-panel.tsx +138 -0
- package/src/components/chat/chat-widget.tsx +41 -0
- package/src/components/chat/index.ts +5 -0
- package/src/components/dashboard/command-center.tsx +614 -0
- package/src/components/instances/friends-tab.test.tsx +265 -0
- package/src/components/instances/friends-tab.tsx +721 -0
- package/src/components/landing/hero.tsx +53 -0
- package/src/components/landing/landing-nav.tsx +21 -0
- package/src/components/landing/landing-page.tsx +71 -0
- package/src/components/landing/portfolio-chart.tsx +349 -0
- package/src/components/landing/story-sections.tsx +50 -0
- package/src/components/landing/terminal-lines.ts +99 -0
- package/src/components/landing/terminal-sequence.tsx +453 -0
- package/src/components/landing/typing-effect.tsx +43 -0
- package/src/components/marketplace/category-filter.tsx +61 -0
- package/src/components/marketplace/empty-state.tsx +61 -0
- package/src/components/marketplace/featured-heroes.tsx +84 -0
- package/src/components/marketplace/first-visit-hero.tsx +110 -0
- package/src/components/marketplace/index.ts +9 -0
- package/src/components/marketplace/install-wizard.tsx +782 -0
- package/src/components/marketplace/marketplace-tabs.tsx +54 -0
- package/src/components/marketplace/plugin-card.tsx +129 -0
- package/src/components/marketplace/superpower-card.tsx +104 -0
- package/src/components/marketplace/superpower-content.tsx +117 -0
- package/src/components/marketplace/terminal-search.tsx +67 -0
- package/src/components/oauth-buttons.tsx +75 -0
- package/src/components/observability/fleet-health.tsx +370 -0
- package/src/components/observability/health-overview.tsx +246 -0
- package/src/components/observability/logs-viewer.tsx +215 -0
- package/src/components/observability/metrics-dashboard.tsx +288 -0
- package/src/components/onboarding/fallback-setup.tsx +137 -0
- package/src/components/onboarding/index.ts +3 -0
- package/src/components/onboarding/setup-checklist.tsx +333 -0
- package/src/components/onboarding/step-superpowers.tsx +122 -0
- package/src/components/plugin-setup/index.ts +1 -0
- package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
- package/src/components/pricing/dividend-calculator.tsx +47 -0
- package/src/components/pricing/dividend-stats.tsx +117 -0
- package/src/components/pricing/pricing-page.tsx +229 -0
- package/src/components/settings/create-org-wizard.tsx +225 -0
- package/src/components/sidebar.tsx +202 -0
- package/src/components/status/status-page.tsx +209 -0
- package/src/components/status-badge.tsx +28 -0
- package/src/components/theme-provider.tsx +8 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/banner.tsx +36 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/checkbox.tsx +52 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/credit-detailed.tsx +33 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +228 -0
- package/src/components/ui/form.tsx +151 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/popover.tsx +74 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/select.tsx +175 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +125 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/switch.tsx +35 -0
- package/src/components/ui/table.tsx +92 -0
- package/src/components/ui/tabs.tsx +81 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +44 -0
- package/src/config/provider-docs.ts +17 -0
- package/src/hooks/__tests__/use-async.test.ts +127 -0
- package/src/hooks/__tests__/use-count-up.test.ts +129 -0
- package/src/hooks/__tests__/use-debounce.test.ts +105 -0
- package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
- package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
- package/src/hooks/__tests__/use-mobile.test.ts +86 -0
- package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
- package/src/hooks/use-async.ts +54 -0
- package/src/hooks/use-capability-meta.ts +99 -0
- package/src/hooks/use-count-up.ts +23 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-fleet-sse.ts +47 -0
- package/src/hooks/use-has-org.ts +18 -0
- package/src/hooks/use-image-status.ts +36 -0
- package/src/hooks/use-local-storage.ts +36 -0
- package/src/hooks/use-mobile.ts +17 -0
- package/src/hooks/use-page-context.ts +24 -0
- package/src/hooks/use-pagination-params.ts +30 -0
- package/src/hooks/use-plugin-registry.ts +247 -0
- package/src/hooks/use-plugin-setup-chat.ts +211 -0
- package/src/hooks/use-save-queue.ts +54 -0
- package/src/hooks/use-webmcp.ts +40 -0
- package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
- package/src/lib/__tests__/admin-api.test.ts +487 -0
- package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
- package/src/lib/__tests__/api-fetch.test.ts +196 -0
- package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
- package/src/lib/__tests__/org-api.test.ts +281 -0
- package/src/lib/__tests__/org-billing-api.test.ts +242 -0
- package/src/lib/__tests__/pricing-data.test.ts +32 -0
- package/src/lib/__tests__/settings-api.test.ts +272 -0
- package/src/lib/admin-affiliate-api.ts +51 -0
- package/src/lib/admin-api.ts +325 -0
- package/src/lib/admin-compliance-api.ts +127 -0
- package/src/lib/admin-gpu-api.ts +82 -0
- package/src/lib/admin-incident-api.ts +121 -0
- package/src/lib/admin-inference-api.ts +47 -0
- package/src/lib/admin-marketplace-api.ts +97 -0
- package/src/lib/api-config.test.ts +111 -0
- package/src/lib/api-config.ts +65 -0
- package/src/lib/api-errors.test.ts +43 -0
- package/src/lib/api.ts +2011 -0
- package/src/lib/auth-client.ts +11 -0
- package/src/lib/bot-settings-data.ts +342 -0
- package/src/lib/brand-config.ts +145 -0
- package/src/lib/brand.ts +669 -0
- package/src/lib/changeset-api.ts +29 -0
- package/src/lib/changeset-types.ts +56 -0
- package/src/lib/channel-manifests.ts +50 -0
- package/src/lib/chat/chat-context.tsx +70 -0
- package/src/lib/chat/chat-store.ts +62 -0
- package/src/lib/chat/types.ts +35 -0
- package/src/lib/chat/use-chat.ts +255 -0
- package/src/lib/cost-comparison-data.test.ts +95 -0
- package/src/lib/cost-comparison-data.ts +54 -0
- package/src/lib/errors.test.ts +64 -0
- package/src/lib/errors.ts +52 -0
- package/src/lib/fetch-utils.test.ts +57 -0
- package/src/lib/fetch-utils.ts +25 -0
- package/src/lib/format-credit.test.ts +66 -0
- package/src/lib/format-credit.ts +24 -0
- package/src/lib/format.test.ts +62 -0
- package/src/lib/format.ts +17 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/marketplace-data.ts +346 -0
- package/src/lib/oauth-errors.ts +19 -0
- package/src/lib/onboarding-data.ts +1265 -0
- package/src/lib/onboarding-store.ts +233 -0
- package/src/lib/org-api.ts +74 -0
- package/src/lib/org-billing-api.ts +81 -0
- package/src/lib/page-prompts.test.ts +32 -0
- package/src/lib/page-prompts.ts +23 -0
- package/src/lib/plugin/index.ts +32 -0
- package/src/lib/plugin/tool-definitions.ts +306 -0
- package/src/lib/pricing-data.ts +115 -0
- package/src/lib/promotions-types.ts +58 -0
- package/src/lib/settings-api.ts +63 -0
- package/src/lib/status-colors.ts +38 -0
- package/src/lib/tenant-context.tsx +134 -0
- package/src/lib/trpc-types.ts +173 -0
- package/src/lib/trpc.tsx +86 -0
- package/src/lib/utils.test.ts +55 -0
- package/src/lib/utils.ts +18 -0
- package/src/lib/validate-redirect-url.ts +39 -0
- package/src/lib/webmcp/feature-detect.ts +13 -0
- package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
- package/src/lib/webmcp/register.ts +44 -0
- package/src/lib/webmcp/tools.ts +422 -0
- package/src/proxy.ts +258 -0
- package/src/types/missing-deps.d.ts +160 -0
- package/src/types/motion-dom.d.ts +162 -0
- package/src/types/vitest-matchers.d.ts +40 -0
- package/src/types/web-mcp.d.ts +22 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
5
|
+
import { fetchDividendStats } from "@/lib/api";
|
|
6
|
+
import { toUserMessage } from "@/lib/errors";
|
|
7
|
+
import { formatCreditStandard } from "@/lib/format-credit";
|
|
8
|
+
|
|
9
|
+
function useCountUp(target: number, duration = 1200) {
|
|
10
|
+
const [value, setValue] = useState(0);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (target === 0) {
|
|
13
|
+
setValue(0);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Respect prefers-reduced-motion: skip animation, set immediately
|
|
17
|
+
if (
|
|
18
|
+
typeof window !== "undefined" &&
|
|
19
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
20
|
+
) {
|
|
21
|
+
setValue(target);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const start = performance.now();
|
|
25
|
+
let raf: number;
|
|
26
|
+
function tick(now: number) {
|
|
27
|
+
const elapsed = now - start;
|
|
28
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
29
|
+
// ease-out cubic
|
|
30
|
+
const eased = 1 - (1 - progress) ** 3;
|
|
31
|
+
setValue(eased * target);
|
|
32
|
+
if (progress < 1) raf = requestAnimationFrame(tick);
|
|
33
|
+
}
|
|
34
|
+
raf = requestAnimationFrame(tick);
|
|
35
|
+
return () => cancelAnimationFrame(raf);
|
|
36
|
+
}, [target, duration]);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatDollars(n: number): string {
|
|
41
|
+
return formatCreditStandard(n);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function DividendStats() {
|
|
45
|
+
const [pool, setPool] = useState(0);
|
|
46
|
+
const [users, setUsers] = useState(0);
|
|
47
|
+
const [dividend, setDividend] = useState(0);
|
|
48
|
+
const [loaded, setLoaded] = useState(false);
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
let cancelled = false;
|
|
53
|
+
fetchDividendStats()
|
|
54
|
+
.then((data) => {
|
|
55
|
+
if (cancelled) return;
|
|
56
|
+
if (data) {
|
|
57
|
+
setPool(data.poolAmountDollars);
|
|
58
|
+
setUsers(data.activeUsers);
|
|
59
|
+
setDividend(data.projectedDailyDividend);
|
|
60
|
+
}
|
|
61
|
+
setLoaded(true);
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
if (cancelled) return;
|
|
65
|
+
setError(toUserMessage(err, "Failed to load dividend stats"));
|
|
66
|
+
setLoaded(true);
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
cancelled = true;
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const animatedPool = useCountUp(pool);
|
|
74
|
+
const animatedUsers = useCountUp(users);
|
|
75
|
+
const animatedDividend = useCountUp(dividend);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="grid gap-4 sm:grid-cols-3">
|
|
79
|
+
{error && <p className="col-span-full text-center text-sm text-red-500">{error}</p>}
|
|
80
|
+
<Card className="border-terminal/30">
|
|
81
|
+
<CardContent className="flex flex-col items-center gap-1 py-6 text-center">
|
|
82
|
+
<p className="text-xs uppercase tracking-widest text-muted-foreground">
|
|
83
|
+
Today's community pool
|
|
84
|
+
</p>
|
|
85
|
+
<p className="text-3xl font-bold text-terminal sm:text-4xl" data-testid="pool-amount">
|
|
86
|
+
{loaded && pool > 0 ? formatDollars(animatedPool) : "--"}
|
|
87
|
+
</p>
|
|
88
|
+
</CardContent>
|
|
89
|
+
</Card>
|
|
90
|
+
|
|
91
|
+
<Card className="border-terminal/30">
|
|
92
|
+
<CardContent className="flex flex-col items-center gap-1 py-6 text-center">
|
|
93
|
+
<p className="text-xs uppercase tracking-widest text-muted-foreground">
|
|
94
|
+
Active users in pool
|
|
95
|
+
</p>
|
|
96
|
+
<p className="text-3xl font-bold text-terminal sm:text-4xl" data-testid="active-users">
|
|
97
|
+
{loaded && users > 0 ? Math.round(animatedUsers).toLocaleString() : "--"}
|
|
98
|
+
</p>
|
|
99
|
+
</CardContent>
|
|
100
|
+
</Card>
|
|
101
|
+
|
|
102
|
+
<Card className="border-terminal/30">
|
|
103
|
+
<CardContent className="flex flex-col items-center gap-1 py-6 text-center">
|
|
104
|
+
<p className="text-xs uppercase tracking-widest text-muted-foreground">
|
|
105
|
+
Your projected daily dividend
|
|
106
|
+
</p>
|
|
107
|
+
<p
|
|
108
|
+
className="text-3xl font-bold text-terminal sm:text-4xl"
|
|
109
|
+
data-testid="projected-dividend"
|
|
110
|
+
>
|
|
111
|
+
{loaded && dividend > 0 ? `~${formatCreditStandard(animatedDividend)}` : "--"}
|
|
112
|
+
</p>
|
|
113
|
+
</CardContent>
|
|
114
|
+
</Card>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { BotIcon, ImageIcon, MicIcon, SmartphoneIcon } from "lucide-react";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
6
|
+
import { fetchPublicPricing } from "@/lib/api";
|
|
7
|
+
import { brandName, getBrandConfig } from "@/lib/brand-config";
|
|
8
|
+
import { formatCreditDetailed } from "@/lib/format-credit";
|
|
9
|
+
import { mergeApiRates, type PricingCapability, pricingData } from "@/lib/pricing-data";
|
|
10
|
+
import { DividendCalculator } from "./dividend-calculator";
|
|
11
|
+
import { DividendStats } from "./dividend-stats";
|
|
12
|
+
|
|
13
|
+
const iconMap = {
|
|
14
|
+
bot: BotIcon,
|
|
15
|
+
mic: MicIcon,
|
|
16
|
+
image: ImageIcon,
|
|
17
|
+
smartphone: SmartphoneIcon,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
function formatPrice(price: number): string {
|
|
21
|
+
return formatCreditDetailed(price);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function PricingPage() {
|
|
25
|
+
let capabilities: PricingCapability[];
|
|
26
|
+
const apiData = await fetchPublicPricing();
|
|
27
|
+
if (apiData) {
|
|
28
|
+
capabilities = mergeApiRates(apiData.rates);
|
|
29
|
+
} else {
|
|
30
|
+
capabilities = pricingData.capabilities.map((c) => ({
|
|
31
|
+
category: c.category,
|
|
32
|
+
icon: c.icon,
|
|
33
|
+
models: c.models.map((m) => ({ name: m.name, unit: m.unit, price: m.price })),
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="bg-background text-foreground">
|
|
39
|
+
{/* --- Dividend Hero --- */}
|
|
40
|
+
<section className="flex min-h-[60dvh] flex-col items-center justify-center px-6 text-center">
|
|
41
|
+
<Badge variant="terminal" className="mb-8">
|
|
42
|
+
Community dividend
|
|
43
|
+
</Badge>
|
|
44
|
+
|
|
45
|
+
<h1 className="max-w-3xl text-3xl font-bold leading-[1.1] tracking-tight sm:text-5xl md:text-6xl">
|
|
46
|
+
{brandName()} pays for itself.
|
|
47
|
+
</h1>
|
|
48
|
+
|
|
49
|
+
<p className="mt-6 max-w-xl text-lg text-muted-foreground">
|
|
50
|
+
Every day, the platform distributes credits back to active users from its own margin. The
|
|
51
|
+
bigger the community grows, the more you receive. Early users get the most.
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<p className="mt-4 max-w-lg text-sm text-muted-foreground">
|
|
55
|
+
At scale, the daily dividend covers your entire credit spend. You're not paying to
|
|
56
|
+
run your bots. {brandName()} is.
|
|
57
|
+
</p>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
{/* --- Live Pool Stats --- */}
|
|
61
|
+
<section className="mx-auto max-w-3xl px-6 pb-16">
|
|
62
|
+
<DividendStats />
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
{/* --- Dividend Math --- */}
|
|
66
|
+
<section className="px-6 pb-24">
|
|
67
|
+
<DividendCalculator />
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
{/* --- Credit Tiers (reframed) --- */}
|
|
71
|
+
<section className="flex flex-col items-center justify-center px-6 pb-12 text-center">
|
|
72
|
+
<h2 className="mb-4 text-2xl font-bold tracking-tight sm:text-3xl">Stay in the pool.</h2>
|
|
73
|
+
<p className="mb-8 max-w-lg text-muted-foreground">
|
|
74
|
+
Your bot is{" "}
|
|
75
|
+
<span className="font-semibold text-terminal">
|
|
76
|
+
${pricingData.bot_price.amount}/{pricingData.bot_price.period}
|
|
77
|
+
</span>
|
|
78
|
+
. That's the minimum to be eligible for the daily dividend. Usage is billed at cost
|
|
79
|
+
from credits.
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<Card className="w-full max-w-md border-terminal">
|
|
83
|
+
<CardHeader className="items-center text-center">
|
|
84
|
+
<CardTitle className="text-sm uppercase tracking-widest text-muted-foreground">
|
|
85
|
+
Pool eligibility
|
|
86
|
+
</CardTitle>
|
|
87
|
+
</CardHeader>
|
|
88
|
+
<CardContent className="flex flex-col items-center gap-4">
|
|
89
|
+
<p className="text-5xl font-bold text-terminal sm:text-6xl" data-testid="bot-price">
|
|
90
|
+
${pricingData.bot_price.amount}
|
|
91
|
+
<span className="text-xl font-normal text-muted-foreground">
|
|
92
|
+
/{pricingData.bot_price.period}
|
|
93
|
+
</span>
|
|
94
|
+
</p>
|
|
95
|
+
<p className="text-muted-foreground">Minimum spend to stay in the dividend pool.</p>
|
|
96
|
+
</CardContent>
|
|
97
|
+
</Card>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
{/* --- Capability Pricing --- */}
|
|
101
|
+
<section className="mx-auto max-w-4xl px-6 pb-24">
|
|
102
|
+
<h2 className="mb-12 text-center text-2xl font-bold tracking-tight sm:text-3xl">
|
|
103
|
+
Usage rates. Nothing hidden.
|
|
104
|
+
</h2>
|
|
105
|
+
|
|
106
|
+
<div className="flex flex-col gap-12">
|
|
107
|
+
{capabilities.map((capability) => {
|
|
108
|
+
const Icon = iconMap[capability.icon];
|
|
109
|
+
return (
|
|
110
|
+
<div key={capability.category}>
|
|
111
|
+
<div className="mb-4 flex items-center gap-3">
|
|
112
|
+
<Icon className="size-5 text-terminal" />
|
|
113
|
+
<h3 className="text-lg font-semibold">{capability.category}</h3>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
117
|
+
{capability.models.map((model) => (
|
|
118
|
+
<Card key={model.name} className="border-border">
|
|
119
|
+
<CardContent className="flex items-center justify-between py-4">
|
|
120
|
+
<div>
|
|
121
|
+
<p className="font-medium">{model.name}</p>
|
|
122
|
+
<p className="text-sm text-muted-foreground">per {model.unit}</p>
|
|
123
|
+
</div>
|
|
124
|
+
<p className="text-lg font-bold text-terminal">
|
|
125
|
+
{formatPrice(model.price)}
|
|
126
|
+
</p>
|
|
127
|
+
</CardContent>
|
|
128
|
+
</Card>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
134
|
+
</div>
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
{/* --- VPS Tier --- */}
|
|
138
|
+
<section className="mx-auto max-w-4xl px-6 pb-24">
|
|
139
|
+
<h2 className="mb-4 text-center text-2xl font-bold tracking-tight sm:text-3xl">
|
|
140
|
+
Need a dedicated machine?
|
|
141
|
+
</h2>
|
|
142
|
+
<p className="mb-8 text-center text-muted-foreground">
|
|
143
|
+
The VPS tier gives your bot a persistent container with fixed monthly pricing — no
|
|
144
|
+
per-credit billing for compute.
|
|
145
|
+
</p>
|
|
146
|
+
<div className="mx-auto max-w-sm">
|
|
147
|
+
<Card className="border-terminal/50">
|
|
148
|
+
<CardHeader className="items-center text-center">
|
|
149
|
+
<CardTitle className="text-sm uppercase tracking-widest text-muted-foreground">
|
|
150
|
+
VPS tier
|
|
151
|
+
</CardTitle>
|
|
152
|
+
</CardHeader>
|
|
153
|
+
<CardContent className="flex flex-col items-center gap-4">
|
|
154
|
+
<p className="text-5xl font-bold text-terminal sm:text-6xl">
|
|
155
|
+
$15
|
|
156
|
+
<span className="text-xl font-normal text-muted-foreground">/mo</span>
|
|
157
|
+
</p>
|
|
158
|
+
<ul className="w-full space-y-1 text-sm text-muted-foreground">
|
|
159
|
+
<li className="flex items-center gap-2">
|
|
160
|
+
<span className="text-terminal">✓</span> 2 GB RAM / 2 vCPU / 20 GB SSD
|
|
161
|
+
</li>
|
|
162
|
+
<li className="flex items-center gap-2">
|
|
163
|
+
<span className="text-terminal">✓</span> Persistent container — data survives
|
|
164
|
+
restarts
|
|
165
|
+
</li>
|
|
166
|
+
<li className="flex items-center gap-2">
|
|
167
|
+
<span className="text-terminal">✓</span> Dedicated hostname
|
|
168
|
+
</li>
|
|
169
|
+
<li className="flex items-center gap-2">
|
|
170
|
+
<span className="text-terminal">✓</span> SSH access via Cloudflare Tunnel
|
|
171
|
+
</li>
|
|
172
|
+
<li className="flex items-center gap-2">
|
|
173
|
+
<span className="text-terminal">✓</span> Flat monthly price — no metered compute
|
|
174
|
+
</li>
|
|
175
|
+
</ul>
|
|
176
|
+
<Button
|
|
177
|
+
data-onboarding-id="pricing.subscribe.vps"
|
|
178
|
+
variant="terminal"
|
|
179
|
+
className="w-full"
|
|
180
|
+
asChild
|
|
181
|
+
>
|
|
182
|
+
<Link href="/signup">Get started</Link>
|
|
183
|
+
</Button>
|
|
184
|
+
</CardContent>
|
|
185
|
+
</Card>
|
|
186
|
+
</div>
|
|
187
|
+
</section>
|
|
188
|
+
|
|
189
|
+
{/* --- Credits Explainer --- */}
|
|
190
|
+
<section className="flex flex-col items-center justify-center gap-6 px-6 pb-24 text-center">
|
|
191
|
+
<p className="max-w-lg text-lg text-muted-foreground">
|
|
192
|
+
Your bot is ${pricingData.bot_price.amount}/mo. Usage is billed from credits. Free tier
|
|
193
|
+
includes ${pricingData.signup_credit} signup credit.
|
|
194
|
+
</p>
|
|
195
|
+
<p className="text-sm text-muted-foreground">
|
|
196
|
+
Credits in, dividend back. The community grows, your costs shrink.
|
|
197
|
+
</p>
|
|
198
|
+
</section>
|
|
199
|
+
|
|
200
|
+
{/* --- CTA --- */}
|
|
201
|
+
<section className="flex min-h-[40dvh] flex-col items-center justify-center gap-8 px-6 text-center">
|
|
202
|
+
<h2 className="max-w-2xl text-2xl font-bold leading-[1.1] tracking-tight sm:text-4xl">
|
|
203
|
+
Join early. The math rewards you.
|
|
204
|
+
</h2>
|
|
205
|
+
|
|
206
|
+
<Button data-onboarding-id="pricing.subscribe.cta" variant="terminal" size="lg" asChild>
|
|
207
|
+
<Link href="/signup">Get Started</Link>
|
|
208
|
+
</Button>
|
|
209
|
+
|
|
210
|
+
<span className="mt-4 text-sm text-muted-foreground opacity-60">
|
|
211
|
+
{getBrandConfig().domain}
|
|
212
|
+
</span>
|
|
213
|
+
</section>
|
|
214
|
+
|
|
215
|
+
{/* --- Footer --- */}
|
|
216
|
+
<footer className="flex justify-center gap-6 px-6 pb-8 text-sm text-muted-foreground">
|
|
217
|
+
<Link href="/" className="underline underline-offset-4 hover:text-foreground">
|
|
218
|
+
Home
|
|
219
|
+
</Link>
|
|
220
|
+
<Link href="/terms" className="underline underline-offset-4 hover:text-foreground">
|
|
221
|
+
Terms
|
|
222
|
+
</Link>
|
|
223
|
+
<Link href="/privacy" className="underline underline-offset-4 hover:text-foreground">
|
|
224
|
+
Privacy
|
|
225
|
+
</Link>
|
|
226
|
+
</footer>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
4
|
+
import { BuildingIcon, CheckCircleIcon } from "lucide-react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { type FormEvent, useCallback, useState } from "react";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
DialogTrigger,
|
|
16
|
+
} from "@/components/ui/dialog";
|
|
17
|
+
import { Input } from "@/components/ui/input";
|
|
18
|
+
import { Label } from "@/components/ui/label";
|
|
19
|
+
import { createOrganization } from "@/lib/org-api";
|
|
20
|
+
|
|
21
|
+
function toSlug(name: string): string {
|
|
22
|
+
return name
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
25
|
+
.replace(/\s+/g, "-")
|
|
26
|
+
.replace(/-+/g, "-")
|
|
27
|
+
.replace(/^-|-$/g, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Step = "name" | "confirm" | "done";
|
|
31
|
+
|
|
32
|
+
export default function CreateOrgWizard() {
|
|
33
|
+
const router = useRouter();
|
|
34
|
+
const [open, setOpen] = useState(false);
|
|
35
|
+
const [step, setStep] = useState<Step>("name");
|
|
36
|
+
const [orgName, setOrgName] = useState("");
|
|
37
|
+
const [slug, setSlug] = useState("");
|
|
38
|
+
const [slugTouched, setSlugTouched] = useState(false);
|
|
39
|
+
const [creating, setCreating] = useState(false);
|
|
40
|
+
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
const reset = useCallback(() => {
|
|
43
|
+
setStep("name");
|
|
44
|
+
setOrgName("");
|
|
45
|
+
setSlug("");
|
|
46
|
+
setSlugTouched(false);
|
|
47
|
+
setCreating(false);
|
|
48
|
+
setError(null);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
function handleNameChange(value: string) {
|
|
52
|
+
setOrgName(value);
|
|
53
|
+
if (!slugTouched) {
|
|
54
|
+
setSlug(toSlug(value));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleSlugChange(value: string) {
|
|
59
|
+
setSlugTouched(true);
|
|
60
|
+
setSlug(toSlug(value));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleNext(e: FormEvent) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (!orgName.trim() || !slug.trim()) return;
|
|
66
|
+
setError(null);
|
|
67
|
+
setStep("confirm");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function handleCreate() {
|
|
71
|
+
setCreating(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
try {
|
|
74
|
+
await createOrganization({ name: orgName.trim(), slug: slug.trim() });
|
|
75
|
+
setStep("done");
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const message =
|
|
78
|
+
err instanceof Error && err.message.includes("409")
|
|
79
|
+
? "This slug is already taken. Go back and choose a different one."
|
|
80
|
+
: "Failed to create organization. Please try again.";
|
|
81
|
+
setError(message);
|
|
82
|
+
} finally {
|
|
83
|
+
setCreating(false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleOpenChange(v: boolean) {
|
|
88
|
+
setOpen(v);
|
|
89
|
+
if (v) reset();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
94
|
+
<DialogTrigger asChild>
|
|
95
|
+
<Button variant="terminal">
|
|
96
|
+
<BuildingIcon className="mr-2 size-4" />
|
|
97
|
+
Create organization
|
|
98
|
+
</Button>
|
|
99
|
+
</DialogTrigger>
|
|
100
|
+
<DialogContent className="sm:max-w-md">
|
|
101
|
+
<AnimatePresence mode="wait">
|
|
102
|
+
{step === "name" && (
|
|
103
|
+
<motion.div
|
|
104
|
+
key="name"
|
|
105
|
+
initial={{ opacity: 0, x: -20 }}
|
|
106
|
+
animate={{ opacity: 1, x: 0 }}
|
|
107
|
+
exit={{ opacity: 0, x: -20 }}
|
|
108
|
+
transition={{ duration: 0.15 }}
|
|
109
|
+
>
|
|
110
|
+
<DialogHeader>
|
|
111
|
+
<DialogTitle>Name your organization</DialogTitle>
|
|
112
|
+
<DialogDescription>Choose a name and URL slug for your team.</DialogDescription>
|
|
113
|
+
</DialogHeader>
|
|
114
|
+
<form onSubmit={handleNext} className="mt-4 flex flex-col gap-4">
|
|
115
|
+
<div className="flex flex-col gap-2">
|
|
116
|
+
<Label htmlFor="org-wizard-name">Organization name</Label>
|
|
117
|
+
<Input
|
|
118
|
+
id="org-wizard-name"
|
|
119
|
+
placeholder="Acme Corp"
|
|
120
|
+
value={orgName}
|
|
121
|
+
onChange={(e) => handleNameChange(e.target.value)}
|
|
122
|
+
required
|
|
123
|
+
autoFocus
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex flex-col gap-2">
|
|
127
|
+
<Label htmlFor="org-wizard-slug">Slug</Label>
|
|
128
|
+
<Input
|
|
129
|
+
id="org-wizard-slug"
|
|
130
|
+
placeholder="acme-corp"
|
|
131
|
+
value={slug}
|
|
132
|
+
onChange={(e) => handleSlugChange(e.target.value)}
|
|
133
|
+
required
|
|
134
|
+
/>
|
|
135
|
+
<p className="text-xs text-muted-foreground">
|
|
136
|
+
Used in URLs. Lowercase letters, numbers, and hyphens only.
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
<DialogFooter>
|
|
140
|
+
<Button type="submit" disabled={!orgName.trim() || !slug.trim()}>
|
|
141
|
+
Next
|
|
142
|
+
</Button>
|
|
143
|
+
</DialogFooter>
|
|
144
|
+
</form>
|
|
145
|
+
</motion.div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{step === "confirm" && (
|
|
149
|
+
<motion.div
|
|
150
|
+
key="confirm"
|
|
151
|
+
initial={{ opacity: 0, x: 20 }}
|
|
152
|
+
animate={{ opacity: 1, x: 0 }}
|
|
153
|
+
exit={{ opacity: 0, x: 20 }}
|
|
154
|
+
transition={{ duration: 0.15 }}
|
|
155
|
+
>
|
|
156
|
+
<DialogHeader>
|
|
157
|
+
<DialogTitle>Confirm</DialogTitle>
|
|
158
|
+
<DialogDescription>
|
|
159
|
+
You{"'"}ll be the admin. You can invite members after setup.
|
|
160
|
+
</DialogDescription>
|
|
161
|
+
</DialogHeader>
|
|
162
|
+
<div className="mt-4 space-y-3">
|
|
163
|
+
<div className="rounded-md border px-4 py-3 text-sm">
|
|
164
|
+
<p>
|
|
165
|
+
<span className="text-muted-foreground">Name:</span> <strong>{orgName}</strong>
|
|
166
|
+
</p>
|
|
167
|
+
<p>
|
|
168
|
+
<span className="text-muted-foreground">Slug:</span>{" "}
|
|
169
|
+
<code className="text-xs">{slug}</code>
|
|
170
|
+
</p>
|
|
171
|
+
</div>
|
|
172
|
+
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
173
|
+
</div>
|
|
174
|
+
<DialogFooter className="mt-4 gap-2 sm:gap-0">
|
|
175
|
+
<Button
|
|
176
|
+
variant="outline"
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={() => {
|
|
179
|
+
setStep("name");
|
|
180
|
+
setError(null);
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
Back
|
|
184
|
+
</Button>
|
|
185
|
+
<Button variant="terminal" onClick={handleCreate} disabled={creating}>
|
|
186
|
+
{creating ? "Creating..." : "Create"}
|
|
187
|
+
</Button>
|
|
188
|
+
</DialogFooter>
|
|
189
|
+
</motion.div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{step === "done" && (
|
|
193
|
+
<motion.div
|
|
194
|
+
key="done"
|
|
195
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
196
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
197
|
+
transition={{ duration: 0.2 }}
|
|
198
|
+
>
|
|
199
|
+
<DialogHeader>
|
|
200
|
+
<DialogTitle className="flex items-center gap-2">
|
|
201
|
+
<CheckCircleIcon className="size-5 text-terminal" />
|
|
202
|
+
Organization created
|
|
203
|
+
</DialogTitle>
|
|
204
|
+
<DialogDescription>
|
|
205
|
+
Your organization is ready. You can now invite members and configure settings.
|
|
206
|
+
</DialogDescription>
|
|
207
|
+
</DialogHeader>
|
|
208
|
+
<DialogFooter className="mt-4">
|
|
209
|
+
<Button
|
|
210
|
+
variant="terminal"
|
|
211
|
+
onClick={() => {
|
|
212
|
+
setOpen(false);
|
|
213
|
+
router.push("/settings/org");
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
Go to organization settings
|
|
217
|
+
</Button>
|
|
218
|
+
</DialogFooter>
|
|
219
|
+
</motion.div>
|
|
220
|
+
)}
|
|
221
|
+
</AnimatePresence>
|
|
222
|
+
</DialogContent>
|
|
223
|
+
</Dialog>
|
|
224
|
+
);
|
|
225
|
+
}
|