@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
@@ -70,9 +70,7 @@ export default function ChannelSetupPage({ params }: { params: Promise<{ plugin:
70
70
  <div className="flex h-full items-center justify-center">
71
71
  <div className="text-center">
72
72
  <h1 className="text-2xl font-bold">Channel Not Found</h1>
73
- <p className="mt-2 text-muted-foreground">
74
- No manifest found for &ldquo;{plugin}&rdquo;.
75
- </p>
73
+ <p className="mt-2 text-muted-foreground">No manifest found for &ldquo;{plugin}&rdquo;.</p>
76
74
  </div>
77
75
  </div>
78
76
  );
@@ -87,8 +85,8 @@ export default function ChannelSetupPage({ params }: { params: Promise<{ plugin:
87
85
  </div>
88
86
  <h1 className="text-lg font-semibold uppercase tracking-wider">NO INSTANCE CONTEXT</h1>
89
87
  <p className="text-sm text-muted-foreground">
90
- Channel setup requires a bot instance. Navigate to your instance and connect a channel
91
- from the Channels tab.
88
+ Channel setup requires a bot instance. Navigate to your instance and connect a channel from the Channels
89
+ tab.
92
90
  </p>
93
91
  <Button variant="terminal" asChild>
94
92
  <Link href="/instances">VIEW INSTANCES</Link>
package/src/app/error.tsx CHANGED
@@ -8,13 +8,7 @@ import { logger } from "@/lib/logger";
8
8
 
9
9
  const log = logger("error-boundary");
10
10
 
11
- export default function GlobalError({
12
- error,
13
- reset,
14
- }: {
15
- error: Error & { digest?: string };
16
- reset: () => void;
17
- }) {
11
+ export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
18
12
  const [showDetails, setShowDetails] = useState(false);
19
13
  const isDev = process.env.NODE_ENV === "development";
20
14
 
@@ -8,13 +8,7 @@ import { logger } from "@/lib/logger";
8
8
 
9
9
  const log = logger("error-boundary:fleet");
10
10
 
11
- export default function FleetError({
12
- error,
13
- reset,
14
- }: {
15
- error: Error & { digest?: string };
16
- reset: () => void;
17
- }) {
11
+ export default function FleetError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
18
12
  const [showDetails, setShowDetails] = useState(false);
19
13
  const isDev = process.env.NODE_ENV === "development";
20
14
 
@@ -1,10 +1,15 @@
1
+ "use client";
2
+
1
3
  import { Sidebar } from "@/components/sidebar";
4
+ import { useRequireAuth } from "@/lib/require-auth";
2
5
 
3
6
  export default function FleetLayout({
4
7
  children,
5
8
  }: Readonly<{
6
9
  children: React.ReactNode;
7
10
  }>) {
11
+ const { isPending, isAuthed } = useRequireAuth();
12
+ if (isPending || !isAuthed) return null;
8
13
  return (
9
14
  <div className="flex h-screen">
10
15
  <Sidebar />
@@ -9,9 +9,7 @@ export default function FleetSettingsPage() {
9
9
  <Settings2 className="h-6 w-6 text-muted-foreground" />
10
10
  Fleet Settings
11
11
  </h1>
12
- <p className="text-muted-foreground mt-1">
13
- Configure update behavior and maintenance windows for your fleet.
14
- </p>
12
+ <p className="text-muted-foreground mt-1">Configure update behavior and maintenance windows for your fleet.</p>
15
13
  </div>
16
14
  <UpdateSettingsCard />
17
15
  </div>
@@ -7,13 +7,7 @@ import { logger } from "@/lib/logger";
7
7
 
8
8
  const log = logger("global-error");
9
9
 
10
- export default function GlobalError({
11
- error,
12
- reset,
13
- }: {
14
- error: Error & { digest?: string };
15
- reset: () => void;
16
- }) {
10
+ export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
17
11
  const [showDetails, setShowDetails] = useState(false);
18
12
  const isDev = process.env.NODE_ENV === "development";
19
13
 
@@ -43,9 +37,7 @@ export default function GlobalError({
43
37
  <div className="flex items-center gap-3">
44
38
  <AlertTriangleIcon className="size-6 text-red-500" />
45
39
  <div>
46
- <p className="text-xs font-semibold uppercase tracking-widest text-neutral-500">
47
- {brandName()}
48
- </p>
40
+ <p className="text-xs font-semibold uppercase tracking-widest text-neutral-500">{brandName()}</p>
49
41
  <h1 className="text-xl font-semibold text-neutral-100">Something went wrong</h1>
50
42
  </div>
51
43
  </div>
@@ -256,10 +256,7 @@
256
256
 
257
257
  /* Grid dot overlay — uses terminal color for brand-agnostic theming */
258
258
  .bg-grid-dots {
259
- background-image: radial-gradient(
260
- color-mix(in srgb, var(--terminal, #00ff41) 8%, transparent) 1px,
261
- transparent 1px
262
- );
259
+ background-image: radial-gradient(color-mix(in srgb, var(--terminal, #00ff41) 8%, transparent) 1px, transparent 1px);
263
260
  background-size: 24px 24px;
264
261
  }
265
262
 
@@ -1,19 +1,9 @@
1
1
  "use client";
2
2
 
3
- import {
4
- ArrowDownToLine,
5
- ArrowLeft,
6
- Check,
7
- Loader2,
8
- Lock,
9
- Pencil,
10
- Plus,
11
- Trash2,
12
- X,
13
- } from "lucide-react";
3
+ import { ArrowDownToLine, ArrowLeft, Check, Loader2, Lock, Pencil, Plus, Trash2, X } from "lucide-react";
14
4
  import Link from "next/link";
15
5
  import { useRouter, useSearchParams } from "next/navigation";
16
- import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
17
7
  import { FriendsTab } from "@/components/instances/friends-tab";
18
8
  import { HealthOverview } from "@/components/observability/health-overview";
19
9
  import { LogsViewer } from "@/components/observability/logs-viewer";
@@ -44,14 +34,7 @@ import { Input } from "@/components/ui/input";
44
34
  import { Separator } from "@/components/ui/separator";
45
35
  import { Skeleton } from "@/components/ui/skeleton";
46
36
  import { Switch } from "@/components/ui/switch";
47
- import {
48
- Table,
49
- TableBody,
50
- TableCell,
51
- TableHead,
52
- TableHeader,
53
- TableRow,
54
- } from "@/components/ui/table";
37
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
55
38
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
56
39
  import { Textarea } from "@/components/ui/textarea";
57
40
  import { useImageStatus } from "@/hooks/use-image-status";
@@ -70,13 +53,32 @@ import {
70
53
  updateInstanceConfig,
71
54
  updateInstanceSecrets,
72
55
  } from "@/lib/api";
56
+ import { getBrandConfig } from "@/lib/brand-config";
73
57
  import { toUserMessage } from "@/lib/errors";
74
58
  import { cn } from "@/lib/utils";
75
59
 
60
+ const ALL_INSTANCE_TABS = [
61
+ "overview",
62
+ "health",
63
+ "metrics",
64
+ "logs",
65
+ "plugins",
66
+ "channels",
67
+ "friends",
68
+ "sessions",
69
+ "snapshots",
70
+ "config",
71
+ ] as const;
72
+
76
73
  export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
77
74
  const router = useRouter();
78
75
  const searchParams = useSearchParams();
79
76
  const defaultTab = searchParams.get("tab") ?? "overview";
77
+ const brand = getBrandConfig();
78
+ const visibleTabs = useMemo(
79
+ () => ALL_INSTANCE_TABS.filter((t) => !brand.hiddenInstanceTabs.includes(t)),
80
+ [brand.hiddenInstanceTabs],
81
+ );
80
82
  const [instance, setInstance] = useState<InstanceDetail | null>(null);
81
83
  const [loading, setLoading] = useState(true);
82
84
  const [error, setError] = useState<string | null>(null);
@@ -98,7 +100,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
98
100
  const [destroyOpen, setDestroyOpen] = useState(false);
99
101
  const [destroyConfirmText, setDestroyConfirmText] = useState("");
100
102
  const [destroying, setDestroying] = useState(false);
101
- const { updateAvailable, error: imageStatusError } = useImageStatus(instanceId);
103
+ const { updateAvailable } = useImageStatus(instanceId);
102
104
  const [pulling, setPulling] = useState(false);
103
105
  const [confirmPull, setConfirmPull] = useState(false);
104
106
  const [togglingPlugin, setTogglingPlugin] = useState<string | null>(null);
@@ -109,9 +111,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
109
111
  const [secretsStatus, setSecretsStatus] = useState<"idle" | "saved" | "error">("idle");
110
112
  const [secretsError, setSecretsError] = useState<string | null>(null);
111
113
  const nextSecretId = useRef(0);
112
- const [newSecretRows, setNewSecretRows] = useState<{ id: number; key: string; value: string }[]>(
113
- [],
114
- );
114
+ const [newSecretRows, setNewSecretRows] = useState<{ id: number; key: string; value: string }[]>([]);
115
115
  const secretsLoaded = useRef(false);
116
116
  const [renaming, setRenaming] = useState(false);
117
117
  const [renameValue, setRenameValue] = useState("");
@@ -254,9 +254,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
254
254
  }
255
255
  }
256
256
  // Check for new rows whose key duplicates an existing secret key.
257
- const duplicates = newSecretRows
258
- .map((row) => row.key.trim())
259
- .filter((k) => k && secretKeys.includes(k));
257
+ const duplicates = newSecretRows.map((row) => row.key.trim()).filter((k) => k && secretKeys.includes(k));
260
258
  if (duplicates.length > 0) {
261
259
  setSecretsStatus("error");
262
260
  setSecretsError(
@@ -412,11 +410,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
412
410
  disabled={renameSaving}
413
411
  aria-label="Confirm rename"
414
412
  >
415
- {renameSaving ? (
416
- <Loader2 className="size-4 animate-spin" />
417
- ) : (
418
- <Check className="size-4" />
419
- )}
413
+ {renameSaving ? <Loader2 className="size-4 animate-spin" /> : <Check className="size-4" />}
420
414
  </Button>
421
415
  <Button
422
416
  variant="ghost"
@@ -433,12 +427,10 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
433
427
  {instance.name}
434
428
  <span
435
429
  className={cn("size-2 rounded-full", {
436
- "bg-emerald-500 animate-[pulse-dot_2s_ease-in-out_infinite]":
437
- instance.status === "running",
430
+ "bg-emerald-500 animate-[pulse-dot_2s_ease-in-out_infinite]": instance.status === "running",
438
431
  "bg-zinc-400": instance.status === "stopped",
439
432
  "bg-yellow-500": instance.status === "degraded",
440
- "bg-red-500 animate-[pulse-dot_0.8s_ease-in-out_infinite]":
441
- instance.status === "error",
433
+ "bg-red-500 animate-[pulse-dot_0.8s_ease-in-out_infinite]": instance.status === "error",
442
434
  })}
443
435
  />
444
436
  <Button
@@ -456,14 +448,9 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
456
448
  )}
457
449
  <div className="flex items-center gap-3 text-sm text-muted-foreground">
458
450
  <StatusBadge status={instance.status} />
459
- {imageStatusError && (
460
- <span className="text-xs text-destructive">{imageStatusError}</span>
461
- )}
451
+ {/* imageStatusError silenced — non-critical, fails for non-GHCR registries */}
462
452
  {updateAvailable && (
463
- <Badge
464
- variant="outline"
465
- className="gap-1.5 bg-amber-500/15 text-amber-500 border-amber-500/25"
466
- >
453
+ <Badge variant="outline" className="gap-1.5 bg-amber-500/15 text-amber-500 border-amber-500/25">
467
454
  <span
468
455
  className={cn(
469
456
  "size-1.5 rounded-full bg-amber-500",
@@ -527,18 +514,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
527
514
  {/* Tabs */}
528
515
  <Tabs value={activeTab} onValueChange={setActiveTab}>
529
516
  <TabsList className="bg-transparent border-b border-border rounded-none p-0 h-auto gap-0">
530
- {[
531
- "overview",
532
- "health",
533
- "metrics",
534
- "logs",
535
- "plugins",
536
- "channels",
537
- "friends",
538
- "sessions",
539
- "snapshots",
540
- "config",
541
- ].map((tab) => (
517
+ {visibleTabs.map((tab) => (
542
518
  <TabsTrigger
543
519
  key={tab}
544
520
  value={tab}
@@ -602,10 +578,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
602
578
  </TableHeader>
603
579
  <TableBody>
604
580
  {instance.plugins.map((plugin) => (
605
- <TableRow
606
- key={plugin.id}
607
- className="transition-colors hover:bg-muted/50 even:bg-muted/20"
608
- >
581
+ <TableRow key={plugin.id} className="transition-colors hover:bg-muted/50 even:bg-muted/20">
609
582
  <TableCell className="font-medium">{plugin.name}</TableCell>
610
583
  <TableCell className="text-muted-foreground">{plugin.version}</TableCell>
611
584
  <TableCell>
@@ -649,10 +622,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
649
622
  </TableHeader>
650
623
  <TableBody>
651
624
  {instance.channelDetails.map((ch) => (
652
- <TableRow
653
- key={ch.id}
654
- className="transition-colors hover:bg-muted/50 even:bg-muted/20"
655
- >
625
+ <TableRow key={ch.id} className="transition-colors hover:bg-muted/50 even:bg-muted/20">
656
626
  <TableCell className="font-medium">{ch.name}</TableCell>
657
627
  <TableCell className="text-muted-foreground">{ch.type}</TableCell>
658
628
  <TableCell>
@@ -689,10 +659,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
689
659
  </TableHeader>
690
660
  <TableBody>
691
661
  {instance.sessions.map((sess) => (
692
- <TableRow
693
- key={sess.id}
694
- className="transition-colors hover:bg-muted/50 even:bg-muted/20"
695
- >
662
+ <TableRow key={sess.id} className="transition-colors hover:bg-muted/50 even:bg-muted/20">
696
663
  <TableCell className="font-mono text-sm">{sess.id}</TableCell>
697
664
  <TableCell>{sess.userId}</TableCell>
698
665
  <TableCell>{sess.messageCount}</TableCell>
@@ -750,10 +717,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
750
717
  </TableHeader>
751
718
  <TableBody>
752
719
  {snapshots.map((snap) => (
753
- <TableRow
754
- key={snap.id}
755
- className="transition-colors hover:bg-muted/50 even:bg-muted/20"
756
- >
720
+ <TableRow key={snap.id} className="transition-colors hover:bg-muted/50 even:bg-muted/20">
757
721
  <TableCell className="font-medium">
758
722
  {snap.name ?? <span className="text-muted-foreground italic">unnamed</span>}
759
723
  </TableCell>
@@ -767,18 +731,10 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
767
731
  </TableCell>
768
732
  <TableCell className="text-right">
769
733
  <div className="flex justify-end gap-1">
770
- <Button
771
- size="sm"
772
- variant="outline"
773
- onClick={() => setConfirmRestore(snap)}
774
- >
734
+ <Button size="sm" variant="outline" onClick={() => setConfirmRestore(snap)}>
775
735
  Restore
776
736
  </Button>
777
- <Button
778
- size="sm"
779
- variant="destructive"
780
- onClick={() => setConfirmDelete(snap)}
781
- >
737
+ <Button size="sm" variant="destructive" onClick={() => setConfirmDelete(snap)}>
782
738
  Delete
783
739
  </Button>
784
740
  </div>
@@ -803,11 +759,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
803
759
  {confirmRestore ? new Date(confirmRestore.createdAt).toLocaleString() : ""}.
804
760
  </p>
805
761
  <DialogFooter>
806
- <Button
807
- variant="outline"
808
- onClick={() => setConfirmRestore(null)}
809
- disabled={restoring}
810
- >
762
+ <Button variant="outline" onClick={() => setConfirmRestore(null)} disabled={restoring}>
811
763
  Cancel
812
764
  </Button>
813
765
  <Button
@@ -831,11 +783,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
831
783
  </DialogDescription>
832
784
  </DialogHeader>
833
785
  <DialogFooter>
834
- <Button
835
- variant="outline"
836
- onClick={() => setConfirmDelete(null)}
837
- disabled={deleting}
838
- >
786
+ <Button variant="outline" onClick={() => setConfirmDelete(null)} disabled={deleting}>
839
787
  Cancel
840
788
  </Button>
841
789
  <Button
@@ -877,15 +825,9 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
877
825
  </div>
878
826
  <div className="flex items-center justify-end gap-3">
879
827
  {saving && <span className="text-sm text-muted-foreground">Saving...</span>}
880
- {configStatus === "saved" && !saving && (
881
- <span className="text-sm text-emerald-500">Config saved</span>
882
- )}
883
- {configStatus === "invalid" && (
884
- <span className="text-sm text-red-500">Invalid JSON</span>
885
- )}
886
- {configStatus === "error" && configError && (
887
- <span className="text-sm text-red-500">{configError}</span>
888
- )}
828
+ {configStatus === "saved" && !saving && <span className="text-sm text-emerald-500">Config saved</span>}
829
+ {configStatus === "invalid" && <span className="text-sm text-red-500">Invalid JSON</span>}
830
+ {configStatus === "error" && configError && <span className="text-sm text-red-500">{configError}</span>}
889
831
  <Button
890
832
  disabled={saving || configStatus === "invalid"}
891
833
  onClick={async () => {
@@ -906,10 +848,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
906
848
  try {
907
849
  const env: Record<string, string> = {};
908
850
  for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
909
- if (
910
- v === null ||
911
- (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean")
912
- ) {
851
+ if (v === null || (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean")) {
913
852
  throw new Error(`Value for key "${k}" must be a string, number, or boolean`);
914
853
  }
915
854
  env[k] = String(v);
@@ -934,9 +873,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
934
873
  <div className="flex items-center justify-between">
935
874
  <div>
936
875
  <h3 className="text-sm font-medium">Secrets</h3>
937
- <p className="text-xs text-muted-foreground">
938
- Write-only. Stored values are never displayed.
939
- </p>
876
+ <p className="text-xs text-muted-foreground">Write-only. Stored values are never displayed.</p>
940
877
  </div>
941
878
  <Button
942
879
  size="sm"
@@ -989,9 +926,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
989
926
  type="password"
990
927
  placeholder="Enter new value..."
991
928
  value={secretValues[key] ?? ""}
992
- onChange={(e) =>
993
- setSecretValues((prev) => ({ ...prev, [key]: e.target.value }))
994
- }
929
+ onChange={(e) => setSecretValues((prev) => ({ ...prev, [key]: e.target.value }))}
995
930
  className="font-mono text-sm bg-transparent border-border focus:ring-terminal/50 focus:border-terminal/50"
996
931
  />
997
932
  </TableCell>
@@ -1006,9 +941,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
1006
941
  value={row.key}
1007
942
  onChange={(e) =>
1008
943
  setNewSecretRows((prev) =>
1009
- prev.map((r) =>
1010
- r.id === row.id ? { ...r, key: e.target.value } : r,
1011
- ),
944
+ prev.map((r) => (r.id === row.id ? { ...r, key: e.target.value } : r)),
1012
945
  )
1013
946
  }
1014
947
  className="font-mono text-sm bg-transparent"
@@ -1021,9 +954,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
1021
954
  value={row.value}
1022
955
  onChange={(e) =>
1023
956
  setNewSecretRows((prev) =>
1024
- prev.map((r) =>
1025
- r.id === row.id ? { ...r, value: e.target.value } : r,
1026
- ),
957
+ prev.map((r) => (r.id === row.id ? { ...r, value: e.target.value } : r)),
1027
958
  )
1028
959
  }
1029
960
  className="font-mono text-sm bg-transparent"
@@ -1033,9 +964,7 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
1033
964
  <Button
1034
965
  size="sm"
1035
966
  variant="ghost"
1036
- onClick={() =>
1037
- setNewSecretRows((prev) => prev.filter((r) => r.id !== row.id))
1038
- }
967
+ onClick={() => setNewSecretRows((prev) => prev.filter((r) => r.id !== row.id))}
1039
968
  >
1040
969
  <Trash2 className="size-3.5 text-muted-foreground hover:text-destructive" />
1041
970
  </Button>
@@ -1078,9 +1007,8 @@ export function InstanceDetailClient({ instanceId }: { instanceId: string }) {
1078
1007
  <DialogHeader>
1079
1008
  <DialogTitle>Destroy {instance.name} permanently?</DialogTitle>
1080
1009
  <DialogDescription>
1081
- This action is permanent and cannot be undone. The instance and all its data will be
1082
- destroyed. Type <strong className="text-foreground">{instance.name}</strong> to
1083
- confirm.
1010
+ This action is permanent and cannot be undone. The instance and all its data will be destroyed. Type{" "}
1011
+ <strong className="text-foreground">{instance.name}</strong> to confirm.
1084
1012
  </DialogDescription>
1085
1013
  </DialogHeader>
1086
1014
 
@@ -1161,9 +1089,7 @@ function MetricCard({
1161
1089
  return (
1162
1090
  <Card className="py-4">
1163
1091
  <CardHeader className="pb-1">
1164
- <CardTitle className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
1165
- {title}
1166
- </CardTitle>
1092
+ <CardTitle className="text-xs font-medium uppercase tracking-wider text-muted-foreground">{title}</CardTitle>
1167
1093
  </CardHeader>
1168
1094
  <CardContent className="space-y-2">
1169
1095
  <div className="flex items-center gap-2">
@@ -8,13 +8,7 @@ import { logger } from "@/lib/logger";
8
8
 
9
9
  const log = logger("error-boundary:instances");
10
10
 
11
- export default function InstancesError({
12
- error,
13
- reset,
14
- }: {
15
- error: Error & { digest?: string };
16
- reset: () => void;
17
- }) {
11
+ export default function InstancesError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
18
12
  const [showDetails, setShowDetails] = useState(false);
19
13
  const isDev = process.env.NODE_ENV === "development";
20
14
 
@@ -32,9 +26,7 @@ export default function InstancesError({
32
26
  </div>
33
27
  </CardHeader>
34
28
  <CardContent className="space-y-4">
35
- <p className="text-muted-foreground">
36
- Failed to load instance data. The API may be temporarily unavailable.
37
- </p>
29
+ <p className="text-muted-foreground">Failed to load instance data. The API may be temporarily unavailable.</p>
38
30
  {isDev && (
39
31
  <Button
40
32
  type="button"