@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
@@ -38,9 +38,7 @@ function newItem(sortOrder: number): NavItem {
38
38
  }
39
39
 
40
40
  export function NavEditor({ initial, onSave }: NavEditorProps) {
41
- const [items, setItems] = useState<NavItem[]>(
42
- [...initial].sort((a, b) => a.sortOrder - b.sortOrder),
43
- );
41
+ const [items, setItems] = useState<NavItem[]>([...initial].sort((a, b) => a.sortOrder - b.sortOrder));
44
42
  const [saving, setSaving] = useState(false);
45
43
 
46
44
  function update(id: string, patch: Partial<NavItem>) {
@@ -84,14 +82,9 @@ export function NavEditor({ initial, onSave }: NavEditorProps) {
84
82
  <CardTitle>Navigation Items</CardTitle>
85
83
  </CardHeader>
86
84
  <CardContent className="space-y-3">
87
- {items.length === 0 && (
88
- <p className="text-sm text-muted-foreground">No navigation items. Add one below.</p>
89
- )}
85
+ {items.length === 0 && <p className="text-sm text-muted-foreground">No navigation items. Add one below.</p>}
90
86
  {items.map((item, index) => (
91
- <div
92
- key={item.id}
93
- className="flex items-start gap-3 rounded-md border border-border bg-muted/30 p-3"
94
- >
87
+ <div key={item.id} className="flex items-start gap-3 rounded-md border border-border bg-muted/30 p-3">
95
88
  <div className="flex flex-col gap-1 pt-1">
96
89
  <Button
97
90
  variant="ghost"
@@ -8,13 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
8
8
  import { Input } from "@/components/ui/input";
9
9
  import { Label } from "@/components/ui/label";
10
10
  import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
11
- import {
12
- Select,
13
- SelectContent,
14
- SelectItem,
15
- SelectTrigger,
16
- SelectValue,
17
- } from "@/components/ui/select";
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
18
12
  import { Textarea } from "@/components/ui/textarea";
19
13
  import { toUserMessage } from "@/lib/errors";
20
14
  import type { Promotion, PromotionType, UserSegment, ValueType } from "@/lib/promotions-types";
@@ -51,21 +45,15 @@ export function PromotionForm({ initialData }: PromotionFormProps) {
51
45
  const [endsAt, setEndsAt] = useState(initialData?.endsAt ?? "");
52
46
 
53
47
  // Eligibility
54
- const [firstPurchaseOnly, setFirstPurchaseOnly] = useState(
55
- initialData?.firstPurchaseOnly ?? false,
56
- );
48
+ const [firstPurchaseOnly, setFirstPurchaseOnly] = useState(initialData?.firstPurchaseOnly ?? false);
57
49
  const [minPurchaseCents, setMinPurchaseCents] = useState(initialData?.minPurchaseCents ?? 0);
58
50
  const [userSegment, setUserSegment] = useState<UserSegment>(initialData?.userSegment ?? "all");
59
51
 
60
52
  // Limits
61
- const [unlimited, setUnlimited] = useState(
62
- initialData ? initialData.totalUseLimit === null : true,
63
- );
53
+ const [unlimited, setUnlimited] = useState(initialData ? initialData.totalUseLimit === null : true);
64
54
  const [totalUseLimit, setTotalUseLimit] = useState(initialData?.totalUseLimit ?? 1000);
65
55
  const [perUserLimit, setPerUserLimit] = useState(initialData?.perUserLimit ?? 1);
66
- const [noBudgetCap, setNoBudgetCap] = useState(
67
- initialData ? initialData.budgetCap === null : true,
68
- );
56
+ const [noBudgetCap, setNoBudgetCap] = useState(initialData ? initialData.budgetCap === null : true);
69
57
  const [budgetCap, setBudgetCap] = useState(initialData?.budgetCap ?? 0);
70
58
 
71
59
  // Coupon
@@ -178,11 +166,7 @@ export function PromotionForm({ initialData }: PromotionFormProps) {
178
166
  <CardTitle className="text-sm">Value</CardTitle>
179
167
  </CardHeader>
180
168
  <CardContent className="space-y-4">
181
- <RadioGroup
182
- value={valueType}
183
- onValueChange={(v) => setValueType(v as ValueType)}
184
- className="flex gap-4"
185
- >
169
+ <RadioGroup value={valueType} onValueChange={(v) => setValueType(v as ValueType)} className="flex gap-4">
186
170
  <div className="flex items-center gap-2">
187
171
  <RadioGroupItem value="flat_credits" id="vt-flat" />
188
172
  <Label htmlFor="vt-flat" className="text-sm cursor-pointer">
@@ -249,22 +233,13 @@ export function PromotionForm({ initialData }: PromotionFormProps) {
249
233
  </div>
250
234
  )}
251
235
  <div className="flex items-center gap-2">
252
- <Checkbox
253
- id="no-expiry"
254
- checked={noExpiry}
255
- onCheckedChange={(v) => setNoExpiry(v === true)}
256
- />
236
+ <Checkbox id="no-expiry" checked={noExpiry} onCheckedChange={(v) => setNoExpiry(v === true)} />
257
237
  <Label htmlFor="no-expiry">No expiry</Label>
258
238
  </div>
259
239
  {!noExpiry && (
260
240
  <div>
261
241
  <Label htmlFor="promo-ends">End date</Label>
262
- <Input
263
- id="promo-ends"
264
- type="datetime-local"
265
- value={endsAt}
266
- onChange={(e) => setEndsAt(e.target.value)}
267
- />
242
+ <Input id="promo-ends" type="datetime-local" value={endsAt} onChange={(e) => setEndsAt(e.target.value)} />
268
243
  </div>
269
244
  )}
270
245
  </CardContent>
@@ -328,11 +303,7 @@ export function PromotionForm({ initialData }: PromotionFormProps) {
328
303
  </CardHeader>
329
304
  <CardContent className="space-y-4">
330
305
  <div className="flex items-center gap-2">
331
- <Checkbox
332
- id="unlimited"
333
- checked={unlimited}
334
- onCheckedChange={(v) => setUnlimited(v === true)}
335
- />
306
+ <Checkbox id="unlimited" checked={unlimited} onCheckedChange={(v) => setUnlimited(v === true)} />
336
307
  <Label htmlFor="unlimited">Unlimited uses</Label>
337
308
  </div>
338
309
  {!unlimited && (
@@ -358,11 +329,7 @@ export function PromotionForm({ initialData }: PromotionFormProps) {
358
329
  />
359
330
  </div>
360
331
  <div className="flex items-center gap-2">
361
- <Checkbox
362
- id="no-budget-cap"
363
- checked={noBudgetCap}
364
- onCheckedChange={(v) => setNoBudgetCap(v === true)}
365
- />
332
+ <Checkbox id="no-budget-cap" checked={noBudgetCap} onCheckedChange={(v) => setNoBudgetCap(v === true)} />
366
333
  <Label htmlFor="no-budget-cap">No budget cap</Label>
367
334
  </div>
368
335
  {!noBudgetCap && (
@@ -6,22 +6,9 @@ import { toast } from "sonner";
6
6
  import { Badge } from "@/components/ui/badge";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Input } from "@/components/ui/input";
9
- import {
10
- Select,
11
- SelectContent,
12
- SelectItem,
13
- SelectTrigger,
14
- SelectValue,
15
- } from "@/components/ui/select";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
16
10
  import { Skeleton } from "@/components/ui/skeleton";
17
- import {
18
- Table,
19
- TableBody,
20
- TableCell,
21
- TableHead,
22
- TableHeader,
23
- TableRow,
24
- } from "@/components/ui/table";
11
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
25
12
  import type { AdminRole, UserRoleAssignment } from "@/lib/admin-api";
26
13
  import { assignRole, getRolesList, revokeRole } from "@/lib/admin-api";
27
14
  import { toUserMessage } from "@/lib/errors";
@@ -86,10 +73,7 @@ function RoleRow({ assignment, onChanged }: RoleRowProps) {
86
73
  <code className="text-xs text-muted-foreground">{assignment.tenant_id}</code>
87
74
  </TableCell>
88
75
  <TableCell>
89
- <Badge
90
- variant="secondary"
91
- className={`${roleBadgeClass(assignment.role)} inline-flex items-center gap-1`}
92
- >
76
+ <Badge variant="secondary" className={`${roleBadgeClass(assignment.role)} inline-flex items-center gap-1`}>
93
77
  <RoleIcon role={assignment.role} />
94
78
  {assignment.role}
95
79
  </Badge>
@@ -134,9 +118,7 @@ function RoleCatalog({ roles }: { roles: AdminRole[] }) {
134
118
  </Badge>
135
119
  {role.is_system && <span className="text-xs text-muted-foreground">(system)</span>}
136
120
  </div>
137
- {role.description && (
138
- <p className="text-xs text-muted-foreground">{role.description}</p>
139
- )}
121
+ {role.description && <p className="text-xs text-muted-foreground">{role.description}</p>}
140
122
  </div>
141
123
  </div>
142
124
  ))}
@@ -173,9 +155,7 @@ export function RolesDashboard() {
173
155
 
174
156
  function handleRoleChanged(userId: string, tenantId: string, newRole: string) {
175
157
  setAssignments((prev) =>
176
- prev.map((a) =>
177
- a.user_id === userId && a.tenant_id === tenantId ? { ...a, role: newRole } : a,
178
- ),
158
+ prev.map((a) => (a.user_id === userId && a.tenant_id === tenantId ? { ...a, role: newRole } : a)),
179
159
  );
180
160
  }
181
161
 
@@ -247,20 +227,13 @@ export function RolesDashboard() {
247
227
  <TableBody>
248
228
  {filtered.length === 0 ? (
249
229
  <TableRow>
250
- <TableCell
251
- colSpan={5}
252
- className="text-center text-muted-foreground py-8 text-sm"
253
- >
230
+ <TableCell colSpan={5} className="text-center text-muted-foreground py-8 text-sm">
254
231
  No users found.
255
232
  </TableCell>
256
233
  </TableRow>
257
234
  ) : (
258
235
  filtered.map((a) => (
259
- <RoleRow
260
- key={`${a.user_id}-${a.tenant_id}`}
261
- assignment={a}
262
- onChanged={handleRoleChanged}
263
- />
236
+ <RoleRow key={`${a.user_id}-${a.tenant_id}`} assignment={a} onChanged={handleRoleChanged} />
264
237
  ))
265
238
  )}
266
239
  </TableBody>
@@ -52,13 +52,12 @@ export function SuspendDialog({ open, onOpenChange, user, onComplete }: SuspendD
52
52
  <DialogDescription>
53
53
  {isSuspended ? (
54
54
  <>
55
- Reactivate <span className="font-mono text-terminal">{user.email}</span>? Their bots
56
- will resume.
55
+ Reactivate <span className="font-mono text-terminal">{user.email}</span>? Their bots will resume.
57
56
  </>
58
57
  ) : (
59
58
  <>
60
- Suspend <span className="font-mono text-terminal">{user.email}</span>? Their running
61
- bots will be paused immediately.
59
+ Suspend <span className="font-mono text-terminal">{user.email}</span>? Their running bots will be paused
60
+ immediately.
62
61
  </>
63
62
  )}
64
63
  </DialogDescription>
@@ -83,13 +82,7 @@ export function SuspendDialog({ open, onOpenChange, user, onComplete }: SuspendD
83
82
  disabled={(!isSuspended && !reason.trim()) || submitting}
84
83
  onClick={handleConfirm}
85
84
  >
86
- {submitting
87
- ? isSuspended
88
- ? "Reactivating..."
89
- : "Suspending..."
90
- : isSuspended
91
- ? "Reactivate"
92
- : "Suspend"}
85
+ {submitting ? (isSuspended ? "Reactivating..." : "Suspending...") : isSuspended ? "Reactivate" : "Suspend"}
93
86
  </Button>
94
87
  </DialogFooter>
95
88
  </DialogContent>
@@ -120,9 +120,7 @@ export function TenantNotesPanel({ tenantId }: TenantNotesPanelProps) {
120
120
  <div key={note.id} className="px-4 py-3 space-y-1">
121
121
  <div className="flex items-center justify-between">
122
122
  <span className="text-xs font-medium text-muted-foreground">{note.admin_user}</span>
123
- <span className="text-xs text-muted-foreground">
124
- {relativeTime(note.created_at)}
125
- </span>
123
+ <span className="text-xs text-muted-foreground">{relativeTime(note.created_at)}</span>
126
124
  </div>
127
125
  <p className="text-sm whitespace-pre-wrap">{note.content}</p>
128
126
  </div>
@@ -42,18 +42,12 @@ export function TenantRowActions({ user, onAction }: TenantRowActionsProps) {
42
42
  </DropdownMenuItem>
43
43
  <DropdownMenuSeparator />
44
44
  {user.status === "active" ? (
45
- <DropdownMenuItem
46
- onClick={() => setSuspendOpen(true)}
47
- className="text-amber-500 focus:text-amber-500"
48
- >
45
+ <DropdownMenuItem onClick={() => setSuspendOpen(true)} className="text-amber-500 focus:text-amber-500">
49
46
  <ShieldBan className="size-4" />
50
47
  Suspend
51
48
  </DropdownMenuItem>
52
49
  ) : user.status === "suspended" ? (
53
- <DropdownMenuItem
54
- onClick={() => setSuspendOpen(true)}
55
- className="text-terminal focus:text-terminal"
56
- >
50
+ <DropdownMenuItem onClick={() => setSuspendOpen(true)} className="text-terminal focus:text-terminal">
57
51
  <ShieldCheck className="size-4" />
58
52
  Reactivate
59
53
  </DropdownMenuItem>
@@ -61,18 +55,8 @@ export function TenantRowActions({ user, onAction }: TenantRowActionsProps) {
61
55
  </DropdownMenuContent>
62
56
  </DropdownMenu>
63
57
 
64
- <SuspendDialog
65
- open={suspendOpen}
66
- onOpenChange={setSuspendOpen}
67
- user={user}
68
- onComplete={onAction}
69
- />
70
- <GrantCreditsDialog
71
- open={grantOpen}
72
- onOpenChange={setGrantOpen}
73
- user={user}
74
- onComplete={onAction}
75
- />
58
+ <SuspendDialog open={suspendOpen} onOpenChange={setSuspendOpen} user={user} onComplete={onAction} />
59
+ <GrantCreditsDialog open={grantOpen} onOpenChange={setGrantOpen} user={user} onComplete={onAction} />
76
60
  </>
77
61
  );
78
62
  }
@@ -7,14 +7,7 @@ import { Button } from "@/components/ui/button";
7
7
  import { Checkbox } from "@/components/ui/checkbox";
8
8
  import { Input } from "@/components/ui/input";
9
9
  import { Skeleton } from "@/components/ui/skeleton";
10
- import {
11
- Table,
12
- TableBody,
13
- TableCell,
14
- TableHead,
15
- TableHeader,
16
- TableRow,
17
- } from "@/components/ui/table";
10
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
18
11
  import {
19
12
  type AdminUserSummary,
20
13
  bulkGrantCredits,
@@ -119,9 +112,7 @@ export function TenantTable() {
119
112
  setSelected(new Set());
120
113
  reload();
121
114
  } catch (err) {
122
- toast.error(
123
- `Failed to suspend tenants: ${err instanceof Error ? err.message : "Unknown error"}`,
124
- );
115
+ toast.error(`Failed to suspend tenants: ${err instanceof Error ? err.message : "Unknown error"}`);
125
116
  }
126
117
  }
127
118
 
@@ -131,9 +122,7 @@ export function TenantTable() {
131
122
  setSelected(new Set());
132
123
  reload();
133
124
  } catch (err) {
134
- toast.error(
135
- `Failed to reactivate tenants: ${err instanceof Error ? err.message : "Unknown error"}`,
136
- );
125
+ toast.error(`Failed to reactivate tenants: ${err instanceof Error ? err.message : "Unknown error"}`);
137
126
  }
138
127
  }
139
128
 
@@ -149,9 +138,7 @@ export function TenantTable() {
149
138
  setSelected(new Set());
150
139
  reload();
151
140
  } catch (err) {
152
- toast.error(
153
- `Failed to grant credits: ${err instanceof Error ? err.message : "Unknown error"}`,
154
- );
141
+ toast.error(`Failed to grant credits: ${err instanceof Error ? err.message : "Unknown error"}`);
155
142
  }
156
143
  }
157
144
 
@@ -193,30 +180,18 @@ export function TenantTable() {
193
180
  <TableHeader>
194
181
  <TableRow className="bg-secondary crt-scanlines">
195
182
  <TableHead className="w-10">
196
- <Checkbox
197
- checked={total > 0 && selected.size === total}
198
- onCheckedChange={toggleAll}
199
- />
183
+ <Checkbox checked={total > 0 && selected.size === total} onCheckedChange={toggleAll} />
200
184
  </TableHead>
201
185
  <TableHead className="text-xs font-medium uppercase tracking-wider">Email</TableHead>
202
186
  <TableHead className="text-xs font-medium uppercase tracking-wider">Name</TableHead>
203
187
  <TableHead className="text-xs font-medium uppercase tracking-wider">Status</TableHead>
204
188
  <TableHead className="text-xs font-medium uppercase tracking-wider">Plan</TableHead>
205
- <TableHead className="text-xs font-medium uppercase tracking-wider text-right">
206
- Credits
207
- </TableHead>
208
- <TableHead className="text-xs font-medium uppercase tracking-wider text-right">
209
- Agents
210
- </TableHead>
189
+ <TableHead className="text-xs font-medium uppercase tracking-wider text-right">Credits</TableHead>
190
+ <TableHead className="text-xs font-medium uppercase tracking-wider text-right">Agents</TableHead>
211
191
  <TableHead className="w-10" />
212
192
  </TableRow>
213
193
  </TableHeader>
214
- <TableBody
215
- className={cn(
216
- "transition-opacity duration-150",
217
- loading && users.length > 0 && "opacity-60",
218
- )}
219
- >
194
+ <TableBody className={cn("transition-opacity duration-150", loading && users.length > 0 && "opacity-60")}>
220
195
  {loading && users.length === 0 ? (
221
196
  Array.from({ length: 8 }).map((_, i) => (
222
197
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows have no stable ID
@@ -242,19 +217,14 @@ export function TenantTable() {
242
217
  &gt; No users found
243
218
  <span className="animate-ellipsis" />
244
219
  </p>
245
- <p className="text-xs text-muted-foreground mt-1">
246
- Try adjusting your search query
247
- </p>
220
+ <p className="text-xs text-muted-foreground mt-1">Try adjusting your search query</p>
248
221
  </TableCell>
249
222
  </TableRow>
250
223
  ) : (
251
224
  users.map((user) => (
252
225
  <TableRow
253
226
  key={user.id}
254
- className={cn(
255
- "h-10 hover:bg-secondary/50",
256
- selected.has(user.tenant_id) && "bg-terminal/5",
257
- )}
227
+ className={cn("h-10 hover:bg-secondary/50", selected.has(user.tenant_id) && "bg-terminal/5")}
258
228
  >
259
229
  <TableCell>
260
230
  <Checkbox
@@ -275,17 +245,10 @@ export function TenantTable() {
275
245
  </span>
276
246
  </TableCell>
277
247
  <TableCell className="text-xs text-muted-foreground">{user.role}</TableCell>
278
- <TableCell
279
- className={cn(
280
- "text-right font-mono text-xs",
281
- creditColor(user.credit_balance_cents),
282
- )}
283
- >
248
+ <TableCell className={cn("text-right font-mono text-xs", creditColor(user.credit_balance_cents))}>
284
249
  {formatCreditStandard(user.credit_balance_cents / 100)}
285
250
  </TableCell>
286
- <TableCell className="text-right text-xs text-muted-foreground">
287
- {user.agent_count}
288
- </TableCell>
251
+ <TableCell className="text-right text-xs text-muted-foreground">{user.agent_count}</TableCell>
289
252
  <TableCell>
290
253
  <TenantRowActions user={user} onAction={reload} />
291
254
  </TableCell>
@@ -1,19 +1,27 @@
1
1
  "use client";
2
2
 
3
- import { useRouter } from "next/navigation";
3
+ import { useRouter, useSearchParams } from "next/navigation";
4
4
  import { useEffect } from "react";
5
5
  import { useSession } from "@/lib/auth-client";
6
6
  import { getBrandConfig } from "@/lib/brand-config";
7
+ import { sanitizeRedirectUrl } from "@/lib/utils";
7
8
 
9
+ /**
10
+ * Redirects authenticated users away from auth pages.
11
+ * Checks callbackUrl param first, falls back to brand homePath.
12
+ */
8
13
  export function AuthRedirect() {
9
14
  const { data: session, isPending } = useSession();
10
15
  const router = useRouter();
16
+ const searchParams = useSearchParams();
11
17
 
12
18
  useEffect(() => {
13
19
  if (!isPending && session) {
14
- router.replace(getBrandConfig().homePath);
20
+ const callback = searchParams.get("callbackUrl");
21
+ const homePath = getBrandConfig().homePath ?? "/dashboard";
22
+ router.replace(callback ? sanitizeRedirectUrl(callback) : homePath);
15
23
  }
16
- }, [isPending, session, router]);
24
+ }, [isPending, session, router, searchParams]);
17
25
 
18
26
  return null;
19
27
  }
@@ -53,9 +53,7 @@ export function EmailVerificationResultBanner() {
53
53
  return (
54
54
  <Banner variant="destructive" role="alert">
55
55
  <XCircle className="size-4 shrink-0" />
56
- <span className="flex-1">
57
- Email verification failed. Please try again or contact support.
58
- </span>
56
+ <span className="flex-1">Email verification failed. Please try again or contact support.</span>
59
57
  <Button
60
58
  variant="ghost"
61
59
  size="xs"
@@ -11,11 +11,7 @@ interface ResendVerificationButtonProps {
11
11
  className?: string;
12
12
  }
13
13
 
14
- export function ResendVerificationButton({
15
- email,
16
- variant = "terminal",
17
- className,
18
- }: ResendVerificationButtonProps) {
14
+ export function ResendVerificationButton({ email, variant = "terminal", className }: ResendVerificationButtonProps) {
19
15
  const [cooldown, setCooldown] = useState(0);
20
16
  const [sending, setSending] = useState(false);
21
17
  const [sent, setSent] = useState(false);
@@ -61,11 +57,7 @@ export function ResendVerificationButton({
61
57
  }
62
58
  }, [email, cooldown, sending]);
63
59
 
64
- const buttonText = sending
65
- ? "Sending..."
66
- : cooldown > 0
67
- ? `Resend in ${cooldown}s`
68
- : "Resend verification email";
60
+ const buttonText = sending ? "Sending..." : cooldown > 0 ? `Resend in ${cooldown}s` : "Resend verification email";
69
61
 
70
62
  return (
71
63
  <div className="flex flex-col gap-1">
@@ -6,9 +6,7 @@ export function BrandWordmark() {
6
6
  const tagline = getBrandConfig().tagline;
7
7
  return (
8
8
  <div className="mb-6 flex flex-col items-center gap-1">
9
- <span className="text-2xl font-bold uppercase tracking-[0.3em] text-terminal">
10
- {brandName()}
11
- </span>
9
+ <span className="text-2xl font-bold uppercase tracking-[0.3em] text-terminal">{brandName()}</span>
12
10
  <span className="text-xs uppercase tracking-[0.2em] text-muted-foreground">{tagline}</span>
13
11
  </div>
14
12
  );
@@ -74,8 +74,7 @@ export function AddPaymentMethodDialog({
74
74
  <DialogHeader>
75
75
  <DialogTitle>{orgId ? "Add org payment method" : "Add payment method"}</DialogTitle>
76
76
  <DialogDescription>
77
- Your card details are handled securely by Stripe. We never see or store your card
78
- number.
77
+ Your card details are handled securely by Stripe. We never see or store your card number.
79
78
  </DialogDescription>
80
79
  </DialogHeader>
81
80
 
@@ -30,10 +30,7 @@ export function AffiliateDashboard() {
30
30
  setLoading(true);
31
31
  setError(null);
32
32
  try {
33
- const [statsData, referralsData] = await Promise.all([
34
- getAffiliateStats(),
35
- getAffiliateReferrals(),
36
- ]);
33
+ const [statsData, referralsData] = await Promise.all([getAffiliateStats(), getAffiliateReferrals()]);
37
34
  setStats(statsData);
38
35
  setReferrals(referralsData.referrals);
39
36
  setTotal(referralsData.total);
@@ -140,9 +137,7 @@ export function AffiliateDashboard() {
140
137
  <div className="max-w-3xl space-y-6">
141
138
  <div>
142
139
  <h1 className="text-2xl font-bold tracking-tight">Refer & Earn</h1>
143
- <p className="text-sm text-muted-foreground">
144
- Share your link and earn credits when friends join
145
- </p>
140
+ <p className="text-sm text-muted-foreground">Share your link and earn credits when friends join</p>
146
141
  </div>
147
142
 
148
143
  {/* Referral link card */}
@@ -205,9 +200,7 @@ export function AffiliateDashboard() {
205
200
  </Card>
206
201
  <Card>
207
202
  <CardContent className="pt-6 text-center">
208
- <div className="text-3xl font-bold text-emerald-500">
209
- {formatCents(stats.totalEarnedCents)}
210
- </div>
203
+ <div className="text-3xl font-bold text-emerald-500">{formatCents(stats.totalEarnedCents)}</div>
211
204
  <p className="text-xs text-muted-foreground mt-1">earned</p>
212
205
  </CardContent>
213
206
  </Card>
@@ -280,12 +273,7 @@ export function AffiliateDashboard() {
280
273
  {error && <p className="text-sm text-destructive">{error}</p>}
281
274
  {offset < total && (
282
275
  <div className="pt-2">
283
- <Button
284
- variant="outline"
285
- size="sm"
286
- onClick={handleLoadMore}
287
- disabled={loadingMore}
288
- >
276
+ <Button variant="outline" size="sm" onClick={handleLoadMore} disabled={loadingMore}>
289
277
  {loadingMore ? "Loading more..." : "Load more"}
290
278
  </Button>
291
279
  </div>
@@ -31,9 +31,7 @@ export function AmountSelector({ onSelect }: AmountSelectorProps) {
31
31
  }}
32
32
  className={cn(
33
33
  "rounded-md border p-3 text-lg font-bold transition-colors hover:bg-accent",
34
- selected === amt && !custom
35
- ? "border-primary bg-primary/5 ring-1 ring-primary"
36
- : "border-border",
34
+ selected === amt && !custom ? "border-primary bg-primary/5 ring-1 ring-primary" : "border-border",
37
35
  )}
38
36
  >
39
37
  ${amt}
@@ -6,13 +6,7 @@ import { useCallback, useEffect, useState } from "react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
8
  import { Label } from "@/components/ui/label";
9
- import {
10
- Select,
11
- SelectContent,
12
- SelectItem,
13
- SelectTrigger,
14
- SelectValue,
15
- } from "@/components/ui/select";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
16
10
  import { Skeleton } from "@/components/ui/skeleton";
17
11
  import { Switch } from "@/components/ui/switch";
18
12
  import type { AutoTopupInterval, AutoTopupSettings } from "@/lib/api";
@@ -73,10 +67,7 @@ export function AutoTopupCard() {
73
67
  load();
74
68
  }, [load]);
75
69
 
76
- async function save(
77
- update: Parameters<typeof updateAutoTopupSettings>[0],
78
- prevSettings: AutoTopupSettings,
79
- ) {
70
+ async function save(update: Parameters<typeof updateAutoTopupSettings>[0], prevSettings: AutoTopupSettings) {
80
71
  setSaving(true);
81
72
  setError(null);
82
73
  try {
@@ -134,9 +134,7 @@ export function BuyCreditsPanel() {
134
134
  </p>
135
135
  </CardHeader>
136
136
  <CardContent>
137
- <p className="text-sm text-muted-foreground">
138
- Credit purchases are not available at this time.
139
- </p>
137
+ <p className="text-sm text-muted-foreground">Credit purchases are not available at this time.</p>
140
138
  </CardContent>
141
139
  </Card>
142
140
  );
@@ -146,9 +144,7 @@ export function BuyCreditsPanel() {
146
144
  <Card>
147
145
  <CardHeader>
148
146
  <CardTitle>Buy Credits</CardTitle>
149
- <p className="text-xs text-muted-foreground">
150
- Every purchase resets your 7-day dividend window
151
- </p>
147
+ <p className="text-xs text-muted-foreground">Every purchase resets your 7-day dividend window</p>
152
148
  </CardHeader>
153
149
  <CardContent className="space-y-4">
154
150
  <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-5">
@@ -169,19 +165,13 @@ export function BuyCreditsPanel() {
169
165
  >
170
166
  <span className="text-lg font-bold">{tier.label}</span>
171
167
  {tier.bonusPercent > 0 && (
172
- <Badge className="bg-primary/15 text-primary border-primary/25 text-xs">
173
- +{tier.bonusPercent}%
174
- </Badge>
168
+ <Badge className="bg-primary/15 text-primary border-primary/25 text-xs">+{tier.bonusPercent}%</Badge>
175
169
  )}
176
170
  </motion.button>
177
171
  ))}
178
172
  </div>
179
173
  {error && <p className="text-sm text-destructive">{error}</p>}
180
- <Button
181
- onClick={handleCheckout}
182
- disabled={selected === null || loading}
183
- className="w-full sm:w-auto"
184
- >
174
+ <Button onClick={handleCheckout} disabled={selected === null || loading} className="w-full sm:w-auto">
185
175
  {loading ? "Redirecting..." : "Buy credits"}
186
176
  </Button>
187
177
  </CardContent>