@wopr-network/platform-ui-core 1.27.7 → 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 +14 -17
  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
@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
8
  import { Skeleton } from "@/components/ui/skeleton";
9
9
  import type { CreditOption } from "@/lib/api";
10
10
  import { createCreditCheckout, getCreditOptions } from "@/lib/api";
11
+ import { getBrandConfig } from "@/lib/brand-config";
11
12
  import { logger } from "@/lib/logger";
12
13
  import { cn } from "@/lib/utils";
13
14
  import { isAllowedRedirectUrl } from "@/lib/validate-redirect-url";
@@ -84,7 +85,9 @@ export function BuyCreditsPanel() {
84
85
  <CardHeader>
85
86
  <CardTitle>Buy Credits</CardTitle>
86
87
  <p className="text-xs text-muted-foreground">
87
- Every purchase resets your 7-day dividend window
88
+ {getBrandConfig().dividendsEnabled
89
+ ? "Every purchase resets your 7-day dividend window"
90
+ : "Top up your credit balance"}
88
91
  </p>
89
92
  </CardHeader>
90
93
  <CardContent>
@@ -104,7 +107,9 @@ export function BuyCreditsPanel() {
104
107
  <CardHeader>
105
108
  <CardTitle>Buy Credits</CardTitle>
106
109
  <p className="text-xs text-muted-foreground">
107
- Every purchase resets your 7-day dividend window
110
+ {getBrandConfig().dividendsEnabled
111
+ ? "Every purchase resets your 7-day dividend window"
112
+ : "Top up your credit balance"}
108
113
  </p>
109
114
  </CardHeader>
110
115
  <CardContent className="space-y-3">
@@ -123,13 +128,13 @@ export function BuyCreditsPanel() {
123
128
  <CardHeader>
124
129
  <CardTitle>Buy Credits</CardTitle>
125
130
  <p className="text-xs text-muted-foreground">
126
- Every purchase resets your 7-day dividend window
131
+ {getBrandConfig().dividendsEnabled
132
+ ? "Every purchase resets your 7-day dividend window"
133
+ : "Top up your credit balance"}
127
134
  </p>
128
135
  </CardHeader>
129
136
  <CardContent>
130
- <p className="text-sm text-muted-foreground">
131
- Credit purchases are not available at this time.
132
- </p>
137
+ <p className="text-sm text-muted-foreground">Credit purchases are not available at this time.</p>
133
138
  </CardContent>
134
139
  </Card>
135
140
  );
@@ -139,9 +144,7 @@ export function BuyCreditsPanel() {
139
144
  <Card>
140
145
  <CardHeader>
141
146
  <CardTitle>Buy Credits</CardTitle>
142
- <p className="text-xs text-muted-foreground">
143
- Every purchase resets your 7-day dividend window
144
- </p>
147
+ <p className="text-xs text-muted-foreground">Every purchase resets your 7-day dividend window</p>
145
148
  </CardHeader>
146
149
  <CardContent className="space-y-4">
147
150
  <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-5">
@@ -162,19 +165,13 @@ export function BuyCreditsPanel() {
162
165
  >
163
166
  <span className="text-lg font-bold">{tier.label}</span>
164
167
  {tier.bonusPercent > 0 && (
165
- <Badge className="bg-primary/15 text-primary border-primary/25 text-xs">
166
- +{tier.bonusPercent}%
167
- </Badge>
168
+ <Badge className="bg-primary/15 text-primary border-primary/25 text-xs">+{tier.bonusPercent}%</Badge>
168
169
  )}
169
170
  </motion.button>
170
171
  ))}
171
172
  </div>
172
173
  {error && <p className="text-sm text-destructive">{error}</p>}
173
- <Button
174
- onClick={handleCheckout}
175
- disabled={selected === null || loading}
176
- className="w-full sm:w-auto"
177
- >
174
+ <Button onClick={handleCheckout} disabled={selected === null || loading} className="w-full sm:w-auto">
178
175
  {loading ? "Redirecting..." : "Buy credits"}
179
176
  </Button>
180
177
  </CardContent>
@@ -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>}