tapimo 0.5.3 → 0.7.0

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 (376) hide show
  1. package/dist/App.d.ts +15830 -7220
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +1011 -412
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/App.js +4 -0
  12. package/dist/admin/App.js.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +2 -1
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.d.ts +148 -0
  16. package/dist/admin/apps/early-access.d.ts.map +1 -0
  17. package/dist/admin/apps/early-access.js +133 -0
  18. package/dist/admin/apps/early-access.js.map +1 -0
  19. package/dist/admin/apps/organizations.d.ts +124 -1
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +97 -6
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  24. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  25. package/dist/admin/apps/verified-tokens.js +214 -1
  26. package/dist/admin/apps/verified-tokens.js.map +1 -1
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.d.ts.map +1 -1
  29. package/dist/admin/ui.generated.js +95 -75
  30. package/dist/admin/ui.generated.js.map +1 -1
  31. package/dist/analytics/Analytics.d.ts.map +1 -1
  32. package/dist/analytics/Analytics.js +20 -0
  33. package/dist/analytics/Analytics.js.map +1 -1
  34. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  36. package/dist/analytics/tables/requestEvents.js +36 -7
  37. package/dist/analytics/tables/requestEvents.js.map +1 -1
  38. package/dist/apps/data/App.d.ts +6260 -4924
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +11 -6
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  43. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  44. package/dist/apps/data/FundingQuoteProviders.js +194 -16
  45. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  46. package/dist/apps/data/FxOracle.d.ts +71 -0
  47. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  48. package/dist/apps/data/FxOracle.js +97 -0
  49. package/dist/apps/data/FxOracle.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +1 -0
  51. package/dist/apps/data/index.d.ts.map +1 -1
  52. package/dist/apps/data/index.js +1 -0
  53. package/dist/apps/data/index.js.map +1 -1
  54. package/dist/apps/data/routes/activities.d.ts +3599 -1382
  55. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  56. package/dist/apps/data/routes/activities.js +250 -88
  57. package/dist/apps/data/routes/activities.js.map +1 -1
  58. package/dist/apps/data/routes/addresses.d.ts +872 -251
  59. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  60. package/dist/apps/data/routes/addresses.js +11 -9
  61. package/dist/apps/data/routes/addresses.js.map +1 -1
  62. package/dist/apps/data/routes/balances.d.ts +77 -26
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  64. package/dist/apps/data/routes/balances.js +88 -45
  65. package/dist/apps/data/routes/balances.js.map +1 -1
  66. package/dist/apps/data/routes/blocks.d.ts +39 -39
  67. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  68. package/dist/apps/data/routes/exchanges.d.ts +856 -105
  69. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  70. package/dist/apps/data/routes/exchanges.js +518 -76
  71. package/dist/apps/data/routes/exchanges.js.map +1 -1
  72. package/dist/apps/data/routes/fee-amm.d.ts +81 -67
  73. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  74. package/dist/apps/data/routes/fee-amm.js +76 -91
  75. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  76. package/dist/apps/data/routes/funding.d.ts +80 -36
  77. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  78. package/dist/apps/data/routes/funding.js +65 -31
  79. package/dist/apps/data/routes/funding.js.map +1 -1
  80. package/dist/apps/data/routes/receipts.d.ts +392 -315
  81. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  82. package/dist/apps/data/routes/receipts.js +152 -77
  83. package/dist/apps/data/routes/receipts.js.map +1 -1
  84. package/dist/apps/data/routes/tokens.d.ts +167 -174
  85. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  86. package/dist/apps/data/routes/tokens.js +15 -13
  87. package/dist/apps/data/routes/tokens.js.map +1 -1
  88. package/dist/apps/data/routes/transactions.d.ts +277 -536
  89. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  90. package/dist/apps/data/routes/transactions.js +96 -77
  91. package/dist/apps/data/routes/transactions.js.map +1 -1
  92. package/dist/apps/data/routes/transfers.d.ts +157 -81
  93. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  94. package/dist/apps/data/routes/transfers.js +272 -130
  95. package/dist/apps/data/routes/transfers.js.map +1 -1
  96. package/dist/apps/data/routes/valuation.d.ts +307 -0
  97. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  98. package/dist/apps/data/routes/valuation.js +292 -0
  99. package/dist/apps/data/routes/valuation.js.map +1 -0
  100. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  101. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/verified-tokens.js +7 -5
  103. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  104. package/dist/apps/data/routes/webhooks.d.ts +103 -123
  105. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  106. package/dist/apps/data/routes/webhooks.js +124 -25
  107. package/dist/apps/data/routes/webhooks.js.map +1 -1
  108. package/dist/apps/index.d.ts +2 -1
  109. package/dist/apps/index.d.ts.map +1 -1
  110. package/dist/apps/index.js +2 -1
  111. package/dist/apps/index.js.map +1 -1
  112. package/dist/apps/management/App.d.ts +8554 -1692
  113. package/dist/apps/management/App.d.ts.map +1 -1
  114. package/dist/apps/management/App.js +12 -1
  115. package/dist/apps/management/App.js.map +1 -1
  116. package/dist/apps/management/routes/api-keys.d.ts +7 -0
  117. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  118. package/dist/apps/management/routes/api-keys.js +17 -3
  119. package/dist/apps/management/routes/api-keys.js.map +1 -1
  120. package/dist/apps/management/routes/billing.d.ts +157 -162
  121. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  122. package/dist/apps/management/routes/billing.js +31 -23
  123. package/dist/apps/management/routes/billing.js.map +1 -1
  124. package/dist/apps/management/routes/faucet.d.ts +232 -0
  125. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  126. package/dist/apps/management/routes/faucet.js +154 -0
  127. package/dist/apps/management/routes/faucet.js.map +1 -0
  128. package/dist/apps/management/routes/invitations.d.ts +60 -60
  129. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  130. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  131. package/dist/apps/management/routes/invite-links.js +26 -1
  132. package/dist/apps/management/routes/invite-links.js.map +1 -1
  133. package/dist/apps/management/routes/me.d.ts +6 -6
  134. package/dist/apps/management/routes/members.d.ts +6 -6
  135. package/dist/apps/management/routes/orgs.d.ts +42 -42
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +5 -0
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +44 -44
  140. package/dist/apps/management/routes/usage.d.ts +3 -0
  141. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  142. package/dist/apps/management/routes/usage.js +4 -0
  143. package/dist/apps/management/routes/usage.js.map +1 -1
  144. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  145. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  146. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  147. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  148. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  149. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  150. package/dist/apps/management/routes/webhooks.js +573 -0
  151. package/dist/apps/management/routes/webhooks.js.map +1 -0
  152. package/dist/apps/mpp/App.d.ts +363 -0
  153. package/dist/apps/mpp/App.d.ts.map +1 -0
  154. package/dist/apps/mpp/App.js +72 -0
  155. package/dist/apps/mpp/App.js.map +1 -0
  156. package/dist/apps/mpp/Mpp.d.ts +178 -0
  157. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  158. package/dist/apps/mpp/Mpp.js +183 -0
  159. package/dist/apps/mpp/Mpp.js.map +1 -0
  160. package/dist/apps/mpp/index.d.ts +3 -0
  161. package/dist/apps/mpp/index.d.ts.map +1 -0
  162. package/dist/apps/mpp/index.js +3 -0
  163. package/dist/apps/mpp/index.js.map +1 -0
  164. package/dist/apps/relay/App.d.ts.map +1 -1
  165. package/dist/apps/relay/App.js +56 -9
  166. package/dist/apps/relay/App.js.map +1 -1
  167. package/dist/db/Db.d.ts +6 -0
  168. package/dist/db/Db.d.ts.map +1 -1
  169. package/dist/db/Db.js +110 -0
  170. package/dist/db/Db.js.map +1 -1
  171. package/dist/db/Schema.d.ts +53 -0
  172. package/dist/db/Schema.d.ts.map +1 -1
  173. package/dist/db/Schema.js +56 -0
  174. package/dist/db/Schema.js.map +1 -1
  175. package/dist/db/tables/earlyAccess.d.ts +50 -0
  176. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  177. package/dist/db/tables/earlyAccess.js +63 -0
  178. package/dist/db/tables/earlyAccess.js.map +1 -0
  179. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  180. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  181. package/dist/db/tables/enabledBillingSources.js +69 -0
  182. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  183. package/dist/db/tables/invitations.d.ts.map +1 -1
  184. package/dist/db/tables/invitations.js +3 -0
  185. package/dist/db/tables/invitations.js.map +1 -1
  186. package/dist/db/tables/inviteLinks.d.ts +10 -2
  187. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  188. package/dist/db/tables/inviteLinks.js +10 -1
  189. package/dist/db/tables/inviteLinks.js.map +1 -1
  190. package/dist/db/tables/organizations.d.ts +5 -2
  191. package/dist/db/tables/organizations.d.ts.map +1 -1
  192. package/dist/db/tables/organizations.js +13 -2
  193. package/dist/db/tables/organizations.js.map +1 -1
  194. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  195. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  196. package/dist/db/tables/sponsoredTransactions.js +2 -0
  197. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  198. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  199. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  200. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  201. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  202. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  203. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  204. package/dist/db/tables/webhookSubscriptions.js +16 -0
  205. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  206. package/dist/handlers/internal/utils.d.ts +1 -16
  207. package/dist/handlers/internal/utils.d.ts.map +1 -1
  208. package/dist/handlers/internal/utils.js +17 -6
  209. package/dist/handlers/internal/utils.js.map +1 -1
  210. package/dist/handlers/relay.d.ts.map +1 -1
  211. package/dist/handlers/relay.js +37 -5
  212. package/dist/handlers/relay.js.map +1 -1
  213. package/dist/internal/Auth.d.ts +11 -2
  214. package/dist/internal/Auth.d.ts.map +1 -1
  215. package/dist/internal/Auth.js +14 -47
  216. package/dist/internal/Auth.js.map +1 -1
  217. package/dist/internal/FundingQuotes.d.ts +147 -47
  218. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  219. package/dist/internal/FundingQuotes.js +119 -33
  220. package/dist/internal/FundingQuotes.js.map +1 -1
  221. package/dist/internal/Log.d.ts +37 -5
  222. package/dist/internal/Log.d.ts.map +1 -1
  223. package/dist/internal/Log.js +52 -3
  224. package/dist/internal/Log.js.map +1 -1
  225. package/dist/internal/MetricSink.d.ts.map +1 -1
  226. package/dist/internal/MetricSink.js +24 -0
  227. package/dist/internal/MetricSink.js.map +1 -1
  228. package/dist/internal/OpenApi.d.ts +2 -2
  229. package/dist/internal/OpenApi.d.ts.map +1 -1
  230. package/dist/internal/OpenApi.js +3 -0
  231. package/dist/internal/OpenApi.js.map +1 -1
  232. package/dist/internal/Response.d.ts +2 -2
  233. package/dist/internal/Response.d.ts.map +1 -1
  234. package/dist/internal/Response.js +12 -4
  235. package/dist/internal/Response.js.map +1 -1
  236. package/dist/internal/Schema.d.ts +14 -5
  237. package/dist/internal/Schema.d.ts.map +1 -1
  238. package/dist/internal/Schema.js +23 -5
  239. package/dist/internal/Schema.js.map +1 -1
  240. package/dist/internal/Value.d.ts +18 -0
  241. package/dist/internal/Value.d.ts.map +1 -1
  242. package/dist/internal/Value.js +11 -1
  243. package/dist/internal/Value.js.map +1 -1
  244. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  245. package/dist/internal/VerifiedTokens.js +10 -1
  246. package/dist/internal/VerifiedTokens.js.map +1 -1
  247. package/dist/internal/Webhooks.d.ts +24 -3
  248. package/dist/internal/Webhooks.d.ts.map +1 -1
  249. package/dist/internal/Webhooks.js +34 -8
  250. package/dist/internal/Webhooks.js.map +1 -1
  251. package/package.json +7 -2
  252. package/src/App.test.ts +9 -4
  253. package/src/App.ts +23 -3
  254. package/src/Client.test-d.ts +733 -20
  255. package/src/Scope.ts +5 -0
  256. package/src/admin/App.test.ts +4 -0
  257. package/src/admin/App.ts +4 -0
  258. package/src/admin/apps/early-access.test.ts +125 -0
  259. package/src/admin/apps/early-access.ts +176 -0
  260. package/src/admin/apps/organizations.test.ts +86 -2
  261. package/src/admin/apps/organizations.ts +155 -31
  262. package/src/admin/apps/scopes.test.ts +5 -0
  263. package/src/admin/apps/verified-tokens.test.ts +201 -0
  264. package/src/admin/apps/verified-tokens.ts +532 -286
  265. package/src/admin/ui/plugin.ts +2 -1
  266. package/src/admin/ui/src/lib/early-access.ts +63 -0
  267. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  268. package/src/admin/ui/src/lib/organizations.ts +92 -0
  269. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  270. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  271. package/src/admin/ui/src/routes/__root.tsx +2 -1
  272. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  273. package/src/admin/ui/src/routes/early-access.tsx +162 -0
  274. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  275. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  276. package/src/admin/ui.generated.ts +95 -94
  277. package/src/analytics/Analytics.test.ts +83 -0
  278. package/src/analytics/Analytics.ts +20 -0
  279. package/src/analytics/tables/requestEvents.ts +60 -7
  280. package/src/apps/composition.test.ts +11 -2
  281. package/src/apps/data/App.test.ts +1 -1
  282. package/src/apps/data/App.ts +22 -14
  283. package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
  284. package/src/apps/data/FundingQuoteProviders.ts +255 -29
  285. package/src/apps/data/FxOracle.test.ts +118 -0
  286. package/src/apps/data/FxOracle.ts +153 -0
  287. package/src/apps/data/index.ts +1 -0
  288. package/src/apps/data/routes/activities.test.ts +177 -3
  289. package/src/apps/data/routes/activities.ts +441 -132
  290. package/src/apps/data/routes/addresses.ts +19 -9
  291. package/src/apps/data/routes/balances.test.ts +148 -11
  292. package/src/apps/data/routes/balances.ts +140 -51
  293. package/src/apps/data/routes/exchanges.test.ts +544 -9
  294. package/src/apps/data/routes/exchanges.ts +690 -103
  295. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  296. package/src/apps/data/routes/fee-amm.ts +98 -119
  297. package/src/apps/data/routes/funding.test.ts +113 -8
  298. package/src/apps/data/routes/funding.ts +77 -51
  299. package/src/apps/data/routes/receipts.test.ts +129 -23
  300. package/src/apps/data/routes/receipts.ts +205 -100
  301. package/src/apps/data/routes/tokens.test.ts +43 -0
  302. package/src/apps/data/routes/tokens.ts +28 -13
  303. package/src/apps/data/routes/transactions.test.ts +72 -19
  304. package/src/apps/data/routes/transactions.ts +162 -92
  305. package/src/apps/data/routes/transfers.test.ts +341 -31
  306. package/src/apps/data/routes/transfers.ts +358 -158
  307. package/src/apps/data/routes/valuation.test.ts +437 -0
  308. package/src/apps/data/routes/valuation.ts +423 -0
  309. package/src/apps/data/routes/verified-tokens.ts +14 -2
  310. package/src/apps/data/routes/webhooks.test.ts +56 -8
  311. package/src/apps/data/routes/webhooks.ts +144 -27
  312. package/src/apps/index.ts +2 -1
  313. package/src/apps/management/App.ts +13 -0
  314. package/src/apps/management/routes/api-keys.test.ts +14 -0
  315. package/src/apps/management/routes/api-keys.ts +27 -3
  316. package/src/apps/management/routes/billing.test.ts +152 -20
  317. package/src/apps/management/routes/billing.ts +34 -18
  318. package/src/apps/management/routes/faucet.test.ts +87 -0
  319. package/src/apps/management/routes/faucet.ts +201 -0
  320. package/src/apps/management/routes/invite-links.test.ts +117 -0
  321. package/src/apps/management/routes/invite-links.ts +43 -1
  322. package/src/apps/management/routes/me.test.ts +9 -99
  323. package/src/apps/management/routes/orgs.test.ts +45 -2
  324. package/src/apps/management/routes/orgs.ts +6 -0
  325. package/src/apps/management/routes/scopes.test.ts +1 -0
  326. package/src/apps/management/routes/usage.test.ts +6 -0
  327. package/src/apps/management/routes/usage.ts +7 -0
  328. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  329. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  330. package/src/apps/management/routes/webhooks.test.ts +426 -0
  331. package/src/apps/management/routes/webhooks.ts +672 -0
  332. package/src/apps/mpp/App.ts +86 -0
  333. package/src/apps/mpp/Mpp.test.ts +193 -0
  334. package/src/apps/mpp/Mpp.ts +353 -0
  335. package/src/apps/mpp/index.ts +2 -0
  336. package/src/apps/mpp/internal/Adapter.ts +11 -0
  337. package/src/apps/mpp/internal/Error.ts +9 -0
  338. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  339. package/src/apps/mpp/internal/Screening.ts +10 -0
  340. package/src/apps/relay/App.test.ts +88 -6
  341. package/src/apps/relay/App.ts +61 -9
  342. package/src/db/Db.test.ts +19 -0
  343. package/src/db/Db.ts +125 -0
  344. package/src/db/Schema.ts +68 -0
  345. package/src/db/tables/earlyAccess.ts +86 -0
  346. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  347. package/src/db/tables/enabledBillingSources.ts +118 -0
  348. package/src/db/tables/invitations.ts +3 -0
  349. package/src/db/tables/inviteLinks.ts +22 -3
  350. package/src/db/tables/organizations.test.ts +103 -0
  351. package/src/db/tables/organizations.ts +18 -2
  352. package/src/db/tables/sponsoredTransactions.ts +4 -0
  353. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  354. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  355. package/src/db/tables/webhookSubscriptions.ts +31 -0
  356. package/src/handlers/internal/utils.ts +22 -6
  357. package/src/handlers/relay.errors.test.ts +153 -0
  358. package/src/handlers/relay.ts +42 -7
  359. package/src/internal/Auth.test.ts +3 -3
  360. package/src/internal/Auth.ts +15 -47
  361. package/src/internal/FundingQuotes.test.ts +161 -6
  362. package/src/internal/FundingQuotes.ts +141 -39
  363. package/src/internal/Log.test-d.ts +21 -0
  364. package/src/internal/Log.test.ts +141 -0
  365. package/src/internal/Log.ts +93 -6
  366. package/src/internal/MetricSink.test.ts +215 -0
  367. package/src/internal/MetricSink.ts +24 -0
  368. package/src/internal/OpenApi.ts +3 -0
  369. package/src/internal/Response.test.ts +28 -0
  370. package/src/internal/Response.ts +12 -4
  371. package/src/internal/Schema.ts +50 -5
  372. package/src/internal/Value.test.ts +20 -0
  373. package/src/internal/Value.ts +24 -1
  374. package/src/internal/VerifiedTokens.ts +9 -1
  375. package/src/internal/Webhooks.test.ts +140 -0
  376. package/src/internal/Webhooks.ts +53 -9
