@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/README.md +13 -0
- package/dist/{controllers-D3_Q5d2t.d.mts → controllers-BT4YzBr9.d.mts} +1 -1
- package/dist/{controllers-Chqyy3HR.mjs → controllers-VwUPB619.mjs} +24 -0
- package/dist/index.d.mts +320 -3
- package/dist/index.mjs +777 -229
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.mjs +1 -1
- package/package.json +3 -3
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-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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()
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
-
})
|
|
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
|
|
553
|
-
|
|
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
|
-
|
|
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
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
608
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
996
|
-
const
|
|
997
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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 };
|