@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.
Files changed (543) hide show
  1. package/.env.paperclip +18 -0
  2. package/.env.wopr +18 -0
  3. package/README.md +36 -0
  4. package/biome.json +52 -0
  5. package/next.config.ts +45 -0
  6. package/package.json +84 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/file.svg +1 -0
  9. package/public/globe.svg +1 -0
  10. package/public/window.svg +1 -0
  11. package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
  12. package/src/__tests__/account-page-redirect.test.tsx +73 -0
  13. package/src/__tests__/account-switcher.test.tsx +85 -0
  14. package/src/__tests__/activity-page.test.tsx +176 -0
  15. package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
  16. package/src/__tests__/admin-api.test.ts +244 -0
  17. package/src/__tests__/admin-gpu-api.test.ts +188 -0
  18. package/src/__tests__/admin-guard.test.tsx +79 -0
  19. package/src/__tests__/admin-marketplace-api.test.ts +179 -0
  20. package/src/__tests__/admin-middleware.test.ts +157 -0
  21. package/src/__tests__/admin-tenant-table.test.tsx +95 -0
  22. package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
  23. package/src/__tests__/api-401-redirect.test.ts +78 -0
  24. package/src/__tests__/api-client.test.ts +316 -0
  25. package/src/__tests__/api-config.test.ts +89 -0
  26. package/src/__tests__/api-control-instance.test.ts +69 -0
  27. package/src/__tests__/api-fleet-resources.test.ts +52 -0
  28. package/src/__tests__/api-fleet-trpc.test.ts +252 -0
  29. package/src/__tests__/api-get-instance-config.test.ts +41 -0
  30. package/src/__tests__/api-null-guards.test.ts +244 -0
  31. package/src/__tests__/api-rename-instance.test.ts +60 -0
  32. package/src/__tests__/api-update-instance-config.test.ts +60 -0
  33. package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
  34. package/src/__tests__/auth-client.test.ts +87 -0
  35. package/src/__tests__/auth-password-reset.test.tsx +435 -0
  36. package/src/__tests__/auth-redirect.test.tsx +60 -0
  37. package/src/__tests__/auth.test.tsx +269 -0
  38. package/src/__tests__/auto-topup-card.test.tsx +257 -0
  39. package/src/__tests__/backups-tab.test.tsx +221 -0
  40. package/src/__tests__/billing-byok-callout.test.tsx +76 -0
  41. package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
  42. package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
  43. package/src/__tests__/billing.test.tsx +509 -0
  44. package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
  45. package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
  46. package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
  47. package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
  48. package/src/__tests__/bot-settings-data-control.test.ts +49 -0
  49. package/src/__tests__/bot-settings-restart.test.tsx +149 -0
  50. package/src/__tests__/bot-settings.test.tsx +678 -0
  51. package/src/__tests__/brand.test.ts +335 -0
  52. package/src/__tests__/buy-credits-panel.test.tsx +249 -0
  53. package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
  54. package/src/__tests__/capability-conflicts.test.ts +88 -0
  55. package/src/__tests__/capability-resolver.test.tsx +173 -0
  56. package/src/__tests__/changeset-detail.test.tsx +156 -0
  57. package/src/__tests__/channel-setup-logger.test.ts +22 -0
  58. package/src/__tests__/channel-setup-toast.test.tsx +60 -0
  59. package/src/__tests__/channel-wizard.test.tsx +505 -0
  60. package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
  61. package/src/__tests__/chat/chat-input.test.tsx +78 -0
  62. package/src/__tests__/chat/chat-message.test.tsx +45 -0
  63. package/src/__tests__/chat/chat-panel.test.tsx +111 -0
  64. package/src/__tests__/chat/chat-widget.test.tsx +82 -0
  65. package/src/__tests__/chat-store.test.ts +87 -0
  66. package/src/__tests__/command-center.test.tsx +246 -0
  67. package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
  68. package/src/__tests__/coupon-input.test.tsx +119 -0
  69. package/src/__tests__/create-instance.test.tsx +96 -0
  70. package/src/__tests__/create-org-wizard.test.tsx +200 -0
  71. package/src/__tests__/credit-balance.test.tsx +103 -0
  72. package/src/__tests__/credits.test.tsx +376 -0
  73. package/src/__tests__/csrf-middleware.test.ts +198 -0
  74. package/src/__tests__/degraded-state-banner.test.tsx +130 -0
  75. package/src/__tests__/dividend-calculator.test.tsx +20 -0
  76. package/src/__tests__/dividend-stats.test.tsx +64 -0
  77. package/src/__tests__/dividend.test.tsx +169 -0
  78. package/src/__tests__/dockerfile.test.ts +110 -0
  79. package/src/__tests__/email-verification-banner.test.tsx +64 -0
  80. package/src/__tests__/env-example.test.ts +25 -0
  81. package/src/__tests__/error-boundaries.test.tsx +64 -0
  82. package/src/__tests__/fetch-pricing.test.ts +121 -0
  83. package/src/__tests__/field-oauth.test.tsx +302 -0
  84. package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
  85. package/src/__tests__/fixtures/mock-manifests.ts +24 -0
  86. package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
  87. package/src/__tests__/fleet-health-update.test.tsx +83 -0
  88. package/src/__tests__/format-credit.test.ts +58 -0
  89. package/src/__tests__/gpu-dashboard.test.tsx +236 -0
  90. package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
  91. package/src/__tests__/instance-detail.test.tsx +571 -0
  92. package/src/__tests__/instance-list.test.tsx +230 -0
  93. package/src/__tests__/landing-hero.test.tsx +27 -0
  94. package/src/__tests__/landing-nav.test.tsx +24 -0
  95. package/src/__tests__/layout-snapshots.test.tsx +167 -0
  96. package/src/__tests__/logger.test.ts +54 -0
  97. package/src/__tests__/login-page-redirect.test.tsx +142 -0
  98. package/src/__tests__/manifest-validation.test.ts +126 -0
  99. package/src/__tests__/marketplace-admin.test.tsx +151 -0
  100. package/src/__tests__/marketplace.test.tsx +609 -0
  101. package/src/__tests__/merge-api-rates.test.ts +70 -0
  102. package/src/__tests__/middleware.test.ts +690 -0
  103. package/src/__tests__/network-page.test.tsx +100 -0
  104. package/src/__tests__/next-config-headers.test.ts +28 -0
  105. package/src/__tests__/not-found.test.tsx +26 -0
  106. package/src/__tests__/notifications.test.tsx +128 -0
  107. package/src/__tests__/oauth-buttons.test.tsx +101 -0
  108. package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
  109. package/src/__tests__/observability.test.tsx +541 -0
  110. package/src/__tests__/onboarding-data.test.ts +363 -0
  111. package/src/__tests__/onboarding-page.test.tsx +113 -0
  112. package/src/__tests__/onboarding-store.test.ts +121 -0
  113. package/src/__tests__/org-billing-api.test.tsx +70 -0
  114. package/src/__tests__/org-billing-null-guards.test.ts +64 -0
  115. package/src/__tests__/org-billing-page.test.tsx +124 -0
  116. package/src/__tests__/plugin-definition.test.ts +43 -0
  117. package/src/__tests__/plugin-install-flow.test.tsx +535 -0
  118. package/src/__tests__/plugin-registry.test.tsx +475 -0
  119. package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
  120. package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
  121. package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
  122. package/src/__tests__/plugin-tool-sync.test.ts +59 -0
  123. package/src/__tests__/portfolio-chart.test.tsx +24 -0
  124. package/src/__tests__/pricing.test.tsx +107 -0
  125. package/src/__tests__/promotion-form.test.tsx +180 -0
  126. package/src/__tests__/promotions-list.test.tsx +194 -0
  127. package/src/__tests__/provider-key-api.test.ts +134 -0
  128. package/src/__tests__/resend-verification-button.test.tsx +104 -0
  129. package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
  130. package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
  131. package/src/__tests__/settings.test.tsx +937 -0
  132. package/src/__tests__/setup-checklist.test.tsx +274 -0
  133. package/src/__tests__/setup.ts +82 -0
  134. package/src/__tests__/smoke.test.tsx +10 -0
  135. package/src/__tests__/snapshot-api.test.ts +104 -0
  136. package/src/__tests__/status-api.test.ts +46 -0
  137. package/src/__tests__/status-badge.test.tsx +33 -0
  138. package/src/__tests__/status-colors.test.ts +83 -0
  139. package/src/__tests__/status-page.test.tsx +86 -0
  140. package/src/__tests__/step-superpowers.test.tsx +218 -0
  141. package/src/__tests__/story-sections.test.tsx +24 -0
  142. package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
  143. package/src/__tests__/superpower-content.test.tsx +44 -0
  144. package/src/__tests__/suspension-banner.test.tsx +140 -0
  145. package/src/__tests__/tenant-context.test.tsx +146 -0
  146. package/src/__tests__/tenant-keys-api.test.ts +114 -0
  147. package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
  148. package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
  149. package/src/__tests__/terminal-sequence.test.tsx +28 -0
  150. package/src/__tests__/transaction-history.test.tsx +325 -0
  151. package/src/__tests__/trpc-types.test.ts +102 -0
  152. package/src/__tests__/use-capability-meta.test.ts +161 -0
  153. package/src/__tests__/use-chat.test.ts +616 -0
  154. package/src/__tests__/use-has-org.test.ts +44 -0
  155. package/src/__tests__/use-image-status.test.ts +77 -0
  156. package/src/__tests__/use-pagination-params.test.ts +88 -0
  157. package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
  158. package/src/__tests__/use-webmcp.test.ts +119 -0
  159. package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
  160. package/src/__tests__/validate-redirect-url.test.ts +61 -0
  161. package/src/__tests__/verify-page.test.tsx +140 -0
  162. package/src/__tests__/verify-redirect.test.tsx +41 -0
  163. package/src/__tests__/verify-result-banner.test.tsx +66 -0
  164. package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
  165. package/src/__tests__/webmcp-hook.test.tsx +72 -0
  166. package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
  167. package/src/__tests__/webmcp-register.test.ts +103 -0
  168. package/src/__tests__/webmcp-set-provider.test.ts +47 -0
  169. package/src/__tests__/webmcp-tools.test.ts +348 -0
  170. package/src/app/(auth)/error.tsx +72 -0
  171. package/src/app/(auth)/forgot-password/page.tsx +137 -0
  172. package/src/app/(auth)/layout.tsx +14 -0
  173. package/src/app/(auth)/loading.tsx +26 -0
  174. package/src/app/(auth)/login/page.tsx +188 -0
  175. package/src/app/(auth)/reset-password/page.tsx +169 -0
  176. package/src/app/(auth)/signup/page.tsx +309 -0
  177. package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
  178. package/src/app/(dashboard)/billing/error.tsx +72 -0
  179. package/src/app/(dashboard)/billing/layout.tsx +73 -0
  180. package/src/app/(dashboard)/billing/loading.tsx +41 -0
  181. package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
  182. package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
  183. package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
  184. package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
  185. package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
  186. package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
  187. package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
  188. package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
  189. package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
  190. package/src/app/(dashboard)/changesets/error.tsx +72 -0
  191. package/src/app/(dashboard)/changesets/page.tsx +10 -0
  192. package/src/app/(dashboard)/chat/page.tsx +74 -0
  193. package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
  194. package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
  195. package/src/app/(dashboard)/dashboard/page.tsx +13 -0
  196. package/src/app/(dashboard)/error.tsx +72 -0
  197. package/src/app/(dashboard)/layout.tsx +113 -0
  198. package/src/app/(dashboard)/loading.tsx +27 -0
  199. package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
  200. package/src/app/(dashboard)/marketplace/error.tsx +72 -0
  201. package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
  202. package/src/app/(dashboard)/marketplace/page.tsx +268 -0
  203. package/src/app/(dashboard)/not-found.tsx +46 -0
  204. package/src/app/(dashboard)/onboarding/page.tsx +267 -0
  205. package/src/app/(dashboard)/settings/account/page.tsx +132 -0
  206. package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
  207. package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
  208. package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
  209. package/src/app/(dashboard)/settings/error.tsx +72 -0
  210. package/src/app/(dashboard)/settings/layout.tsx +114 -0
  211. package/src/app/(dashboard)/settings/loading.tsx +31 -0
  212. package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
  213. package/src/app/(dashboard)/settings/org/page.tsx +617 -0
  214. package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
  215. package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
  216. package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
  217. package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
  218. package/src/app/admin/accounting/loading.tsx +32 -0
  219. package/src/app/admin/accounting/page.tsx +5 -0
  220. package/src/app/admin/affiliates/loading.tsx +32 -0
  221. package/src/app/admin/affiliates/page.tsx +5 -0
  222. package/src/app/admin/audit/loading.tsx +32 -0
  223. package/src/app/admin/audit/page.tsx +5 -0
  224. package/src/app/admin/billing-health/loading.tsx +17 -0
  225. package/src/app/admin/billing-health/page.tsx +10 -0
  226. package/src/app/admin/compliance/page.tsx +5 -0
  227. package/src/app/admin/error.tsx +72 -0
  228. package/src/app/admin/gpu/loading.tsx +38 -0
  229. package/src/app/admin/gpu/page.tsx +5 -0
  230. package/src/app/admin/incidents/page.tsx +10 -0
  231. package/src/app/admin/inference/loading.tsx +32 -0
  232. package/src/app/admin/inference/page.tsx +5 -0
  233. package/src/app/admin/layout.tsx +44 -0
  234. package/src/app/admin/loading.tsx +32 -0
  235. package/src/app/admin/marketplace/loading.tsx +32 -0
  236. package/src/app/admin/marketplace/page.tsx +5 -0
  237. package/src/app/admin/migrations/loading.tsx +22 -0
  238. package/src/app/admin/migrations/page.tsx +5 -0
  239. package/src/app/admin/onboarding/loading.tsx +18 -0
  240. package/src/app/admin/onboarding/page.tsx +5 -0
  241. package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
  242. package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
  243. package/src/app/admin/promotions/[id]/loading.tsx +15 -0
  244. package/src/app/admin/promotions/[id]/page.tsx +311 -0
  245. package/src/app/admin/promotions/loading.tsx +21 -0
  246. package/src/app/admin/promotions/new/loading.tsx +16 -0
  247. package/src/app/admin/promotions/new/page.tsx +12 -0
  248. package/src/app/admin/promotions/page.tsx +266 -0
  249. package/src/app/admin/rate-overrides/loading.tsx +17 -0
  250. package/src/app/admin/rate-overrides/page.tsx +290 -0
  251. package/src/app/admin/roles/loading.tsx +27 -0
  252. package/src/app/admin/roles/page.tsx +5 -0
  253. package/src/app/admin/tenants/loading.tsx +32 -0
  254. package/src/app/admin/tenants/page.tsx +5 -0
  255. package/src/app/apple-icon.tsx +32 -0
  256. package/src/app/auth/callback/[provider]/page.tsx +104 -0
  257. package/src/app/auth/verify/page.tsx +224 -0
  258. package/src/app/channels/error.tsx +72 -0
  259. package/src/app/channels/loading.tsx +29 -0
  260. package/src/app/channels/page.tsx +262 -0
  261. package/src/app/channels/setup/[plugin]/page.tsx +136 -0
  262. package/src/app/error.tsx +72 -0
  263. package/src/app/favicon.ico +0 -0
  264. package/src/app/fleet/error.tsx +72 -0
  265. package/src/app/fleet/health/page.tsx +9 -0
  266. package/src/app/fleet/layout.tsx +14 -0
  267. package/src/app/fleet/loading.tsx +33 -0
  268. package/src/app/fleet/page.tsx +5 -0
  269. package/src/app/global-error.tsx +96 -0
  270. package/src/app/globals.css +251 -0
  271. package/src/app/icon.svg +4 -0
  272. package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
  273. package/src/app/instances/[id]/page.tsx +10 -0
  274. package/src/app/instances/error.tsx +72 -0
  275. package/src/app/instances/instance-list-client.tsx +540 -0
  276. package/src/app/instances/loading.tsx +33 -0
  277. package/src/app/instances/new/create-instance-client.tsx +377 -0
  278. package/src/app/instances/new/page.tsx +9 -0
  279. package/src/app/instances/page.tsx +9 -0
  280. package/src/app/layout.tsx +83 -0
  281. package/src/app/not-found.tsx +38 -0
  282. package/src/app/og/route.tsx +50 -0
  283. package/src/app/page.tsx +39 -0
  284. package/src/app/plugins/error.tsx +72 -0
  285. package/src/app/plugins/layout.tsx +14 -0
  286. package/src/app/plugins/loading.tsx +30 -0
  287. package/src/app/plugins/page.tsx +555 -0
  288. package/src/app/pricing/error.tsx +72 -0
  289. package/src/app/pricing/loading.tsx +25 -0
  290. package/src/app/pricing/page.tsx +20 -0
  291. package/src/app/privacy/page.tsx +406 -0
  292. package/src/app/robots.ts +9 -0
  293. package/src/app/sitemap.ts +11 -0
  294. package/src/app/status/error.tsx +72 -0
  295. package/src/app/status/loading.tsx +21 -0
  296. package/src/app/status/page.tsx +20 -0
  297. package/src/app/terms/page.tsx +414 -0
  298. package/src/components/account-switcher.tsx +82 -0
  299. package/src/components/admin/accounting-dashboard.tsx +190 -0
  300. package/src/components/admin/admin-guard.tsx +36 -0
  301. package/src/components/admin/admin-nav.tsx +71 -0
  302. package/src/components/admin/affiliate-dashboard.tsx +564 -0
  303. package/src/components/admin/audit-log-table.tsx +336 -0
  304. package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
  305. package/src/components/admin/billing-health-dashboard.tsx +416 -0
  306. package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
  307. package/src/components/admin/bulk-actions-bar.tsx +80 -0
  308. package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
  309. package/src/components/admin/bulk-export-dialog.tsx +189 -0
  310. package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
  311. package/src/components/admin/bulk-grant-dialog.tsx +147 -0
  312. package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
  313. package/src/components/admin/bulk-preview-dialog.tsx +106 -0
  314. package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
  315. package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
  316. package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
  317. package/src/components/admin/bulk-select-all-banner.tsx +44 -0
  318. package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
  319. package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
  320. package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
  321. package/src/components/admin/bulk-undo-toast.tsx +121 -0
  322. package/src/components/admin/compliance-dashboard.tsx +1341 -0
  323. package/src/components/admin/gpu-dashboard.tsx +552 -0
  324. package/src/components/admin/grant-credits-dialog.tsx +121 -0
  325. package/src/components/admin/incident-dashboard.test.tsx +44 -0
  326. package/src/components/admin/incident-dashboard.tsx +717 -0
  327. package/src/components/admin/inference-dashboard.tsx +415 -0
  328. package/src/components/admin/marketplace-admin.tsx +765 -0
  329. package/src/components/admin/migrations-dashboard.tsx +404 -0
  330. package/src/components/admin/onboarding-dashboard.tsx +422 -0
  331. package/src/components/admin/promotions/promotion-form.tsx +440 -0
  332. package/src/components/admin/roles-dashboard.tsx +278 -0
  333. package/src/components/admin/suspend-dialog.tsx +98 -0
  334. package/src/components/admin/tenant-notes-panel.tsx +134 -0
  335. package/src/components/admin/tenant-row-actions.tsx +78 -0
  336. package/src/components/admin/tenant-table.tsx +339 -0
  337. package/src/components/auth/auth-error.tsx +22 -0
  338. package/src/components/auth/auth-redirect.tsx +18 -0
  339. package/src/components/auth/auth-shell.tsx +25 -0
  340. package/src/components/auth/email-verification-banner.tsx +25 -0
  341. package/src/components/auth/email-verification-result-banner.tsx +70 -0
  342. package/src/components/auth/resend-verification-button.tsx +94 -0
  343. package/src/components/auth/wopr-wordmark.tsx +19 -0
  344. package/src/components/billing/add-payment-method-dialog.tsx +267 -0
  345. package/src/components/billing/affiliate-dashboard.tsx +300 -0
  346. package/src/components/billing/auto-topup-card.tsx +432 -0
  347. package/src/components/billing/buy-credits-panel.tsx +180 -0
  348. package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
  349. package/src/components/billing/byok-callout.tsx +87 -0
  350. package/src/components/billing/coupon-input.tsx +86 -0
  351. package/src/components/billing/credit-balance.tsx +79 -0
  352. package/src/components/billing/degraded-state-banner.tsx +95 -0
  353. package/src/components/billing/dividend-banner.tsx +97 -0
  354. package/src/components/billing/dividend-eligibility.tsx +86 -0
  355. package/src/components/billing/dividend-pool-stats.tsx +86 -0
  356. package/src/components/billing/first-dividend-dialog.tsx +109 -0
  357. package/src/components/billing/low-balance-banner.tsx +50 -0
  358. package/src/components/billing/org-billing-page.tsx +360 -0
  359. package/src/components/billing/suspension-banner.tsx +53 -0
  360. package/src/components/billing/transaction-history.tsx +239 -0
  361. package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
  362. package/src/components/bot-settings/backups-tab.tsx +377 -0
  363. package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
  364. package/src/components/bot-settings/resources-tab.tsx +203 -0
  365. package/src/components/bot-settings/storage-tab.tsx +248 -0
  366. package/src/components/bot-settings/vps-info-panel.tsx +132 -0
  367. package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
  368. package/src/components/capability/CapabilityResolver.tsx +113 -0
  369. package/src/components/channel-wizard/field-interactive.tsx +48 -0
  370. package/src/components/channel-wizard/field-oauth.tsx +181 -0
  371. package/src/components/channel-wizard/field-paste.tsx +47 -0
  372. package/src/components/channel-wizard/field-qr.tsx +302 -0
  373. package/src/components/channel-wizard/index.ts +6 -0
  374. package/src/components/channel-wizard/step-renderer.tsx +103 -0
  375. package/src/components/channel-wizard/wizard.tsx +200 -0
  376. package/src/components/chat/ambient-dot.tsx +32 -0
  377. package/src/components/chat/chat-input.tsx +56 -0
  378. package/src/components/chat/chat-message.tsx +36 -0
  379. package/src/components/chat/chat-panel.tsx +138 -0
  380. package/src/components/chat/chat-widget.tsx +41 -0
  381. package/src/components/chat/index.ts +5 -0
  382. package/src/components/dashboard/command-center.tsx +614 -0
  383. package/src/components/instances/friends-tab.test.tsx +265 -0
  384. package/src/components/instances/friends-tab.tsx +721 -0
  385. package/src/components/landing/hero.tsx +53 -0
  386. package/src/components/landing/landing-nav.tsx +21 -0
  387. package/src/components/landing/landing-page.tsx +71 -0
  388. package/src/components/landing/portfolio-chart.tsx +349 -0
  389. package/src/components/landing/story-sections.tsx +50 -0
  390. package/src/components/landing/terminal-lines.ts +99 -0
  391. package/src/components/landing/terminal-sequence.tsx +453 -0
  392. package/src/components/landing/typing-effect.tsx +43 -0
  393. package/src/components/marketplace/category-filter.tsx +61 -0
  394. package/src/components/marketplace/empty-state.tsx +61 -0
  395. package/src/components/marketplace/featured-heroes.tsx +84 -0
  396. package/src/components/marketplace/first-visit-hero.tsx +110 -0
  397. package/src/components/marketplace/index.ts +9 -0
  398. package/src/components/marketplace/install-wizard.tsx +782 -0
  399. package/src/components/marketplace/marketplace-tabs.tsx +54 -0
  400. package/src/components/marketplace/plugin-card.tsx +129 -0
  401. package/src/components/marketplace/superpower-card.tsx +104 -0
  402. package/src/components/marketplace/superpower-content.tsx +117 -0
  403. package/src/components/marketplace/terminal-search.tsx +67 -0
  404. package/src/components/oauth-buttons.tsx +75 -0
  405. package/src/components/observability/fleet-health.tsx +370 -0
  406. package/src/components/observability/health-overview.tsx +246 -0
  407. package/src/components/observability/logs-viewer.tsx +215 -0
  408. package/src/components/observability/metrics-dashboard.tsx +288 -0
  409. package/src/components/onboarding/fallback-setup.tsx +137 -0
  410. package/src/components/onboarding/index.ts +3 -0
  411. package/src/components/onboarding/setup-checklist.tsx +333 -0
  412. package/src/components/onboarding/step-superpowers.tsx +122 -0
  413. package/src/components/plugin-setup/index.ts +1 -0
  414. package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
  415. package/src/components/pricing/dividend-calculator.tsx +47 -0
  416. package/src/components/pricing/dividend-stats.tsx +117 -0
  417. package/src/components/pricing/pricing-page.tsx +229 -0
  418. package/src/components/settings/create-org-wizard.tsx +225 -0
  419. package/src/components/sidebar.tsx +202 -0
  420. package/src/components/status/status-page.tsx +209 -0
  421. package/src/components/status-badge.tsx +28 -0
  422. package/src/components/theme-provider.tsx +8 -0
  423. package/src/components/ui/alert-dialog.tsx +141 -0
  424. package/src/components/ui/badge.tsx +47 -0
  425. package/src/components/ui/banner.tsx +36 -0
  426. package/src/components/ui/button.tsx +64 -0
  427. package/src/components/ui/card.tsx +75 -0
  428. package/src/components/ui/checkbox.tsx +52 -0
  429. package/src/components/ui/collapsible.tsx +31 -0
  430. package/src/components/ui/credit-detailed.tsx +33 -0
  431. package/src/components/ui/dialog.tsx +143 -0
  432. package/src/components/ui/dropdown-menu.tsx +228 -0
  433. package/src/components/ui/form.tsx +151 -0
  434. package/src/components/ui/input.tsx +21 -0
  435. package/src/components/ui/label.tsx +21 -0
  436. package/src/components/ui/popover.tsx +74 -0
  437. package/src/components/ui/progress.tsx +28 -0
  438. package/src/components/ui/radio-group.tsx +45 -0
  439. package/src/components/ui/select.tsx +175 -0
  440. package/src/components/ui/separator.tsx +28 -0
  441. package/src/components/ui/sheet.tsx +125 -0
  442. package/src/components/ui/skeleton.tsx +15 -0
  443. package/src/components/ui/switch.tsx +35 -0
  444. package/src/components/ui/table.tsx +92 -0
  445. package/src/components/ui/tabs.tsx +81 -0
  446. package/src/components/ui/textarea.tsx +18 -0
  447. package/src/components/ui/tooltip.tsx +44 -0
  448. package/src/config/provider-docs.ts +17 -0
  449. package/src/hooks/__tests__/use-async.test.ts +127 -0
  450. package/src/hooks/__tests__/use-count-up.test.ts +129 -0
  451. package/src/hooks/__tests__/use-debounce.test.ts +105 -0
  452. package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
  453. package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
  454. package/src/hooks/__tests__/use-mobile.test.ts +86 -0
  455. package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
  456. package/src/hooks/use-async.ts +54 -0
  457. package/src/hooks/use-capability-meta.ts +99 -0
  458. package/src/hooks/use-count-up.ts +23 -0
  459. package/src/hooks/use-debounce.ts +12 -0
  460. package/src/hooks/use-fleet-sse.ts +47 -0
  461. package/src/hooks/use-has-org.ts +18 -0
  462. package/src/hooks/use-image-status.ts +36 -0
  463. package/src/hooks/use-local-storage.ts +36 -0
  464. package/src/hooks/use-mobile.ts +17 -0
  465. package/src/hooks/use-page-context.ts +24 -0
  466. package/src/hooks/use-pagination-params.ts +30 -0
  467. package/src/hooks/use-plugin-registry.ts +247 -0
  468. package/src/hooks/use-plugin-setup-chat.ts +211 -0
  469. package/src/hooks/use-save-queue.ts +54 -0
  470. package/src/hooks/use-webmcp.ts +40 -0
  471. package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
  472. package/src/lib/__tests__/admin-api.test.ts +487 -0
  473. package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
  474. package/src/lib/__tests__/api-fetch.test.ts +196 -0
  475. package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
  476. package/src/lib/__tests__/org-api.test.ts +281 -0
  477. package/src/lib/__tests__/org-billing-api.test.ts +242 -0
  478. package/src/lib/__tests__/pricing-data.test.ts +32 -0
  479. package/src/lib/__tests__/settings-api.test.ts +272 -0
  480. package/src/lib/admin-affiliate-api.ts +51 -0
  481. package/src/lib/admin-api.ts +325 -0
  482. package/src/lib/admin-compliance-api.ts +127 -0
  483. package/src/lib/admin-gpu-api.ts +82 -0
  484. package/src/lib/admin-incident-api.ts +121 -0
  485. package/src/lib/admin-inference-api.ts +47 -0
  486. package/src/lib/admin-marketplace-api.ts +97 -0
  487. package/src/lib/api-config.test.ts +111 -0
  488. package/src/lib/api-config.ts +65 -0
  489. package/src/lib/api-errors.test.ts +43 -0
  490. package/src/lib/api.ts +2011 -0
  491. package/src/lib/auth-client.ts +11 -0
  492. package/src/lib/bot-settings-data.ts +342 -0
  493. package/src/lib/brand-config.ts +145 -0
  494. package/src/lib/brand.ts +669 -0
  495. package/src/lib/changeset-api.ts +29 -0
  496. package/src/lib/changeset-types.ts +56 -0
  497. package/src/lib/channel-manifests.ts +50 -0
  498. package/src/lib/chat/chat-context.tsx +70 -0
  499. package/src/lib/chat/chat-store.ts +62 -0
  500. package/src/lib/chat/types.ts +35 -0
  501. package/src/lib/chat/use-chat.ts +255 -0
  502. package/src/lib/cost-comparison-data.test.ts +95 -0
  503. package/src/lib/cost-comparison-data.ts +54 -0
  504. package/src/lib/errors.test.ts +64 -0
  505. package/src/lib/errors.ts +52 -0
  506. package/src/lib/fetch-utils.test.ts +57 -0
  507. package/src/lib/fetch-utils.ts +25 -0
  508. package/src/lib/format-credit.test.ts +66 -0
  509. package/src/lib/format-credit.ts +24 -0
  510. package/src/lib/format.test.ts +62 -0
  511. package/src/lib/format.ts +17 -0
  512. package/src/lib/logger.ts +28 -0
  513. package/src/lib/marketplace-data.ts +346 -0
  514. package/src/lib/oauth-errors.ts +19 -0
  515. package/src/lib/onboarding-data.ts +1265 -0
  516. package/src/lib/onboarding-store.ts +233 -0
  517. package/src/lib/org-api.ts +74 -0
  518. package/src/lib/org-billing-api.ts +81 -0
  519. package/src/lib/page-prompts.test.ts +32 -0
  520. package/src/lib/page-prompts.ts +23 -0
  521. package/src/lib/plugin/index.ts +32 -0
  522. package/src/lib/plugin/tool-definitions.ts +306 -0
  523. package/src/lib/pricing-data.ts +115 -0
  524. package/src/lib/promotions-types.ts +58 -0
  525. package/src/lib/settings-api.ts +63 -0
  526. package/src/lib/status-colors.ts +38 -0
  527. package/src/lib/tenant-context.tsx +134 -0
  528. package/src/lib/trpc-types.ts +173 -0
  529. package/src/lib/trpc.tsx +86 -0
  530. package/src/lib/utils.test.ts +55 -0
  531. package/src/lib/utils.ts +18 -0
  532. package/src/lib/validate-redirect-url.ts +39 -0
  533. package/src/lib/webmcp/feature-detect.ts +13 -0
  534. package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
  535. package/src/lib/webmcp/register.ts +44 -0
  536. package/src/lib/webmcp/tools.ts +422 -0
  537. package/src/proxy.ts +258 -0
  538. package/src/types/missing-deps.d.ts +160 -0
  539. package/src/types/motion-dom.d.ts +162 -0
  540. package/src/types/vitest-matchers.d.ts +40 -0
  541. package/src/types/web-mcp.d.ts +22 -0
  542. package/tsconfig.json +34 -0
  543. 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&apos;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&apos;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&apos;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
+ }