@wopr-network/platform-ui-core 1.27.8 → 1.27.9

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 (353) hide show
  1. package/next.config.ts +1 -2
  2. package/package.json +17 -17
  3. package/src/__tests__/account-switcher.test.tsx +21 -20
  4. package/src/__tests__/activity-page.test.tsx +2 -6
  5. package/src/__tests__/add-payment-method-dialog.test.tsx +9 -32
  6. package/src/__tests__/admin-api.test.ts +1 -6
  7. package/src/__tests__/admin-gpu-api.test.ts +1 -3
  8. package/src/__tests__/admin-marketplace-api.test.ts +1 -4
  9. package/src/__tests__/admin-middleware.test.ts +76 -83
  10. package/src/__tests__/affiliate-dashboard.test.tsx +3 -3
  11. package/src/__tests__/api-401-redirect.test.ts +46 -9
  12. package/src/__tests__/api-client.test.ts +3 -5
  13. package/src/__tests__/api-config.test.ts +22 -42
  14. package/src/__tests__/api-fleet-resources.test.ts +1 -2
  15. package/src/__tests__/api-fleet-trpc.test.ts +2 -8
  16. package/src/__tests__/api-null-guards.test.ts +3 -1
  17. package/src/__tests__/audit-log-table-pagination.test.tsx +2 -6
  18. package/src/__tests__/auth-password-reset.test.tsx +7 -21
  19. package/src/__tests__/auth-redirect.test.tsx +8 -2
  20. package/src/__tests__/auth.test.tsx +25 -23
  21. package/src/__tests__/auto-topup-card.test.tsx +4 -12
  22. package/src/__tests__/backups-tab.test.tsx +3 -4
  23. package/src/__tests__/billing-layout-nav-hidden.test.tsx +5 -37
  24. package/src/__tests__/billing-payment-org-invoices.test.tsx +2 -18
  25. package/src/__tests__/billing.test.tsx +8 -39
  26. package/src/__tests__/bot-settings/resources-tab.test.tsx +1 -3
  27. package/src/__tests__/bot-settings/storage-tab.test.tsx +1 -3
  28. package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +1 -3
  29. package/src/__tests__/bot-settings-restart.test.tsx +1 -3
  30. package/src/__tests__/bot-settings.test.tsx +2 -6
  31. package/src/__tests__/brand.test.ts +6 -26
  32. package/src/__tests__/buy-credits-panel.test.tsx +1 -3
  33. package/src/__tests__/buy-crypto-credits-panel.test.tsx +101 -119
  34. package/src/__tests__/capability-conflicts.test.ts +2 -8
  35. package/src/__tests__/capability-resolver.test.tsx +2 -12
  36. package/src/__tests__/channel-wizard.test.tsx +4 -17
  37. package/src/__tests__/chat/chat-panel.test.tsx +1 -4
  38. package/src/__tests__/chat-store.test.ts +5 -15
  39. package/src/__tests__/command-center.test.tsx +10 -12
  40. package/src/__tests__/compliance-retention-edit.test.tsx +3 -6
  41. package/src/__tests__/confirmation-tracker.test.tsx +3 -18
  42. package/src/__tests__/coupon-input.test.tsx +1 -3
  43. package/src/__tests__/create-instance.test.tsx +1 -3
  44. package/src/__tests__/credit-balance.test.tsx +4 -12
  45. package/src/__tests__/credits.test.tsx +32 -85
  46. package/src/__tests__/email-verification-banner.test.tsx +2 -6
  47. package/src/__tests__/error-boundaries.test.tsx +0 -1
  48. package/src/__tests__/fetch-pricing.test.ts +2 -1
  49. package/src/__tests__/field-oauth.test.tsx +2 -6
  50. package/src/__tests__/fixtures/mock-manifests-data.js +1 -3
  51. package/src/__tests__/fixtures/mock-manifests.ts +2 -4
  52. package/src/__tests__/fleet-health-timestamp.test.tsx +1 -8
  53. package/src/__tests__/fleet-health-update.test.tsx +1 -8
  54. package/src/__tests__/gpu-dashboard.test.tsx +2 -6
  55. package/src/__tests__/instance-detail.test.tsx +3 -9
  56. package/src/__tests__/instance-list.test.tsx +1 -5
  57. package/src/__tests__/layout-snapshots.test.tsx +64 -11
  58. package/src/__tests__/marketplace-admin.test.tsx +2 -6
  59. package/src/__tests__/marketplace.test.tsx +11 -35
  60. package/src/__tests__/merge-api-rates.test.ts +1 -6
  61. package/src/__tests__/middleware.test.ts +32 -219
  62. package/src/__tests__/next-config-headers.test.ts +1 -3
  63. package/src/__tests__/notifications.test.tsx +4 -11
  64. package/src/__tests__/oauth-buttons.test.tsx +36 -59
  65. package/src/__tests__/oauth-error-mapping.test.tsx +2 -6
  66. package/src/__tests__/observability.test.tsx +23 -36
  67. package/src/__tests__/onboarding-page.test.tsx +4 -6
  68. package/src/__tests__/org-billing-api.test.tsx +1 -6
  69. package/src/__tests__/plugin-install-flow.test.tsx +28 -58
  70. package/src/__tests__/plugin-registry.test.tsx +3 -11
  71. package/src/__tests__/plugin-tool-sync.test.ts +1 -3
  72. package/src/__tests__/plugins-catalog-error.test.tsx +2 -6
  73. package/src/__tests__/plugins-toggle-race.test.tsx +3 -5
  74. package/src/__tests__/portfolio-chart.test.tsx +2 -6
  75. package/src/__tests__/promotion-form.test.tsx +2 -6
  76. package/src/__tests__/promotions-list.test.tsx +1 -3
  77. package/src/__tests__/provider-key-api.test.ts +2 -1
  78. package/src/__tests__/resend-verification-button.test.tsx +8 -24
  79. package/src/__tests__/secrets-audit-pagination.test.tsx +1 -3
  80. package/src/__tests__/settings.test.tsx +11 -21
  81. package/src/__tests__/setup-checklist.test.tsx +3 -9
  82. package/src/__tests__/setup.ts +25 -6
  83. package/src/__tests__/snapshot-api.test.ts +2 -1
  84. package/src/__tests__/step-superpowers.test.tsx +1 -3
  85. package/src/__tests__/tenant-context.test.tsx +1 -6
  86. package/src/__tests__/tenant-keys-api.test.ts +3 -4
  87. package/src/__tests__/tenant-table-pagination.test.tsx +2 -6
  88. package/src/__tests__/terminal-log-cleanup.test.tsx +0 -1
  89. package/src/__tests__/transaction-history.test.tsx +190 -238
  90. package/src/__tests__/trpc-types.test.ts +2 -6
  91. package/src/__tests__/use-chat.test.ts +1 -3
  92. package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +1 -4
  93. package/src/__tests__/use-sidecar-bridge.test.tsx +105 -0
  94. package/src/__tests__/use-webmcp.test.ts +1 -3
  95. package/src/__tests__/validate-elevenlabs-key.test.ts +2 -1
  96. package/src/__tests__/verify-page.test.tsx +4 -13
  97. package/src/__tests__/verify-redirect.test.tsx +2 -6
  98. package/src/app/(auth)/error.tsx +1 -7
  99. package/src/app/(auth)/forgot-password/page.tsx +4 -18
  100. package/src/app/(auth)/login/page.tsx +5 -22
  101. package/src/app/(auth)/reset-password/page.tsx +2 -12
  102. package/src/app/(auth)/signup/page.tsx +10 -44
  103. package/src/app/(auth)/verify/page.tsx +47 -0
  104. package/src/app/(dashboard)/billing/credits/page.tsx +14 -67
  105. package/src/app/(dashboard)/billing/error.tsx +2 -10
  106. package/src/app/(dashboard)/billing/layout.tsx +12 -62
  107. package/src/app/(dashboard)/billing/payment/page.tsx +17 -68
  108. package/src/app/(dashboard)/billing/plans/page.tsx +3 -9
  109. package/src/app/(dashboard)/billing/usage/hosted/page.tsx +8 -25
  110. package/src/app/(dashboard)/billing/usage/page.tsx +63 -103
  111. package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +9 -27
  112. package/src/app/(dashboard)/changesets/[id]/error.tsx +2 -6
  113. package/src/app/(dashboard)/changesets/error.tsx +1 -7
  114. package/src/app/(dashboard)/chat/page.tsx +2 -6
  115. package/src/app/(dashboard)/dashboard/network/page.tsx +5 -19
  116. package/src/app/(dashboard)/error.tsx +1 -7
  117. package/src/app/(dashboard)/layout.tsx +15 -36
  118. package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +14 -51
  119. package/src/app/(dashboard)/marketplace/error.tsx +1 -7
  120. package/src/app/(dashboard)/marketplace/page.tsx +6 -27
  121. package/src/app/(dashboard)/not-found.tsx +2 -5
  122. package/src/app/(dashboard)/onboarding/page.tsx +5 -22
  123. package/src/app/(dashboard)/settings/account/page.tsx +1 -6
  124. package/src/app/(dashboard)/settings/activity/page.tsx +8 -34
  125. package/src/app/(dashboard)/settings/api-keys/page.tsx +15 -60
  126. package/src/app/(dashboard)/settings/brain/page.tsx +9 -31
  127. package/src/app/(dashboard)/settings/error.tsx +2 -10
  128. package/src/app/(dashboard)/settings/notifications/page.tsx +2 -6
  129. package/src/app/(dashboard)/settings/org/page.tsx +13 -56
  130. package/src/app/(dashboard)/settings/page.tsx +1 -0
  131. package/src/app/(dashboard)/settings/profile/page.tsx +126 -73
  132. package/src/app/(dashboard)/settings/providers/page.tsx +21 -78
  133. package/src/app/(dashboard)/settings/secrets/page.tsx +13 -58
  134. package/src/app/(dashboard)/settings/security/page.tsx +31 -111
  135. package/src/app/admin/email-templates/email-templates-client.tsx +15 -58
  136. package/src/app/admin/error.tsx +1 -7
  137. package/src/app/admin/fleet-updates/error.tsx +1 -7
  138. package/src/app/admin/fleet-updates/fleet-updates-client.tsx +10 -50
  139. package/src/app/admin/layout.tsx +4 -0
  140. package/src/app/admin/payment-methods/page.tsx +9 -38
  141. package/src/app/admin/products/error.tsx +2 -7
  142. package/src/app/admin/products/page.tsx +1 -4
  143. package/src/app/admin/promotions/[id]/page.tsx +9 -38
  144. package/src/app/admin/promotions/page.tsx +9 -36
  145. package/src/app/admin/rate-overrides/page.tsx +9 -45
  146. package/src/app/auth/callback/[provider]/page.tsx +1 -8
  147. package/src/app/auth/verify/page.tsx +9 -36
  148. package/src/app/channels/error.tsx +2 -10
  149. package/src/app/channels/layout.tsx +9 -0
  150. package/src/app/channels/page.tsx +8 -20
  151. package/src/app/channels/setup/[plugin]/page.tsx +3 -5
  152. package/src/app/error.tsx +1 -7
  153. package/src/app/fleet/error.tsx +1 -7
  154. package/src/app/fleet/layout.tsx +5 -0
  155. package/src/app/fleet/settings/page.tsx +1 -3
  156. package/src/app/global-error.tsx +2 -10
  157. package/src/app/globals.css +1 -4
  158. package/src/app/instances/[id]/instance-detail-client.tsx +51 -125
  159. package/src/app/instances/error.tsx +2 -10
  160. package/src/app/instances/instance-list-client.tsx +20 -69
  161. package/src/app/instances/layout.tsx +9 -0
  162. package/src/app/instances/new/create-instance-client.tsx +10 -31
  163. package/src/app/layout.tsx +2 -10
  164. package/src/app/not-found.tsx +1 -3
  165. package/src/app/page.tsx +1 -2
  166. package/src/app/plugins/error.tsx +2 -10
  167. package/src/app/plugins/layout.tsx +5 -0
  168. package/src/app/plugins/page.tsx +16 -48
  169. package/src/app/pricing/error.tsx +1 -7
  170. package/src/app/privacy/page.tsx +93 -150
  171. package/src/app/status/error.tsx +1 -7
  172. package/src/app/terms/page.tsx +89 -144
  173. package/src/components/account-switcher.tsx +25 -52
  174. package/src/components/admin/accounting-dashboard.tsx +1 -3
  175. package/src/components/admin/admin-guard.tsx +1 -3
  176. package/src/components/admin/admin-nav.tsx +1 -3
  177. package/src/components/admin/affiliate-dashboard.tsx +25 -94
  178. package/src/components/admin/audit-log-table.tsx +13 -49
  179. package/src/components/admin/billing-health-dashboard.tsx +7 -25
  180. package/src/components/admin/bulk-actions-bar.test.tsx +1 -7
  181. package/src/components/admin/bulk-actions-bar.tsx +1 -3
  182. package/src/components/admin/bulk-export-dialog.test.tsx +1 -7
  183. package/src/components/admin/bulk-export-dialog.tsx +6 -32
  184. package/src/components/admin/bulk-grant-dialog.test.tsx +2 -6
  185. package/src/components/admin/bulk-grant-dialog.tsx +4 -15
  186. package/src/components/admin/bulk-preview-dialog.tsx +3 -12
  187. package/src/components/admin/bulk-reactivate-dialog.tsx +1 -7
  188. package/src/components/admin/bulk-select-all-banner.tsx +1 -6
  189. package/src/components/admin/bulk-suspend-dialog.tsx +5 -12
  190. package/src/components/admin/bulk-undo-toast.tsx +1 -2
  191. package/src/components/admin/compliance-dashboard.tsx +31 -101
  192. package/src/components/admin/gpu-dashboard.tsx +21 -70
  193. package/src/components/admin/grant-credits-dialog.tsx +4 -17
  194. package/src/components/admin/incident-dashboard.tsx +10 -25
  195. package/src/components/admin/inference-dashboard.tsx +14 -54
  196. package/src/components/admin/marketplace-admin.tsx +18 -60
  197. package/src/components/admin/migrations-dashboard.tsx +9 -42
  198. package/src/components/admin/onboarding-dashboard.tsx +14 -64
  199. package/src/components/admin/pool-config-dashboard.tsx +4 -10
  200. package/src/components/admin/products/fleet-form.tsx +2 -11
  201. package/src/components/admin/products/nav-editor.tsx +3 -10
  202. package/src/components/admin/promotions/promotion-form.tsx +9 -42
  203. package/src/components/admin/roles-dashboard.tsx +7 -34
  204. package/src/components/admin/suspend-dialog.tsx +4 -11
  205. package/src/components/admin/tenant-notes-panel.tsx +1 -3
  206. package/src/components/admin/tenant-row-actions.tsx +4 -20
  207. package/src/components/admin/tenant-table.tsx +12 -49
  208. package/src/components/auth/auth-redirect.tsx +11 -3
  209. package/src/components/auth/email-verification-result-banner.tsx +1 -3
  210. package/src/components/auth/resend-verification-button.tsx +2 -10
  211. package/src/components/auth/wopr-wordmark.tsx +1 -3
  212. package/src/components/billing/add-payment-method-dialog.tsx +1 -2
  213. package/src/components/billing/affiliate-dashboard.tsx +4 -16
  214. package/src/components/billing/amount-selector.tsx +1 -3
  215. package/src/components/billing/auto-topup-card.tsx +2 -11
  216. package/src/components/billing/buy-credits-panel.tsx +4 -14
  217. package/src/components/billing/byok-callout.tsx +6 -8
  218. package/src/components/billing/confirmation-tracker.tsx +4 -14
  219. package/src/components/billing/credit-balance-badge.tsx +22 -0
  220. package/src/components/billing/credit-balance.tsx +3 -9
  221. package/src/components/billing/crypto-checkout.tsx +5 -24
  222. package/src/components/billing/degraded-state-banner.tsx +1 -3
  223. package/src/components/billing/deposit-view.tsx +301 -41
  224. package/src/components/billing/dividend-banner.tsx +1 -3
  225. package/src/components/billing/dividend-eligibility.tsx +3 -12
  226. package/src/components/billing/dividend-pool-stats.tsx +6 -20
  227. package/src/components/billing/first-dividend-dialog.tsx +2 -2
  228. package/src/components/billing/org-billing-page.tsx +8 -31
  229. package/src/components/billing/payment-method-picker.tsx +2 -10
  230. package/src/components/billing/suspension-banner.tsx +2 -7
  231. package/src/components/billing/transaction-history.tsx +10 -58
  232. package/src/components/billing/unified-checkout.tsx +547 -0
  233. package/src/components/bot-settings/backups-tab.tsx +9 -33
  234. package/src/components/bot-settings/bot-settings-client.tsx +32 -134
  235. package/src/components/bot-settings/resources-tab.tsx +2 -9
  236. package/src/components/bot-settings/storage-tab.tsx +19 -48
  237. package/src/components/bot-settings/vps-info-panel.tsx +3 -11
  238. package/src/components/bot-settings/vps-upgrade-card.tsx +3 -4
  239. package/src/components/brand-hydrator.tsx +13 -0
  240. package/src/components/channel-wizard/field-interactive.tsx +1 -3
  241. package/src/components/channel-wizard/field-qr.tsx +10 -39
  242. package/src/components/channel-wizard/step-renderer.tsx +5 -28
  243. package/src/components/channel-wizard/wizard.tsx +6 -31
  244. package/src/components/chat/chat-message.tsx +1 -4
  245. package/src/components/chat/chat-panel.tsx +4 -18
  246. package/src/components/chat/chat-widget.tsx +3 -14
  247. package/src/components/dashboard/command-center.tsx +15 -61
  248. package/src/components/fleet/update-settings-card.tsx +7 -23
  249. package/src/components/instance-update-banner.tsx +130 -0
  250. package/src/components/instances/friends-tab.test.tsx +2 -9
  251. package/src/components/instances/friends-tab.tsx +18 -74
  252. package/src/components/instances/update-available-badge.tsx +2 -11
  253. package/src/components/landing/hero.tsx +3 -9
  254. package/src/components/landing/landing-page.tsx +1 -3
  255. package/src/components/landing/portfolio-chart.tsx +4 -9
  256. package/src/components/landing/story-sections.tsx +1 -3
  257. package/src/components/landing/terminal-sequence.tsx +4 -17
  258. package/src/components/marketplace/empty-state.tsx +2 -6
  259. package/src/components/marketplace/first-visit-hero.tsx +1 -3
  260. package/src/components/marketplace/install-wizard.tsx +20 -77
  261. package/src/components/marketplace/marketplace-tabs.tsx +1 -4
  262. package/src/components/marketplace/plugin-card.tsx +2 -9
  263. package/src/components/marketplace/superpower-content.tsx +1 -3
  264. package/src/components/marketplace/terminal-search.tsx +2 -8
  265. package/src/components/oauth-buttons.tsx +29 -14
  266. package/src/components/observability/fleet-health.tsx +5 -18
  267. package/src/components/observability/health-overview.tsx +7 -20
  268. package/src/components/observability/logs-viewer.tsx +8 -32
  269. package/src/components/observability/metrics-dashboard.tsx +2 -15
  270. package/src/components/onboarding/fallback-setup.tsx +6 -25
  271. package/src/components/onboarding/setup-checklist.tsx +18 -51
  272. package/src/components/onboarding/step-superpowers.tsx +1 -4
  273. package/src/components/plugin-setup/setup-chat-panel.tsx +6 -22
  274. package/src/components/pricing/dividend-calculator.tsx +6 -12
  275. package/src/components/pricing/dividend-stats.tsx +5 -17
  276. package/src/components/pricing/pricing-page.tsx +17 -36
  277. package/src/components/settings/create-org-wizard.tsx +2 -5
  278. package/src/components/sidebar.tsx +7 -42
  279. package/src/components/sidecar-frame.tsx +78 -0
  280. package/src/components/status/status-page.tsx +6 -28
  281. package/src/components/ui/alert-dialog.tsx +8 -25
  282. package/src/components/ui/badge.tsx +2 -8
  283. package/src/components/ui/banner.tsx +1 -6
  284. package/src/components/ui/card.tsx +5 -24
  285. package/src/components/ui/checkbox.tsx +1 -5
  286. package/src/components/ui/collapsible.tsx +3 -8
  287. package/src/components/ui/dialog.tsx +4 -10
  288. package/src/components/ui/dropdown-menu.tsx +9 -18
  289. package/src/components/ui/form.tsx +2 -16
  290. package/src/components/ui/popover.tsx +3 -23
  291. package/src/components/ui/progress.tsx +1 -5
  292. package/src/components/ui/radio-group.tsx +3 -15
  293. package/src/components/ui/select.tsx +4 -17
  294. package/src/components/ui/sheet.tsx +5 -19
  295. package/src/components/ui/skeleton.tsx +1 -7
  296. package/src/components/ui/table.tsx +5 -22
  297. package/src/components/ui/tabs.tsx +3 -13
  298. package/src/components/ui/tooltip.tsx +1 -1
  299. package/src/components/unified-sidebar.tsx +493 -0
  300. package/src/hooks/__tests__/use-fleet-sse.test.ts +1 -4
  301. package/src/hooks/__tests__/use-save-queue.test.ts +2 -8
  302. package/src/hooks/use-credit-balance.ts +27 -0
  303. package/src/hooks/use-my-org-role.ts +1 -3
  304. package/src/hooks/use-plugin-registry.ts +8 -14
  305. package/src/hooks/use-plugin-setup-chat.ts +2 -5
  306. package/src/hooks/use-sidecar-bridge.tsx +148 -0
  307. package/src/hooks/use-webmcp.ts +1 -4
  308. package/src/lib/__tests__/admin-api.test.ts +1 -3
  309. package/src/lib/__tests__/api-bot-crud.test.ts +8 -18
  310. package/src/lib/__tests__/api-fetch.test.ts +4 -16
  311. package/src/lib/__tests__/org-billing-api.test.ts +1 -3
  312. package/src/lib/__tests__/pricing-data.test.ts +0 -8
  313. package/src/lib/__tests__/settings-api.test.ts +1 -3
  314. package/src/lib/admin-affiliate-api.ts +2 -7
  315. package/src/lib/admin-api.ts +6 -26
  316. package/src/lib/admin-incident-api.ts +11 -19
  317. package/src/lib/admin-marketplace-api.ts +1 -5
  318. package/src/lib/api-config.test.ts +5 -50
  319. package/src/lib/api.ts +143 -122
  320. package/src/lib/auth-client.ts +1 -2
  321. package/src/lib/bot-settings-data.ts +11 -36
  322. package/src/lib/brand-config.ts +56 -115
  323. package/src/lib/brand.ts +2 -15
  324. package/src/lib/chat/use-chat.ts +2 -7
  325. package/src/lib/cost-comparison-data.test.ts +1 -3
  326. package/src/lib/cost-comparison-data.ts +1 -4
  327. package/src/lib/errors.ts +1 -4
  328. package/src/lib/fetch-utils.test.ts +26 -9
  329. package/src/lib/fetch-utils.ts +40 -11
  330. package/src/lib/logger.ts +2 -0
  331. package/src/lib/marketplace-data.ts +3 -11
  332. package/src/lib/oauth-errors.ts +2 -4
  333. package/src/lib/onboarding-data.ts +3 -11
  334. package/src/lib/org-api.ts +2 -10
  335. package/src/lib/org-billing-api.ts +5 -19
  336. package/src/lib/plugin/tool-definitions.ts +1 -2
  337. package/src/lib/require-auth.ts +57 -0
  338. package/src/lib/settings-api.ts +1 -4
  339. package/src/lib/sidecar-routes.ts +43 -0
  340. package/src/lib/trpc-server.ts +49 -0
  341. package/src/lib/trpc-types.ts +4 -6
  342. package/src/lib/trpc.tsx +12 -4
  343. package/src/lib/validate-redirect-url.ts +1 -4
  344. package/src/lib/webmcp/marketplace-onboarding-tools.ts +6 -16
  345. package/src/lib/webmcp/register.ts +1 -4
  346. package/src/lib/webmcp/tools.ts +2 -9
  347. package/src/proxy.ts +35 -212
  348. package/src/types/missing-deps.d.ts +2 -8
  349. package/tsconfig.json +1 -8
  350. package/biome.json +0 -52
  351. package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +0 -741
  352. package/src/__tests__/billing-byok-callout.test.tsx +0 -76
  353. package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +0 -112
