@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
@@ -34,8 +34,7 @@ export function ByokCallout({ compact }: { compact?: boolean }) {
34
34
  if (compact) {
35
35
  return (
36
36
  <p className="text-xs text-muted-foreground">
37
- All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your
38
- inference.
37
+ All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your inference.
39
38
  </p>
40
39
  );
41
40
  }
@@ -49,8 +48,8 @@ export function ByokCallout({ compact }: { compact?: boolean }) {
49
48
  <div className="space-y-1">
50
49
  <p className="text-sm font-medium">Bring Your Own Keys</p>
51
50
  <p className="text-sm text-muted-foreground">
52
- All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your
53
- inference. We only charge for the orchestration layer: containers, plugins, and support.
51
+ All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your inference. We only
52
+ charge for the orchestration layer: containers, plugins, and support.
54
53
  </p>
55
54
  </div>
56
55
  </CardContent>
@@ -62,8 +61,7 @@ function HostedCallout({ compact }: { compact?: boolean }) {
62
61
  if (compact) {
63
62
  return (
64
63
  <p className="text-xs text-muted-foreground">
65
- Hosted adapter — transparent per-use pricing with no markup surprises. Switch to BYOK
66
- anytime in settings.
64
+ Hosted adapter — transparent per-use pricing with no markup surprises. Switch to BYOK anytime in settings.
67
65
  </p>
68
66
  );
69
67
  }
@@ -77,8 +75,8 @@ function HostedCallout({ compact }: { compact?: boolean }) {
77
75
  <div className="space-y-1">
78
76
  <p className="text-sm font-medium">Hosted AI Adapter</p>
79
77
  <p className="text-sm text-muted-foreground">
80
- BYOK users pay their providers directly. Hosted users pay per use — transparent pricing,
81
- no markup surprises. Each plan includes a monthly hosted credit.
78
+ BYOK users pay their providers directly. Hosted users pay per use — transparent pricing, no markup
79
+ surprises. Each plan includes a monthly hosted credit.
82
80
  </p>
83
81
  </div>
84
82
  </CardContent>
@@ -17,17 +17,12 @@ export function ConfirmationTracker({
17
17
  credited,
18
18
  txHash,
19
19
  }: ConfirmationTrackerProps) {
20
- const pct =
21
- confirmationsRequired > 0
22
- ? Math.min(100, Math.round((confirmations / confirmationsRequired) * 100))
23
- : 0;
20
+ const pct = confirmationsRequired > 0 ? Math.min(100, Math.round((confirmations / confirmationsRequired) * 100)) : 0;
24
21
  const detected = confirmations > 0 || credited;
25
22
 
26
23
  return (
27
24
  <div className="space-y-4 text-center">
28
- <p className="text-sm text-muted-foreground">
29
- {credited ? "Payment complete!" : "Payment received!"}
30
- </p>
25
+ <p className="text-sm text-muted-foreground">{credited ? "Payment complete!" : "Payment received!"}</p>
31
26
  <p className="text-xl font-semibold">{displayAmount}</p>
32
27
 
33
28
  <div className="rounded-lg border border-border p-3 space-y-2">
@@ -44,10 +39,7 @@ export function ConfirmationTracker({
44
39
  aria-valuemin={0}
45
40
  aria-valuemax={100}
46
41
  >
47
- <div
48
- className="h-full rounded-full bg-primary transition-all duration-500"
49
- style={{ width: `${pct}%` }}
50
- />
42
+ <div className="h-full rounded-full bg-primary transition-all duration-500" style={{ width: `${pct}%` }} />
51
43
  </div>
52
44
  </div>
53
45
 
@@ -58,9 +50,7 @@ export function ConfirmationTracker({
58
50
  >
59
51
  {detected && <Check className="h-2.5 w-2.5" />}
60
52
  </div>
61
- <span
62
- className={`text-xs ${detected ? "text-muted-foreground" : "text-muted-foreground/50"}`}
63
- >
53
+ <span className={`text-xs ${detected ? "text-muted-foreground" : "text-muted-foreground/50"}`}>
64
54
  Payment detected
65
55
  </span>
66
56
  </div>
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { useCreditBalance } from "@/hooks/use-credit-balance";
4
+ import { formatCreditStandard } from "@/lib/format-credit";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function balanceColorClass(balance: number): string {
8
+ if (balance <= 0) return "text-red-500";
9
+ if (balance <= 2) return "text-amber-500";
10
+ return "text-emerald-500";
11
+ }
12
+
13
+ /** Compact credit balance badge — use in sidebar, nav, etc. */
14
+ export function CreditBalanceBadge({ className }: { className?: string }) {
15
+ const { balance } = useCreditBalance();
16
+ if (balance == null) return null;
17
+ return (
18
+ <span className={cn("text-xs font-mono", balanceColorClass(balance), className)}>
19
+ {formatCreditStandard(balance)}
20
+ </span>
21
+ );
22
+ }
@@ -53,19 +53,13 @@ export function CreditBalance({ data }: { data: CreditBalanceData }) {
53
53
  <CardTitle>Credit Balance</CardTitle>
54
54
  </CardHeader>
55
55
  <CardContent className="space-y-4">
56
- <div
57
- className={cn("text-4xl font-bold font-mono", balanceColor(data.balance, data.runway))}
58
- >
56
+ <div className={cn("text-4xl font-bold font-mono", balanceColor(data.balance, data.runway))}>
59
57
  {formatCreditStandard(animatedBalance)}
60
58
  </div>
61
59
  <div className="flex flex-wrap gap-6 text-sm text-muted-foreground">
62
60
  <div>
63
- <span className="block text-xs uppercase tracking-wider text-primary/60">
64
- Daily burn
65
- </span>
66
- <span className="font-medium text-foreground">
67
- {formatCreditStandard(data.dailyBurn)}/day
68
- </span>
61
+ <span className="block text-xs uppercase tracking-wider text-primary/60">Daily burn</span>
62
+ <span className="font-medium text-foreground">{formatCreditStandard(data.dailyBurn)}/day</span>
69
63
  </div>
70
64
  <div>
71
65
  <span className="block text-xs uppercase tracking-wider text-primary/60">Runway</span>
@@ -51,10 +51,7 @@ export function CryptoCheckout() {
51
51
  } else if (res.status === "expired" || res.status === "failed") {
52
52
  setStatus(res.status as PaymentStatus);
53
53
  clearInterval(interval);
54
- } else if (
55
- res.amountReceivedCents > 0 &&
56
- res.amountReceivedCents >= res.amountExpectedCents
57
- ) {
54
+ } else if (res.amountReceivedCents > 0 && res.amountReceivedCents >= res.amountExpectedCents) {
58
55
  setStatus("confirming");
59
56
  setStep("confirming");
60
57
  } else if (res.amountReceivedCents > 0) {
@@ -100,11 +97,7 @@ export function CryptoCheckout() {
100
97
  if (methods.length === 0) return null;
101
98
 
102
99
  return (
103
- <motion.div
104
- initial={{ opacity: 0, y: 8 }}
105
- animate={{ opacity: 1, y: 0 }}
106
- transition={{ duration: 0.3 }}
107
- >
100
+ <motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
108
101
  <Card>
109
102
  <CardHeader>
110
103
  <CardTitle className="flex items-center gap-2">
@@ -131,16 +124,8 @@ export function CryptoCheckout() {
131
124
  animate={{ opacity: 1, x: 0 }}
132
125
  exit={{ opacity: 0, x: 20 }}
133
126
  >
134
- <PaymentMethodPicker
135
- methods={methods}
136
- onSelect={handleMethod}
137
- onBack={() => setStep("amount")}
138
- />
139
- {loading && (
140
- <p className="mt-2 text-xs text-muted-foreground animate-pulse">
141
- Creating checkout...
142
- </p>
143
- )}
127
+ <PaymentMethodPicker methods={methods} onSelect={handleMethod} onBack={() => setStep("amount")} />
128
+ {loading && <p className="mt-2 text-xs text-muted-foreground animate-pulse">Creating checkout...</p>}
144
129
  </motion.div>
145
130
  )}
146
131
  {step === "deposit" && checkout && (
@@ -167,11 +152,7 @@ export function CryptoCheckout() {
167
152
  credited={status === "credited"}
168
153
  />
169
154
  {status === "credited" && (
170
- <button
171
- type="button"
172
- onClick={handleReset}
173
- className="mt-4 text-sm text-primary hover:underline"
174
- >
155
+ <button type="button" onClick={handleReset} className="mt-4 text-sm text-primary hover:underline">
175
156
  Done — buy more credits
176
157
  </button>
177
158
  )}
@@ -38,9 +38,7 @@ export function DegradedStateBanner() {
38
38
  <Banner variant="warning" role="alert">
39
39
  <span className="flex-1">
40
40
  ACTION REQUIRED —{" "}
41
- {days !== null
42
- ? `${days} day${days === 1 ? "" : "s"} to resolve`
43
- : "resolve soon to avoid suspension"}
41
+ {days !== null ? `${days} day${days === 1 ? "" : "s"} to resolve` : "resolve soon to avoid suspension"}
44
42
  {reason ? ` (${reason})` : ""}
45
43
  </span>
46
44
  <Link href="/billing" className="font-semibold underline underline-offset-4">
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { Check, Copy } from "lucide-react";
3
+ import { Check, Copy, Wallet } from "lucide-react";
4
4
  import { QRCodeSVG } from "qrcode.react";
5
- import { useCallback, useEffect, useState } from "react";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import type { CheckoutResult } from "@/lib/api";
8
8
 
@@ -10,11 +10,200 @@ interface DepositViewProps {
10
10
  checkout: CheckoutResult;
11
11
  status: "waiting" | "partial" | "confirming" | "credited" | "expired" | "failed";
12
12
  onBack: () => void;
13
+ expectedAmount?: string | null;
14
+ receivedAmount?: string | null;
15
+ token?: string;
16
+ decimals?: number;
13
17
  }
14
18
 
15
- export function DepositView({ checkout, status, onBack }: DepositViewProps) {
19
+ function formatCrypto(raw: string, decimals: number): string {
20
+ const n = Number(raw) / 10 ** decimals;
21
+ return n.toFixed(Math.min(decimals, 8)).replace(/\.?0+$/, "");
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Payment URI builder — triggers wallet apps on QR scan
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const CHAIN_URI_SCHEMES: Record<string, string> = {
29
+ bitcoin: "bitcoin",
30
+ litecoin: "litecoin",
31
+ dogecoin: "dogecoin",
32
+ ethereum: "ethereum",
33
+ base: "ethereum",
34
+ "base-sepolia": "ethereum",
35
+ sepolia: "ethereum",
36
+ arbitrum: "ethereum",
37
+ optimism: "ethereum",
38
+ polygon: "ethereum",
39
+ avalanche: "ethereum",
40
+ solana: "solana",
41
+ tron: "tron",
42
+ };
43
+
44
+ function buildPaymentUri(chain: string, address: string, nativeAmount?: string | null, decimals?: number): string {
45
+ const scheme = CHAIN_URI_SCHEMES[chain.toLowerCase()];
46
+ if (!scheme) return address;
47
+
48
+ if (scheme === "bitcoin" || scheme === "litecoin" || scheme === "dogecoin") {
49
+ const d = decimals ?? 8;
50
+ const human = nativeAmount ? formatCrypto(nativeAmount, d) : undefined;
51
+ return human ? `${scheme}:${address}?amount=${human}` : `${scheme}:${address}`;
52
+ }
53
+
54
+ if (scheme === "ethereum") {
55
+ return nativeAmount ? `${scheme}:${address}?value=${nativeAmount}` : `${scheme}:${address}`;
56
+ }
57
+
58
+ if (scheme === "solana") {
59
+ const d = decimals ?? 9;
60
+ const human = nativeAmount ? formatCrypto(nativeAmount, d) : undefined;
61
+ return human ? `${scheme}:${address}?amount=${human}` : `${scheme}:${address}`;
62
+ }
63
+
64
+ return `${scheme}:${address}`;
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Wallet detection + direct transaction submission
69
+ // ---------------------------------------------------------------------------
70
+
71
+ type WalletType = "metamask" | "solana" | "tron" | null;
72
+
73
+ function detectWallet(chain: string): WalletType {
74
+ if (typeof window === "undefined") return null;
75
+ const scheme = CHAIN_URI_SCHEMES[chain.toLowerCase()];
76
+ if (scheme === "ethereum" && (window as { ethereum?: unknown }).ethereum) return "metamask";
77
+ if (scheme === "solana" && (window as { solana?: { isPhantom?: boolean } }).solana?.isPhantom) return "solana";
78
+ if (scheme === "tron" && (window as { tronWeb?: unknown }).tronWeb) return "tron";
79
+ return null;
80
+ }
81
+
82
+ function walletLabel(type: WalletType): string {
83
+ switch (type) {
84
+ case "metamask":
85
+ return "Pay with Wallet";
86
+ case "solana":
87
+ return "Pay with Phantom";
88
+ case "tron":
89
+ return "Pay with TronLink";
90
+ default:
91
+ return "Pay with Wallet";
92
+ }
93
+ }
94
+
95
+ // ERC-20 transfer(address,uint256) function selector
96
+ const TRANSFER_SELECTOR = "0xa9059cbb";
97
+
98
+ function encodeErc20Transfer(to: string, amount: string): string {
99
+ const addr = to.slice(2).toLowerCase().padStart(64, "0");
100
+ const val = BigInt(amount).toString(16).padStart(64, "0");
101
+ return `${TRANSFER_SELECTOR}${addr}${val}`;
102
+ }
103
+
104
+ interface WalletTxOpts {
105
+ walletType: WalletType;
106
+ depositAddress: string;
107
+ amount: string | null;
108
+ tokenType?: string;
109
+ contractAddress?: string | null;
110
+ }
111
+
112
+ async function sendViaWallet(opts: WalletTxOpts): Promise<string | null> {
113
+ const { walletType, depositAddress, amount, tokenType, contractAddress } = opts;
114
+ if (!amount) return null;
115
+
116
+ if (walletType === "metamask") {
117
+ // Find the REAL MetaMask provider — TronLink and other extensions also inject window.ethereum
118
+ type EthProvider = {
119
+ request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
120
+ isMetaMask?: boolean;
121
+ };
122
+ const root = (window as { ethereum?: EthProvider & { providers?: EthProvider[] } }).ethereum;
123
+ if (!root) return null;
124
+ const eth: EthProvider = root.providers?.find((p) => p.isMetaMask) ?? root;
125
+
126
+ // Check if already connected (non-prompting), only request if needed
127
+ let accounts = (await eth.request({ method: "eth_accounts" })) as string[];
128
+ if (!accounts[0]) {
129
+ accounts = (await eth.request({ method: "eth_requestAccounts" })) as string[];
130
+ }
131
+ if (!accounts[0]) return null;
132
+
133
+ // Build the single transaction object
134
+ const tx: Record<string, string> = { from: accounts[0] };
135
+ if (tokenType === "erc20" && contractAddress) {
136
+ tx.to = contractAddress;
137
+ tx.value = "0x0";
138
+ tx.data = encodeErc20Transfer(depositAddress, amount);
139
+ } else {
140
+ tx.to = depositAddress;
141
+ tx.value = `0x${BigInt(amount).toString(16)}`;
142
+ }
143
+
144
+ const txHash = (await eth.request({
145
+ method: "eth_sendTransaction",
146
+ params: [tx],
147
+ })) as string;
148
+ return txHash;
149
+ }
150
+
151
+ if (walletType === "solana") {
152
+ // Solana requires @solana/web3.js for tx construction — fall back to URI
153
+ return null;
154
+ }
155
+
156
+ if (walletType === "tron") {
157
+ type TronWeb = {
158
+ trx: { sendTransaction: (to: string, amount: number) => Promise<{ txid: string }> };
159
+ contract: () => {
160
+ at: (addr: string) => Promise<{ transfer: (to: string, amount: string) => { send: () => Promise<string> } }>;
161
+ };
162
+ };
163
+ const tw = (window as { tronWeb?: TronWeb }).tronWeb;
164
+ if (!tw) return null;
165
+
166
+ // TRC-20: call transfer() on the contract via tronWeb.contract()
167
+ if (tokenType === "erc20" && contractAddress) {
168
+ const contract = await tw.contract().at(contractAddress);
169
+ const txHash = await contract.transfer(depositAddress, amount).send();
170
+ return txHash;
171
+ }
172
+ const result = await tw.trx.sendTransaction(depositAddress, Number(amount));
173
+ return result.txid;
174
+ }
175
+
176
+ return null;
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Component
181
+ // ---------------------------------------------------------------------------
182
+
183
+ export function DepositView({
184
+ checkout,
185
+ status,
186
+ onBack,
187
+ expectedAmount,
188
+ receivedAmount,
189
+ token,
190
+ decimals,
191
+ }: DepositViewProps) {
16
192
  const [copied, setCopied] = useState(false);
17
- const [timeLeft, setTimeLeft] = useState(30 * 60);
193
+ const [walletType, setWalletType] = useState<WalletType>(null);
194
+ const [sending, setSending] = useState(false);
195
+ const sendingRef = useRef(false);
196
+ const [walletError, setWalletError] = useState<string | null>(null);
197
+ const [txSent, setTxSent] = useState(false);
198
+
199
+ useEffect(() => {
200
+ setWalletType(detectWallet(checkout.chain));
201
+ }, [checkout.chain]);
202
+
203
+ const paymentUri = useMemo(
204
+ () => buildPaymentUri(checkout.chain, checkout.depositAddress, expectedAmount, decimals),
205
+ [checkout.chain, checkout.depositAddress, expectedAmount, decimals],
206
+ );
18
207
 
19
208
  const handleCopy = useCallback(() => {
20
209
  navigator.clipboard.writeText(checkout.depositAddress);
@@ -22,48 +211,108 @@ export function DepositView({ checkout, status, onBack }: DepositViewProps) {
22
211
  setTimeout(() => setCopied(false), 2000);
23
212
  }, [checkout.depositAddress]);
24
213
 
25
- useEffect(() => {
26
- if (status !== "waiting") return;
27
- const timer = setInterval(() => setTimeLeft((t) => Math.max(0, t - 1)), 1000);
28
- return () => clearInterval(timer);
29
- }, [status]);
30
-
31
- const mins = Math.floor(timeLeft / 60);
32
- const secs = timeLeft % 60;
214
+ const handleWalletPay = useCallback(async () => {
215
+ if (sendingRef.current) {
216
+ return;
217
+ }
218
+ sendingRef.current = true;
219
+ setSending(true);
220
+ setWalletError(null);
221
+ try {
222
+ const amountToSend =
223
+ expectedAmount && receivedAmount
224
+ ? String(BigInt(expectedAmount) - BigInt(receivedAmount))
225
+ : (checkout.expectedAmount ?? expectedAmount);
226
+ const txHash = await sendViaWallet({
227
+ walletType,
228
+ depositAddress: checkout.depositAddress,
229
+ amount: amountToSend ?? null,
230
+ tokenType: checkout.type,
231
+ contractAddress: checkout.contractAddress,
232
+ });
233
+ if (txHash) {
234
+ setTxSent(true);
235
+ } else if (walletType === "solana") {
236
+ // Solana needs SDK — fall back to URI
237
+ window.open(paymentUri);
238
+ }
239
+ } catch (err) {
240
+ const msg = err instanceof Error ? err.message : "Transaction rejected";
241
+ if (!msg.includes("User denied") && !msg.includes("rejected")) {
242
+ setWalletError(msg);
243
+ }
244
+ } finally {
245
+ sendingRef.current = false;
246
+ setSending(false);
247
+ }
248
+ }, [
249
+ walletType,
250
+ checkout.depositAddress,
251
+ checkout.type,
252
+ checkout.contractAddress,
253
+ checkout.expectedAmount,
254
+ expectedAmount,
255
+ receivedAmount,
256
+ paymentUri,
257
+ ]);
33
258
 
34
259
  return (
35
260
  <div className="space-y-4 text-center">
36
- <button
37
- type="button"
38
- onClick={onBack}
39
- className="text-sm text-muted-foreground hover:text-foreground self-start"
40
- >
261
+ <button type="button" onClick={onBack} className="text-sm text-muted-foreground hover:text-foreground self-start">
41
262
  &larr; Back
42
263
  </button>
43
- <p className="text-sm text-muted-foreground">Send exactly</p>
44
- <p className="text-2xl font-semibold">{checkout.displayAmount}</p>
45
- <p className="text-xs text-muted-foreground">
46
- on {checkout.chain} &middot; ${checkout.amountUsd.toFixed(2)} USD
47
- </p>
48
- <div
49
- className="mx-auto w-fit rounded-lg border border-border bg-background p-3"
50
- aria-hidden="true"
51
- >
52
- <QRCodeSVG
53
- value={checkout.depositAddress}
54
- size={140}
55
- bgColor="hsl(var(--background))"
56
- fgColor="hsl(var(--foreground))"
57
- />
264
+ {status === "partial" && expectedAmount && receivedAmount && decimals != null && token ? (
265
+ BigInt(receivedAmount) >= BigInt(expectedAmount) ? (
266
+ <>
267
+ <p className="text-sm text-primary font-medium">Full amount received</p>
268
+ <p className="text-2xl font-semibold">
269
+ {formatCrypto(receivedAmount, decimals)} {token}
270
+ </p>
271
+ <p className="text-xs text-muted-foreground animate-pulse">Waiting for on-chain confirmation...</p>
272
+ </>
273
+ ) : (
274
+ <>
275
+ <p className="text-sm text-muted-foreground">Send remaining</p>
276
+ <p className="text-2xl font-semibold">
277
+ {formatCrypto(String(BigInt(expectedAmount) - BigInt(receivedAmount)), decimals)} {token}
278
+ </p>
279
+ <p className="text-xs text-muted-foreground">
280
+ on {checkout.chain} &middot; {formatCrypto(receivedAmount, decimals)} of{" "}
281
+ {formatCrypto(expectedAmount, decimals)} {token} received
282
+ </p>
283
+ </>
284
+ )
285
+ ) : (
286
+ <>
287
+ <p className="text-sm text-muted-foreground">Send exactly</p>
288
+ <p className="text-2xl font-semibold">{checkout.displayAmount}</p>
289
+ <p className="text-xs text-muted-foreground">
290
+ on {checkout.chain} &middot; ${checkout.amountUsd.toFixed(2)} USD
291
+ </p>
292
+ </>
293
+ )}
294
+
295
+ {/* Wallet button — primary action when wallet detected */}
296
+ {walletType && !txSent && (
297
+ <Button onClick={handleWalletPay} disabled={sending} className="w-full gap-2">
298
+ <Wallet className="h-4 w-4" />
299
+ {sending ? "Confirm in wallet..." : walletLabel(walletType)}
300
+ </Button>
301
+ )}
302
+ {txSent && <p className="text-sm text-primary font-medium">Transaction sent — waiting for confirmation...</p>}
303
+ {walletError && <p className="text-xs text-destructive">{walletError}</p>}
304
+
305
+ {/* QR + manual address — fallback or mobile */}
306
+ <div className="mx-auto w-fit rounded-lg border border-border bg-white p-3" aria-hidden="true">
307
+ <QRCodeSVG value={paymentUri} size={140} bgColor="#ffffff" fgColor="#000000" />
58
308
  </div>
309
+ <p className="text-[10px] text-muted-foreground">
310
+ {walletType ? "Or scan with another wallet" : "Scan with your wallet app"}
311
+ </p>
59
312
  <div className="flex items-center gap-2 rounded-lg border border-border bg-muted/50 px-3 py-2">
60
313
  <code className="flex-1 truncate text-xs font-mono">{checkout.depositAddress}</code>
61
314
  <Button variant="ghost" size="sm" onClick={handleCopy} aria-label="Copy address">
62
- {copied ? (
63
- <Check className="h-3.5 w-3.5 text-primary" />
64
- ) : (
65
- <Copy className="h-3.5 w-3.5" />
66
- )}
315
+ {copied ? <Check className="h-3.5 w-3.5 text-primary" /> : <Copy className="h-3.5 w-3.5" />}
67
316
  </Button>
68
317
  </div>
69
318
  <div className="flex items-center justify-center gap-2 rounded-lg border border-border p-2">
@@ -71,15 +320,26 @@ export function DepositView({ checkout, status, onBack }: DepositViewProps) {
71
320
  <>
72
321
  <span className="h-2 w-2 animate-pulse rounded-full bg-yellow-500" />
73
322
  <span className="text-xs text-yellow-500">Waiting for payment...</span>
74
- <span className="text-xs text-muted-foreground">
75
- &middot; {mins}:{secs.toString().padStart(2, "0")}
76
- </span>
77
323
  </>
78
324
  )}
79
325
  {status === "partial" && (
80
326
  <>
81
327
  <span className="h-2 w-2 rounded-full bg-blue-500" />
82
- <span className="text-xs text-blue-500">Partial payment received</span>
328
+ <span className="text-xs text-blue-500">
329
+ {expectedAmount && receivedAmount && decimals != null && token ? (
330
+ BigInt(receivedAmount) >= BigInt(expectedAmount) ? (
331
+ "Full amount received — confirming on chain"
332
+ ) : (
333
+ <>
334
+ Received {formatCrypto(receivedAmount, decimals)} of {formatCrypto(expectedAmount, decimals)}{" "}
335
+ {token} &mdash; send{" "}
336
+ {formatCrypto(String(BigInt(expectedAmount) - BigInt(receivedAmount)), decimals)} more
337
+ </>
338
+ )
339
+ ) : (
340
+ "Partial payment received"
341
+ )}
342
+ </span>
83
343
  </>
84
344
  )}
85
345
  {status === "expired" && <span className="text-xs text-destructive">Payment expired</span>}
@@ -46,9 +46,7 @@ export function DividendBanner({ todayAmountCents, stats }: DividendBannerProps)
46
46
  </span>{" "}
47
47
  today.
48
48
  </p>
49
- <p className="text-sm text-muted-foreground">
50
- Community dividend from the daily pool
51
- </p>
49
+ <p className="text-sm text-muted-foreground">Community dividend from the daily pool</p>
52
50
  </div>
53
51
  </div>
54
52
  </motion.div>
@@ -55,17 +55,10 @@ export function DividendEligibility({ windowExpiresAt, eligible }: DividendEligi
55
55
  <Card>
56
56
  <CardContent className="p-4 space-y-3">
57
57
  <div className="flex items-center gap-2">
58
- <ClockIcon
59
- className={cn(
60
- "size-4 shrink-0",
61
- isExpiringSoon ? "text-terminal-dim" : "text-terminal",
62
- )}
63
- />
58
+ <ClockIcon className={cn("size-4 shrink-0", isExpiringSoon ? "text-terminal-dim" : "text-terminal")} />
64
59
  <p className="text-sm font-medium">
65
60
  You&apos;re in the pool for{" "}
66
- <span
67
- className={cn("font-bold", isExpiringSoon ? "text-terminal-dim" : "text-terminal")}
68
- >
61
+ <span className={cn("font-bold", isExpiringSoon ? "text-terminal-dim" : "text-terminal")}>
69
62
  {daysRemaining}
70
63
  </span>{" "}
71
64
  more day{daysRemaining === 1 ? "" : "s"}
@@ -76,9 +69,7 @@ export function DividendEligibility({ windowExpiresAt, eligible }: DividendEligi
76
69
  className="h-2"
77
70
  aria-label={`${daysRemaining} days remaining in dividend pool`}
78
71
  />
79
- {isExpiringSoon && (
80
- <p className="text-xs text-terminal-dim">Buy credits to reset your 7-day window</p>
81
- )}
72
+ {isExpiringSoon && <p className="text-xs text-terminal-dim">Buy credits to reset your 7-day window</p>}
82
73
  </CardContent>
83
74
  </Card>
84
75
  </motion.div>