@capxul/sdk-react 2.1.1 → 2.3.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.
package/dist/index.mjs CHANGED
@@ -1,166 +1,9 @@
1
1
  "use client";
2
- import { a as useCapxulAuth, c as useCapxulIdentityOrNull, d as capxulKeys, f as useCapxulClientOrNull, i as entered, l as useCapxulSend, n as CapxulOnboardingController, o as useCapxulDestination, p as useCapxul, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-Chqyy3HR.mjs";
3
- import { useEffect, useState } from "react";
2
+ import { a as useCapxulAuth, c as useCapxulIdentityOrNull, d as capxulKeys, f as useCapxulClientOrNull, i as entered, l as useCapxulSend, n as CapxulOnboardingController, o as useCapxulDestination, p as useCapxul, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-VwUPB619.mjs";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
- import { fingerprintPaymentIntent } from "@capxul/sdk";
6
- //#region ../errors/src/errors.ts
7
- const CAPXUL_ERROR_CODES = [
8
- "NOT_AUTHENTICATED",
9
- "EMAIL_DELIVERY_FAILED",
10
- "PROFILE_NOT_FOUND",
11
- "SMART_ACCOUNT_MISSING",
12
- "PLAYER_NOT_FOUND",
13
- "ACCOUNT_NOT_FOUND",
14
- "PROVIDER_ERROR",
15
- "CAPABILITY_UNAVAILABLE",
16
- "INVALID_INPUT",
17
- "ENV_MISSING",
18
- "NOT_IMPLEMENTED",
19
- "VERIFICATION_REQUIRED",
20
- "INSUFFICIENT_BALANCE",
21
- "INVALID_RECIPIENT",
22
- "ROLE_PERMISSION_DENIED",
23
- "TRANSACTION_FAILED",
24
- "RATE_LIMITED",
25
- "NETWORK_ERROR",
26
- "UNKNOWN",
27
- "OTP_EXPIRED",
28
- "SIGNER_REJECTED",
29
- "CANCELLED",
30
- "WRONG_STATE",
31
- "STALE_EPOCH",
32
- "SUPERSEDED",
33
- "WORK_DIED",
34
- "ACTOR_STOPPED"
35
- ];
36
- var CapxulError$1 = class extends Error {
37
- code;
38
- details;
39
- correlationId;
40
- layer;
41
- constructor(code, message, options = {}) {
42
- super(message, "cause" in options ? { cause: options.cause } : void 0);
43
- this.name = "CapxulError";
44
- this.code = code;
45
- if (options.details !== void 0) this.details = options.details;
46
- if (options.correlationId !== void 0) this.correlationId = options.correlationId;
47
- if (options.layer !== void 0) this.layer = options.layer;
48
- }
49
- };
50
- const Errors = {
51
- notAuthenticated: (message, opts) => new CapxulError$1("NOT_AUTHENTICATED", message ?? "Not authenticated", opts?.failure_mode ? { details: { failure_mode: opts.failure_mode } } : void 0),
52
- emailDeliveryFailed: (detail) => new CapxulError$1("EMAIL_DELIVERY_FAILED", "Failed to send email", { details: { detail } }),
53
- profileNotFound: (authUserId) => new CapxulError$1("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`, { details: { authUserId } }),
54
- smartAccountMissing: (authUserId) => new CapxulError$1("SMART_ACCOUNT_MISSING", "Smart account not provisioned", { details: { authUserId } }),
55
- playerNotFound: (playerId) => new CapxulError$1("PLAYER_NOT_FOUND", playerId ? `Openfort player ${playerId} not found` : "Openfort player not found", playerId === void 0 ? void 0 : { details: { playerId } }),
56
- accountNotFound: (accountId) => new CapxulError$1("ACCOUNT_NOT_FOUND", accountId ? `Openfort account ${accountId} not found` : "Openfort account not found", accountId === void 0 ? void 0 : { details: { accountId } }),
57
- providerError: (provider, operation, cause, opts) => {
58
- const details = {
59
- provider,
60
- operation
61
- };
62
- if (opts?.failure_mode) details.failure_mode = opts.failure_mode;
63
- return new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation}`, {
64
- cause,
65
- details
66
- });
67
- },
68
- capabilityUnavailable: (provider, operation) => new CapxulError$1("CAPABILITY_UNAVAILABLE", "Provider capability is unavailable", { details: {
69
- provider,
70
- operation
71
- } }),
72
- invalidInput: (field, reason) => new CapxulError$1("INVALID_INPUT", `Invalid ${field}: ${reason}`, { details: {
73
- field,
74
- reason
75
- } }),
76
- envMissing: (name) => new CapxulError$1("ENV_MISSING", `Environment variable ${name} not configured`, { details: { name } }),
77
- notImplemented: (domain, method) => new CapxulError$1("NOT_IMPLEMENTED", `${domain}.${method} is not yet implemented. This feature is planned for a future release.`, { details: {
78
- domain,
79
- method
80
- } }),
81
- /**
82
- * Sibling factory to {@link Errors.providerError} for the per-state timeout
83
- * path in flows. Same `PROVIDER_ERROR` code as
84
- * `providerError`, plus a `details.reason: "timeout"` discriminator so
85
- * downstream observers can distinguish failure modes without parsing the
86
- * message string. The redacted message names the timeout budget; the
87
- * native `cause` carries the same information for `reportError` fidelity.
88
- */
89
- providerTimeout: (provider, operation, timeoutMs) => new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation} (timeout exceeded ${timeoutMs}ms)`, {
90
- details: {
91
- provider,
92
- operation,
93
- reason: "timeout"
94
- },
95
- cause: /* @__PURE__ */ new Error(`timeout: ${operation} exceeded ${timeoutMs}ms`)
96
- }),
97
- verificationRequired: (details) => {
98
- return new CapxulError$1("VERIFICATION_REQUIRED", "rail" in details ? `Verification is required before ${details.rail} can use ${details.currentKind}.` : `Verification tier ${details.requiredTier} is required.`, { details });
99
- },
100
- insufficientBalance: (asset, available, required) => new CapxulError$1("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, { details: {
101
- asset,
102
- available,
103
- required
104
- } }),
105
- invalidRecipient: (reason) => new CapxulError$1("INVALID_RECIPIENT", `Invalid recipient: ${reason}`, { details: { reason } }),
106
- /**
107
- * The org Zodiac Roles modifier REFUSED the spend on-chain (G4 · #547): the
108
- * member's role condition (per-tx cap, per-day allowance, allowed recipient,
109
- * or membership) was violated, so `execTransactionWithRole` reverted. This is
110
- * a PERMISSION denial — explicitly NOT an `INSUFFICIENT_BALANCE` (the treasury
111
- * held the funds; the role's authority is what bound). `reason` discriminates
112
- * the violated condition (`over_cap` / `daily_cap` / `not_member` /
113
- * `disallowed_recipient` / `condition_violation`); leak-safe — no on-chain
114
- * identifiers ever enter the details.
115
- */
116
- rolePermissionDenied: (details) => new CapxulError$1("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: details.operation === void 0 ? { reason: details.reason } : {
117
- reason: details.reason,
118
- operation: details.operation
119
- } }),
120
- /**
121
- * A transaction (or sponsored UserOp) failed. `details.reason` discriminates
122
- * the failure mode for callers that must distinguish a CONFIRMED on-chain
123
- * revert (`"onchain_revert"` — the op executed and reverted, e.g. a Zodiac
124
- * Roles condition violation) from an inconclusive infra failure. A confirmed
125
- * revert is the ONLY mode the org spend port may map to a roles denial.
126
- */
127
- transactionFailed: (operation, cause, extra) => new CapxulError$1("TRANSACTION_FAILED", `Transaction failed: ${operation}`, {
128
- cause,
129
- details: extra?.reason === void 0 ? { operation } : {
130
- operation,
131
- reason: extra.reason
132
- }
133
- }),
134
- rateLimited: (details) => new CapxulError$1("RATE_LIMITED", "Rate limit exceeded", details === void 0 ? void 0 : { details: { ...details } }),
135
- networkError: (operation, cause) => new CapxulError$1("NETWORK_ERROR", `Network error during ${operation}`, {
136
- cause,
137
- details: { operation }
138
- }),
139
- unknown: (cause) => new CapxulError$1("UNKNOWN", "Unknown error", { cause }),
140
- otpExpired: (details) => new CapxulError$1("OTP_EXPIRED", "Verification code has expired. Request a new one.", details === void 0 ? void 0 : { details: { ...details } }),
141
- signerRejected: (details) => new CapxulError$1("SIGNER_REJECTED", "Signer rejected the request.", {
142
- cause: details.cause,
143
- details: details.reason === void 0 ? { source: details.source } : {
144
- source: details.source,
145
- reason: details.reason
146
- }
147
- }),
148
- cancelled: (details) => new CapxulError$1("CANCELLED", "Operation was cancelled.", details === void 0 ? void 0 : { details: { ...details } }),
149
- /**
150
- * Method called from a flow state where its precondition fails (TA16). The
151
- * SDK's method API short-circuits with this error before driving the
152
- * internal state machine. `currentState` is the Effect-machine snapshot
153
- * tag (stringified from the SDK's actor-shell snapshot; see
154
- * `packages/errors/CONTEXT.md`); `validStates`
155
- * enumerates the states the method accepts.
156
- */
157
- wrongState: (details) => new CapxulError$1("WRONG_STATE", `${details.method} called from state '${details.currentState}'; valid states: ${details.validStates.join(", ")}`, { details: {
158
- ...details,
159
- validStates: [...details.validStates]
160
- } })
161
- };
162
- new Set(CAPXUL_ERROR_CODES);
163
- //#endregion
5
+ import { CapxulError, Errors, fingerprintPaymentIntent, formatMoney, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseMoney, resolveIdentityDestination } from "@capxul/sdk";
6
+ import { Fragment, jsx } from "react/jsx-runtime";
164
7
  //#region src/internal/require-bootstrapped-client.ts
