tapimo 0.6.0 → 0.7.1
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.
- package/dist/App.d.ts +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +47 -39
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +8050 -8019
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +669 -669
- package/dist/apps/data/routes/fee-amm.d.ts +157 -143
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +30 -30
- package/dist/apps/data/routes/receipts.d.ts +8 -8
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +12 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +5 -1
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +6 -6
- package/dist/apps/data/routes/transfers.d.ts +101 -90
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +2 -2
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +34 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +2 -2
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +48 -34
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +73 -73
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +57 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.js +1 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +32 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +13 -3
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +3 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +2 -2
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +49 -39
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +14 -2
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokenlist.test.ts +15 -0
- package/src/apps/data/routes/tokenlist.ts +18 -5
- package/src/apps/data/routes/tokens.ts +5 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/data/routes/verified-tokens.test.ts +51 -0
- package/src/apps/data/routes/verified-tokens.ts +52 -15
- package/src/apps/data/routes/webhooks.test.ts +56 -0
- package/src/apps/data/routes/webhooks.ts +17 -8
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +90 -8
- package/src/apps/relay/App.ts +62 -10
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +191 -0
- package/src/db/tables/organizations.ts +44 -3
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -6,6 +6,7 @@ import * as ApiKeys from '../../ApiKeys.js'
|
|
|
6
6
|
import * as core_Billing from '../../apps/management/Billing.js'
|
|
7
7
|
import * as BillingSettings from '../../db/tables/billingSettings.js'
|
|
8
8
|
import * as Db from '../../db/Db.js'
|
|
9
|
+
import * as EnabledBillingSources from '../../db/tables/enabledBillingSources.js'
|
|
9
10
|
import * as Memberships from '../../db/tables/memberships.js'
|
|
10
11
|
import * as Organizations from '../../db/tables/organizations.js'
|
|
11
12
|
import * as Projects from '../../db/tables/projects.js'
|
|
@@ -26,6 +27,21 @@ export namespace schema {
|
|
|
26
27
|
.string()
|
|
27
28
|
.check(z.describe('An ISO 8601 timestamp.'), z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }))
|
|
28
29
|
|
|
30
|
+
const enabledBillingSources = z
|
|
31
|
+
.array(z.enum(EnabledBillingSources.sources))
|
|
32
|
+
.check(
|
|
33
|
+
z.describe('Billing-source types available for new setup.'),
|
|
34
|
+
z.meta({ examples: [['stripe', 'tempo']] }),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const organizationId = z
|
|
38
|
+
.string()
|
|
39
|
+
.check(
|
|
40
|
+
z.minLength(1),
|
|
41
|
+
z.describe('Stable organization id.'),
|
|
42
|
+
z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
43
|
+
)
|
|
44
|
+
|
|
29
45
|
const Organization = Schema.describe(
|
|
30
46
|
z.object({
|
|
31
47
|
createdAt: timestamp,
|
|
@@ -118,13 +134,7 @@ export namespace schema {
|
|
|
118
134
|
/** Path parameters identifying an organization. */
|
|
119
135
|
export const Params = z
|
|
120
136
|
.object({
|
|
121
|
-
orgId:
|
|
122
|
-
.string()
|
|
123
|
-
.check(
|
|
124
|
-
z.minLength(1),
|
|
125
|
-
z.describe('Stable organization id.'),
|
|
126
|
-
z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
127
|
-
),
|
|
137
|
+
orgId: organizationId,
|
|
128
138
|
})
|
|
129
139
|
.check(z.describe('Path parameters for one organization.'))
|
|
130
140
|
|
|
@@ -252,29 +262,58 @@ export namespace schema {
|
|
|
252
262
|
/** Detailed organization lookup response. */
|
|
253
263
|
export const Response = Schema.describe(
|
|
254
264
|
z.extend(listOrganizations.OrganizationSummary, {
|
|
255
|
-
billing: z
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
billing: z
|
|
266
|
+
.object({
|
|
267
|
+
enabledSources: enabledBillingSources,
|
|
268
|
+
production: Billing,
|
|
269
|
+
sandbox: Billing,
|
|
270
|
+
})
|
|
271
|
+
.check(
|
|
272
|
+
z.describe('Organization-wide availability and environment billing state.'),
|
|
273
|
+
z.meta({
|
|
274
|
+
examples: [
|
|
275
|
+
{
|
|
276
|
+
enabledSources: ['stripe', 'tempo'],
|
|
277
|
+
production: {
|
|
278
|
+
settings: null,
|
|
279
|
+
source: {
|
|
280
|
+
status: 'active',
|
|
281
|
+
stripeCustomerId: 'cus_abc123',
|
|
282
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
283
|
+
},
|
|
266
284
|
},
|
|
285
|
+
sandbox: { settings: null, source: null },
|
|
267
286
|
},
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}),
|
|
272
|
-
),
|
|
287
|
+
],
|
|
288
|
+
}),
|
|
289
|
+
),
|
|
273
290
|
}),
|
|
274
291
|
'Detailed organization information for administrators.',
|
|
275
292
|
)
|
|
276
293
|
}
|
|
277
294
|
|
|
295
|
+
/** Schemas for the setBillingSource operation. */
|
|
296
|
+
export namespace setBillingSource {
|
|
297
|
+
/** Path parameters identifying an organization billing source. */
|
|
298
|
+
export const Params = z
|
|
299
|
+
.object({
|
|
300
|
+
orgId: organizationId,
|
|
301
|
+
source: z
|
|
302
|
+
.enum(EnabledBillingSources.sources)
|
|
303
|
+
.check(
|
|
304
|
+
z.describe('Billing-source type to enable or disable.'),
|
|
305
|
+
z.meta({ examples: ['stripe'] }),
|
|
306
|
+
),
|
|
307
|
+
})
|
|
308
|
+
.check(z.describe('Path parameters for one organization billing source.'))
|
|
309
|
+
|
|
310
|
+
/** Current enabled billing sources. */
|
|
311
|
+
export const Response = Schema.describe(
|
|
312
|
+
z.object({ data: enabledBillingSources }),
|
|
313
|
+
'Current enabled billing sources for the organization.',
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
278
317
|
/** Schemas for the listOrganizationMembers operation. */
|
|
279
318
|
export namespace listOrganizationMembers {
|
|
280
319
|
/** Query parameters for paginating organization members. */
|
|
@@ -502,7 +541,7 @@ export namespace schema {
|
|
|
502
541
|
}
|
|
503
542
|
}
|
|
504
543
|
|
|
505
|
-
/**
|
|
544
|
+
/** Organization lookup and support routes for administrators. */
|
|
506
545
|
export function organizations() {
|
|
507
546
|
return new Hono<App.Environment>()
|
|
508
547
|
.get(
|
|
@@ -576,17 +615,24 @@ export function organizations() {
|
|
|
576
615
|
const organization = await Organizations.getSummary(db, orgId)
|
|
577
616
|
if (!organization) return organizationNotFound(c)
|
|
578
617
|
|
|
579
|
-
const [
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
618
|
+
const [
|
|
619
|
+
enabledSources,
|
|
620
|
+
productionSettings,
|
|
621
|
+
productionSource,
|
|
622
|
+
sandboxSettings,
|
|
623
|
+
sandboxSource,
|
|
624
|
+
] = await Promise.all([
|
|
625
|
+
EnabledBillingSources.listByOrg(db, orgId),
|
|
626
|
+
BillingSettings.get(db, orgId, 'production'),
|
|
627
|
+
StripeCustomers.get(db, orgId, 'production'),
|
|
628
|
+
BillingSettings.get(db, orgId, 'sandbox'),
|
|
629
|
+
StripeCustomers.get(db, orgId, 'sandbox'),
|
|
630
|
+
])
|
|
586
631
|
return c.json(
|
|
587
632
|
Response.validated(schema.getOrganization.Response, {
|
|
588
633
|
...organization,
|
|
589
634
|
billing: {
|
|
635
|
+
enabledSources: enabledSources.map((record) => record.source),
|
|
590
636
|
production: serializeBilling(productionSettings, productionSource),
|
|
591
637
|
sandbox: serializeBilling(sandboxSettings, sandboxSource),
|
|
592
638
|
},
|
|
@@ -598,6 +644,84 @@ export function organizations() {
|
|
|
598
644
|
}
|
|
599
645
|
},
|
|
600
646
|
)
|
|
647
|
+
.put(
|
|
648
|
+
'/:orgId/billing-sources/:source',
|
|
649
|
+
OpenApi.validate('param', schema.setBillingSource.Params, {
|
|
650
|
+
code: 'param_invalid',
|
|
651
|
+
message: 'Invalid path parameters',
|
|
652
|
+
}),
|
|
653
|
+
OpenApi.describeRoute({
|
|
654
|
+
operationId: 'enableAdminOrganizationBillingSource',
|
|
655
|
+
responses: OpenApi.responses({
|
|
656
|
+
errors: { 404: 'Organization not found.' },
|
|
657
|
+
success: {
|
|
658
|
+
description: 'Current enabled billing sources.',
|
|
659
|
+
schema: schema.setBillingSource.Response,
|
|
660
|
+
},
|
|
661
|
+
}),
|
|
662
|
+
summary: 'Enable billing source',
|
|
663
|
+
tags: ['Organizations'],
|
|
664
|
+
}),
|
|
665
|
+
async (c) => {
|
|
666
|
+
const { orgId, source } = c.req.valid('param')
|
|
667
|
+
const db = Db.get(c.get('db'))
|
|
668
|
+
try {
|
|
669
|
+
if (!(await Organizations.get(db, orgId))) return organizationNotFound(c)
|
|
670
|
+
await EnabledBillingSources.add(db, {
|
|
671
|
+
createdBy: c.get('identity')!.email,
|
|
672
|
+
orgId,
|
|
673
|
+
source,
|
|
674
|
+
})
|
|
675
|
+
return c.json(
|
|
676
|
+
Response.validated(schema.setBillingSource.Response, {
|
|
677
|
+
data: (await EnabledBillingSources.listByOrg(db, orgId)).map(
|
|
678
|
+
(record) => record.source,
|
|
679
|
+
),
|
|
680
|
+
}),
|
|
681
|
+
200,
|
|
682
|
+
)
|
|
683
|
+
} catch (cause) {
|
|
684
|
+
return Response.upstream(c, cause)
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
)
|
|
688
|
+
.delete(
|
|
689
|
+
'/:orgId/billing-sources/:source',
|
|
690
|
+
OpenApi.validate('param', schema.setBillingSource.Params, {
|
|
691
|
+
code: 'param_invalid',
|
|
692
|
+
message: 'Invalid path parameters',
|
|
693
|
+
}),
|
|
694
|
+
OpenApi.describeRoute({
|
|
695
|
+
operationId: 'disableAdminOrganizationBillingSource',
|
|
696
|
+
responses: OpenApi.responses({
|
|
697
|
+
errors: { 404: 'Organization not found.' },
|
|
698
|
+
success: {
|
|
699
|
+
description: 'Current enabled billing sources.',
|
|
700
|
+
schema: schema.setBillingSource.Response,
|
|
701
|
+
},
|
|
702
|
+
}),
|
|
703
|
+
summary: 'Disable billing source',
|
|
704
|
+
tags: ['Organizations'],
|
|
705
|
+
}),
|
|
706
|
+
async (c) => {
|
|
707
|
+
const { orgId, source } = c.req.valid('param')
|
|
708
|
+
const db = Db.get(c.get('db'))
|
|
709
|
+
try {
|
|
710
|
+
if (!(await Organizations.get(db, orgId))) return organizationNotFound(c)
|
|
711
|
+
await EnabledBillingSources.remove(db, { orgId, source })
|
|
712
|
+
return c.json(
|
|
713
|
+
Response.validated(schema.setBillingSource.Response, {
|
|
714
|
+
data: (await EnabledBillingSources.listByOrg(db, orgId)).map(
|
|
715
|
+
(record) => record.source,
|
|
716
|
+
),
|
|
717
|
+
}),
|
|
718
|
+
200,
|
|
719
|
+
)
|
|
720
|
+
} catch (cause) {
|
|
721
|
+
return Response.upstream(c, cause)
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
)
|
|
601
725
|
.get(
|
|
602
726
|
'/:orgId/members',
|
|
603
727
|
OpenApi.validate('param', schema.getOrganization.Params, {
|
|
@@ -34,6 +34,11 @@ describe('GET /scopes', () => {
|
|
|
34
34
|
"scope": "management:write",
|
|
35
35
|
"selfServe": true,
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Validate and broadcast MPP payment credentials through the Tempo relay.",
|
|
39
|
+
"scope": "mpp:write",
|
|
40
|
+
"selfServe": true,
|
|
41
|
+
},
|
|
37
42
|
{
|
|
38
43
|
"description": "Fill and submit transactions through the relay.",
|
|
39
44
|
"scope": "rpc-relay:read",
|
|
@@ -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) {
|
|
@@ -21,7 +21,7 @@ function RouteComponent() {
|
|
|
21
21
|
<Dialog.Root onOpenChange={setOpen} open={open}>
|
|
22
22
|
<Page
|
|
23
23
|
title="Early Access"
|
|
24
|
-
description="
|
|
24
|
+
description="Accounts whose new organizations receive early access features by default."
|
|
25
25
|
action={
|
|
26
26
|
<Dialog.Trigger
|
|
27
27
|
render={
|
|
@@ -69,7 +69,7 @@ function AddEntryForm(props: {
|
|
|
69
69
|
<>
|
|
70
70
|
<Dialog.Title>Add Entry</Dialog.Title>
|
|
71
71
|
<Dialog.Description>
|
|
72
|
-
Grant early access to a whole domain or a single email
|
|
72
|
+
Grant early access features to new organizations created by a whole domain or a single email
|
|
73
73
|
</Dialog.Description>
|
|
74
74
|
<form className="mt-6 flex flex-col gap-8" onSubmit={onSubmit}>
|
|
75
75
|
<Fields className="gap-6" variant="default">
|
|
@@ -122,7 +122,12 @@ function EntryRow(props: { entry: EarlyAccess.Entry }) {
|
|
|
122
122
|
const remove = EarlyAccess.remove.useMutation()
|
|
123
123
|
|
|
124
124
|
function onRemove() {
|
|
125
|
-
if (
|
|
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
|
|
126
131
|
remove.mutate({ entry: entry.entry }, { onError: (error) => alert(error.message) })
|
|
127
132
|
}
|
|
128
133
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Switch } from '@base-ui/react/switch'
|
|
1
2
|
import {
|
|
2
3
|
type UseQueryResult,
|
|
3
4
|
useInfiniteQuery,
|
|
@@ -30,6 +31,8 @@ function PageComponent() {
|
|
|
30
31
|
const { period: period_search, query } = Route.useSearch()
|
|
31
32
|
const navigate = useNavigate({ from: Route.fullPath })
|
|
32
33
|
const period = period_search ?? '7d'
|
|
34
|
+
const disableBillingSource = Organizations.disableBillingSource.useMutation(orgId)
|
|
35
|
+
const enableBillingSource = Organizations.enableBillingSource.useMutation(orgId)
|
|
33
36
|
const organizationQuery = useSuspenseQuery(Organizations.get.options(orgId))
|
|
34
37
|
const organization = organizationQuery.data
|
|
35
38
|
const members = useInfiniteQuery(Organizations.members.options(orgId))
|
|
@@ -54,6 +57,8 @@ function PageComponent() {
|
|
|
54
57
|
const updateComplete = operationalQueries.every((result) => result.dataUpdatedAt > 0)
|
|
55
58
|
const updateError = operationalQueries.some((result) => result.error)
|
|
56
59
|
const refreshing = operationalQueries.some((result) => result.isFetching)
|
|
60
|
+
const billingSourceError = disableBillingSource.error ?? enableBillingSource.error
|
|
61
|
+
const billingSourcePending = disableBillingSource.isPending || enableBillingSource.isPending
|
|
57
62
|
|
|
58
63
|
function refresh() {
|
|
59
64
|
void Promise.all([
|
|
@@ -67,6 +72,16 @@ function PageComponent() {
|
|
|
67
72
|
])
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
function setBillingSource(options: BillingSourceChange) {
|
|
76
|
+
if (options.enabled) {
|
|
77
|
+
disableBillingSource.reset()
|
|
78
|
+
enableBillingSource.mutate(options.source)
|
|
79
|
+
} else {
|
|
80
|
+
enableBillingSource.reset()
|
|
81
|
+
disableBillingSource.mutate(options.source)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
return (
|
|
71
86
|
<Page
|
|
72
87
|
title={organization.name}
|
|
@@ -176,6 +191,12 @@ function PageComponent() {
|
|
|
176
191
|
|
|
177
192
|
<section className="flex flex-col gap-3">
|
|
178
193
|
<h2 className="text-heading-18 text-foreground">Billing</h2>
|
|
194
|
+
<BillingSourcesCard
|
|
195
|
+
enabledSources={organization.billing.enabledSources}
|
|
196
|
+
error={billingSourceError}
|
|
197
|
+
onChange={setBillingSource}
|
|
198
|
+
pending={billingSourcePending}
|
|
199
|
+
/>
|
|
179
200
|
<div className="grid gap-3 md:grid-cols-2">
|
|
180
201
|
<BillingCard
|
|
181
202
|
environment="Production"
|
|
@@ -550,6 +571,88 @@ declare namespace SectionError {
|
|
|
550
571
|
type Props = { error: Error; onRetry: () => unknown }
|
|
551
572
|
}
|
|
552
573
|
|
|
574
|
+
type BillingSourceChange = {
|
|
575
|
+
/** Whether the source should be enabled. */
|
|
576
|
+
enabled: boolean
|
|
577
|
+
/** Billing source being changed. */
|
|
578
|
+
source: Organizations.BillingSource
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const billingSourceOptions = [
|
|
582
|
+
{
|
|
583
|
+
description: 'Allow the organization to set up Stripe as a billing method.',
|
|
584
|
+
label: 'Stripe',
|
|
585
|
+
source: 'stripe',
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
description: 'Allow the organization to set up a Tempo account as a billing method.',
|
|
589
|
+
label: 'Tempo',
|
|
590
|
+
source: 'tempo',
|
|
591
|
+
},
|
|
592
|
+
] as const
|
|
593
|
+
|
|
594
|
+
/** Organization-wide controls for billing-source setup. */
|
|
595
|
+
export function BillingSourcesCard(options: BillingSourcesCard.Props) {
|
|
596
|
+
return (
|
|
597
|
+
<Card>
|
|
598
|
+
<div className="p-4">
|
|
599
|
+
<h3 className="text-label-14 text-foreground">Enabled sources</h3>
|
|
600
|
+
<p className="mt-1 text-copy-13 text-foreground-secondary">
|
|
601
|
+
Disabling a source only blocks new setup. Existing billing connections continue to work.
|
|
602
|
+
</p>
|
|
603
|
+
</div>
|
|
604
|
+
<div className="divide-y divide-border border-t border-border">
|
|
605
|
+
{billingSourceOptions.map((source) => {
|
|
606
|
+
const enabled = options.enabledSources.includes(source.source)
|
|
607
|
+
return (
|
|
608
|
+
<div className="flex items-center justify-between gap-4 p-4" key={source.source}>
|
|
609
|
+
<div className="min-w-0">
|
|
610
|
+
<div className="flex items-center gap-2">
|
|
611
|
+
<span className="text-label-14 text-foreground">{source.label}</span>
|
|
612
|
+
<span className="text-label-12 text-foreground-secondary">
|
|
613
|
+
{enabled ? 'Enabled' : 'Disabled'}
|
|
614
|
+
</span>
|
|
615
|
+
</div>
|
|
616
|
+
<p className="mt-1 text-copy-13 text-foreground-secondary">{source.description}</p>
|
|
617
|
+
</div>
|
|
618
|
+
<Switch.Root
|
|
619
|
+
aria-label={`${source.label} billing source`}
|
|
620
|
+
checked={enabled}
|
|
621
|
+
className="flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-border bg-secondary p-0.5 outline-none transition-colors hover:bg-secondary-hover focus-visible:border-focus data-[checked]:bg-invert disabled:cursor-not-allowed disabled:opacity-50"
|
|
622
|
+
disabled={options.pending}
|
|
623
|
+
onCheckedChange={(checked) =>
|
|
624
|
+
options.onChange({ enabled: checked, source: source.source })
|
|
625
|
+
}
|
|
626
|
+
>
|
|
627
|
+
<Switch.Thumb className="block size-4 translate-x-0 rounded-full bg-foreground-secondary transition-transform data-[checked]:translate-x-4 data-[checked]:bg-on-invert" />
|
|
628
|
+
</Switch.Root>
|
|
629
|
+
</div>
|
|
630
|
+
)
|
|
631
|
+
})}
|
|
632
|
+
</div>
|
|
633
|
+
{options.error ? (
|
|
634
|
+
<p className="border-t border-border p-4 text-copy-13 text-negative" role="alert">
|
|
635
|
+
{options.error.message}
|
|
636
|
+
</p>
|
|
637
|
+
) : null}
|
|
638
|
+
</Card>
|
|
639
|
+
)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export declare namespace BillingSourcesCard {
|
|
643
|
+
/** Billing-source control properties. */
|
|
644
|
+
type Props = {
|
|
645
|
+
/** Currently enabled billing sources. */
|
|
646
|
+
enabledSources: readonly Organizations.BillingSource[]
|
|
647
|
+
/** Latest mutation error. */
|
|
648
|
+
error: Error | null
|
|
649
|
+
/** Handles a requested source change. */
|
|
650
|
+
onChange: (options: BillingSourceChange) => void
|
|
651
|
+
/** Whether a source change is pending. */
|
|
652
|
+
pending: boolean
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
553
656
|
function BillingCard(options: BillingCard.Props) {
|
|
554
657
|
const { settings, source } = options.record
|
|
555
658
|
const remaining =
|