@@ -1,4 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { trpcVanillaProxy } from "./setup.js";
2
3
 
3
4
  vi.mock("@/lib/api-config", () => ({
4
5
  API_BASE_URL: "https://test-api.local/api",
@@ -6,7 +7,7 @@ vi.mock("@/lib/api-config", () => ({
6
7
  }));
7
8
 
8
9
  vi.mock("@/lib/trpc", () => ({
9
- trpcVanilla: {},
10
+ trpcVanilla: trpcVanillaProxy,
10
11
  }));
11
12
 
12
13
  const mockHandleUnauthorized = vi.fn(() => {
@@ -122,10 +123,7 @@ describe("apiFetch (via getProfile)", () => {
122
123
  const { getProfile } = await import("@/lib/api");
123
124
  await getProfile();
124
125
 
125
- expect(mockFetch).toHaveBeenCalledWith(
126
- "https://test-api.local/api/settings/profile",
127
- expect.any(Object),
128
- );
126
+ expect(mockFetch).toHaveBeenCalledWith("https://test-api.local/api/settings/profile", expect.any(Object));
129
127
  });
130
128
 
131
129
  it("throws on network failure", async () => {
@@ -1,15 +1,12 @@
1
1
  /**
2
- * Tests for api-config.ts — validateProductionApiUrl guard behaviour.
2
+ * Tests for api-config.ts — URL resolution behaviour.
3
3
  *
4
- * The module throws at import time if validation fails, so each test
4
+ * The module resolves API URL at import time, so each test
5
5
  * uses vi.resetModules() + a dynamic import to re-evaluate the module
6
6
  * with the desired environment variables.
7
7
  */
8
8
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
9
9
 
10
- const VALID_PRODUCTION_URL = "https://api.example.com";
11
- const INTERNAL_URL = "http://localhost:3001";
12
-
13
10
  function setEnv(vars: Record<string, string | undefined>) {
14
11
  for (const [k, v] of Object.entries(vars)) {
15
12
  if (v === undefined) {
@@ -20,7 +17,7 @@ function setEnv(vars: Record<string, string | undefined>) {
20
17
  }
21
18
  }
22
19
 
23
- describe("validateProductionApiUrl", () => {
20
+ describe("api-config URL resolution", () => {
24
21
  const originalEnv = { ...process.env };
25
22
 
26
23
  beforeEach(() => {
@@ -35,55 +32,38 @@ describe("validateProductionApiUrl", () => {
35
32
  Object.assign(process.env, originalEnv);
36
33
  });
37
34
 
38
- it("does NOT throw in development regardless of URL", async () => {
35
+ it("uses NEXT_PUBLIC_API_URL when set", async () => {
39
36
  setEnv({
40
- NODE_ENV: "development",
41
- NEXT_RUNTIME: undefined,
42
- NEXT_PHASE: undefined,
43
- NEXT_PUBLIC_API_URL: INTERNAL_URL,
37
+ NEXT_PUBLIC_API_URL: "https://api.example.com",
44
38
  });
45
- await expect(import("../lib/api-config")).resolves.toHaveProperty("API_BASE_URL");
39
+ const mod = await import("../lib/api-config");
40
+ expect(mod.PLATFORM_BASE_URL).toBe("https://api.example.com");
41
+ expect(mod.API_BASE_URL).toBe("https://api.example.com/api");
46
42
  });
47
43
 
48
- it("does NOT throw during the Next.js build phase (NEXT_PHASE=phase-production-build)", async () => {
49
- // This is the key regression test: CI sets NODE_ENV=production and
50
- // NEXT_PUBLIC_API_URL=http://localhost:3001. The build must not crash.
44
+ it("falls back to localhost:3001 when no env var and no window", async () => {
51
45
  setEnv({
52
- NODE_ENV: "production",
53
- NEXT_PHASE: "phase-production-build",
54
- NEXT_RUNTIME: "nodejs",
55
- NEXT_PUBLIC_API_URL: INTERNAL_URL,
56
- });
57
- await expect(import("../lib/api-config")).resolves.toHaveProperty("API_BASE_URL");
58
- });
59
-
60
- it("throws at production runtime with an internal URL", async () => {
61
- setEnv({
62
- NODE_ENV: "production",
63
- NEXT_PHASE: undefined,
64
- NEXT_RUNTIME: "nodejs",
65
- NEXT_PUBLIC_API_URL: INTERNAL_URL,
46
+ NEXT_PUBLIC_API_URL: undefined,
66
47
  });
67
- await expect(import("../lib/api-config")).rejects.toThrow(/contains an internal hostname/);
48
+ const mod = await import("../lib/api-config");
49
+ // In test env (jsdom with localhost), should resolve to localhost
50
+ expect(mod.PLATFORM_BASE_URL).toContain("localhost");
68
51
  });
69
52
 
70
- it("throws at production runtime when NEXT_PUBLIC_API_URL is not set", async () => {
53
+ it("exports SITE_URL from NEXT_PUBLIC_SITE_URL env var", async () => {
71
54
  setEnv({
72
- NODE_ENV: "production",
73
- NEXT_PHASE: undefined,
74
- NEXT_RUNTIME: "nodejs",
75
- NEXT_PUBLIC_API_URL: undefined,
55
+ NEXT_PUBLIC_SITE_URL: "https://mysite.com",
56
+ NEXT_PUBLIC_API_URL: "http://localhost:3001",
76
57
  });
77
- await expect(import("../lib/api-config")).rejects.toThrow(/NEXT_PUBLIC_API_URL is not set/);
58
+ const mod = await import("../lib/api-config");
59
+ expect(mod.SITE_URL).toBe("https://mysite.com");
78
60
  });
79
61
 
80
- it("does NOT throw at production runtime with a valid public HTTPS URL", async () => {
62
+ it("exports API_BASE_URL as PLATFORM_BASE_URL + /api", async () => {
81
63
  setEnv({
82
- NODE_ENV: "production",
83
- NEXT_PHASE: undefined,
84
- NEXT_RUNTIME: "nodejs",
85
- NEXT_PUBLIC_API_URL: VALID_PRODUCTION_URL,
64
+ NEXT_PUBLIC_API_URL: "https://api.test.com",
86
65
  });
87
- await expect(import("../lib/api-config")).resolves.toHaveProperty("API_BASE_URL");
66
+ const mod = await import("../lib/api-config");
67
+ expect(mod.API_BASE_URL).toBe(`${mod.PLATFORM_BASE_URL}/api`);
88
68
  });
89
69
  });
@@ -18,8 +18,7 @@ describe("getFleetResources", () => {
18
18
  it("calls /api/fleet/resources with credentials", async () => {
19
19
  mockFetch.mockResolvedValue({
20
20
  ok: true,
21
- json: () =>
22
- Promise.resolve({ totalCpuPercent: 42, totalMemoryMb: 512, memoryCapacityMb: 1024 }),
21
+ json: () => Promise.resolve({ totalCpuPercent: 42, totalMemoryMb: 512, memoryCapacityMb: 1024 }),
23
22
  });
24
23
 
25
24
  const { getFleetResources } = await import("@/lib/api");
@@ -61,10 +61,7 @@ describe("listInstances uses tRPC", () => {
61
61
  const result = await listInstances();
62
62
 
63
63
  expect(mockListInstances).toHaveBeenCalled();
64
- expect(fetchSpy).not.toHaveBeenCalledWith(
65
- expect.stringContaining("/fleet/"),
66
- expect.anything(),
67
- );
64
+ expect(fetchSpy).not.toHaveBeenCalledWith(expect.stringContaining("/fleet/"), expect.anything());
68
65
  expect(result).toHaveLength(1);
69
66
  expect(result[0].id).toBe("bot-1");
70
67
  expect(result[0].status).toBe("running");
@@ -187,10 +184,7 @@ describe("getInstanceLogs uses tRPC", () => {
187
184
 
188
185
  it("calls trpcVanilla.fleet.getInstanceLogs.query", async () => {
189
186
  mockGetInstanceLogs.mockResolvedValueOnce({
190
- logs: [
191
- "2026-02-20T10:00:00Z [INFO] Bot started",
192
- "2026-02-20T10:00:01Z [ERROR] Connection failed",
193
- ],
187
+ logs: ["2026-02-20T10:00:00Z [INFO] Bot started", "2026-02-20T10:00:01Z [ERROR] Connection failed"],
194
188
  });
195
189
 
196
190
  const { getInstanceLogs } = await import("@/lib/api");
@@ -22,6 +22,7 @@ interface MockTrpcVanilla {
22
22
  billing: {
23
23
  creditsBalance: MockQuery;
24
24
  creditsHistory: MockQuery;
25
+ creditsDailySummary: MockQuery;
25
26
  creditOptions: MockQuery;
26
27
  affiliateStats: MockQuery;
27
28
  affiliateReferrals: MockQuery;
@@ -61,6 +62,7 @@ vi.mock("@/lib/trpc", () => ({
61
62
  billing: {
62
63
  creditsBalance: { query: vi.fn() },
63
64
  creditsHistory: { query: vi.fn() },
65
+ creditsDailySummary: { query: vi.fn() },
64
66
  creditOptions: { query: vi.fn() },
65
67
  affiliateStats: { query: vi.fn() },
66
68
  affiliateReferrals: { query: vi.fn() },
@@ -154,7 +156,7 @@ describe("API null guards", () => {
154
156
  it("getCreditHistory handles empty response", async () => {
155
157
  const { trpcVanilla } = await import("@/lib/trpc");
156
158
  const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
157
- billing.creditsHistory.query.mockResolvedValue({});
159
+ billing.creditsDailySummary.query.mockResolvedValue({});
158
160
 
159
161
  const { getCreditHistory } = await import("@/lib/api");
160
162
  const result = await getCreditHistory();
@@ -56,9 +56,7 @@ describe("AuditLogTable pagination", () => {
56
56
  render(<AuditLogTable />);
57
57
 
58
58
  await screen.findByText("Bot 0");
59
- expect(mockFetchAuditLog).toHaveBeenCalledWith(
60
- expect.objectContaining({ offset: 0, limit: 50 }),
61
- );
59
+ expect(mockFetchAuditLog).toHaveBeenCalledWith(expect.objectContaining({ offset: 0, limit: 50 }));
62
60
  });
63
61
 
64
62
  it("clicking Next requests offset 50 (page 2)", async () => {
@@ -79,9 +77,7 @@ describe("AuditLogTable pagination", () => {
79
77
  await user.click(screen.getByRole("button", { name: "Next" }));
80
78
  await screen.findByText("Bot 50");
81
79
 
82
- expect(mockFetchAuditLog).toHaveBeenLastCalledWith(
83
- expect.objectContaining({ offset: 50, limit: 50 }),
84
- );
80
+ expect(mockFetchAuditLog).toHaveBeenLastCalledWith(expect.objectContaining({ offset: 50, limit: 50 }));
85
81
  });
86
82
 
87
83
  it("disables Next when hasMore is false", async () => {
@@ -26,12 +26,8 @@ vi.mock("better-auth/react", () => ({
26
26
 
27
27
  vi.mock("framer-motion", () => ({
28
28
  motion: {
29
- div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
30
- <div {...props}>{children}</div>
31
- ),
32
- p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
33
- <p {...props}>{children}</p>
34
- ),
29
+ div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
30
+ p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <p {...props}>{children}</p>,
35
31
  },
36
32
  }));
37
33
 
@@ -150,12 +146,8 @@ describe("Reset password page", () => {
150
146
  beforeEach(() => {
151
147
  mockFetch = vi.fn();
152
148
  mockPush.mockClear();
153
- vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<
154
- typeof useRouter
155
- >);
156
- vi.mocked(useSearchParams).mockReturnValue(
157
- new URLSearchParams() as ReturnType<typeof useSearchParams>,
158
- );
149
+ vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<typeof useRouter>);
150
+ vi.mocked(useSearchParams).mockReturnValue(new URLSearchParams() as ReturnType<typeof useSearchParams>);
159
151
  });
160
152
 
161
153
  it("shows access denied when no token is present", async () => {
@@ -346,9 +338,7 @@ describe("Reset password page", () => {
346
338
  await user.click(screen.getByRole("button", { name: "Reset password" }));
347
339
 
348
340
  await waitFor(() => {
349
- expect(
350
- screen.getByText("Password must contain at least one uppercase letter"),
351
- ).toBeInTheDocument();
341
+ expect(screen.getByText("Password must contain at least one uppercase letter")).toBeInTheDocument();
352
342
  });
353
343
  expect(mockFetch).not.toHaveBeenCalled();
354
344
  });
@@ -366,9 +356,7 @@ describe("Reset password page", () => {
366
356
  await user.click(screen.getByRole("button", { name: "Reset password" }));
367
357
 
368
358
  await waitFor(() => {
369
- expect(
370
- screen.getByText("Password must contain at least one lowercase letter"),
371
- ).toBeInTheDocument();
359
+ expect(screen.getByText("Password must contain at least one lowercase letter")).toBeInTheDocument();
372
360
  });
373
361
  expect(mockFetch).not.toHaveBeenCalled();
374
362
  });
@@ -404,9 +392,7 @@ describe("Reset password page", () => {
404
392
  await user.click(screen.getByRole("button", { name: "Reset password" }));
405
393
 
406
394
  await waitFor(() => {
407
- expect(
408
- screen.getByText("Password must contain at least one special character"),
409
- ).toBeInTheDocument();
395
+ expect(screen.getByText("Password must contain at least one special character")).toBeInTheDocument();
410
396
  });
411
397
  expect(mockFetch).not.toHaveBeenCalled();
412
398
  });
@@ -4,6 +4,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  const mockReplace = vi.fn();
5
5
  vi.mock("next/navigation", () => ({
6
6
  useRouter: () => ({ replace: mockReplace }),
7
+ useSearchParams: () => new URLSearchParams(),
8
+ }));
9
+
10
+ vi.mock("@/lib/utils", () => ({
11
+ sanitizeRedirectUrl: (url: string) => url,
12
+ cn: (...args: unknown[]) => args.filter(Boolean).join(" "),
7
13
  }));
8
14
 
9
15
  const mockUseSession = vi.fn();
@@ -20,14 +26,14 @@ describe("AuthRedirect", () => {
20
26
  mockReplace.mockClear();
21
27
  });
22
28
 
23
- it("redirects authenticated user to /marketplace", async () => {
29
+ it("redirects authenticated user to homePath", async () => {
24
30
  mockUseSession.mockReturnValue({
25
31
  data: { user: { id: "1", email: "test@test.com" } },
26
32
  isPending: false,
27
33
  });
28
34
  render(<AuthRedirect />);
29
35
  await waitFor(() => {
30
- expect(mockReplace).toHaveBeenCalledWith("/marketplace");
36
+ expect(mockReplace).toHaveBeenCalledWith("/");
31
37
  });
32
38
  });
33
39
 
@@ -27,31 +27,37 @@ vi.mock("better-auth/react", () => ({
27
27
  }),
28
28
  }));
29
29
 
30
- // Mock tRPC so OAuthButtons can query enabled social providers
31
- vi.mock("@/lib/trpc", () => ({
32
- trpc: {
33
- authSocial: {
34
- enabledSocialProviders: {
35
- useQuery: () => ({ data: ["github", "discord", "google"], isLoading: false }),
36
- },
37
- },
38
- },
39
- TRPCProvider: ({ children }: { children: React.ReactNode }) => children,
30
+ // Mock @/lib/api-config for OAuthButtons fetch URL
31
+ vi.mock("@/lib/api-config", () => ({
32
+ API_BASE_URL: "https://api.test/api",
33
+ PLATFORM_BASE_URL: "https://api.test",
34
+ SITE_URL: "https://api.test",
40
35
  }));
41
36
 
42
37
  // Mock framer-motion to prevent animation issues in JSDOM
43
38
  vi.mock("framer-motion", () => ({
44
39
  motion: {
45
- div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
46
- <div {...props}>{children}</div>
47
- ),
48
- p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
49
- <p {...props}>{children}</p>
50
- ),
40
+ div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
41
+ p: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <p {...props}>{children}</p>,
51
42
  },
52
43
  }));
53
44
 
54
45
  describe("Login page", () => {
46
+ beforeEach(() => {
47
+ // OAuthButtons now fetches providers from API_BASE_URL/auth/providers via fetch
48
+ vi.stubGlobal(
49
+ "fetch",
50
+ vi.fn().mockResolvedValue({
51
+ ok: true,
52
+ json: () => Promise.resolve(["github", "discord", "google"]),
53
+ }),
54
+ );
55
+ });
56
+
57
+ afterEach(() => {
58
+ vi.unstubAllGlobals();
59
+ });
60
+
55
61
  it("renders email and password fields", async () => {
56
62
  const { default: LoginPage } = await import("../app/(auth)/login/page");
57
63
  render(<LoginPage />);
@@ -71,7 +77,7 @@ describe("Login page", () => {
71
77
  const { default: LoginPage } = await import("../app/(auth)/login/page");
72
78
  render(<LoginPage />);
73
79
 
74
- expect(screen.getByRole("button", { name: "Continue with GitHub" })).toBeInTheDocument();
80
+ expect(await screen.findByRole("button", { name: "Continue with GitHub" })).toBeInTheDocument();
75
81
  expect(screen.getByRole("button", { name: "Continue with Discord" })).toBeInTheDocument();
76
82
  expect(screen.getByRole("button", { name: "Continue with Google" })).toBeInTheDocument();
77
83
  });
@@ -152,13 +158,9 @@ describe("OAuth callback page", () => {
152
158
  beforeEach(() => {
153
159
  vi.useFakeTimers();
154
160
  mockPush.mockClear();
155
- vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<
156
- typeof useRouter
157
- >);
161
+ vi.mocked(useRouter).mockReturnValue({ push: mockPush } as unknown as ReturnType<typeof useRouter>);
158
162
  vi.mocked(useParams).mockReturnValue({ provider: "github" });
159
- vi.mocked(useSearchParams).mockReturnValue(
160
- new URLSearchParams() as ReturnType<typeof useSearchParams>,
161
- );
163
+ vi.mocked(useSearchParams).mockReturnValue(new URLSearchParams() as ReturnType<typeof useSearchParams>);
162
164
  });
163
165
 
164
166
  afterEach(() => {
@@ -12,9 +12,7 @@ const { mockGetAutoTopupSettings, mockUpdateAutoTopupSettings } = vi.hoisted(()
12
12
  // Mock framer-motion to prevent animation issues in JSDOM
13
13
  vi.mock("framer-motion", () => ({
14
14
  motion: {
15
- div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
16
- <div {...props}>{children}</div>
17
- ),
15
+ div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <div {...props}>{children}</div>,
18
16
  },
19
17
  }));
20
18
 
@@ -163,9 +161,7 @@ describe("AutoTopupCard", () => {
163
161
  const { AutoTopupCard } = await import("../components/billing/auto-topup-card");
164
162
  render(<AutoTopupCard />);
165
163
 
166
- expect(
167
- await screen.findByText("Add a payment method to enable auto-topup."),
168
- ).toBeInTheDocument();
164
+ expect(await screen.findByText("Add a payment method to enable auto-topup.")).toBeInTheDocument();
169
165
  });
170
166
 
171
167
  it("shows error state with retry on fetch failure", async () => {
@@ -229,9 +225,7 @@ describe("AutoTopupCard", () => {
229
225
  render(<AutoTopupCard />);
230
226
 
231
227
  expect(
232
- await screen.findByText(
233
- "Tip: scheduled top-ups keep you in the dividend pool automatically.",
234
- ),
228
+ await screen.findByText("Tip: scheduled top-ups keep you in the dividend pool automatically."),
235
229
  ).toBeInTheDocument();
236
230
  });
237
231
 
@@ -241,9 +235,7 @@ describe("AutoTopupCard", () => {
241
235
  render(<AutoTopupCard />);
242
236
 
243
237
  expect(
244
- await screen.findByText(
245
- "Tip: scheduled top-ups keep you in the dividend pool for 7 days each month.",
246
- ),
238
+ await screen.findByText("Tip: scheduled top-ups keep you in the dividend pool for 7 days each month."),
247
239
  ).toBeInTheDocument();
248
240
  });
249
241
 
@@ -1,6 +1,7 @@
1
1
  import { render, screen, waitFor } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { trpcVanillaProxy } from "./setup.js";
4
5
 
5
6
  vi.mock("@/lib/api", () => ({
6
7
  listSnapshots: vi.fn(),
@@ -20,7 +21,7 @@ vi.mock("@/lib/fetch-utils", () => ({
20
21
  }));
21
22
 
22
23
  vi.mock("@/lib/trpc", () => ({
23
- trpcVanilla: {},
24
+ trpcVanilla: trpcVanillaProxy,
24
25
  }));
25
26
 
26
27
  vi.mock("sonner", () => ({
@@ -183,9 +184,7 @@ describe("BackupsTab", () => {
183
184
  });
184
185
 
185
186
  it("retries load when retry button is clicked", async () => {
186
- mockListSnapshots
187
- .mockRejectedValueOnce(new Error("Network error"))
188
- .mockResolvedValueOnce(MOCK_SNAPSHOTS);
187
+ mockListSnapshots.mockRejectedValueOnce(new Error("Network error")).mockResolvedValueOnce(MOCK_SNAPSHOTS);
189
188
  const user = userEvent.setup();
190
189
  render(<BackupsTab botId="bot-1" />);
191
190
  await waitFor(() => {
@@ -1,47 +1,15 @@
1
1
  import { render, screen } from "@testing-library/react";
2
- import { expect, test, vi } from "vitest";
2
+ import { expect, test } from "vitest";
3
3
  import BillingLayout from "@/app/(dashboard)/billing/layout";
4
4
 
5
- // Mock next/navigation
6
- vi.mock("next/navigation", () => ({
7
- usePathname: () => "/billing/plans",
8
- }));
9
-
10
- // Mock next/link
11
- vi.mock("next/link", () => ({
12
- default: ({
13
- children,
14
- href,
15
- ...props
16
- }: {
17
- children: React.ReactNode;
18
- href: string;
19
- [key: string]: unknown;
20
- }) => (
21
- <a href={href} {...props}>
22
- {children}
23
- </a>
24
- ),
25
- }));
26
-
27
- // Mock API — mode never resolves (stays null)
28
- vi.mock("@/lib/api", () => ({
29
- apiFetch: vi.fn(),
30
- getInferenceMode: () =>
31
- new Promise((_resolve) => {
32
- /* never resolves */
33
- }),
34
- }));
35
-
36
- test("hostedOnly nav item is hidden (not just invisible) while mode is loading", () => {
5
+ test("billing layout renders children and footer links", () => {
37
6
  render(
38
7
  <BillingLayout>
39
8
  <div>child</div>
40
9
  </BillingLayout>,
41
10
  );
42
11
 
43
- const hostedLink = screen.getByText("Hosted Usage");
44
- const li = hostedLink.closest("li");
45
- expect(li?.className).toMatch(/\bhidden\b/);
46
- expect(li?.className).not.toMatch(/\binvisible\b/);
12
+ expect(screen.getByText("child")).toBeInTheDocument();
13
+ expect(screen.getByText("Terms of Service")).toBeInTheDocument();
14
+ expect(screen.getByText("Privacy Policy")).toBeInTheDocument();
47
15
  });
@@ -58,27 +58,11 @@ vi.mock("framer-motion", () => ({
58
58
  AnimatePresence: ({ children }: { children: React.ReactNode }) => children,
59
59
  motion: {
60
60
  div: ({ children, ...props }: Record<string, unknown>) => {
61
- const {
62
- initial: _i,
63
- animate: _a,
64
- exit: _e,
65
- variants: _v,
66
- custom: _c,
67
- transition: _t,
68
- ...rest
69
- } = props;
61
+ const { initial: _i, animate: _a, exit: _e, variants: _v, custom: _c, transition: _t, ...rest } = props;
70
62
  return <div {...(rest as Record<string, unknown>)}>{children as React.ReactNode}</div>;
71
63
  },
72
64
  tr: ({ children, ...props }: Record<string, unknown>) => {
73
- const {
74
- initial: _i,
75
- animate: _a,
76
- exit: _e,
77
- variants: _v,
78
- custom: _c,
79
- transition: _t,
80
- ...rest
81
- } = props;
65
+ const { initial: _i, animate: _a, exit: _e, variants: _v, custom: _c, transition: _t, ...rest } = props;
82
66
  return <tr {...(rest as Record<string, unknown>)}>{children as React.ReactNode}</tr>;
83
67
  },
84
68
  },
@@ -233,16 +233,6 @@ describe("Plans page", () => {
233
233
  expect(screen.getByText(/\/month/)).toBeInTheDocument();
234
234
  });
235
235
 
236
- it("shows BYOK callout", async () => {
237
- const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
238
- render(<PlansPage />);
239
-
240
- expect(await screen.findByText("Bring Your Own Keys")).toBeInTheDocument();
241
- expect(
242
- screen.getAllByText(/Platform never touches your inference/).length,
243
- ).toBeGreaterThanOrEqual(1);
244
- });
245
-
246
236
  it("links to full pricing page", async () => {
247
237
  const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
248
238
  render(<PlansPage />);
@@ -251,14 +241,13 @@ describe("Plans page", () => {
251
241
  expect(link).toHaveAttribute("href", "/pricing");
252
242
  });
253
243
 
254
- it("shows included features", async () => {
244
+ it("renders features section (empty by default brand config)", async () => {
255
245
  const { default: PlansPage } = await import("../app/(dashboard)/billing/plans/page");
256
246
  render(<PlansPage />);
257
247
 
258
- expect(screen.getByText(/signup credit included/)).toBeInTheDocument();
259
- expect(screen.getByText("All channels")).toBeInTheDocument();
260
- expect(screen.getByText("All plugins")).toBeInTheDocument();
261
- expect(screen.getByText("All providers")).toBeInTheDocument();
248
+ // Default brand config has empty planFeatures[], so no feature items render.
249
+ // Just verify the page doesn't crash.
250
+ expect(screen.getByText("Your Plan")).toBeInTheDocument();
262
251
  });
263
252
  });
264
253
 
@@ -310,9 +299,7 @@ describe("Usage page", () => {
310
299
 
311
300
  expect(await screen.findByText("Anthropic")).toBeInTheDocument();
312
301
  expect(screen.getByText("OpenAI")).toBeInTheDocument();
313
- expect(
314
- screen.getByText((_, element) => element?.textContent === "~$23.40"),
315
- ).toBeInTheDocument();
302
+ expect(screen.getByText((_, element) => element?.textContent === "~$23.40")).toBeInTheDocument();
316
303
  expect(screen.getByText((_, element) => element?.textContent === "~$8.12")).toBeInTheDocument();
317
304
  expect(screen.getByText(/Platform does not charge for inference/)).toBeInTheDocument();
318
305
  });
@@ -455,7 +442,7 @@ describe("Payment page", () => {
455
442
  });
456
443
 
457
444
  describe("Billing layout", () => {
458
- it("renders billing navigation links", async () => {
445
+ it("renders child content and footer", async () => {
459
446
  const { default: BillingLayout } = await import("../app/(dashboard)/billing/layout");
460
447
  render(
461
448
  <BillingLayout>
@@ -463,27 +450,9 @@ describe("Billing layout", () => {
463
450
  </BillingLayout>,
464
451
  );
465
452
 
466
- expect(screen.getByText("Your Plan")).toBeInTheDocument();
467
- expect(screen.getByText("Usage")).toBeInTheDocument();
468
- expect(screen.getByText("Payment")).toBeInTheDocument();
469
453
  expect(screen.getByText("child content")).toBeInTheDocument();
470
- });
471
-
472
- it("shows Hosted Usage nav for hosted users", async () => {
473
- const api = await import("@/lib/api");
474
- vi.mocked(api.getInferenceMode).mockResolvedValue("hosted");
475
-
476
- const { default: BillingLayout } = await import("../app/(dashboard)/billing/layout");
477
- render(
478
- <BillingLayout>
479
- <div>child content</div>
480
- </BillingLayout>,
481
- );
482
-
483
- expect(await screen.findByText("Hosted Usage")).toBeInTheDocument();
484
-
485
- // Reset for other tests
486
- vi.mocked(api.getInferenceMode).mockResolvedValue("byok");
454
+ expect(screen.getByText("Terms of Service")).toBeInTheDocument();
455
+ expect(screen.getByText("Privacy Policy")).toBeInTheDocument();
487
456
  });
488
457
  });
489
458
 
@@ -99,9 +99,7 @@ describe("ResourcesTab error state", () => {
99
99
 
100
100
  it("retries loading when Retry button is clicked", async () => {
101
101
  const { getResourceTier } = await import("@/lib/bot-settings-data");
102
- vi.mocked(getResourceTier)
103
- .mockRejectedValueOnce(new Error("fail"))
104
- .mockResolvedValueOnce({ tier: "pro" });
102
+ vi.mocked(getResourceTier).mockRejectedValueOnce(new Error("fail")).mockResolvedValueOnce({ tier: "pro" });
105
103
 
106
104
  render(<ResourcesTab botId="bot-1" />);
107
105
  await waitFor(() => {
@@ -72,9 +72,7 @@ describe("StorageTab", () => {
72
72
  it("shows billing explanation text", async () => {
73
73
  render(<StorageTab botId="bot-1" />);
74
74
  await waitFor(() => {
75
- expect(
76
- screen.getByText(/Storage costs are billed daily from your credit balance/),
77
- ).toBeInTheDocument();
75
+ expect(screen.getByText(/Storage costs are billed daily from your credit balance/)).toBeInTheDocument();
78
76
  });
79
77
  });
80
78
  });
@@ -23,9 +23,7 @@ describe("VpsUpgradeCard", () => {
23
23
 
24
24
  it("displays the description", () => {
25
25
  render(<VpsUpgradeCard botId="bot-1" />);
26
- expect(
27
- screen.getByText(/dedicated persistent container with fixed monthly pricing/),
28
- ).toBeInTheDocument();
26
+ expect(screen.getByText(/dedicated persistent container with fixed monthly pricing/)).toBeInTheDocument();
29
27
  });
30
28
 
31
29
  it("lists all VPS features", () => {