165
8
  /**
166
9
  * Narrow the bootstrap-nullable client to a ready client inside a query /
@@ -182,12 +25,10 @@ function requireBootstrappedClient(client, method) {
182
25
  * Unwrap a `CapxulResult` for TanStack query/mutation functions and throw into
183
26
  * React Query's error path.
184
27
  *
185
- * Failure observation belongs to the core SDK method boundary. The legacy
186
- * telemetry/operation arguments remain temporarily source-compatible with the
187
- * existing hook call sites, but are deliberately ignored so React cannot
28
+ * Failure observation belongs to the core SDK method boundary. React does not
188
29
  * report the same logical failure a second time.
189
30
  */
190
- function unwrapCapxulResult(result, _legacyTelemetry, _legacyOperation = "query") {
31
+ function unwrapCapxulResult(result) {
191
32
  if (result.ok) return result.value;
192
33
  throw result.error;
193
34
  }
@@ -198,7 +39,7 @@ function useCapxulProfile() {
198
39
  const identity = useCapxulIdentityOrNull();
199
40
  return useQuery({
200
41
  queryKey: capxulKeys.profile,
201
- queryFn: async () => unwrapCapxulResult(await requireBootstrappedClient(client, "identity.loadCurrent").identity.loadCurrent(), client._internal.telemetry),
42
+ queryFn: async () => unwrapCapxulResult(await requireBootstrappedClient(client, "identity.loadCurrent").identity.loadCurrent()),
202
43
  enabled: client !== null && identity?.phase === "authenticated"
203
44
  });
204
45
  }
@@ -209,8 +50,7 @@ function useCapxulAccountBalance(options) {
209
50
  return useQuery({
210
51
  queryKey: capxulKeys.accountBalance,
211
52
  queryFn: async () => {
212
- const bootstrappedClient = requireBootstrappedClient(client, "accounts.read");
213
- return unwrapCapxulResult(await bootstrappedClient.accounts.read(), bootstrappedClient._internal.telemetry);
53
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "accounts.read").accounts.read());
214
54
  },
215
55
  enabled: client !== null && (options?.enabled ?? true)
216
56
  });
@@ -222,8 +62,7 @@ function useCapxulAccountFund() {
222
62
  const queryClient = useQueryClient();
223
63
  return useMutation({
224
64
  mutationFn: async (amount) => {
225
- const bootstrappedClient = requireBootstrappedClient(client, "_internal.accounts.fund");
226
- return unwrapCapxulResult(await bootstrappedClient._internal.accounts.fund(amount), bootstrappedClient._internal.telemetry, "mutation");
65
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "_internal.accounts.fund")._internal.accounts.fund(amount));
227
66
  },
228
67
  onSuccess: async () => {
229
68
  await queryClient.invalidateQueries({ queryKey: capxulKeys.accountBalance });
@@ -255,7 +94,7 @@ async function storageKey(operation, intent) {
255
94
  }
256
95
  function paymentLockManager() {
257
96
  const manager = navigator.locks;
258
- if (manager === void 0 || typeof manager.query !== "function") throw new Error("Web Locks are required for payment request keys");
97
+ if (manager === void 0 || typeof manager.query !== "function") throw Errors.providerError("sdk-react", "paymentRequestKey", /* @__PURE__ */ new Error("Web Locks are required for payment request keys"));
259
98
  return manager;
260
99
  }
261
100
  function attemptLockName(storageSlot, attemptId) {
@@ -299,14 +138,14 @@ function readState(key) {
299
138
  const stored = localStorage.getItem(key);
300
139
  if (stored === null) return null;
301
140
  const state = JSON.parse(stored);
302
- if (typeof state.key !== "string" || !Array.isArray(state.active) || state.active.some((attempt) => typeof attempt !== "string") || typeof state.resolved !== "boolean") throw new TypeError("Invalid payment request key state");
141
+ if (typeof state.key !== "string" || !Array.isArray(state.active) || state.active.some((attempt) => typeof attempt !== "string") || typeof state.resolved !== "boolean") throw Errors.providerError("sdk-react", "paymentRequestKey", /* @__PURE__ */ new TypeError("Invalid payment request key state"));
303
142
  return state;
304
143
  }
305
144
  async function beginPaymentRequestKey(operation, intent) {
306
- const storageSlot = await storageKey(operation, intent);
307
145
  let releaseAttemptLock;
308
146
  let forgetPagehideRelease;
309
147
  try {
148
+ const storageSlot = await storageKey(operation, intent);
310
149
  const attemptId = `attempt_${crypto.randomUUID()}`;
311
150
  releaseAttemptLock = await holdAttemptLock(attemptLockName(storageSlot, attemptId));
312
151
  forgetPagehideRelease = releaseAttemptOnPagehide(releaseAttemptLock);
@@ -408,7 +247,7 @@ function useCapxulPay() {
408
247
  }, async (requestKey) => unwrapCapxulResult(await bootstrappedClient.payments.pay({
409
248
  ...input,
410
249
  requestKey
411
- }), bootstrappedClient._internal.telemetry, "mutation"));
250
+ })));
412
251
  },