@@ -23,7 +23,8 @@ const outFile = path.join(here, '..', 'ui.generated.ts')
23
23
  // Total gzipped budget for the whole UI; the React + TanStack + Regen baseline
24
24
  // is heavier than a hand-rolled SPA, so fail loudly before it bloats the
25
25
  // package. Raise deliberately (and revisit code-splitting) if it trips.
26
- const maxGzipBytes = 405_000
26
+ // Raised from 405 KB for the early-access page.
27
+ const maxGzipBytes = 415_000
27
28
 
28
29
  const contentTypes: Record<string, string> = {
29
30
  '.css': 'text/css; charset=utf-8',
@@ -0,0 +1,63 @@
1
+ import * as ReactQuery from '@tanstack/react-query'
2
+ import type { InferResponseType } from 'hono/client'
3
+
4
+ import { client } from './client'
5
+
6
+ /** A stored allowlist entry (domain or exact email) from the list endpoint. */
7
+ export type Entry = InferResponseType<(typeof client)['early-access']['$get'], 200>['data'][number]
8
+
9
+ /** List early-access allowlist entries. */
10
+ export namespace list {
11
+ export const key = ['early-access'] as const
12
+ export async function fn() {
13
+ const response = await client['early-access'].$get()
14
+ if (!response.ok) throw new Error('Failed to load entries.')
15
+ return response.json()
16
+ }
17
+ export function options() {
18
+ return ReactQuery.queryOptions({ queryFn: fn, queryKey: key, staleTime: 30_000 })
19
+ }
20
+ export function useQuery() {
21
+ return ReactQuery.useQuery(options())
22
+ }
23
+ }
24
+
25
+ /** Add an allowlist entry: a bare domain or a full email. */
26
+ export namespace add {
27
+ export type Input = { entry: string }
28
+ export async function fn(input: Input) {
29
+ const response = await client['early-access'].$post({ json: input })
30
+ if (!response.ok) throw new Error(await errorMessage(response, 'Failed to add entry.'))
31
+ return response.json()
32
+ }
33
+ export function useMutation() {
34
+ const queryClient = ReactQuery.useQueryClient()
35
+ return ReactQuery.useMutation({
36
+ mutationFn: fn,
37
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: list.key }),
38
+ })
39
+ }
40
+ }
41
+
42
+ /** Remove an allowlist entry. */
43
+ export namespace remove {
44
+ export type Input = { entry: string }
45
+ export async function fn({ entry }: Input) {
46
+ const response = await client['early-access'].$delete({ json: { entry } })
47
+ if (!response.ok) throw new Error(await errorMessage(response, 'Failed to remove entry.'))
48
+ return response.json()
49
+ }
50
+ export function useMutation() {
51
+ const queryClient = ReactQuery.useQueryClient()
52
+ return ReactQuery.useMutation({
53
+ mutationFn: fn,
54
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: list.key }),
55
+ })
56
+ }
57
+ }
58
+
59
+ /** Server error-envelope message, with a fallback for non-JSON bodies. */
60
+ async function errorMessage(response: { json(): Promise<unknown> }, fallback: string) {
61
+ const body = (await response.json().catch(() => null)) as { error?: { message?: string } } | null
62
+ return body?.error?.message ?? fallback
63
+ }
@@ -1,9 +1,11 @@
1
1
  import { QueryClient } from '@tanstack/react-query'
2
2
  import { createMemoryHistory, createRouter } from '@tanstack/react-router'
3
+ import { createElement } from 'react'
4
+ import { renderToStaticMarkup } from 'react-dom/server'
3
5
  import { describe, expect, it, vi } from 'vite-plus/test'
4
6
 
5
7
  import { routeTree } from '../routeTree.gen'
6
- import { Route as DetailRoute } from '../routes/organizations_.$orgId'
8
+ import { BillingSourcesCard, Route as DetailRoute } from '../routes/organizations_.$orgId'
7
9
  import { Route as ListRoute } from '../routes/organizations'
8
10
  import * as Organizations from './organizations'
9
11
 
@@ -49,6 +51,84 @@ describe('searchParameters', () => {
49
51
  })
50
52
  })
51
53
 
54
+ describe('withEnabledSources', () => {
55
+ it('replaces only organization-wide billing availability', () => {
56
+ const detail = {
57
+ billing: {
58
+ enabledSources: [],
59
+ production: { settings: null, source: null },
60
+ sandbox: { settings: null, source: null },
61
+ },
62
+ createdAt: '2026-01-01T00:00:00.000Z',
63
+ createdBy: null,
64
+ id: 'org_example',
65
+ memberCount: 0,
66
+ name: 'Example',
67
+ projectCount: 0,
68
+ updatedAt: '2026-01-01T00:00:00.000Z',
69
+ userId: null,
70
+ } satisfies Organizations.Detail
71
+
72
+ const updated = Organizations.withEnabledSources({
73
+ detail,
74
+ enabledSources: ['stripe', 'tempo'],
75
+ })
76
+
77
+ expect({
78
+ original: detail.billing.enabledSources,
79
+ productionPreserved: updated.billing.production === detail.billing.production,
80
+ updated: updated.billing.enabledSources,
81
+ }).toMatchInlineSnapshot(`
82
+ {
83
+ "original": [],
84
+ "productionPreserved": true,
85
+ "updated": [
86
+ "stripe",
87
+ "tempo",
88
+ ],
89
+ }
90
+ `)
91
+ })
92
+ })
93
+
94
+ describe('BillingSourcesCard', () => {
95
+ it('renders source state and the setup-only warning', () => {
96
+ const markup = renderToStaticMarkup(
97
+ createElement(BillingSourcesCard, {
98
+ enabledSources: ['stripe'],
99
+ error: null,
100
+ onChange() {},
101
+ pending: false,
102
+ }),
103
+ )
104
+
105
+ function isChecked(label: string) {
106
+ const marker = `aria-label="${label} billing source"`
107
+ const markerIndex = markup.indexOf(marker)
108
+ if (markerIndex === -1) return undefined
109
+ const tag = markup.slice(
110
+ markup.lastIndexOf('<', markerIndex),
111
+ markup.indexOf('>', markerIndex),
112
+ )
113
+ return tag.includes('aria-checked="true"')
114
+ }
115
+
116
+ expect({
117
+ stripe: isChecked('Stripe'),
118
+ tempo: isChecked('Tempo'),
119
+ warning: markup.includes(
120
+ 'Disabling a source only blocks new setup. Existing billing connections continue to work.',
121
+ ),
122
+ }).toMatchInlineSnapshot(`
123
+ {
124
+ "stripe": true,
125
+ "tempo": false,
126
+ "warning": true,
127
+ }
128
+ `)
129
+ })
130
+ })
131
+
52
132
  describe('organization routes', () => {
53
133
  it('matches a direct detail URL without the list route as a parent', () => {
54
134
  const router = createRouter({
@@ -6,6 +6,10 @@ import { client } from './client'
6
6
 
7
7
  type ListResponse = InferResponseType<(typeof client)['organizations']['$get'], 200>
8
8
  type DetailResponse = InferResponseType<(typeof client)['organizations'][':orgId']['$get'], 200>
9
+ type BillingSourcesResponse = InferResponseType<
10
+ (typeof client)['organizations'][':orgId']['billing-sources'][':source']['$put'],
11
+ 200
12
+ >
9
13
  type MembersResponse = InferResponseType<
10
14
  (typeof client)['organizations'][':orgId']['members']['$get'],
11
15
  200
@@ -37,6 +41,9 @@ export type Organization = ListResponse['data'][number]
37
41
  /** Detailed organization information, including billing state. */
38
42
  export type Detail = DetailResponse
39
43
 
44
+ /** An organization-wide billing source. */
45
+ export type BillingSource = Detail['billing']['enabledSources'][number]
46
+
40
47
  /** An organization member with resolved identity. */
41
48
  export type Member = MembersResponse['data'][number]
42
49
 
@@ -138,6 +145,91 @@ export namespace get {
138
145
  }
139
146
  }
140
147
 
148
+ /** Returns organization detail with its enabled billing sources replaced. */
149
+ export function withEnabledSources(options: withEnabledSources.Options): Detail {
150
+ return {
151
+ ...options.detail,
152
+ billing: {
153
+ ...options.detail.billing,
154
+ enabledSources: options.enabledSources,
155
+ },
156
+ }
157
+ }
158
+
159
+ export declare namespace withEnabledSources {
160
+ /** Inputs for replacing enabled billing sources in cached organization detail. */
161
+ type Options = {
162
+ /** Organization detail to update. */
163
+ detail: Detail
164
+ /** Current enabled billing sources. */
165
+ enabledSources: Detail['billing']['enabledSources']
166
+ }
167
+ }
168
+
169
+ /** Enable an organization-wide billing source. */
170
+ export namespace enableBillingSource {
171
+ /** Billing source to enable for an organization. */
172
+ export type Input = {
173
+ /** Organization id. */
174
+ orgId: string
175
+ /** Billing source to enable. */
176
+ source: BillingSource
177
+ }
178
+
179
+ /** Enables a billing source and returns the current enabled sources. */
180
+ export async function fn(options: Input) {
181
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$put({
182
+ param: options,
183
+ })
184
+ if (!response.ok) throw new Error('Failed to enable billing source.')
185
+ return (await response.json()) as BillingSourcesResponse
186
+ }
187
+
188
+ /** Creates a mutation that enables a billing source. */
189
+ export function useMutation(orgId: string) {
190
+ const queryClient = ReactQuery.useQueryClient()
191
+ return ReactQuery.useMutation({
192
+ mutationFn: (source: BillingSource) => fn({ orgId, source }),
193
+ onSuccess: (response) =>
194
+ queryClient.setQueryData(get.key(orgId), (detail: Detail | undefined) =>
195
+ detail ? withEnabledSources({ detail, enabledSources: response.data }) : detail,
196
+ ),
197
+ })
198
+ }
199
+ }
200
+
201
+ /** Disable an organization-wide billing source. */
202
+ export namespace disableBillingSource {
203
+ /** Billing source to disable for an organization. */
204
+ export type Input = {
205
+ /** Organization id. */
206
+ orgId: string
207
+ /** Billing source to disable. */
208
+ source: BillingSource
209
+ }
210
+
211
+ /** Disables a billing source and returns the current enabled sources. */
212
+ export async function fn(options: Input) {
213
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$delete({
214
+ param: options,
215
+ })
216
+ if (!response.ok) throw new Error('Failed to disable billing source.')
217
+ return (await response.json()) as BillingSourcesResponse
218
+ }
219
+
220
+ /** Creates a mutation that disables a billing source. */
221
+ export function useMutation(orgId: string) {
222
+ const queryClient = ReactQuery.useQueryClient()
223
+ return ReactQuery.useMutation({
224
+ mutationFn: (source: BillingSource) => fn({ orgId, source }),
225
+ onSuccess: (response) =>
226
+ queryClient.setQueryData(get.key(orgId), (detail: Detail | undefined) =>
227
+ detail ? withEnabledSources({ detail, enabledSources: response.data }) : detail,
228
+ ),
229
+ })
230
+ }
231
+ }
232
+
141
233
  /** List members for one organization. */
142
234
  export namespace members {
143
235
  export function key(orgId: string) {
@@ -1,8 +1,14 @@
1
1
  import * as ReactQuery from '@tanstack/react-query'
2
2
  import type { InferRequestType, InferResponseType } from 'hono/client'
3
+ import * as z from 'zod/mini'
3
4
 
4
5
  import { client } from './client'
5
6
 
7
+ /** Route search: the active page tab. */
8
+ export const search = z.object({
9
+ tab: z.catch(z.optional(z.enum(['pending', 'verified'])), undefined),
10
+ })
11
+
6
12
  /** The verified-token list endpoint's success body. */
7
13
  type ListResponse = InferResponseType<(typeof client)['verified-tokens']['$get'], 200>
8
14
 
@@ -67,6 +73,79 @@ export namespace create {
67
73
  }
68
74
  }
69
75
 
76
+ /** Review queue over organization verification requests. */
77
+ export namespace requests {
78
+ /** A verification request record from the queue endpoint. */
79
+ export type VerifiedTokenRequest = InferResponseType<
80
+ (typeof client)['verified-tokens']['requests']['$get'],
81
+ 200
82
+ >['data'][number]
83
+
84
+ export namespace list {
85
+ export function key() {
86
+ return ['verified-token-requests'] as const
87
+ }
88
+ export async function fn() {
89
+ const response = await client['verified-tokens'].requests.$get({ query: {} })
90
+ if (!response.ok) throw new Error('Failed to load requests.')
91
+ return response.json()
92
+ }
93
+ export function options() {
94
+ return ReactQuery.queryOptions({ queryFn: fn, queryKey: key(), staleTime: 30_000 })
95
+ }
96
+ export function useQuery() {
97
+ return ReactQuery.useQuery(options())
98
+ }
99
+ }
100
+
101
+ export namespace approve {
102
+ export type Input = { chainId: number; requestId: string }
103
+ export async function fn({ requestId }: Input) {
104
+ const response = await client['verified-tokens'].requests[':requestId'].approve.$post({
105
+ param: { requestId },
106
+ })
107
+ if (!response.ok) throw new Error(await errorMessage(response, 'Failed to approve request.'))
108
+ return response.json()
109
+ }
110
+ export function useMutation() {
111
+ const queryClient = ReactQuery.useQueryClient()
112
+ return ReactQuery.useMutation({
113
+ mutationFn: fn,
114
+ onSuccess: (_data, input) => {
115
+ void queryClient.invalidateQueries({ queryKey: list.key() })
116
+ // Approval publishes the token onto the chain's verified list.
117
+ void queryClient.invalidateQueries({ queryKey: get.key({ chainId: input.chainId }) })
118
+ },
119
+ })
120
+ }
121
+ }
122
+
123
+ export namespace deny {
124
+ export type Input = { requestId: string; reviewNote?: string }
125
+ export async function fn({ requestId, reviewNote }: Input) {
126
+ const response = await client['verified-tokens'].requests[':requestId'].deny.$post({
127
+ json: reviewNote ? { reviewNote } : {},
128
+ param: { requestId },
129
+ })
130
+ if (!response.ok) throw new Error(await errorMessage(response, 'Failed to deny request.'))
131
+ return response.json()
132
+ }
133
+ export function useMutation() {
134
+ const queryClient = ReactQuery.useQueryClient()
135
+ return ReactQuery.useMutation({
136
+ mutationFn: fn,
137
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: list.key() }),
138
+ })
139
+ }
140
+ }
141
+ }
142
+
143
+ /** Server error-envelope message, with a fallback for non-JSON bodies. */
144
+ async function errorMessage(response: { json(): Promise<unknown> }, fallback: string) {
145
+ const body = (await response.json().catch(() => null)) as { error?: { message?: string } } | null
146
+ return body?.error?.message ?? fallback
147
+ }
148
+
70
149
  /** Remove a verified token from a chain. */
71
150
  export namespace remove {
72
151
  export type Input = { address: `0x${string}`; chainId: number }
@@ -11,6 +11,7 @@
11
11
  import { Route as rootRouteImport } from './routes/__root'
12
12
  import { Route as VerifiedTokensRouteImport } from './routes/verified-tokens'
13
13
  import { Route as OrganizationsRouteImport } from './routes/organizations'
14
+ import { Route as EarlyAccessRouteImport } from './routes/early-access'
14
15
  import { Route as ApiKeysRouteImport } from './routes/api-keys'
15
16
  import { Route as IndexRouteImport } from './routes/index'
16
17
  import { Route as OrganizationsOrgIdRouteImport } from './routes/organizations_.$orgId'
@@ -25,6 +26,11 @@ const OrganizationsRoute = OrganizationsRouteImport.update({
25
26
  path: '/organizations',
26
27
  getParentRoute: () => rootRouteImport,
27
28
  } as any)
29
+ const EarlyAccessRoute = EarlyAccessRouteImport.update({
30
+ id: '/early-access',
31
+ path: '/early-access',
32
+ getParentRoute: () => rootRouteImport,
33
+ } as any)
28
34
  const ApiKeysRoute = ApiKeysRouteImport.update({
29
35
  id: '/api-keys',
30
36
  path: '/api-keys',
@@ -44,6 +50,7 @@ const OrganizationsOrgIdRoute = OrganizationsOrgIdRouteImport.update({
44
50
  export interface FileRoutesByFullPath {
45
51
  '/': typeof IndexRoute
46
52
  '/api-keys': typeof ApiKeysRoute
53
+ '/early-access': typeof EarlyAccessRoute
47
54
  '/organizations': typeof OrganizationsRoute
48
55
  '/verified-tokens': typeof VerifiedTokensRoute
49
56
  '/organizations/$orgId': typeof OrganizationsOrgIdRoute
@@ -51,6 +58,7 @@ export interface FileRoutesByFullPath {
51
58
  export interface FileRoutesByTo {
52
59
  '/': typeof IndexRoute
53
60
  '/api-keys': typeof ApiKeysRoute
61
+ '/early-access': typeof EarlyAccessRoute
54
62
  '/organizations': typeof OrganizationsRoute
55
63
  '/verified-tokens': typeof VerifiedTokensRoute
56
64
  '/organizations/$orgId': typeof OrganizationsOrgIdRoute
@@ -59,19 +67,33 @@ export interface FileRoutesById {
59
67
  __root__: typeof rootRouteImport
60
68
  '/': typeof IndexRoute
61
69
  '/api-keys': typeof ApiKeysRoute
70
+ '/early-access': typeof EarlyAccessRoute
62
71
  '/organizations': typeof OrganizationsRoute
63
72
  '/verified-tokens': typeof VerifiedTokensRoute
64
73
  '/organizations_/$orgId': typeof OrganizationsOrgIdRoute
65
74
  }
66
75
  export interface FileRouteTypes {
67
76
  fileRoutesByFullPath: FileRoutesByFullPath
68
- fullPaths: '/' | '/api-keys' | '/organizations' | '/verified-tokens' | '/organizations/$orgId'
77
+ fullPaths:
78
+ | '/'
79
+ | '/api-keys'
80
+ | '/early-access'
81
+ | '/organizations'
82
+ | '/verified-tokens'
83
+ | '/organizations/$orgId'
69
84
  fileRoutesByTo: FileRoutesByTo
70
- to: '/' | '/api-keys' | '/organizations' | '/verified-tokens' | '/organizations/$orgId'
85
+ to:
86
+ | '/'
87
+ | '/api-keys'
88
+ | '/early-access'
89
+ | '/organizations'
90
+ | '/verified-tokens'
91
+ | '/organizations/$orgId'
71
92
  id:
72
93
  | '__root__'
73
94
  | '/'
74
95
  | '/api-keys'
96
+ | '/early-access'
75
97
  | '/organizations'
76
98
  | '/verified-tokens'
77
99
  | '/organizations_/$orgId'
@@ -80,6 +102,7 @@ export interface FileRouteTypes {
80
102
  export interface RootRouteChildren {
81
103
  IndexRoute: typeof IndexRoute
82
104
  ApiKeysRoute: typeof ApiKeysRoute
105
+ EarlyAccessRoute: typeof EarlyAccessRoute
83
106
  OrganizationsRoute: typeof OrganizationsRoute
84
107
  VerifiedTokensRoute: typeof VerifiedTokensRoute
85
108
  OrganizationsOrgIdRoute: typeof OrganizationsOrgIdRoute
@@ -101,6 +124,13 @@ declare module '@tanstack/react-router' {
101
124
  preLoaderRoute: typeof OrganizationsRouteImport
102
125
  parentRoute: typeof rootRouteImport
103
126
  }
127
+ '/early-access': {
128
+ id: '/early-access'
129
+ path: '/early-access'
130
+ fullPath: '/early-access'
131
+ preLoaderRoute: typeof EarlyAccessRouteImport
132
+ parentRoute: typeof rootRouteImport
133
+ }
104
134
  '/api-keys': {
105
135
  id: '/api-keys'
106
136
  path: '/api-keys'
@@ -128,6 +158,7 @@ declare module '@tanstack/react-router' {
128
158
  const rootRouteChildren: RootRouteChildren = {
129
159
  IndexRoute: IndexRoute,
130
160
  ApiKeysRoute: ApiKeysRoute,
161
+ EarlyAccessRoute: EarlyAccessRoute,
131
162
  OrganizationsRoute: OrganizationsRoute,
132
163
  VerifiedTokensRoute: VerifiedTokensRoute,
133
164
  OrganizationsOrgIdRoute: OrganizationsOrgIdRoute,
@@ -10,7 +10,7 @@ import type { QueryClient } from '@tanstack/react-query'
10
10
  import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
11
11
  import { type ComponentType } from 'react'
12
12
  import { Button, Identicon } from 'regen-ui'
13
- import { BadgeCheck, Building2, KeyRound, LogOut } from 'lucide-react'
13
+ import { BadgeCheck, Building2, KeyRound, LogOut, Ticket } from 'lucide-react'
14
14
  import { WagmiProvider } from 'wagmi'
15
15
 
16
16
  import appCss from '../styles.css?url'
@@ -115,6 +115,7 @@ const nav = [
115
115
  { exact: false, href: '/organizations', icon: Building2, label: 'Organizations' },
116
116
  { exact: false, href: '/api-keys', icon: KeyRound, label: 'API Keys' },
117
117
  { exact: false, href: '/verified-tokens', icon: BadgeCheck, label: 'Verified Tokens' },
118
+ { exact: false, href: '/early-access', icon: Ticket, label: 'Early Access' },
118
119
  ] as const
119
120
 
120
121
  function Sidebar(props: { email: string; logoutUrl?: string }) {
@@ -218,7 +218,7 @@ function ProvisionForm(props: {
218
218
  </select>
219
219
  </div>
220
220
  <Input
221
- defaultValue="100"
221
+ defaultValue="10000"
222
222
  label="Rate limit"
223
223
  min={0}
224
224
  name="rateLimit"
@@ -0,0 +1,162 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useState } from 'react'
3
+ import { Button, Fields, Input } from 'regen-ui'
4
+ import { Plus, Trash2 } from 'lucide-react'
5
+
6
+ import { Card, Notice, Page } from '../components/Page'
7
+ import { Dialog } from '../components/Dialog'
8
+ import * as EarlyAccess from '../lib/early-access'
9
+
10
+ export const Route = createFileRoute('/early-access')({
11
+ loader: ({ context }) => context.queryClient.prefetchQuery(EarlyAccess.list.options()),
12
+ component: RouteComponent,
13
+ })
14
+
15
+ function RouteComponent() {
16
+ const [open, setOpen] = useState(false)
17
+ const entries = EarlyAccess.list.useQuery()
18
+ const add = EarlyAccess.add.useMutation()
19
+
20
+ return (
21
+ <Dialog.Root onOpenChange={setOpen} open={open}>
22
+ <Page
23
+ title="Early Access"
24
+ description="Accounts whose new organizations receive early access features by default."
25
+ action={
26
+ <Dialog.Trigger
27
+ render={
28
+ <Button variant="primary">
29
+ <Plus className="size-4" />
30
+ Add entry
31
+ </Button>
32
+ }
33
+ />
34
+ }
35
+ >
36
+ <EntryList
37
+ entries={entries.data?.data ?? []}
38
+ error={entries.error}
39
+ loading={entries.isPending}
40
+ />
41
+ <Dialog.Popup>
42
+ <AddEntryForm
43
+ onSubmit={(input) =>
44
+ add.mutate(input, {
45
+ onError: (error) => alert(error.message),
46
+ onSuccess: () => setOpen(false),
47
+ })
48
+ }
49
+ pending={add.isPending}
50
+ />
51
+ </Dialog.Popup>
52
+ </Page>
53
+ </Dialog.Root>
54
+ )
55
+ }
56
+
57
+ function AddEntryForm(props: {
58
+ onSubmit: (input: EarlyAccess.add.Input) => void
59
+ pending: boolean
60
+ }) {
61
+ function onSubmit(event: React.FormEvent<HTMLFormElement>) {
62
+ event.preventDefault()
63
+ const entry = new FormData(event.currentTarget).get('entry')
64
+ if (typeof entry !== 'string' || !entry.trim()) return
65
+ props.onSubmit({ entry: entry.trim() })
66
+ }
67
+
68
+ return (
69
+ <>
70
+ <Dialog.Title>Add Entry</Dialog.Title>
71
+ <Dialog.Description>
72
+ Grant early access features to new organizations created by a whole domain or a single email
73
+ </Dialog.Description>
74
+ <form className="mt-6 flex flex-col gap-8" onSubmit={onSubmit}>
75
+ <Fields className="gap-6" variant="default">
76
+ <Input
77
+ autoComplete="off"
78
+ label="Domain or email"
79
+ name="entry"
80
+ placeholder="example.org or user@example.org"
81
+ required
82
+ spellCheck={false}
83
+ />
84
+ </Fields>
85
+ <div className="flex justify-end gap-2">
86
+ <Dialog.Close render={<Button type="button">Cancel</Button>} />
87
+ <Button loading={props.pending} type="submit" variant="primary">
88
+ Add entry
89
+ </Button>
90
+ </div>
91
+ </form>
92
+ </>
93
+ )
94
+ }
95
+
96
+ function EntryList(props: {
97
+ entries: readonly EarlyAccess.Entry[]
98
+ error: Error | null
99
+ loading: boolean
100
+ }) {
101
+ return (
102
+ <Card>
103
+ {props.loading ? (
104
+ <Notice>Loading entries…</Notice>
105
+ ) : props.error ? (
106
+ <Notice>{props.error.message}</Notice>
107
+ ) : props.entries.length === 0 ? (
108
+ <Notice>No entries yet.</Notice>
109
+ ) : (
110
+ <ul className="divide-y divide-border">
111
+ {props.entries.map((entry) => (
112
+ <EntryRow entry={entry} key={entry.entry} />
113
+ ))}
114
+ </ul>
115
+ )}
116
+ </Card>
117
+ )
118
+ }
119
+
120
+ function EntryRow(props: { entry: EarlyAccess.Entry }) {
121
+ const { entry } = props
122
+ const remove = EarlyAccess.remove.useMutation()
123
+
124
+ function onRemove() {
125
+ if (
126
+ !confirm(
127
+ `Remove ${entry.entry}? Future organizations created by matching users start without early access features. Existing organizations keep their enabled billing sources.`,
128
+ )
129
+ )
130
+ return
131
+ remove.mutate({ entry: entry.entry }, { onError: (error) => alert(error.message) })
132
+ }
133
+
134
+ return (
135
+ <li className="flex items-center justify-between gap-4 px-5 py-2.5">
136
+ <div className="flex min-w-0 flex-col gap-1">
137
+ <span className="truncate text-label-14 text-foreground">{entry.entry}</span>
138
+ <span className="text-label-12 text-foreground-secondary">
139
+ Added by {entry.createdBy} · {formatDate(entry.createdAt)}
140
+ </span>
141
+ </div>
142
+ <Button.Icon
143
+ className="text-foreground-secondary hover:text-negative"
144
+ disabled={remove.isPending}
145
+ onClick={onRemove}
146
+ size="lg"
147
+ title="Remove entry"
148
+ variant="plain"
149
+ >
150
+ <Trash2 />
151
+ </Button.Icon>
152
+ </li>
153
+ )
154
+ }
155
+
156
+ function formatDate(iso: string) {
157
+ return new Date(iso).toLocaleDateString(undefined, {
158
+ day: 'numeric',
159
+ month: 'short',
160
+ year: 'numeric',
161
+ })
162
+ }