413
252
  onSuccess: async (payment) => {
414
253
  await invalidateMoneyState(queryClient, {
@@ -423,8 +262,7 @@ function useCapxulCreateCommitment() {
423
262
  const queryClient = useQueryClient();
424
263
  return useMutation({
425
264
  mutationFn: async (input) => {
426
- const bootstrapped = requireBootstrappedClient(client, "payments.createCommitment");
427
- return unwrapCapxulResult(await bootstrapped.payments.createCommitment(input), bootstrapped._internal.telemetry, "mutation");
265
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "payments.createCommitment").payments.createCommitment(input));
428
266
  },
429
267
  onSuccess: (payment) => invalidateMoneyState(queryClient, {
430
268
  actor: { kind: "personal" },
@@ -437,8 +275,7 @@ function usePaymentIdMutation(method) {
437
275
  const queryClient = useQueryClient();
438
276
  return useMutation({
439
277
  mutationFn: async (paymentId) => {
440
- const bootstrapped = requireBootstrappedClient(client, `payments.${method}`);
441
- return unwrapCapxulResult(await bootstrapped.payments[method](paymentId), bootstrapped._internal.telemetry, "mutation");
278
+ return unwrapCapxulResult(await requireBootstrappedClient(client, `payments.${method}`).payments[method](paymentId));
442
279
  },
443
280
  onSuccess: (payment) => invalidateMoneyState(queryClient, {
444
281
  actor: { kind: "personal" },
@@ -453,8 +290,7 @@ function useCapxulRedirectPayment() {
453
290
  const queryClient = useQueryClient();
454
291
  return useMutation({
455
292
  mutationFn: async (input) => {
456
- const bootstrapped = requireBootstrappedClient(client, "payments.redirect");
457
- return unwrapCapxulResult(await bootstrapped.payments.redirect(input), bootstrapped._internal.telemetry, "mutation");
293
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "payments.redirect").payments.redirect(input));
458
294
  },
459
295
  onSuccess: (payment) => invalidateMoneyState(queryClient, {
460
296
  actor: { kind: "personal" },
@@ -468,10 +304,9 @@ function useCapxulPayments(options) {
468
304
  return useQuery({
469
305
  queryKey: capxulKeys.payments,
470
306
  queryFn: async () => {
471
- const bootstrappedClient = requireBootstrappedClient(client, "payments.list");
472
- return unwrapCapxulResult(await bootstrappedClient.payments.list(), bootstrappedClient._internal.telemetry);
307
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "payments.list").payments.list());
473
308
  },
474
- enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed" && (options?.enabled ?? true)
309
+ enabled: client !== null && identity !== null && isClaimed$1(identity) && (options?.enabled ?? true)
475
310
  });
476
311
  }
477
312
  function useCapxulPayment(paymentId, options) {
@@ -480,8 +315,7 @@ function useCapxulPayment(paymentId, options) {
480
315
  queryKey: capxulKeys.payment(paymentId),
481
316
  queryFn: async () => {
482
317
  if (paymentId === void 0) throw Errors.invalidInput("paymentId", "required for payments.get");
483
- const bootstrappedClient = requireBootstrappedClient(client, "payments.get");
484
- return unwrapCapxulResult(await bootstrappedClient.payments.get(paymentId), bootstrappedClient._internal.telemetry);
318
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "payments.get").payments.get(paymentId));
485
319
  },
486
320
  enabled: client !== null && paymentId !== void 0 && (options?.enabled ?? true)
487
321
  });
@@ -502,7 +336,7 @@ function useCapxulOrganizationPay(orgId) {
502
336
  }, async (requestKey) => unwrapCapxulResult(await bootstrapped.org(orgId).payments.pay({
503
337
  ...input,
504
338
  requestKey
505
- }), bootstrapped._internal.telemetry, "mutation"));
339
+ })));
506
340
  },
507
341
  onSuccess: async (payment) => {
508
342
  return invalidateMoneyState(queryClient, {
@@ -529,7 +363,7 @@ function useCapxulOrganizationPayBatch(orgId) {
529
363
  }, async (requestKey) => unwrapCapxulResult(await bootstrapped.org(orgId).payments.payBatch({
530
364
  ...input,
531
365
  requestKey
532
- }), bootstrapped._internal.telemetry, "mutation"));
366
+ })));
533
367
  },
534
368
  onSuccess: async (payments) => {
535
369
  return Promise.all(payments.map((payment) => invalidateMoneyState(queryClient, {
@@ -549,9 +383,8 @@ function usePermissionMutation(orgId, operation) {
549
383
  const queryClient = useQueryClient();
550
384
  return useMutation({
551
385
  mutationFn: async (input) => {
552
- const bootstrapped = requireBootstrappedClient(client, `permissions.${operation}`);
553
- const method = bootstrapped.org(orgId).permissions[operation];
554
- return unwrapCapxulResult(await method(input), bootstrapped._internal.telemetry, "mutation");
386
+ const method = requireBootstrappedClient(client, `permissions.${operation}`).org(orgId).permissions[operation];
387
+ return unwrapCapxulResult(await method(input));
555
388
  },
556
389
  onSuccess: () => queryClient.invalidateQueries({ queryKey: capxulKeys.orgPermissions(orgId) })
557
390
  });
@@ -563,13 +396,13 @@ const useCapxulPermissionRevoke = (orgId) => usePermissionMutation(orgId, "revok
563
396
  const useCapxulPermissionReplace = (orgId) => usePermissionMutation(orgId, "replace");
564
397
  function useCapxulPermissions(orgId, options) {
565
398
  const client = useCapxulClientOrNull();
399
+ const identity = useCapxulIdentityOrNull();
566
400
  return useQuery({
567
401
  queryKey: capxulKeys.orgPermissions(orgId),
568
402
  queryFn: async () => {
569
- const bootstrapped = requireBootstrappedClient(client, "permissions.list");
570
- return unwrapCapxulResult(await bootstrapped.org(orgId).permissions.list(), bootstrapped._internal.telemetry);
403
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "permissions.list").org(orgId).permissions.list());
571
404
  },
572
- enabled: client !== null && (options?.enabled ?? true)
405
+ enabled: client !== null && identity !== null && isClaimed$1(identity) && (options?.enabled ?? true)
573
406
  });
574
407
  }
575
408
  function useCapxulPermission(orgId, permissionId) {
@@ -577,9 +410,8 @@ function useCapxulPermission(orgId, permissionId) {
577
410
  return useQuery({
578
411
  queryKey: [...capxulKeys.orgPermissions(orgId), permissionId ?? "pending"],
579
412
  queryFn: async () => {
580
- if (permissionId === void 0) throw new Error("permissionId is required");
581
- const bootstrapped = requireBootstrappedClient(client, "permissions.get");
582
- return unwrapCapxulResult(await bootstrapped.org(orgId).permissions.get(permissionId), bootstrapped._internal.telemetry);
413
+ if (permissionId === void 0) throw Errors.invalidInput("permissionId", "required for permissions.get");
414
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "permissions.get").org(orgId).permissions.get(permissionId));
583
415
  },
584
416
  enabled: client !== null && permissionId !== void 0
585
417
  });
@@ -592,10 +424,9 @@ function useCapxulActivity(params) {
592
424
  return useQuery({
593
425
  queryKey: [...capxulKeys.activity, params ?? {}],
594
426
  queryFn: async () => {
595
- const bootstrapped = requireBootstrappedClient(client, "activity.list");
596
- return unwrapCapxulResult(await bootstrapped.activity.list(params), bootstrapped._internal.telemetry);
427
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.list").activity.list(params));
597
428
  },
598
- enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed"
429
+ enabled: client !== null && identity !== null && isClaimed$1(identity)
599
430
  });
600
431
  }
601
432
  function useCapxulActivityDetail(reference) {
@@ -604,11 +435,10 @@ function useCapxulActivityDetail(reference) {
604
435
  return useQuery({
605
436
  queryKey: capxulKeys.activityDetail(reference),
606
437
  queryFn: async () => {
607
- if (reference === void 0) throw new Error("activity reference is required");
608
- const bootstrapped = requireBootstrappedClient(client, "activity.get");
609
- return unwrapCapxulResult(await bootstrapped.activity.get(reference), bootstrapped._internal.telemetry);
438
+ if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
439
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.get").activity.get(reference));
610
440
  },
611
- enabled: client !== null && reference !== void 0 && identity?.phase === "authenticated" && identity.account.at === "claimed"
441
+ enabled: client !== null && reference !== void 0 && identity !== null && isClaimed$1(identity)
612
442
  });
613
443
  }
614
444
  function useCapxulAnnotateMovement() {
@@ -616,8 +446,7 @@ function useCapxulAnnotateMovement() {
616
446
  const queryClient = useQueryClient();
617
447
  return useMutation({
618
448
  mutationFn: async (input) => {
619
- const bootstrapped = requireBootstrappedClient(client, "activity.annotate");
620
- return unwrapCapxulResult(await bootstrapped.activity.annotate(input), bootstrapped._internal.telemetry, "mutation");
449
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.annotate").activity.annotate(input));
621
450
  },
622
451
  onSuccess: () => queryClient.invalidateQueries({ queryKey: capxulKeys.activity })
623
452
  });
@@ -630,10 +459,9 @@ function useCapxulHoldings() {
630
459
  return useQuery({
631
460
  queryKey: capxulKeys.holdings,
632
461
  queryFn: async () => {
633
- const bootstrapped = requireBootstrappedClient(client, "holdings.current");
634
- return unwrapCapxulResult(await bootstrapped.holdings.current(), bootstrapped._internal.telemetry);
462
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "holdings.current").holdings.current());
635
463
  },
636
- enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed"
464
+ enabled: client !== null && identity !== null && isClaimed$1(identity)
637
465
  });
638
466
  }
639
467
  //#endregion
@@ -643,8 +471,7 @@ function useCapxulOrgs(options) {
643
471
  return useQuery({
644
472
  queryKey: capxulKeys.orgs,
645
473
  queryFn: async () => {
646
- const bootstrappedClient = requireBootstrappedClient(client, "orgs");
647
- return unwrapCapxulResult(await bootstrappedClient.orgs(), bootstrappedClient._internal.telemetry);
474
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "orgs").orgs());
648
475
  },
649
476
  enabled: client !== null && (options?.enabled ?? true)
650
477
  });
@@ -658,8 +485,7 @@ function useCapxulOrgMembers(orgId, options) {
658
485
  queryKey: capxulKeys.orgMembers(orgId),
659
486
  queryFn: async () => {
660
487
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgMembers");
661
- const bootstrappedClient = requireBootstrappedClient(client, "org.members");
662
- return unwrapCapxulResult(await bootstrappedClient.org(orgId).members(), bootstrappedClient._internal.telemetry);
488
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.members").org(orgId).members());
663
489
  },
664
490
  enabled: client !== null && enabled && orgId !== void 0
665
491
  });
@@ -673,8 +499,7 @@ function useCapxulOrgRoles(orgId, options) {
673
499
  queryKey: capxulKeys.orgRoles(orgId),
674
500
  queryFn: async () => {
675
501
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgRoles");
676
- const bootstrappedClient = requireBootstrappedClient(client, "org.roles");
677
- return unwrapCapxulResult(await bootstrappedClient.org(orgId).roles(), bootstrappedClient._internal.telemetry);
502
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.roles").org(orgId).roles());
678
503
  },
679
504
  enabled: client !== null && enabled && orgId !== void 0
680
505
  });
@@ -688,8 +513,7 @@ function useCapxulOrgTreasury(orgId, options) {
688
513
  queryKey: capxulKeys.orgTreasury(orgId),
689
514
  queryFn: async () => {
690
515
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgTreasury");
691
- const bootstrappedClient = requireBootstrappedClient(client, "org.treasury");
692
- return unwrapCapxulResult(await bootstrappedClient.org(orgId).treasury(), bootstrappedClient._internal.telemetry);
516
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.treasury").org(orgId).treasury());
693
517
  },
694
518
  enabled: client !== null && enabled && orgId !== void 0
695
519
  });
@@ -701,8 +525,7 @@ function useCapxulCreateOrg() {
701
525
  const queryClient = useQueryClient();
702
526
  return useMutation({
703
527
  mutationFn: async (input) => {
704
- const bootstrappedClient = requireBootstrappedClient(client, "createOrg");
705
- return unwrapCapxulResult(await bootstrappedClient.createOrg(input), bootstrappedClient._internal.telemetry, "mutation");
528
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "createOrg").createOrg(input));
706
529
  },
707
530
  onSuccess: async () => {
708
531
  await queryClient.invalidateQueries({ queryKey: capxulKeys.orgs });
@@ -717,8 +540,7 @@ function useCapxulInviteMember(orgId) {
717
540
  return useMutation({
718
541
  mutationFn: async (input) => {
719
542
  if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulInviteMember");
720
- const bootstrappedClient = requireBootstrappedClient(client, "org.invite");
721
- return unwrapCapxulResult(await bootstrappedClient.org(orgId).invite(input), bootstrappedClient._internal.telemetry, "mutation");
543
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.invite").org(orgId).invite(input));
722
544
  },
723
545
  onSuccess: async () => {
724
546
  if (orgId === void 0) return;
@@ -973,8 +795,7 @@ function useCapxulUsernameAvailability(username, options) {
973
795
  return useQuery({
974
796
  queryKey: capxulKeys.usernameAvailability(candidate),
975
797
  queryFn: async () => {
976
- const bootstrappedClient = requireBootstrappedClient(client, "identity.usernameAvailable");
977
- return unwrapCapxulResult(await bootstrappedClient.identity.usernameAvailable(candidate), bootstrappedClient._internal.telemetry);
798
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "identity.usernameAvailable").identity.usernameAvailable(candidate));
978
799
  },
979
800
  enabled: client !== null && candidate.length >= 3 && (options?.enabled ?? true)
980
801
  });
@@ -992,15 +813,13 @@ function useCapxulImageUpload() {
992
813
  const queryClient = useQueryClient();
993
814
  return useMutation({
994
815
  mutationFn: async ({ blob, target }) => {
995
- const bootstrappedClient = requireBootstrappedClient(client, "media.uploadImage");
996
- const media = bootstrappedClient.media;
997
- const telemetry = bootstrappedClient._internal.telemetry;
998
- const uploaded = unwrapCapxulResult(await media.uploadImage(blob), telemetry);
999
- if (target.kind === "profile") return { url: unwrapCapxulResult(await media.setProfileImage({ storageId: uploaded.storageId }), telemetry).imageUrl };
816
+ const media = requireBootstrappedClient(client, "media.uploadImage").media;
817
+ const uploaded = unwrapCapxulResult(await media.uploadImage(blob));
818
+ if (target.kind === "profile") return { url: unwrapCapxulResult(await media.setProfileImage({ storageId: uploaded.storageId })).imageUrl };
1000
819
  return { url: unwrapCapxulResult(await media.setOrgLogo({
1001
820
  orgId: target.orgId,
1002
821
  storageId: uploaded.storageId
1003
- }), telemetry).logoUrl };
822
+ })).logoUrl };
1004
823
  },
1005
824
  onSuccess: async (_value, input) => {
1006
825
  await queryClient.invalidateQueries({ queryKey: input.target.kind === "profile" ? capxulKeys.profile : capxulKeys.orgs });
@@ -1008,4 +827,733 @@ function useCapxulImageUpload() {
1008
827
  });
1009
828
  }
1010
829
  //#endregion
1011
- export { CapxulAuthenticationController, CapxulOnboardingController, CapxulProvider, acknowledgeOnboardingDestination, activeOnboardingRecovery, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUsernameAvailability };
830
+ //#region src/headless/contacts/use-contacts.ts
831
+ function useContacts(input) {
832
+ const { actor, onAdded, onFailed } = input;
833
+ const client = useCapxulClientOrNull();
834
+ const queryClient = useQueryClient();
835
+ const orgId = actor.kind === "organization" ? actor.orgId : void 0;
836
+ const scope = actor.kind === "personal" ? "personal" : orgId ?? "pending";
837
+ const refusal = actor.kind === "organization" && orgId === void 0 ? "org-unavailable" : null;
838
+ const book = useMemo(() => addressBookOf(client, actor.kind, orgId), [
839
+ client,
840
+ actor.kind,
841
+ orgId
842
+ ]);
843
+ const invalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: capxulKeys.contacts(scope) }), [queryClient, scope]);
844
+ const writeMutate = useMutation({
845
+ mutationFn: async (command) => {
846
+ const target = requireBook(book, `addressBook.${command.kind}`);
847
+ return unwrapCapxulResult(command.kind === "rename" ? await target.label({
848
+ entryId: command.id,
849
+ label: command.name
850
+ }) : command.kind === "hide" ? await target.hide(command.id) : await target.unhide(command.id));
851
+ },
852
+ onSettled: invalidate,
853
+ onError: onFailed
854
+ }).mutate;
855
+ const [value, setValue] = useState("");
856
+ const [addError, setAddError] = useState(null);
857
+ const addContact = useMutation({
858
+ mutationFn: async (ref) => unwrapCapxulResult(await requireBook(book, "addressBook.add").add({ ref })),
859
+ onSettled: invalidate
860
+ });
861
+ const addMutate = addContact.mutate;
862
+ const blockedReason = refusal !== null ? "org-unavailable" : value.trim() === "" ? "empty" : null;
863
+ const submitGate = useRef(false);
864
+ const submit = useCallback(() => {
865
+ if (blockedReason !== null || submitGate.current) return;
866
+ submitGate.current = true;
867
+ setAddError(null);
868
+ addMutate(refFromTypedText(value), {
869
+ onSuccess: (entry) => {
870
+ setValue("");
871
+ onAdded(toContact(entry));
872
+ },
873
+ onError: (error) => {
874
+ setAddError(error.code === "INVALID_INPUT" ? "unresolved" : "failed");
875
+ onFailed(error);
876
+ },
877
+ onSettled: () => {
878
+ submitGate.current = false;
879
+ }
880
+ });
881
+ }, [
882
+ addMutate,
883
+ blockedReason,
884
+ onAdded,
885
+ onFailed,
886
+ value
887
+ ]);
888
+ const change = useCallback((text) => {
889
+ setValue(text);
890
+ setAddError(null);
891
+ }, []);
892
+ return {
893
+ book,
894
+ scope,
895
+ refusal,
896
+ rename: useCallback((id, name) => writeMutate({
897
+ kind: "rename",
898
+ id,
899
+ name
900
+ }), [writeMutate]),
901
+ hide: useCallback((id) => writeMutate({
902
+ kind: "hide",
903
+ id
904
+ }), [writeMutate]),
905
+ unhide: useCallback((id) => writeMutate({
906
+ kind: "unhide",
907
+ id
908
+ }), [writeMutate]),
909
+ add: {
910
+ value,
911
+ change,
912
+ submit,
913
+ isSubmitting: addContact.isPending,
914
+ blocked: blockedReason !== null,
915
+ blockedReason,
916
+ error: addError
917
+ }
918
+ };
919
+ }
920
+ /**
921
+ * The read every part stands on. Two placements with the same `show` share one
922
+ * query key, so React Query issues ONE request for them.
923
+ */
924
+ function useContactsList(engine, options) {
925
+ const includeHidden = options.show === "all";
926
+ const { book, refusal } = engine;
927
+ const query = useQuery({
928
+ queryKey: capxulKeys.contactsList(engine.scope, includeHidden),
929
+ queryFn: async () => unwrapCapxulResult(await requireBook(book, "addressBook.list").list({ includeHidden })),
930
+ enabled: book !== null
931
+ });
932
+ const reason = refusal ?? (query.isError ? "unavailable" : query.data === void 0 ? "loading" : null);
933
+ const entries = query.data;
934
+ const { limit } = options;
935
+ const { rename, hide, unhide } = engine;
936
+ return {
937
+ rows: useMemo(() => {
938
+ if (entries === void 0) return [];
939
+ return (limit === void 0 ? entries : entries.slice(0, limit)).map((entry) => Object.assign(toContact(entry), {
940
+ initials: initialsOf(entry.label),
941
+ rename: (name) => rename(entry.id, name),
942
+ hide: () => hide(entry.id),
943
+ unhide: () => unhide(entry.id)
944
+ }));
945
+ }, [
946
+ entries,
947
+ hide,
948
+ limit,
949
+ rename,
950
+ unhide
951
+ ]),
952
+ isLoading: reason === "loading",
953
+ reason
954
+ };
955
+ }
956
+ /** Counts over the same visible read `.List` uses — one request, two parts. */
957
+ function useContactsSummary(engine) {
958
+ const { rows, isLoading, reason } = useContactsList(engine, {});
959
+ const settled = reason === null;
960
+ return {
961
+ total: settled ? rows.length : null,
962
+ activeCount: settled ? rows.filter((row) => row.relationship.includes("paid")).length : null,
963
+ isLoading,
964
+ reason
965
+ };
966
+ }
967
+ function addressBookOf(client, kind, orgId) {
968
+ if (client === null) return null;
969
+ if (kind === "personal") return client.account.addressBook;
970
+ return orgId === void 0 ? null : client.org(orgId).addressBook;
971
+ }
972
+ function requireBook(book, method) {
973
+ if (book === null) throw Errors.wrongState({
974
+ method,
975
+ currentState: "bootstrapping",
976
+ validStates: ["ready"]
977
+ });
978
+ return book;
979
+ }
980
+ function toContact(entry) {
981
+ return {
982
+ id: entry.id,
983
+ name: entry.label,
984
+ relationship: entry.relationship,
985
+ hidden: entry.hidden,
986
+ lastActivityAt: entry.lastActivityAt
987
+ };
988
+ }
989
+ /**
990
+ * One typed field, both shapes. A leading `@` names a handle; an `@` anywhere
991
+ * else names an email — what the app already builds for Quick Pay, and what the
992
+ * backend resolver already accepts.
993
+ */
994
+ function refFromTypedText(text) {
995
+ const trimmed = text.trim();
996
+ return trimmed.includes("@") && !trimmed.startsWith("@") ? {
997
+ kind: "email",
998
+ email: trimmed
999
+ } : {
1000
+ kind: "handle",
1001
+ handle: trimmed.replace(/^@/, "")
1002
+ };
1003
+ }
1004
+ /** First letters of the first two words, uppercased. */
1005
+ function initialsOf(name) {
1006
+ return name.trim().split(/\s+/).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
1007
+ }
1008
+ //#endregion
1009
+ //#region src/headless/contacts/contacts.tsx
1010
+ const ContactsContext = createContext(null);
1011
+ function useContactsContext() {
1012
+ const engine = useContext(ContactsContext);
1013
+ if (engine === null) throw new Error("CapxulContacts parts must be used inside <CapxulContacts>");
1014
+ return engine;
1015
+ }
1016
+ function Root$3({ actor, onAdded, onFailed, children }) {
1017
+ const engine = useContacts({
1018
+ actor,
1019
+ onAdded,
1020
+ onFailed
1021
+ });
1022
+ return /* @__PURE__ */ jsx(ContactsContext.Provider, {
1023
+ value: engine,
1024
+ children
1025
+ });
1026
+ }
1027
+ function Summary({ children }) {
1028
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsSummary(useContactsContext())) });
1029
+ }
1030
+ function List({ limit, show, children }) {
1031
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsList(useContactsContext(), {
1032
+ limit,
1033
+ show
1034
+ })) });
1035
+ }
1036
+ function Add({ children }) {
1037
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsContext().add) });
1038
+ }
1039
+ /** The compound shape the app's own design system already uses (`StatusTabs`). */
1040
+ const CapxulContacts = Object.assign(Root$3, {
1041
+ Summary,
1042
+ List,
1043
+ Add
1044
+ });
1045
+ //#endregion
1046
+ //#region src/headless/send-money/signer-status.ts
1047
+ const NO_SUBSCRIPTION = () => () => {};
1048
+ function useSignerStatus(client) {
1049
+ const signer = client?.signer;
1050
+ return useSyncExternalStore(useMemo(() => signer === void 0 ? NO_SUBSCRIPTION : (listener) => signer.subscribe(listener), [signer]), () => signer === void 0 ? "unknown" : signer.status(), () => "unknown");
1051
+ }
1052
+ //#endregion
1053
+ //#region src/headless/send-money/use-recipient-resolution.ts
1054
+ function toTargetReference(method, text) {
1055
+ const trimmed = text.trim();
1056
+ if (trimmed === "") return null;
1057
+ return method === "username" ? {
1058
+ kind: "handle",
1059
+ handle: trimmed.replace(/^@/, "")
1060
+ } : {
1061
+ kind: "email",
1062
+ email: trimmed
1063
+ };
1064
+ }
1065
+ /** How long the engine waits after the last keystroke before resolving. */
1066
+ const RESOLVE_DEBOUNCE_MS = 400;
1067
+ function useRecipientResolution(client) {
1068
+ const [method, setMethodState] = useState("email-address");
1069
+ const [text, setText] = useState("");
1070
+ const reference = useMemo(() => toTargetReference(method, text), [method, text]);
1071
+ const debouncedReference = useDebouncedValue(reference, RESOLVE_DEBOUNCE_MS);
1072
+ const [outcome, setOutcome] = useState(null);
1073
+ useEffect(() => {
1074
+ if (debouncedReference === null || client === null) {
1075
+ setOutcome(null);
1076
+ return;
1077
+ }
1078
+ const controller = new AbortController();
1079
+ setOutcome({
1080
+ reference: debouncedReference,
1081
+ status: "checking",
1082
+ target: null
1083
+ });
1084
+ client.targets.resolve(debouncedReference, { signal: controller.signal }).then((result) => {
1085
+ if (controller.signal.aborted) return;
1086
+ setOutcome(result.ok ? {
1087
+ reference: debouncedReference,
1088
+ status: "found",
1089
+ target: result.value
1090
+ } : {
1091
+ reference: debouncedReference,
1092
+ status: result.error.code === "INVALID_INPUT" ? "not-found" : "failed",
1093
+ target: null
1094
+ });
1095
+ });
1096
+ return () => controller.abort();
1097
+ }, [client, debouncedReference]);
1098
+ const setMethod = useCallback((next) => {
1099
+ setMethodState(next);
1100
+ setText("");
1101
+ }, []);
1102
+ const settled = outcome !== null && outcome.reference === debouncedReference ? outcome : null;
1103
+ const status = reference === null ? "empty" : reference !== debouncedReference || settled === null ? "checking" : settled.status;
1104
+ const resolvedTarget = status === "found" ? settled?.target ?? null : null;
1105
+ return {
1106
+ slice: {
1107
+ method,
1108
+ setMethod,
1109
+ value: text,
1110
+ change: setText,
1111
+ status,
1112
+ label: resolvedTarget?.label ?? null
1113
+ },
1114
+ resolvedTarget,
1115
+ trimmed: text.trim()
1116
+ };
1117
+ }
1118
+ //#endregion
1119
+ //#region src/headless/send-money/use-send-money.ts
1120
+ function useSendMoney(input) {
1121
+ const { actor, onSent, onFailed } = input;
1122
+ const client = useCapxulClientOrNull();
1123
+ const signerStatus = useSignerStatus(client);
1124
+ const balance = useCapxulAccountBalance({ enabled: actor.kind === "personal" });
1125
+ const account = actor.kind === "personal" ? balance.data ?? null : null;
1126
+ const assetOptions = useMemo(() => account === null ? [] : [{
1127
+ id: account.id,
1128
+ symbol: account.available.currency,
1129
+ availableDisplay: formatMoney(account.available, { grammar: "code" })
1130
+ }], [account]);
1131
+ const [selectedAssetId, setSelectedAssetId] = useState(null);
1132
+ useEffect(() => {
1133
+ if (!assetOptions.some((option) => option.id === selectedAssetId)) setSelectedAssetId(assetOptions[0]?.id ?? null);
1134
+ }, [assetOptions, selectedAssetId]);
1135
+ const selectedAssetOption = assetOptions.find((option) => option.id === selectedAssetId) ?? null;
1136
+ const selectedAssetMoney = selectedAssetOption !== null && account !== null ? account.available : null;
1137
+ const selectAsset = useCallback((id) => {
1138
+ setSelectedAssetId((current) => assetOptions.some((option) => option.id === id) ? id : current);
1139
+ }, [assetOptions]);
1140
+ const [amountText, setAmountText] = useState("");
1141
+ const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null : parseMoney(amountText, selectedAssetMoney), [amountText, selectedAssetMoney]);
1142
+ const parsedMoney = parsed !== null && !isMoneyParseError(parsed) ? parsed : null;
1143
+ const amountError = parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null;
1144
+ const recipient = useRecipientResolution(client);
1145
+ const resolvedTarget = recipient.resolvedTarget;
1146
+ const pay = useCapxulPay();
1147
+ const blockedReason = signerStatus !== "ready" ? "signer-not-ready" : actor.kind === "organization" ? "no-source" : selectedAssetOption === null ? "no-asset" : parsedMoney === null ? "amount-invalid" : resolvedTarget === null ? "recipient-unresolved" : null;
1148
+ const submitGate = useRef(false);
1149
+ const submit = useCallback(() => {
1150
+ if (blockedReason !== null || submitGate.current) return;
1151
+ if (parsedMoney === null || resolvedTarget === null) return;
1152
+ submitGate.current = true;
1153
+ const amount = parsedMoney;
1154
+ const sentRecipient = recipient.trimmed;
1155
+ pay.mutate({
1156
+ to: resolvedTarget.reference,
1157
+ amount
1158
+ }, {
1159
+ onSuccess: (payment) => onSent({
1160
+ payment,
1161
+ amount,
1162
+ recipient: sentRecipient
1163
+ }),
1164
+ onError: (error) => onFailed(error),
1165
+ onSettled: () => {
1166
+ submitGate.current = false;
1167
+ }
1168
+ });
1169
+ }, [
1170
+ blockedReason,
1171
+ onFailed,
1172
+ onSent,
1173
+ parsedMoney,
1174
+ pay,
1175
+ recipient.trimmed,
1176
+ resolvedTarget
1177
+ ]);
1178
+ return {
1179
+ asset: {
1180
+ options: assetOptions,
1181
+ selected: selectedAssetOption,
1182
+ select: selectAsset,
1183
+ error: actor.kind === "personal" && balance.error !== null ? "read-failed" : null
1184
+ },
1185
+ amount: {
1186
+ value: amountText,
1187
+ change: setAmountText,
1188
+ error: amountError,
1189
+ availableDisplay: selectedAssetOption?.availableDisplay ?? null
1190
+ },
1191
+ recipient: recipient.slice,
1192
+ actions: {
1193
+ submit,
1194
+ isSubmitting: pay.isPending,
1195
+ blocked: blockedReason !== null,
1196
+ blockedReason
1197
+ }
1198
+ };
1199
+ }
1200
+ //#endregion
1201
+ //#region src/headless/send-money/send-money.tsx
1202
+ const SendMoneyContext = createContext(null);
1203
+ function useSendMoneyContext() {
1204
+ const engine = useContext(SendMoneyContext);
1205
+ if (engine === null) throw new Error("CapxulSendMoney parts must be used inside <CapxulSendMoney>");
1206
+ return engine;
1207
+ }
1208
+ function Root$2({ actor, onSent, onFailed, children }) {
1209
+ const engine = useSendMoney({
1210
+ actor,
1211
+ onSent,
1212
+ onFailed
1213
+ });
1214
+ return /* @__PURE__ */ jsx(SendMoneyContext.Provider, {
1215
+ value: engine,
1216
+ children
1217
+ });
1218
+ }
1219
+ function Asset({ children }) {
1220
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().asset) });
1221
+ }
1222
+ function Amount({ children }) {
1223
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().amount) });
1224
+ }
1225
+ function Recipient({ children }) {
1226
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().recipient) });
1227
+ }
1228
+ function Actions({ children }) {
1229
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().actions) });
1230
+ }
1231
+ /** The compound shape the app's own design system already uses (`StatusTabs`). */
1232
+ const CapxulSendMoney = Object.assign(Root$2, {
1233
+ Asset,
1234
+ Amount,
1235
+ Recipient,
1236
+ Actions
1237
+ });
1238
+ //#endregion
1239
+ //#region src/headless/access/org-member.tsx
1240
+ const DENIED_NO_ORG = {
1241
+ allowed: false,
1242
+ isLoading: false,
1243
+ reason: "org-unavailable"
1244
+ };
1245
+ const DENIED_LOADING = {
1246
+ allowed: false,
1247
+ isLoading: true,
1248
+ reason: "loading"
1249
+ };
1250
+ const ALLOWED = {
1251
+ allowed: true,
1252
+ isLoading: false,
1253
+ reason: null
1254
+ };
1255
+ const DENIED_NO_PERMISSION = {
1256
+ allowed: false,
1257
+ isLoading: false,
1258
+ reason: "no-permission"
1259
+ };
1260
+ function canSlice(reading, held) {
1261
+ switch (reading.kind) {
1262
+ case "no-org":
1263
+ case "unavailable": return DENIED_NO_ORG;
1264
+ case "loading": return DENIED_LOADING;
1265
+ case "ready": return held(reading.me) ? ALLOWED : DENIED_NO_PERMISSION;
1266
+ }
1267
+ }
1268
+ /**
1269
+ * The classification rule, derived line by line from the app's own expression
1270
+ * at `use-current-organization.ts:124-134`. Same order, same folds:
1271
+ *
1272
+ * | Test, in order | standing |
1273
+ * | ------------------------------------------- | -------------- |
1274
+ * | the `me()` read has not settled | `"loading"` |
1275
+ * | the read errored, or no `orgId` was given | `"unavailable"`|
1276
+ * | `role === null` | `"unavailable"`|
1277
+ * | `canManagePeople \|\| canSpend` | `"active"` |
1278
+ * | neither capability | `"restricted"` |
1279
+ *
1280
+ * `"unavailable"` folds TWO causes — a failed read and a member with no Role
1281
+ * label — exactly as today. And because the `role === null` test runs BEFORE
1282
+ * the capability test, `"active"` guarantees a non-null label, so no screen
1283
+ * needs a fallback word in that branch.
1284
+ */
1285
+ function standingSlice(reading) {
1286
+ if (reading.kind === "loading") return {
1287
+ standing: "loading",
1288
+ roleLabel: null
1289
+ };
1290
+ if (reading.kind !== "ready") return {
1291
+ standing: "unavailable",
1292
+ roleLabel: null
1293
+ };
1294
+ const { role, capabilities } = reading.me;
1295
+ if (role === null) return {
1296
+ standing: "unavailable",
1297
+ roleLabel: null
1298
+ };
1299
+ return {
1300
+ standing: capabilities.canManagePeople || capabilities.canSpend ? "active" : "restricted",
1301
+ roleLabel: role.label
1302
+ };
1303
+ }
1304
+ /** The engine — all the logic. NOT exported from the package barrel. */
1305
+ function useOrgMember(orgId) {
1306
+ const client = useCapxulClientOrNull();
1307
+ const identity = useCapxulIdentityOrNull();
1308
+ const ready = client !== null && identity !== null && isClaimed$1(identity);
1309
+ const mayStillOpen = client === null || identity === null || isRestoring$1(identity);
1310
+ const query = useQuery({
1311
+ queryKey: capxulKeys.orgMe(orgId),
1312
+ queryFn: async () => {
1313
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.me").org(orgId).me());
1314
+ },
1315
+ enabled: ready && orgId !== void 0
1316
+ });
1317
+ const reading = useMemo(() => {
1318
+ if (orgId === void 0) return { kind: "no-org" };
1319
+ if (!ready) return { kind: mayStillOpen ? "loading" : "unavailable" };
1320
+ if (query.error !== null) return { kind: "unavailable" };
1321
+ if (query.data !== void 0) return {
1322
+ kind: "ready",
1323
+ me: query.data
1324
+ };
1325
+ return { kind: "loading" };
1326
+ }, [
1327
+ mayStillOpen,
1328
+ orgId,
1329
+ query.data,
1330
+ query.error,
1331
+ ready
1332
+ ]);
1333
+ return useMemo(() => ({
1334
+ can: {
1335
+ spend: canSlice(reading, (me) => me.capabilities.canSpend),
1336
+ managePeople: canSlice(reading, (me) => me.capabilities.canManagePeople)
1337
+ },
1338
+ standing: standingSlice(reading)
1339
+ }), [reading]);
1340
+ }
1341
+ const OrgMemberContext = createContext(null);
1342
+ function useOrgMemberContext() {
1343
+ const state = useContext(OrgMemberContext);
1344
+ if (state === null) throw new Error("CapxulOrgMember parts must be used inside <CapxulOrgMember>");
1345
+ return state;
1346
+ }
1347
+ function Root$1({ orgId, children }) {
1348
+ const state = useOrgMember(orgId);
1349
+ return /* @__PURE__ */ jsx(OrgMemberContext.Provider, {
1350
+ value: state,
1351
+ children
1352
+ });
1353
+ }
1354
+ /**
1355
+ * One part serves all four denial idioms the app already uses: disabled with no
1356
+ * explanation, disabled plus a native `title`, a whole-surface swap, and
1357
+ * buttons that vanish. The part branches on nothing — the root computes both
1358
+ * slices and this picks the one the `do` prop names.
1359
+ */
1360
+ function Can({ do: action, children }) {
1361
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useOrgMemberContext().can[action]) });
1362
+ }
1363
+ function Standing({ children }) {
1364
+ return /* @__PURE__ */ jsx(Fragment, { children: children(useOrgMemberContext().standing) });
1365
+ }
1366
+ /** The compound shape the app's own design system already uses (`StatusTabs`). */
1367
+ const CapxulOrgMember = Object.assign(Root$1, {
1368
+ Can,
1369
+ Standing
1370
+ });
1371
+ //#endregion
1372
+ //#region src/headless/access/dashboard-access.tsx
1373
+ const PART_NAME = {
1374
+ checking: "Checking",
1375
+ error: "Error",
1376
+ redirect: "Redirect",
1377
+ granted: "Granted"
1378
+ };
1379
+ /**
1380
+ * Mirrors `publicIdentityFailure`, which is internal to `@capxul/sdk`
1381
+ * (`src/surface/_shared/effect-actor-bridge.ts`). The identity machine carries
1382
+ * its own failure representation; `.Error` hands a `CapxulError`, so the
1383
+ * projection happens once, here.
1384
+ */
1385
+ function identityError(state) {
1386
+ const { failure } = state;
1387
+ if (failure.error !== void 0) return failure.error;
1388
+ return new CapxulError(failure.code === "WORK_DIED" ? "UNKNOWN" : failure.code, failure.message, { layer: "identity" });
1389
+ }
1390
+ /**
1391
+ * Whether the Organization lane has reached a position that names a real
1392
+ * screen. `isRestoring` deliberately leaves this question to the gate (#1518),
1393
+ * because every org lane sits under a claimed account and `destination()`
1394
+ * already names a screen for each of its positions — except `loading`, which
1395
+ * means "still finding out" and whose destination is the provisioning screen.
1396
+ *
1397
+ * NEGATIVE classification, the same discipline `isRestoring` uses: only the
1398
+ * settled positions are enumerated, so a lane position added later HOLDS the
1399
+ * gate instead of bouncing a member who is in fact inside a ready Organization.
1400
+ * It gates the REDIRECT only, never the grant, so it can never blank a screen
1401
+ * the probes have already said yes to.
1402
+ */
1403
+ function isOrgLaneSettled(identity) {
1404
+ if (identity.phase !== "authenticated" || identity.account.at !== "claimed") return true;
1405
+ const org = identity.account.org;
1406
+ if (org === null) return true;
1407
+ switch (org.at) {
1408
+ case "ready":
1409
+ case "failed": return true;
1410
+ default: return false;
1411
+ }
1412
+ }
1413
+ /** Exactly the Organization asked for, and ready to be used. */
1414
+ function isExactOrgReady(lifecycle, orgId) {
1415
+ return lifecycle?.status === "ready" && String(lifecycle.orgId) === orgId;
1416
+ }
1417
+ function isLifecycleSettled(lifecycle) {
1418
+ return lifecycle?.status === "ready" || lifecycle?.status === "failed";
1419
+ }
1420
+ function destinationOrNull(identity) {
1421
+ return identity === null ? null : resolveIdentityDestination(identity);
1422
+ }
1423
+ function isClaimedOrFalse(identity) {
1424
+ return identity !== null && isClaimed$1(identity);
1425
+ }
1426
+ /**
1427
+ * The branch, as one pure decision over four settled facts.
1428
+ *
1429
+ * The order is the frozen one: an error wins over everything, an unsettled
1430
+ * probe holds, and only then does the judgment speak. The last two arms are the
1431
+ * whole bug class the deleted app guard kept re-fixing — a destination is
1432
+ * obeyed only once every lane that could still change it has settled, and
1433
+ * anything else HOLDS rather than guessing.
1434
+ */
1435
+ function decidePhase(input) {
1436
+ if (input.error !== null) return "error";
1437
+ if (!input.settled) return "checking";
1438
+ if (input.granted) return "granted";
1439
+ if (input.orgLaneSettled && input.destination !== null) return "redirect";
1440
+ return "checking";
1441
+ }
1442
+ /** The engine — all the logic. NOT exported from the package barrel. */
1443
+ function useDashboardAccess(scope) {
1444
+ const client = useCapxulClientOrNull();
1445
+ const identity = useCapxulIdentityOrNull();
1446
+ const destination = useMemo(() => destinationOrNull(identity), [identity]);
1447
+ const orgId = scope.kind === "organization" ? scope.orgId : void 0;
1448
+ const claimed = isClaimedOrFalse(identity);
1449
+ const sessionError = useMemo(() => identity?.phase === "faulted" ? identityError(identity) : null, [identity]);
1450
+ const wantsMemberships = orgId !== void 0 && claimed;
1451
+ const memberships = useCapxulOrgs({ enabled: wantsMemberships });
1452
+ const membershipError = wantsMemberships ? memberships.error : null;
1453
+ const membershipsSettled = !wantsMemberships || memberships.data !== void 0 || membershipError !== null;
1454
+ const hasMembership = orgId !== void 0 && (memberships.data ?? []).some((organization) => String(organization.id) === orgId);
1455
+ const wantsLifecycle = wantsMemberships && membershipsSettled && hasMembership;
1456
+ const lifecycle = useQuery({
1457
+ queryKey: capxulKeys.orgLifecycle(orgId),
1458
+ queryFn: async () => {
1459
+ return unwrapCapxulResult(await requireBootstrappedClient(client, "org.getLifecycle").org(orgId).getLifecycle());
1460
+ },
1461
+ enabled: wantsLifecycle && client !== null
1462
+ });
1463
+ const lifecycleError = wantsLifecycle ? lifecycle.error : null;
1464
+ const lifecycleSettled = !wantsLifecycle || lifecycleError !== null || isLifecycleSettled(lifecycle.data);
1465
+ const retry = useCallback(() => {
1466
+ if (sessionError !== null && client !== null) client.auth.getSession();
1467
+ if (membershipError !== null) memberships.refetch();
1468
+ if (lifecycleError !== null) lifecycle.refetch();
1469
+ }, [
1470
+ client,
1471
+ sessionError,
1472
+ membershipError,
1473
+ lifecycleError,
1474
+ memberships.refetch,
1475
+ lifecycle.refetch
1476
+ ]);
1477
+ const error = sessionError ?? membershipError ?? lifecycleError;
1478
+ const phase = decidePhase({
1479
+ error,
1480
+ settled: client !== null && identity !== null && !isRestoring$1(identity) && membershipsSettled && lifecycleSettled,
1481
+ granted: scope.kind === "personal" ? claimed : claimed && orgId !== void 0 && hasMembership && isExactOrgReady(lifecycle.data, orgId),
1482
+ orgLaneSettled: identity !== null && isOrgLaneSettled(identity),
1483
+ destination
1484
+ });
1485
+ return useMemo(() => ({
1486
+ phase,
1487
+ error,
1488
+ destination,
1489
+ retry
1490
+ }), [
1491
+ phase,
1492
+ error,
1493
+ destination,
1494
+ retry
1495
+ ]);
1496
+ }
1497
+ const DashboardAccessContext = createContext(null);
1498
+ function useDashboardAccessContext() {
1499
+ const value = useContext(DashboardAccessContext);
1500
+ if (value === null) throw new Error("CapxulDashboardAccess parts must be used inside <CapxulDashboardAccess>");
1501
+ return value;
1502
+ }
1503
+ /** A part's own turn, and its registration for the missing-part warning. */
1504
+ function usePart(phase) {
1505
+ const { state, placed } = useDashboardAccessContext();
1506
+ useEffect(() => {
1507
+ placed.add(phase);
1508
+ return () => {
1509
+ placed.delete(phase);
1510
+ };
1511
+ }, [placed, phase]);
1512
+ return state.phase === phase ? state : null;
1513
+ }
1514
+ function Root({ scope, children }) {
1515
+ const state = useDashboardAccess(scope);
1516
+ const [placed] = useState(() => /* @__PURE__ */ new Set());
1517
+ const value = useMemo(() => ({
1518
+ state,
1519
+ placed
1520
+ }), [state, placed]);
1521
+ useEffect(() => {
1522
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
1523
+ if (placed.has(state.phase)) return;
1524
+ console.warn(`<CapxulDashboardAccess> reached "${state.phase}" but no <CapxulDashboardAccess.${PART_NAME[state.phase]}> is placed, so nothing renders.`);
1525
+ }, [placed, state.phase]);
1526
+ return /* @__PURE__ */ jsx(DashboardAccessContext.Provider, {
1527
+ value,
1528
+ children
1529
+ });
1530
+ }
1531
+ /** Hands nothing — today's app renders `null` here, exactly as it does now. */
1532
+ function Checking({ children }) {
1533
+ return usePart("checking") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
1534
+ }
1535
+ function DashboardError({ children }) {
1536
+ const state = usePart("error");
1537
+ if (state === null || state.error === null) return null;
1538
+ return /* @__PURE__ */ jsx(Fragment, { children: children({
1539
+ error: state.error,
1540
+ retry: state.retry
1541
+ }) });
1542
+ }
1543
+ function Redirect({ children }) {
1544
+ const state = usePart("redirect");
1545
+ if (state === null || state.destination === null) return null;
1546
+ return /* @__PURE__ */ jsx(Fragment, { children: children({ destination: state.destination }) });
1547
+ }
1548
+ function Granted({ children }) {
1549
+ return usePart("granted") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
1550
+ }
1551
+ /** The compound shape the app's own design system already uses (`StatusTabs`). */
1552
+ const CapxulDashboardAccess = Object.assign(Root, {
1553
+ Checking,
1554
+ Error: DashboardError,
1555
+ Redirect,
1556
+ Granted
1557
+ });
1558
+ //#endregion
1559
+ export { CapxulAuthenticationController, CapxulContacts, CapxulDashboardAccess, CapxulOnboardingController, CapxulOrgMember, CapxulProvider, CapxulSendMoney, acknowledgeOnboardingDestination, activeOnboardingRecovery, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUsernameAvailability };