@capxul/sdk-react 4.20.0-beta.1 → 4.20.0-beta.10
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 +15 -0
- package/dist/{controllers-DXmEHrnw.mjs → controllers-L1UqwWS1.mjs} +5 -2
- package/dist/index.d.mts +102 -50
- package/dist/index.mjs +572 -225
- package/dist/testing/index.mjs +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { _ as useCapxul, a as useCapxulAuth, c as useCapxulIdentityOrNull, d as toInvocationControls, f as toInvocationOptions, g as useCapxulClientOrNull, h as capxulKeys, i as entered, l as useCapxulSend, m as actingAccount, n as CapxulOnboardingController, o as useCapxulDestination, p as accountKey, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-
|
|
2
|
+
import { _ as useCapxul, a as useCapxulAuth, c as useCapxulIdentityOrNull, d as toInvocationControls, f as toInvocationOptions, g as useCapxulClientOrNull, h as capxulKeys, i as entered, l as useCapxulSend, m as actingAccount, n as CapxulOnboardingController, o as useCapxulDestination, p as accountKey, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-L1UqwWS1.mjs";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { CAPXUL_OPERATIONS, CapxulError, EVM_ADDRESS_RE, Errors, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, fingerprintPaymentIntent, formatMoney, isCapxulError, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseMoney, paymentPhase, resolveIdentityDestination, toEvmAddress, toPartyId } from "@capxul/sdk";
|
|
5
|
+
import { CAPXUL_OPERATIONS, CapxulError, EVM_ADDRESS_RE, Errors, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, assetSymbolFor, fingerprintPaymentIntent, formatAssetAmount, formatMoney, isCapxulError, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseAssetAmount, parseMoney, paymentPhase, requireAsset, resolveIdentityDestination, toEvmAddress, toPartyId, toRaw, valueActivitySummary } from "@capxul/sdk";
|
|
6
6
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/internal/require-bootstrapped-client.ts
|
|
8
8
|
/**
|
|
@@ -546,22 +546,6 @@ function useCapxulCreateOrg() {
|
|
|
546
546
|
});
|
|
547
547
|
}
|
|
548
548
|
//#endregion
|
|
549
|
-
//#region src/hooks/use-capxul-invite-member.ts
|
|
550
|
-
function useCapxulInviteMember(orgId) {
|
|
551
|
-
const client = useCapxulClientOrNull();
|
|
552
|
-
const queryClient = useQueryClient();
|
|
553
|
-
return useMutation({
|
|
554
|
-
mutationFn: async ({ options, ...input }) => {
|
|
555
|
-
if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulInviteMember");
|
|
556
|
-
return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.invite).org(orgId).invite(input, toInvocationControls(options)));
|
|
557
|
-
},
|
|
558
|
-
onSuccess: async () => {
|
|
559
|
-
if (orgId === void 0) return;
|
|
560
|
-
await queryClient.invalidateQueries({ queryKey: capxulKeys.orgMembers(orgId) });
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
//#endregion
|
|
565
549
|
//#region src/headless/journey/journey-state.ts
|
|
566
550
|
const JOURNEY_KEY = "capxul.onboarding.journey.v1";
|
|
567
551
|
const VALIDATED_OWNER_KEY = "capxul.onboarding.validated-owner.v1";
|
|
@@ -1079,7 +1063,7 @@ function useContactsContext() {
|
|
|
1079
1063
|
if (engine === null) throw new Error("CapxulContacts parts must be used inside <CapxulContacts>");
|
|
1080
1064
|
return engine;
|
|
1081
1065
|
}
|
|
1082
|
-
function Root$
|
|
1066
|
+
function Root$7({ actor, onAdded, onFailed, children }) {
|
|
1083
1067
|
const engine = useContacts({
|
|
1084
1068
|
actor,
|
|
1085
1069
|
onAdded,
|
|
@@ -1103,7 +1087,7 @@ function Add({ children }) {
|
|
|
1103
1087
|
return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsContext().add) });
|
|
1104
1088
|
}
|
|
1105
1089
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
1106
|
-
const CapxulContacts = Object.assign(Root$
|
|
1090
|
+
const CapxulContacts = Object.assign(Root$7, {
|
|
1107
1091
|
Summary: Summary$3,
|
|
1108
1092
|
List,
|
|
1109
1093
|
Add
|
|
@@ -1141,15 +1125,8 @@ function useActivityEnabled() {
|
|
|
1141
1125
|
}
|
|
1142
1126
|
//#endregion
|
|
1143
1127
|
//#region src/headless/activity/detail.ts
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
* the payslip, else the receipt, else nothing. Those two kinds are the ones the
|
|
1147
|
-
* Download receipt button is for. A Movement detail carries no documents.
|
|
1148
|
-
*/
|
|
1149
|
-
function pickReceiptDocument(detail) {
|
|
1150
|
-
if (detail === null || detail.kind !== "payment") return null;
|
|
1151
|
-
const documents = detail.payment.documents;
|
|
1152
|
-
return documents.find((document) => document.kind === "payslip") ?? documents.find((document) => document.kind === "receipt") ?? null;
|
|
1128
|
+
function detailDocuments(detail) {
|
|
1129
|
+
return detail === null ? [] : detail.kind === "payment" ? detail.payment.documents : detail.documents;
|
|
1153
1130
|
}
|
|
1154
1131
|
/**
|
|
1155
1132
|
* The root actor and the whole reference are included. A payment reference can
|
|
@@ -1184,27 +1161,76 @@ function useActivityDetail(reference, actor) {
|
|
|
1184
1161
|
const enabled = useActivityEnabled() && reference !== void 0;
|
|
1185
1162
|
const detail = useQuery({
|
|
1186
1163
|
queryKey: accountKey(capxulKeys.activityDetail(reference, actor), identity),
|
|
1187
|
-
queryFn: async () => {
|
|
1164
|
+
queryFn: async ({ signal }) => {
|
|
1188
1165
|
if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
|
|
1189
|
-
return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference,
|
|
1166
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference, {
|
|
1167
|
+
...actor === void 0 ? {} : { actor },
|
|
1168
|
+
signal
|
|
1169
|
+
}));
|
|
1190
1170
|
},
|
|
1191
1171
|
enabled
|
|
1192
1172
|
});
|
|
1193
|
-
const
|
|
1173
|
+
const documents = detailDocuments(detail.data ?? null);
|
|
1174
|
+
const [selected, setSelected] = useState(null);
|
|
1175
|
+
const selectedDocument = (selected === null ? void 0 : documents.find((doc) => doc.documentHash === selected.documentHash && doc.contentHash === selected.contentHash)) ?? documents.find((doc) => doc.role === "instruction") ?? documents.find((doc) => doc.role === "movement") ?? documents[0] ?? null;
|
|
1176
|
+
const pending = useRef(/* @__PURE__ */ new Set());
|
|
1177
|
+
const mounted = useRef(true);
|
|
1178
|
+
useEffect(() => {
|
|
1179
|
+
mounted.current = true;
|
|
1180
|
+
const reads = pending.current;
|
|
1181
|
+
return () => {
|
|
1182
|
+
mounted.current = false;
|
|
1183
|
+
for (const read of reads) read.abort();
|
|
1184
|
+
reads.clear();
|
|
1185
|
+
};
|
|
1186
|
+
}, []);
|
|
1187
|
+
const selectDocument = useCallback((ref) => {
|
|
1188
|
+
if (!documents.some((doc) => doc.documentHash === ref.documentHash && doc.contentHash === ref.contentHash)) throw Errors.invalidInput("documentHash", "document not found");
|
|
1189
|
+
for (const read of pending.current) read.abort();
|
|
1190
|
+
pending.current.clear();
|
|
1191
|
+
setIsOpeningDocument(false);
|
|
1192
|
+
setSelected({
|
|
1193
|
+
documentHash: ref.documentHash,
|
|
1194
|
+
contentHash: ref.contentHash
|
|
1195
|
+
});
|
|
1196
|
+
}, [documents]);
|
|
1194
1197
|
const [isOpeningDocument, setIsOpeningDocument] = useState(false);
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1198
|
+
const readDocument = useCallback(async (download) => {
|
|
1199
|
+
const operation = download ? CAPXUL_OPERATIONS.paymentDocuments.get : CAPXUL_OPERATIONS.paymentDocuments.render;
|
|
1200
|
+
if (!mounted.current) throw Errors.cancelled({ operation });
|
|
1201
|
+
if (reference === void 0 || selectedDocument === null) throw Errors.invalidInput("documentHash", "no document on this row");
|
|
1202
|
+
const controller = new AbortController();
|
|
1203
|
+
pending.current.add(controller);
|
|
1197
1204
|
setIsOpeningDocument(true);
|
|
1198
1205
|
try {
|
|
1199
|
-
|
|
1206
|
+
const bootstrapped = requireBootstrappedClient(client, download ? CAPXUL_OPERATIONS.paymentDocuments.get : CAPXUL_OPERATIONS.paymentDocuments.render);
|
|
1207
|
+
const pair = {
|
|
1208
|
+
documentHash: selectedDocument.documentHash,
|
|
1209
|
+
contentHash: selectedDocument.contentHash
|
|
1210
|
+
};
|
|
1211
|
+
const options = { signal: controller.signal };
|
|
1212
|
+
const result = download ? await bootstrapped.paymentDocuments.get(pair, options) : await bootstrapped.paymentDocuments.render(pair, options);
|
|
1213
|
+
if (controller.signal.aborted) throw Errors.cancelled({ operation });
|
|
1214
|
+
return unwrapCapxulResult(result);
|
|
1200
1215
|
} finally {
|
|
1201
|
-
|
|
1216
|
+
pending.current.delete(controller);
|
|
1217
|
+
if (!controller.signal.aborted) setIsOpeningDocument(pending.current.size > 0);
|
|
1202
1218
|
}
|
|
1203
1219
|
}, [
|
|
1204
1220
|
client,
|
|
1205
|
-
|
|
1221
|
+
selectedDocument,
|
|
1206
1222
|
reference
|
|
1207
1223
|
]);
|
|
1224
|
+
const renderDocument = useCallback(async () => {
|
|
1225
|
+
const result = await readDocument(false);
|
|
1226
|
+
if (!("html" in result)) throw Errors.invalidInput("documentHash", "invalid document render");
|
|
1227
|
+
return result;
|
|
1228
|
+
}, [readDocument]);
|
|
1229
|
+
const downloadDocument = useCallback(async () => {
|
|
1230
|
+
const result = await readDocument(true);
|
|
1231
|
+
if (!("bytesBase64" in result)) throw Errors.invalidInput("documentHash", "invalid document download");
|
|
1232
|
+
return result;
|
|
1233
|
+
}, [readDocument]);
|
|
1208
1234
|
const [typed, setTyped] = useState(null);
|
|
1209
1235
|
const annotate = useCapxulAnnotateMovement();
|
|
1210
1236
|
const saveMemo = useCallback(async () => {
|
|
@@ -1229,8 +1255,15 @@ function useActivityDetail(reference, actor) {
|
|
|
1229
1255
|
typed
|
|
1230
1256
|
]);
|
|
1231
1257
|
return {
|
|
1232
|
-
|
|
1233
|
-
|
|
1258
|
+
...detail.data?.kind === "payment" ? { payment: {
|
|
1259
|
+
status: detail.data.payment.status,
|
|
1260
|
+
...detail.data.payment.execution === void 0 ? {} : { execution: detail.data.payment.execution }
|
|
1261
|
+
} } : {},
|
|
1262
|
+
documents,
|
|
1263
|
+
selectedDocument,
|
|
1264
|
+
selectDocument,
|
|
1265
|
+
renderDocument,
|
|
1266
|
+
downloadDocument,
|
|
1234
1267
|
isOpeningDocument,
|
|
1235
1268
|
memo: typed ?? detail.data?.annotation?.memo ?? "",
|
|
1236
1269
|
setMemo: setTyped,
|
|
@@ -1243,6 +1276,7 @@ function useActivityDetail(reference, actor) {
|
|
|
1243
1276
|
//#endregion
|
|
1244
1277
|
//#region src/headless/activity/summary.ts
|
|
1245
1278
|
const DAY_MS = 864e5;
|
|
1279
|
+
const display = (money) => money === null ? null : formatMoney(money);
|
|
1246
1280
|
/**
|
|
1247
1281
|
* `"last30days"` is the last 30 whole UTC days including today. Quantizing to
|
|
1248
1282
|
* the UTC day keeps the cache key stable for a whole day instead of changing on
|
|
@@ -1253,57 +1287,6 @@ function resolveWindow(window, now) {
|
|
|
1253
1287
|
if (window === "last30days") return { from: Math.floor(now / DAY_MS) * DAY_MS - 29 * DAY_MS };
|
|
1254
1288
|
return window;
|
|
1255
1289
|
}
|
|
1256
|
-
/** An integer raw-unit string lifted into a major-unit decimal string. */
|
|
1257
|
-
function majorUnits(raw, decimals) {
|
|
1258
|
-
const negative = raw.startsWith("-");
|
|
1259
|
-
const digits = negative ? raw.slice(1) : raw;
|
|
1260
|
-
const sign = negative ? "-" : "";
|
|
1261
|
-
if (decimals === 0) return `${sign}${digits}`;
|
|
1262
|
-
const padded = digits.padStart(decimals + 1, "0");
|
|
1263
|
-
const cut = padded.length - decimals;
|
|
1264
|
-
return `${sign}${padded.slice(0, cut)}.${padded.slice(cut)}`;
|
|
1265
|
-
}
|
|
1266
|
-
function absoluteNet(total) {
|
|
1267
|
-
const net = BigInt(total.net);
|
|
1268
|
-
return net < 0n ? -net : net;
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* Buckets carry different `decimals`, so their raw integers are not comparable.
|
|
1272
|
-
* Both are scaled to the wider precision before the magnitudes are ordered.
|
|
1273
|
-
*/
|
|
1274
|
-
function compareNet(left, right) {
|
|
1275
|
-
const width = Math.max(left.decimals, right.decimals);
|
|
1276
|
-
const scaledLeft = absoluteNet(left) * 10n ** BigInt(width - left.decimals);
|
|
1277
|
-
const scaledRight = absoluteNet(right) * 10n ** BigInt(width - right.decimals);
|
|
1278
|
-
if (scaledLeft === scaledRight) return 0;
|
|
1279
|
-
return scaledLeft < scaledRight ? -1 : 1;
|
|
1280
|
-
}
|
|
1281
|
-
/**
|
|
1282
|
-
* Which currency `.Summary` shows: the bucket matching the primary holding's
|
|
1283
|
-
* currency, else the bucket with the largest absolute `net`, breaking a tie on
|
|
1284
|
-
* currency code ascending so the choice is stable across reloads.
|
|
1285
|
-
*
|
|
1286
|
-
* The primary holding is matched on `available.currency` — the consumer
|
|
1287
|
-
* `CurrencyCode` — and NOT on `Holding.symbol`, which is the raw token ticker
|
|
1288
|
-
* ("USDX") and can never equal a bucket's currency ("USD"). See the contract
|
|
1289
|
-
* correction on #1449.
|
|
1290
|
-
*/
|
|
1291
|
-
function pickBucket(totals, primaryCurrency) {
|
|
1292
|
-
if (primaryCurrency !== null) {
|
|
1293
|
-
const matched = totals.find((total) => total.currency === primaryCurrency);
|
|
1294
|
-
if (matched !== void 0) return matched;
|
|
1295
|
-
}
|
|
1296
|
-
let best = null;
|
|
1297
|
-
for (const total of totals) {
|
|
1298
|
-
if (best === null) {
|
|
1299
|
-
best = total;
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
const order = compareNet(total, best);
|
|
1303
|
-
if (order > 0 || order === 0 && total.currency < best.currency) best = total;
|
|
1304
|
-
}
|
|
1305
|
-
return best;
|
|
1306
|
-
}
|
|
1307
1290
|
/**
|
|
1308
1291
|
* The current UTC day number, which advances ON ITS OWN at UTC midnight.
|
|
1309
1292
|
*
|
|
@@ -1347,47 +1330,36 @@ function useActivitySummary(actor, window) {
|
|
|
1347
1330
|
const params = {};
|
|
1348
1331
|
if (actor !== void 0) params.actor = actor;
|
|
1349
1332
|
if (range !== void 0) params.window = range;
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
queryKey: accountKey([...capxulKeys.holdings(actor), "primary"], identity),
|
|
1356
|
-
queryFn: async () => {
|
|
1357
|
-
return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.holdings.primary).holdings.primary(actor === void 0 ? void 0 : { actor }));
|
|
1333
|
+
const result = unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.summary).activity.summary(params));
|
|
1334
|
+
return {
|
|
1335
|
+
...result,
|
|
1336
|
+
valuation: valueActivitySummary(result.totals)
|
|
1337
|
+
};
|
|
1358
1338
|
},
|
|
1359
1339
|
enabled
|
|
1360
1340
|
});
|
|
1361
1341
|
const totals = summary.data?.totals;
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
const bucket = useMemo(() => totals === void 0 || isLoading ? null : pickBucket(totals, primaryCurrency), [
|
|
1365
|
-
isLoading,
|
|
1366
|
-
primaryCurrency,
|
|
1367
|
-
totals
|
|
1368
|
-
]);
|
|
1369
|
-
if (bucket === null) return {
|
|
1342
|
+
const isLoading = summary.isLoading;
|
|
1343
|
+
if (summary.data === void 0 || totals === void 0 || totals.length === 0 || isLoading) return {
|
|
1370
1344
|
currency: null,
|
|
1371
1345
|
moneyIn: null,
|
|
1372
1346
|
moneyOut: null,
|
|
1373
1347
|
net: null,
|
|
1374
1348
|
pending: null,
|
|
1349
|
+
unrated: [],
|
|
1375
1350
|
count: summary.data?.count ?? 0,
|
|
1376
1351
|
isLoading,
|
|
1377
1352
|
error: asCapxulError$1(summary.error),
|
|
1378
1353
|
...summary.data?.readWarning === void 0 ? {} : { readWarning: summary.data.readWarning }
|
|
1379
1354
|
};
|
|
1380
|
-
const
|
|
1381
|
-
currency: bucket.currency,
|
|
1382
|
-
value: majorUnits(raw, bucket.decimals),
|
|
1383
|
-
decimals: bucket.decimals
|
|
1384
|
-
});
|
|
1355
|
+
const valuation = summary.data.valuation;
|
|
1385
1356
|
return {
|
|
1386
|
-
currency:
|
|
1387
|
-
moneyIn: display(
|
|
1388
|
-
moneyOut: display(
|
|
1389
|
-
net: display(
|
|
1390
|
-
pending: display(
|
|
1357
|
+
currency: valuation.moneyIn?.currency ?? null,
|
|
1358
|
+
moneyIn: display(valuation.moneyIn),
|
|
1359
|
+
moneyOut: display(valuation.moneyOut),
|
|
1360
|
+
net: display(valuation.net),
|
|
1361
|
+
pending: display(valuation.pending),
|
|
1362
|
+
unrated: valuation.unrated,
|
|
1391
1363
|
count: summary.data?.count ?? 0,
|
|
1392
1364
|
isLoading,
|
|
1393
1365
|
error: asCapxulError$1(summary.error),
|
|
@@ -1414,7 +1386,7 @@ const SIGN = Object.freeze({
|
|
|
1414
1386
|
self: ""
|
|
1415
1387
|
});
|
|
1416
1388
|
function toActivityRow(item, actor) {
|
|
1417
|
-
const amountDisplay =
|
|
1389
|
+
const amountDisplay = formatAssetAmount(item.amount);
|
|
1418
1390
|
const signedAmountDisplay = `${SIGN[item.direction]}${amountDisplay}`;
|
|
1419
1391
|
if (item.kind === "movement") return {
|
|
1420
1392
|
kind: "movement",
|
|
@@ -1426,32 +1398,37 @@ function toActivityRow(item, actor) {
|
|
|
1426
1398
|
counterpartyLabel: item.counterpartyLabel,
|
|
1427
1399
|
amountDisplay,
|
|
1428
1400
|
signedAmountDisplay,
|
|
1429
|
-
currency: item.amount.
|
|
1401
|
+
currency: assetSymbolFor(item.amount.asset),
|
|
1430
1402
|
createdAt: item.createdAt,
|
|
1431
1403
|
classification: item.classification,
|
|
1432
1404
|
phase: MOVEMENT_PHASE,
|
|
1433
1405
|
receipt: item.receipt
|
|
1434
1406
|
};
|
|
1407
|
+
const reference = actor === void 0 ? {
|
|
1408
|
+
kind: "payment",
|
|
1409
|
+
id: item.id
|
|
1410
|
+
} : {
|
|
1411
|
+
kind: "payment",
|
|
1412
|
+
id: item.id,
|
|
1413
|
+
actor
|
|
1414
|
+
};
|
|
1435
1415
|
return {
|
|
1436
1416
|
kind: "payment",
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
id: item.id
|
|
1440
|
-
} : {
|
|
1441
|
-
kind: "payment",
|
|
1442
|
-
id: item.id,
|
|
1443
|
-
actor
|
|
1444
|
-
},
|
|
1417
|
+
...item.execution === void 0 ? {} : { execution: item.execution },
|
|
1418
|
+
reference,
|
|
1445
1419
|
direction: item.direction,
|
|
1446
1420
|
counterpartyLabel: item.counterpartyLabel,
|
|
1447
1421
|
amountDisplay,
|
|
1448
1422
|
signedAmountDisplay,
|
|
1449
|
-
currency: item.amount.
|
|
1423
|
+
currency: assetSymbolFor(item.amount.asset),
|
|
1450
1424
|
createdAt: item.createdAt,
|
|
1451
1425
|
ledgerKind: item.ledgerKind,
|
|
1452
1426
|
paymentType: item.paymentType,
|
|
1453
1427
|
status: item.status,
|
|
1454
|
-
phase:
|
|
1428
|
+
phase: item.execution?.notSent ? {
|
|
1429
|
+
...paymentPhase(item.status),
|
|
1430
|
+
needsAction: item.direction !== "in"
|
|
1431
|
+
} : paymentPhase(item.status),
|
|
1455
1432
|
receipt: item.receipt
|
|
1456
1433
|
};
|
|
1457
1434
|
}
|
|
@@ -1524,8 +1501,8 @@ function buildCsv(items, actor, search) {
|
|
|
1524
1501
|
row.counterpartyLabel,
|
|
1525
1502
|
row.direction,
|
|
1526
1503
|
item.amount.value,
|
|
1527
|
-
item.amount.
|
|
1528
|
-
row.kind === "payment" ? row.status : "",
|
|
1504
|
+
assetSymbolFor(item.amount.asset),
|
|
1505
|
+
row.kind === "payment" ? row.execution?.notSent ? "not_sent" : row.execution?.status === "unresolved" ? "verifying" : row.status : "",
|
|
1529
1506
|
row.reference.id,
|
|
1530
1507
|
row.receipt?.transactionHash ?? ""
|
|
1531
1508
|
]));
|
|
@@ -1642,6 +1619,16 @@ function useActivity(input) {
|
|
|
1642
1619
|
searchText
|
|
1643
1620
|
]);
|
|
1644
1621
|
const { fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = query;
|
|
1622
|
+
const emptyContinuation = pages?.at(-1)?.items.length === 0 && hasNextPage && query.error === null;
|
|
1623
|
+
useEffect(() => {
|
|
1624
|
+
if (enabled && emptyContinuation && !isFetchingNextPage) fetchNextPage();
|
|
1625
|
+
}, [
|
|
1626
|
+
enabled,
|
|
1627
|
+
emptyContinuation,
|
|
1628
|
+
fetchNextPage,
|
|
1629
|
+
isFetchingNextPage,
|
|
1630
|
+
pages
|
|
1631
|
+
]);
|
|
1645
1632
|
const pagingRef = useRef({
|
|
1646
1633
|
fetchNextPage,
|
|
1647
1634
|
hasNextPage,
|
|
@@ -1744,7 +1731,7 @@ function useActivity(input) {
|
|
|
1744
1731
|
},
|
|
1745
1732
|
rows: {
|
|
1746
1733
|
rows,
|
|
1747
|
-
isLoading: query.isLoading,
|
|
1734
|
+
isLoading: query.isLoading || rows.length === 0 && emptyContinuation,
|
|
1748
1735
|
error: asCapxulError$1(query.error),
|
|
1749
1736
|
retry,
|
|
1750
1737
|
observedAt: pages === void 0 ? null : pages[pages.length - 1]?.observedAt ?? null,
|
|
@@ -1770,7 +1757,7 @@ function useActivityContext() {
|
|
|
1770
1757
|
if (engine === null) throw new Error("CapxulActivity parts must be used inside <CapxulActivity>");
|
|
1771
1758
|
return engine;
|
|
1772
1759
|
}
|
|
1773
|
-
function Root$
|
|
1760
|
+
function Root$6({ actor, pageSize, children }) {
|
|
1774
1761
|
const engine = useActivity({
|
|
1775
1762
|
...actor === void 0 ? {} : { actor },
|
|
1776
1763
|
...pageSize === void 0 ? {} : { pageSize }
|
|
@@ -1821,7 +1808,7 @@ function Export({ children }) {
|
|
|
1821
1808
|
return /* @__PURE__ */ jsx(Fragment, { children: children(useActivityContext().csv) });
|
|
1822
1809
|
}
|
|
1823
1810
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
1824
|
-
const CapxulActivity = Object.assign(Root$
|
|
1811
|
+
const CapxulActivity = Object.assign(Root$6, {
|
|
1825
1812
|
Summary: Summary$2,
|
|
1826
1813
|
Filters,
|
|
1827
1814
|
Search,
|
|
@@ -1862,6 +1849,26 @@ function useOrgMeReading(orgId) {
|
|
|
1862
1849
|
]);
|
|
1863
1850
|
}
|
|
1864
1851
|
//#endregion
|
|
1852
|
+
//#region src/internal/write-names-asset.ts
|
|
1853
|
+
/**
|
|
1854
|
+
* Identity law: "A payment names an asset or inherits it from an authorized
|
|
1855
|
+
* fact. Missing asset refuses." The rule was recorded in the retired
|
|
1856
|
+
* treasury-cut handoff (removed from the current tree by the exact-asset
|
|
1857
|
+
* contraction; its record survives in Git history).
|
|
1858
|
+
*
|
|
1859
|
+
* A Capxul payment writer takes `PaymentMoney` — a peg currency, a value and
|
|
1860
|
+
* `decimals` — and settles one fixed legacy token. No `AssetId` crosses that
|
|
1861
|
+
* seam, so a payment composed from a named position cannot name the asset it
|
|
1862
|
+
* moves: a position selected as USDT authorizes a different token.
|
|
1863
|
+
*
|
|
1864
|
+
* `false` until the writer takes the command's asset. The Send and Payroll
|
|
1865
|
+
* composers read this to refuse a submission rather than authorize a token the
|
|
1866
|
+
* person did not pick.
|
|
1867
|
+
*/
|
|
1868
|
+
function writeNamesAsset() {
|
|
1869
|
+
return false;
|
|
1870
|
+
}
|
|
1871
|
+
//#endregion
|
|
1865
1872
|
//#region src/headless/send-money/signer-status.ts
|
|
1866
1873
|
const NO_SUBSCRIPTION = () => () => {};
|
|
1867
1874
|
function useSignerStatus(client) {
|
|
@@ -2011,6 +2018,37 @@ function useSelectedOption(options) {
|
|
|
2011
2018
|
}, [options]);
|
|
2012
2019
|
return [options.find((option) => option.id === selectedId) ?? null, select];
|
|
2013
2020
|
}
|
|
2021
|
+
/**
|
|
2022
|
+
* One Send `.Asset` option per position the Account holds, taken from
|
|
2023
|
+
* `account.balances` — never from `account.available`, which is the Account's
|
|
2024
|
+
* fiat VALUATION of those positions (ADR-0028 R5/R6). Labelling the valuation
|
|
2025
|
+
* was the false identity this cut removes, and it collapsed USDC+USDT into one
|
|
2026
|
+
* "USD" row.
|
|
2027
|
+
*
|
|
2028
|
+
* The option is keyed on the ASSET id, never the symbol and never the account:
|
|
2029
|
+
* several positions share one account id, and a symbol is display only
|
|
2030
|
+
* (ADR-0028 R3). Every row is listed, so a real multi-position Account still
|
|
2031
|
+
* offers its holdings.
|
|
2032
|
+
*/
|
|
2033
|
+
function assetSelectionsFromAccount$1(account) {
|
|
2034
|
+
if (account === null) return [];
|
|
2035
|
+
return account.balances.map((position) => {
|
|
2036
|
+
return {
|
|
2037
|
+
option: {
|
|
2038
|
+
id: position.asset,
|
|
2039
|
+
symbol: position.symbol,
|
|
2040
|
+
availableDisplay: formatAssetAmount({
|
|
2041
|
+
asset: position.asset,
|
|
2042
|
+
value: position.available
|
|
2043
|
+
})
|
|
2044
|
+
},
|
|
2045
|
+
money: {
|
|
2046
|
+
asset: position.asset,
|
|
2047
|
+
value: position.available
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2014
2052
|
function useSendMoney(input) {
|
|
2015
2053
|
const { actor, onSent, onFailed } = input;
|
|
2016
2054
|
const client = useCapxulClientOrNull();
|
|
@@ -2023,15 +2061,12 @@ function useSendMoney(input) {
|
|
|
2023
2061
|
const treasury = useCapxulOrgTreasury(orgId, { enabled: actor.kind === "organization" });
|
|
2024
2062
|
const accountQuery = actor.kind === "personal" ? balance : treasury;
|
|
2025
2063
|
const account = accountQuery.data ?? null;
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
symbol: account.available.currency,
|
|
2029
|
-
availableDisplay: formatMoney(account.available, { grammar: "code" })
|
|
2030
|
-
}], [account]);
|
|
2064
|
+
const assetSelections = useMemo(() => assetSelectionsFromAccount$1(account), [account]);
|
|
2065
|
+
const assetOptions = useMemo(() => assetSelections.map((selection) => selection.option), [assetSelections]);
|
|
2031
2066
|
const [selectedAssetOption, selectAsset] = useSelectedOption(assetOptions);
|
|
2032
|
-
const selectedAssetMoney =
|
|
2067
|
+
const selectedAssetMoney = useMemo(() => assetSelections.find((selection) => selection.option.id === selectedAssetOption?.id)?.money ?? null, [assetSelections, selectedAssetOption]);
|
|
2033
2068
|
const [amountText, setAmountText] = useState("");
|
|
2034
|
-
const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null :
|
|
2069
|
+
const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null : parseAssetAmount(amountText, requireAsset(selectedAssetMoney.asset)), [amountText, selectedAssetMoney]);
|
|
2035
2070
|
const parsedMoney = parsed !== null && !isMoneyParseError(parsed) ? parsed : null;
|
|
2036
2071
|
const amountError = parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null;
|
|
2037
2072
|
const recipient = useRecipientResolution(client, JSON.stringify([
|
|
@@ -2043,7 +2078,7 @@ function useSendMoney(input) {
|
|
|
2043
2078
|
const personalPay = useCapxulPay();
|
|
2044
2079
|
const organizationPay = useCapxulOrganizationPay(orgId);
|
|
2045
2080
|
const pay = actor.kind === "personal" ? personalPay : organizationPay;
|
|
2046
|
-
const blockedReason = signerStatus !== "ready" ? "signer-not-ready" : actor.kind === "organization" && selectedSource === null ? "no-source" : selectedAssetOption === null ? "no-asset" : parsedMoney === null ? "amount-invalid" : resolvedReference === null ? "recipient-unresolved" : null;
|
|
2081
|
+
const blockedReason = signerStatus !== "ready" ? "signer-not-ready" : actor.kind === "organization" && selectedSource === null ? "no-source" : selectedAssetOption === null ? "no-asset" : parsedMoney === null ? "amount-invalid" : resolvedReference === null ? "recipient-unresolved" : writeNamesAsset() ? null : "asset-unnamed";
|
|
2047
2082
|
const submitGate = useRef(false);
|
|
2048
2083
|
const submit = useCallback(() => {
|
|
2049
2084
|
if (blockedReason !== null || submitGate.current) return;
|
|
@@ -2130,7 +2165,7 @@ function useSendMoneyContext() {
|
|
|
2130
2165
|
if (engine === null) throw new Error("CapxulSendMoney parts must be used inside <CapxulSendMoney>");
|
|
2131
2166
|
return engine;
|
|
2132
2167
|
}
|
|
2133
|
-
function Root$
|
|
2168
|
+
function Root$5({ actor, onSent, onFailed, children }) {
|
|
2134
2169
|
const engine = useSendMoney({
|
|
2135
2170
|
actor,
|
|
2136
2171
|
onSent,
|
|
@@ -2157,7 +2192,7 @@ function Actions$1({ children }) {
|
|
|
2157
2192
|
return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().actions) });
|
|
2158
2193
|
}
|
|
2159
2194
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
2160
|
-
const CapxulSendMoney = Object.assign(Root$
|
|
2195
|
+
const CapxulSendMoney = Object.assign(Root$5, {
|
|
2161
2196
|
Source,
|
|
2162
2197
|
Asset: Asset$1,
|
|
2163
2198
|
Amount,
|
|
@@ -2247,7 +2282,7 @@ function useOrgMemberContext() {
|
|
|
2247
2282
|
if (state === null) throw new Error("CapxulOrgMember parts must be used inside <CapxulOrgMember>");
|
|
2248
2283
|
return state;
|
|
2249
2284
|
}
|
|
2250
|
-
function Root$
|
|
2285
|
+
function Root$4({ orgId, children }) {
|
|
2251
2286
|
const state = useOrgMember(orgId);
|
|
2252
2287
|
return /* @__PURE__ */ jsx(OrgMemberContext.Provider, {
|
|
2253
2288
|
value: state,
|
|
@@ -2267,7 +2302,7 @@ function Standing({ children }) {
|
|
|
2267
2302
|
return /* @__PURE__ */ jsx(Fragment, { children: children(useOrgMemberContext().standing) });
|
|
2268
2303
|
}
|
|
2269
2304
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
2270
|
-
const CapxulOrgMember = Object.assign(Root$
|
|
2305
|
+
const CapxulOrgMember = Object.assign(Root$4, {
|
|
2271
2306
|
Can,
|
|
2272
2307
|
Standing
|
|
2273
2308
|
});
|
|
@@ -2427,7 +2462,7 @@ function usePart(phase) {
|
|
|
2427
2462
|
}, [placed, phase]);
|
|
2428
2463
|
return state.phase === phase ? state : null;
|
|
2429
2464
|
}
|
|
2430
|
-
function Root$
|
|
2465
|
+
function Root$3({ scope, children }) {
|
|
2431
2466
|
const state = useDashboardAccess(scope);
|
|
2432
2467
|
const [placed] = useState(() => /* @__PURE__ */ new Set());
|
|
2433
2468
|
const value = useMemo(() => ({
|
|
@@ -2445,7 +2480,7 @@ function Root$2({ scope, children }) {
|
|
|
2445
2480
|
});
|
|
2446
2481
|
}
|
|
2447
2482
|
/** Hands nothing — today's app renders `null` here, exactly as it does now. */
|
|
2448
|
-
function Checking({ children }) {
|
|
2483
|
+
function Checking$1({ children }) {
|
|
2449
2484
|
return usePart("checking") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
|
|
2450
2485
|
}
|
|
2451
2486
|
function DashboardError({ children }) {
|
|
@@ -2472,19 +2507,14 @@ function Granted({ children }) {
|
|
|
2472
2507
|
return usePart("granted") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
|
|
2473
2508
|
}
|
|
2474
2509
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
2475
|
-
const CapxulDashboardAccess = Object.assign(Root$
|
|
2476
|
-
Checking,
|
|
2510
|
+
const CapxulDashboardAccess = Object.assign(Root$3, {
|
|
2511
|
+
Checking: Checking$1,
|
|
2477
2512
|
Error: DashboardError,
|
|
2478
2513
|
Redirect,
|
|
2479
2514
|
Granted
|
|
2480
2515
|
});
|
|
2481
2516
|
//#endregion
|
|
2482
2517
|
//#region src/headless/payroll/money.ts
|
|
2483
|
-
/** `{ value: "12.34", decimals: 6 }` → `12340000n`. */
|
|
2484
|
-
function toMinorUnits(money) {
|
|
2485
|
-
const [whole = "0", fraction = ""] = money.value.split(".");
|
|
2486
|
-
return BigInt(`${whole}${fraction.padEnd(money.decimals, "0")}`);
|
|
2487
|
-
}
|
|
2488
2518
|
const TYPED_AMOUNT = /^(-?)(\d+)(?:\.(\d+))?$/;
|
|
2489
2519
|
/**
|
|
2490
2520
|
* Σ of the employer's TYPED amounts on a saved group — a DISPLAY total.
|
|
@@ -2517,6 +2547,17 @@ function fromMinorUnits(raw, asset) {
|
|
|
2517
2547
|
decimals: asset.decimals
|
|
2518
2548
|
};
|
|
2519
2549
|
}
|
|
2550
|
+
/** Convert exact raw units to one asset quantity without a fiat denomination. */
|
|
2551
|
+
function assetFromMinorUnits(raw, asset) {
|
|
2552
|
+
const money = fromMinorUnits(raw, {
|
|
2553
|
+
currency: "USD",
|
|
2554
|
+
decimals: asset.decimals
|
|
2555
|
+
});
|
|
2556
|
+
return {
|
|
2557
|
+
asset: asset.asset,
|
|
2558
|
+
value: money.value
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2520
2561
|
//#endregion
|
|
2521
2562
|
//#region src/headless/payroll/reads.ts
|
|
2522
2563
|
function toRecipientOption(entry) {
|
|
@@ -2633,7 +2674,7 @@ function scopedClient(client, method, orgId) {
|
|
|
2633
2674
|
//#endregion
|
|
2634
2675
|
//#region src/headless/payroll/use-payroll.ts
|
|
2635
2676
|
/** Money reads the same way everywhere on this screen: amount then code. */
|
|
2636
|
-
const GRAMMAR
|
|
2677
|
+
const GRAMMAR = { grammar: "code" };
|
|
2637
2678
|
/**
|
|
2638
2679
|
* One captured snapshot, or nothing — never a fabricated empty options object,
|
|
2639
2680
|
* and never a DOM event a screen wired straight to `onClick`.
|
|
@@ -2649,7 +2690,7 @@ function toPayrollGroupRow(group) {
|
|
|
2649
2690
|
name: group.name,
|
|
2650
2691
|
tone: group.tone,
|
|
2651
2692
|
memberCount: group.members.length,
|
|
2652
|
-
totalDisplay: currency === void 0 ? "" : formatMoney(sumTypedAmounts(group.members.map((member) => member.amount), currency), GRAMMAR
|
|
2693
|
+
totalDisplay: currency === void 0 ? "" : formatMoney(sumTypedAmounts(group.members.map((member) => member.amount), currency), GRAMMAR),
|
|
2653
2694
|
members: group.members.map((member) => ({
|
|
2654
2695
|
recipientId: member.partyId,
|
|
2655
2696
|
amount: member.amount,
|
|
@@ -2739,8 +2780,8 @@ function usePayroll(orgId) {
|
|
|
2739
2780
|
const started = orgId !== void 0;
|
|
2740
2781
|
return {
|
|
2741
2782
|
summary: {
|
|
2742
|
-
walletBalanceDisplay: treasury.data === void 0 ? null : formatMoney(treasury.data.available, GRAMMAR
|
|
2743
|
-
paidThisMonthDisplay: runs.data === void 0 ? null :
|
|
2783
|
+
walletBalanceDisplay: treasury.data?.available === void 0 || treasury.data.available === null ? null : formatMoney(treasury.data.available, GRAMMAR),
|
|
2784
|
+
paidThisMonthDisplay: runs.data === void 0 ? null : runs.data.settledThisMonth.map(formatAssetAmount).join(", "),
|
|
2744
2785
|
isLoading: started && (treasury.data === void 0 && treasury.error === null || runs.data === void 0 && runs.error === null),
|
|
2745
2786
|
error: treasury.error ?? runs.error
|
|
2746
2787
|
},
|
|
@@ -2773,7 +2814,7 @@ function usePayrollContext() {
|
|
|
2773
2814
|
if (engine === null) throw new Error("CapxulPayroll parts must be used inside <CapxulPayroll>");
|
|
2774
2815
|
return engine;
|
|
2775
2816
|
}
|
|
2776
|
-
function Root$
|
|
2817
|
+
function Root$2({ orgId, children }) {
|
|
2777
2818
|
const engine = usePayroll(orgId);
|
|
2778
2819
|
return /* @__PURE__ */ jsx(PayrollContext.Provider, {
|
|
2779
2820
|
value: engine,
|
|
@@ -2791,7 +2832,7 @@ function Groups({ children }) {
|
|
|
2791
2832
|
* There is NO `.Runs` part (ruling Q6): no shipped region has the right shape
|
|
2792
2833
|
* for a run list, and a part without a production caller may not ship.
|
|
2793
2834
|
*/
|
|
2794
|
-
const CapxulPayroll = Object.assign(Root$
|
|
2835
|
+
const CapxulPayroll = Object.assign(Root$2, {
|
|
2795
2836
|
Summary: Summary$1,
|
|
2796
2837
|
Groups
|
|
2797
2838
|
});
|
|
@@ -2801,8 +2842,14 @@ function record(value) {
|
|
|
2801
2842
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw Errors.invalidInput("payrollDraft", "Invalid run draft");
|
|
2802
2843
|
return value;
|
|
2803
2844
|
}
|
|
2845
|
+
/**
|
|
2846
|
+
* A draft-scoped opaque key. `/` is in the class so `asset.id` can be the
|
|
2847
|
+
* canonical CAIP-19 `AssetId` (`eip155:<chainId>/erc20:<address>`): the draft
|
|
2848
|
+
* records WHICH asset was authorized, and a symbol is not an identity
|
|
2849
|
+
* (ADR-0028 R3). The field never reaches the backend — only `input` is sent.
|
|
2850
|
+
*/
|
|
2804
2851
|
function identifier(value) {
|
|
2805
|
-
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._
|
|
2852
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u.test(value)) throw Errors.invalidInput("payrollDraft", "Invalid draft identifier");
|
|
2806
2853
|
return value;
|
|
2807
2854
|
}
|
|
2808
2855
|
function minorAmount(value) {
|
|
@@ -2818,8 +2865,9 @@ function decodeDraft(value, scope) {
|
|
|
2818
2865
|
const items = input.items.map((raw) => {
|
|
2819
2866
|
const item = record(raw);
|
|
2820
2867
|
const to = record(item.to);
|
|
2868
|
+
const amount = record(item.amount);
|
|
2821
2869
|
const partyId = toPartyId(identifier(item.partyId));
|
|
2822
|
-
if (to.kind !== "party" || to.partyId !== partyId || item.gross !== item.net || !Array.isArray(item.adjustments) || item.adjustments.length !== 0) throw Errors.invalidInput("payrollDraft", "Invalid draft recipient");
|
|
2870
|
+
if (to.kind !== "party" || to.partyId !== partyId || item.gross !== item.net || typeof amount.asset !== "string" || typeof amount.value !== "string" || typeof item.currency !== "string" || typeof item.decimals !== "number" || !Number.isInteger(item.decimals) || !Array.isArray(item.adjustments) || item.adjustments.length !== 0) throw Errors.invalidInput("payrollDraft", "Invalid draft recipient");
|
|
2823
2871
|
const net = minorAmount(item.net);
|
|
2824
2872
|
return {
|
|
2825
2873
|
to: {
|
|
@@ -2827,12 +2875,20 @@ function decodeDraft(value, scope) {
|
|
|
2827
2875
|
partyId
|
|
2828
2876
|
},
|
|
2829
2877
|
partyId,
|
|
2878
|
+
amount: {
|
|
2879
|
+
asset: requireAsset(amount.asset).assetId,
|
|
2880
|
+
value: amount.value
|
|
2881
|
+
},
|
|
2830
2882
|
gross: net,
|
|
2831
2883
|
net,
|
|
2884
|
+
currency: item.currency,
|
|
2885
|
+
decimals: item.decimals,
|
|
2832
2886
|
adjustments: []
|
|
2833
2887
|
};
|
|
2834
2888
|
});
|
|
2835
2889
|
if (new Set(items.map((item) => item.partyId)).size !== items.length) throw Errors.invalidInput("payrollDraft", "Duplicate draft recipient");
|
|
2890
|
+
const exactAsset = requireAsset(identifier(asset.id));
|
|
2891
|
+
if (items.some((item) => item.amount.asset !== exactAsset.assetId)) throw Errors.invalidInput("payrollDraft", "Draft item asset does not match the run asset");
|
|
2836
2892
|
return {
|
|
2837
2893
|
version: 1,
|
|
2838
2894
|
scope,
|
|
@@ -2843,6 +2899,7 @@ function decodeDraft(value, scope) {
|
|
|
2843
2899
|
},
|
|
2844
2900
|
input: {
|
|
2845
2901
|
permissionId: identifier(input.permissionId),
|
|
2902
|
+
asset: exactAsset.assetId,
|
|
2846
2903
|
period: {
|
|
2847
2904
|
start: period.start,
|
|
2848
2905
|
end: period.start
|
|
@@ -3046,7 +3103,6 @@ function termsSeeds(terms, asset, period) {
|
|
|
3046
3103
|
}
|
|
3047
3104
|
//#endregion
|
|
3048
3105
|
//#region src/headless/payroll/use-payroll-run.ts
|
|
3049
|
-
const GRAMMAR = { grammar: "code" };
|
|
3050
3106
|
/**
|
|
3051
3107
|
* The period this composer composes: an INSTANT run (ruling Q3). `authorizeRun`
|
|
3052
3108
|
* stamps `start === end === runAt` at submit, and the payslip's period renders
|
|
@@ -3070,6 +3126,32 @@ const INSTANT_RUN_PERIOD = {
|
|
|
3070
3126
|
function amountText(amounts, seeds, recipientId) {
|
|
3071
3127
|
return amounts[recipientId]?.value ?? seeds.get(recipientId) ?? "";
|
|
3072
3128
|
}
|
|
3129
|
+
function freshRunItems(input) {
|
|
3130
|
+
const items = [];
|
|
3131
|
+
for (const [index, recipientId] of input.selected.entries()) {
|
|
3132
|
+
const to = input.payableRefs[index];
|
|
3133
|
+
const amount = input.amounts[index];
|
|
3134
|
+
const terms = input.compensationTerms.get(recipientId);
|
|
3135
|
+
if (to === void 0 || amount === void 0 || terms === void 0) throw Errors.unknown();
|
|
3136
|
+
items.push({
|
|
3137
|
+
to: {
|
|
3138
|
+
kind: "party",
|
|
3139
|
+
partyId: toPartyId(recipientId)
|
|
3140
|
+
},
|
|
3141
|
+
partyId: recipientId,
|
|
3142
|
+
amount,
|
|
3143
|
+
gross: terms.rate,
|
|
3144
|
+
net: terms.rate,
|
|
3145
|
+
currency: terms.currency,
|
|
3146
|
+
decimals: terms.decimals,
|
|
3147
|
+
adjustments: []
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
return items;
|
|
3151
|
+
}
|
|
3152
|
+
function hasRequiredCompensation(frozen, writerNamesSelectedAsset, selected, terms) {
|
|
3153
|
+
return frozen || !writerNamesSelectedAsset || selected.every((recipientId) => terms.has(recipientId));
|
|
3154
|
+
}
|
|
3073
3155
|
/**
|
|
3074
3156
|
* WHICH run the composer is composing, by MEANING rather than by object shape.
|
|
3075
3157
|
*
|
|
@@ -3101,6 +3183,33 @@ function initialAmounts(prefill) {
|
|
|
3101
3183
|
};
|
|
3102
3184
|
return seeded;
|
|
3103
3185
|
}
|
|
3186
|
+
/**
|
|
3187
|
+
* One `.Asset` option per position the treasury Account holds, taken from
|
|
3188
|
+
* `account.balances` — never from `account.available`, which is the Account's
|
|
3189
|
+
* fiat VALUATION of those positions (ADR-0028 R5/R6). The option is keyed on
|
|
3190
|
+
* the ASSET id: several positions share one account id, and a symbol is display
|
|
3191
|
+
* only (ADR-0028 R3). Every row is listed, so a real multi-position treasury
|
|
3192
|
+
* still offers its holdings.
|
|
3193
|
+
*/
|
|
3194
|
+
function assetSelectionsFromAccount(account) {
|
|
3195
|
+
if (account === null) return [];
|
|
3196
|
+
return account.balances.map((position) => {
|
|
3197
|
+
return {
|
|
3198
|
+
option: {
|
|
3199
|
+
id: position.asset,
|
|
3200
|
+
symbol: position.symbol,
|
|
3201
|
+
availableDisplay: formatAssetAmount({
|
|
3202
|
+
asset: position.asset,
|
|
3203
|
+
value: position.available
|
|
3204
|
+
})
|
|
3205
|
+
},
|
|
3206
|
+
money: {
|
|
3207
|
+
asset: position.asset,
|
|
3208
|
+
value: position.available
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3104
3213
|
function usePayrollRun(input) {
|
|
3105
3214
|
const { orgId, prefill, onRun, onFailed } = input;
|
|
3106
3215
|
const client = useCapxulClientOrNull();
|
|
@@ -3114,17 +3223,13 @@ function usePayrollRun(input) {
|
|
|
3114
3223
|
const terms = useOrgTerms(orgId);
|
|
3115
3224
|
const [selected, setSelected] = useState(() => prefill?.recipientIds ?? []);
|
|
3116
3225
|
const [amounts, setAmounts] = useState(() => initialAmounts(prefill));
|
|
3117
|
-
const restoreCurrency = treasury.data?.available.currency;
|
|
3118
3226
|
const { draftScope, currentScope, draft, draftError, restorationPending, activateDraft, restoreDraft, setDraft, recovery, draftBlocked } = useRunDraft(client, orgId, useCallback((restored) => {
|
|
3119
3227
|
setSelected(restored.input.items.map((item) => item.partyId));
|
|
3120
|
-
if (
|
|
3121
|
-
value:
|
|
3122
|
-
currency: restoreCurrency,
|
|
3123
|
-
decimals: restored.asset.decimals
|
|
3124
|
-
}).value,
|
|
3228
|
+
if (requireAsset(restored.input.asset).assetId === restored.input.asset) setAmounts(Object.fromEntries(restored.input.items.map((item) => [item.partyId, {
|
|
3229
|
+
value: item.amount.value,
|
|
3125
3230
|
prefilled: false
|
|
3126
3231
|
}])));
|
|
3127
|
-
}, [
|
|
3232
|
+
}, []));
|
|
3128
3233
|
const frozen = draft !== null;
|
|
3129
3234
|
const composerKey = composerIdentity(orgId, prefill, draftScope, frozen);
|
|
3130
3235
|
const [seededKey, setSeededKey] = useState(composerKey);
|
|
@@ -3164,11 +3269,8 @@ function usePayrollRun(input) {
|
|
|
3164
3269
|
draftError
|
|
3165
3270
|
]);
|
|
3166
3271
|
const account = treasury.data ?? null;
|
|
3167
|
-
const
|
|
3168
|
-
|
|
3169
|
-
symbol: account.available.currency,
|
|
3170
|
-
availableDisplay: formatMoney(account.available, GRAMMAR)
|
|
3171
|
-
}], [account]);
|
|
3272
|
+
const assetSelections = useMemo(() => assetSelectionsFromAccount(account), [account]);
|
|
3273
|
+
const assetOptions = useMemo(() => assetSelections.map((selection) => selection.option), [assetSelections]);
|
|
3172
3274
|
const [selectedAssetId, setSelectedAssetId] = useState(null);
|
|
3173
3275
|
useEffect(() => {
|
|
3174
3276
|
if (!assetOptions.some((option) => option.id === selectedAssetId)) setSelectedAssetId(assetOptions[0]?.id ?? null);
|
|
@@ -3177,7 +3279,7 @@ function usePayrollRun(input) {
|
|
|
3177
3279
|
const selectAsset = useCallback((id) => {
|
|
3178
3280
|
if (!submitGate.current && !frozen) setSelectedAssetId((current) => assetOptions.some((option) => option.id === id) ? id : current);
|
|
3179
3281
|
}, [assetOptions, frozen]);
|
|
3180
|
-
const assetMoney =
|
|
3282
|
+
const assetMoney = assetSelections.find((selection) => selection.option.id === selectedAsset?.id)?.money ?? null;
|
|
3181
3283
|
const named = useMemo(() => new Map(roster.options.map((option) => [option.id, option])), [roster.options]);
|
|
3182
3284
|
const refs = roster.refs;
|
|
3183
3285
|
const payableRefs = useMemo(() => {
|
|
@@ -3189,12 +3291,19 @@ function usePayrollRun(input) {
|
|
|
3189
3291
|
}
|
|
3190
3292
|
return resolved;
|
|
3191
3293
|
}, [refs, selected]);
|
|
3192
|
-
const seeds = useMemo(() =>
|
|
3294
|
+
const seeds = useMemo(() => {
|
|
3295
|
+
if (assetMoney === null) return /* @__PURE__ */ new Map();
|
|
3296
|
+
const metadata = requireAsset(assetMoney.asset);
|
|
3297
|
+
return termsSeeds(terms.data ?? [], metadata.peg === null ? null : {
|
|
3298
|
+
currency: metadata.peg,
|
|
3299
|
+
decimals: metadata.decimals
|
|
3300
|
+
}, INSTANT_RUN_PERIOD);
|
|
3301
|
+
}, [assetMoney, terms.data]);
|
|
3193
3302
|
const entries = useMemo(() => {
|
|
3194
3303
|
if (assetMoney === null) return [];
|
|
3195
3304
|
return selected.map((recipientId) => {
|
|
3196
3305
|
const value = amountText(amounts, seeds, recipientId);
|
|
3197
|
-
const parsed = value.trim() === "" ? null :
|
|
3306
|
+
const parsed = value.trim() === "" ? null : parseAssetAmount(value, requireAsset(assetMoney.asset));
|
|
3198
3307
|
return {
|
|
3199
3308
|
recipientId,
|
|
3200
3309
|
name: named.get(recipientId)?.name ?? recipientId,
|
|
@@ -3202,7 +3311,7 @@ function usePayrollRun(input) {
|
|
|
3202
3311
|
change: (text) => change(recipientId, text),
|
|
3203
3312
|
error: parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null,
|
|
3204
3313
|
prefilled: amounts[recipientId]?.prefilled ?? seeds.has(recipientId),
|
|
3205
|
-
currency: assetMoney.
|
|
3314
|
+
currency: requireAsset(assetMoney.asset).symbol
|
|
3206
3315
|
};
|
|
3207
3316
|
});
|
|
3208
3317
|
}, [
|
|
@@ -3217,7 +3326,7 @@ function usePayrollRun(input) {
|
|
|
3217
3326
|
if (assetMoney === null || selected.length === 0) return null;
|
|
3218
3327
|
const money = [];
|
|
3219
3328
|
for (const recipientId of selected) {
|
|
3220
|
-
const parsed =
|
|
3329
|
+
const parsed = parseAssetAmount(amountText(amounts, seeds, recipientId), requireAsset(assetMoney.asset));
|
|
3221
3330
|
if (isMoneyParseError(parsed)) return null;
|
|
3222
3331
|
money.push(parsed);
|
|
3223
3332
|
}
|
|
@@ -3228,16 +3337,25 @@ function usePayrollRun(input) {
|
|
|
3228
3337
|
seeds,
|
|
3229
3338
|
selected
|
|
3230
3339
|
]);
|
|
3231
|
-
const total = useMemo(() => parsedAmounts === null || assetMoney === null ? null :
|
|
3232
|
-
|
|
3233
|
-
|
|
3340
|
+
const total = useMemo(() => parsedAmounts === null || assetMoney === null ? null : {
|
|
3341
|
+
asset: assetMoney.asset,
|
|
3342
|
+
value: assetFromMinorUnits(parsedAmounts.reduce((sum, amount) => sum + toRaw(amount, requireAsset(amount.asset)), 0n), {
|
|
3343
|
+
asset: assetMoney.asset,
|
|
3344
|
+
decimals: requireAsset(assetMoney.asset).decimals
|
|
3345
|
+
}).value
|
|
3346
|
+
}, [assetMoney, parsedAmounts]);
|
|
3347
|
+
const availableRaw = assetMoney === null ? null : toRaw(assetMoney, requireAsset(assetMoney.asset));
|
|
3348
|
+
const totalRaw = total === null ? null : toRaw(total, requireAsset(total.asset));
|
|
3234
3349
|
const funds = availableRaw === null || totalRaw === null ? "unknown" : totalRaw > availableRaw ? "insufficient" : "available";
|
|
3235
3350
|
const budgets = me.data?.budgets ?? [];
|
|
3351
|
+
const compensationTerms = useMemo(() => new Map((terms.data ?? []).filter((row) => row.unit === "paycheck" && /^[1-9][0-9]*$/u.test(row.rate)).map((row) => [row.partyId, row])), [terms.data]);
|
|
3352
|
+
const writerNamesSelectedAsset = writeNamesAsset();
|
|
3236
3353
|
const blockedReason = firstFalseFact({
|
|
3237
3354
|
signerReady: signerStatus === "ready",
|
|
3238
3355
|
...runAuthorityFacts(draft, budgets, selectedAsset, assetMoney, funds),
|
|
3239
3356
|
recipientCount: payableRefs === null ? 0 : selected.length,
|
|
3240
|
-
amountsParse: total !== null
|
|
3357
|
+
amountsParse: total !== null && hasRequiredCompensation(frozen, writerNamesSelectedAsset, selected, compensationTerms),
|
|
3358
|
+
writeNamesAsset: writerNamesSelectedAsset
|
|
3241
3359
|
});
|
|
3242
3360
|
const authorize = useMutation({
|
|
3243
3361
|
mutationFn: async ({ candidate, options }) => {
|
|
@@ -3259,7 +3377,7 @@ function usePayrollRun(input) {
|
|
|
3259
3377
|
if (candidate.scope === currentScope.current) restoreDraft(candidate.scope);
|
|
3260
3378
|
}
|
|
3261
3379
|
});
|
|
3262
|
-
const totalDisplay = total === null ? null :
|
|
3380
|
+
const totalDisplay = total === null ? null : formatAssetAmount(total);
|
|
3263
3381
|
const authorizeMutate = authorize.mutate;
|
|
3264
3382
|
const submit = useCallback((options) => {
|
|
3265
3383
|
const attempt = toInvocationOptions(options);
|
|
@@ -3268,41 +3386,35 @@ function usePayrollRun(input) {
|
|
|
3268
3386
|
if (permissionId === void 0 || totalDisplay === null || parsedAmounts === null) return;
|
|
3269
3387
|
if (payableRefs === null) return;
|
|
3270
3388
|
const recipientCount = selected.length;
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
const
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
kind: "party",
|
|
3280
|
-
partyId: toPartyId(recipientId)
|
|
3281
|
-
},
|
|
3282
|
-
partyId: recipientId,
|
|
3283
|
-
gross: raw,
|
|
3284
|
-
net: raw,
|
|
3285
|
-
adjustments: []
|
|
3389
|
+
let candidate;
|
|
3390
|
+
if (frozen) candidate = draft;
|
|
3391
|
+
else {
|
|
3392
|
+
const items = freshRunItems({
|
|
3393
|
+
selected,
|
|
3394
|
+
payableRefs,
|
|
3395
|
+
amounts: parsedAmounts,
|
|
3396
|
+
compensationTerms
|
|
3286
3397
|
});
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
decimals: assetMoney.decimals
|
|
3296
|
-
},
|
|
3297
|
-
input: {
|
|
3298
|
-
permissionId,
|
|
3299
|
-
period: {
|
|
3300
|
-
start: runAt,
|
|
3301
|
-
end: runAt
|
|
3398
|
+
const runAt = Date.now();
|
|
3399
|
+
candidate = {
|
|
3400
|
+
version: 1,
|
|
3401
|
+
scope: draftScope,
|
|
3402
|
+
asset: {
|
|
3403
|
+
id: selectedAsset.id,
|
|
3404
|
+
currency: requireAsset(assetMoney.asset).symbol,
|
|
3405
|
+
decimals: requireAsset(assetMoney.asset).decimals
|
|
3302
3406
|
},
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3407
|
+
input: {
|
|
3408
|
+
permissionId,
|
|
3409
|
+
asset: selectedAsset.id,
|
|
3410
|
+
period: {
|
|
3411
|
+
start: runAt,
|
|
3412
|
+
end: runAt
|
|
3413
|
+
},
|
|
3414
|
+
items
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3306
3418
|
submitGate.current = true;
|
|
3307
3419
|
authorizeMutate({
|
|
3308
3420
|
candidate,
|
|
@@ -3334,6 +3446,7 @@ function usePayrollRun(input) {
|
|
|
3334
3446
|
selectedAsset,
|
|
3335
3447
|
blockedReason,
|
|
3336
3448
|
budgets,
|
|
3449
|
+
compensationTerms,
|
|
3337
3450
|
onFailed,
|
|
3338
3451
|
onRun,
|
|
3339
3452
|
parsedAmounts,
|
|
@@ -3382,18 +3495,25 @@ function usePayrollRun(input) {
|
|
|
3382
3495
|
};
|
|
3383
3496
|
}
|
|
3384
3497
|
function remainingBalanceDisplay(available, total, asset) {
|
|
3385
|
-
|
|
3498
|
+
if (available === null || total === null || asset === null) return null;
|
|
3499
|
+
const remaining = available - total;
|
|
3500
|
+
const magnitude = remaining < 0n ? -remaining : remaining;
|
|
3501
|
+
const formatted = formatAssetAmount(assetFromMinorUnits(magnitude, {
|
|
3502
|
+
asset: asset.asset,
|
|
3503
|
+
decimals: requireAsset(asset.asset).decimals
|
|
3504
|
+
}));
|
|
3505
|
+
return remaining < 0n ? `-${formatted}` : formatted;
|
|
3386
3506
|
}
|
|
3387
3507
|
/** Persisted replay keeps its authorized Budget and asset; a prior debit cannot block its own recovery. */
|
|
3388
3508
|
function runAuthorityFacts(draft, budgets, asset, money, funds) {
|
|
3389
3509
|
return {
|
|
3390
3510
|
budgetCount: draft !== null && !budgets.some((budget) => budget.id === draft.input.permissionId) ? 0 : budgets.length,
|
|
3391
|
-
hasAsset: asset !== null && (draft === null || draft.asset.id === asset.id && draft.asset
|
|
3511
|
+
hasAsset: asset !== null && (draft === null || draft.asset.id === asset.id && draft.input.asset === money?.asset),
|
|
3392
3512
|
funds: draft?.input.requestKey === void 0 ? funds : "available"
|
|
3393
3513
|
};
|
|
3394
3514
|
}
|
|
3395
3515
|
/**
|
|
3396
|
-
* The
|
|
3516
|
+
* The facts, in the ONE order the contract fixes. Exactly one code comes
|
|
3397
3517
|
* out, and it is always the first thing that is false — so two problems never
|
|
3398
3518
|
* race to name the button.
|
|
3399
3519
|
*/
|
|
@@ -3404,7 +3524,8 @@ function firstFalseFact(facts) {
|
|
|
3404
3524
|
if (!facts.hasAsset) return "no-asset";
|
|
3405
3525
|
if (facts.recipientCount === 0) return "no-recipients";
|
|
3406
3526
|
if (!facts.amountsParse) return "amount-invalid";
|
|
3407
|
-
|
|
3527
|
+
if (facts.funds === "insufficient") return "insufficient-funds";
|
|
3528
|
+
return facts.writeNamesAsset ? null : "asset-unnamed";
|
|
3408
3529
|
}
|
|
3409
3530
|
//#endregion
|
|
3410
3531
|
//#region src/headless/payroll/payroll-run.tsx
|
|
@@ -3414,7 +3535,7 @@ function usePayrollRunContext() {
|
|
|
3414
3535
|
if (engine === null) throw new Error("CapxulPayrollRun parts must be used inside <CapxulPayrollRun>");
|
|
3415
3536
|
return engine;
|
|
3416
3537
|
}
|
|
3417
|
-
function Root({ orgId, prefill, onRun, onFailed, children }) {
|
|
3538
|
+
function Root$1({ orgId, prefill, onRun, onFailed, children }) {
|
|
3418
3539
|
const engine = usePayrollRun({
|
|
3419
3540
|
orgId,
|
|
3420
3541
|
prefill,
|
|
@@ -3444,7 +3565,7 @@ function Actions({ children }) {
|
|
|
3444
3565
|
return /* @__PURE__ */ jsx(Fragment, { children: children(engine.actions) });
|
|
3445
3566
|
}
|
|
3446
3567
|
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
3447
|
-
const CapxulPayrollRun = Object.assign(Root, {
|
|
3568
|
+
const CapxulPayrollRun = Object.assign(Root$1, {
|
|
3448
3569
|
Recipients,
|
|
3449
3570
|
Amounts,
|
|
3450
3571
|
Asset,
|
|
@@ -3452,4 +3573,230 @@ const CapxulPayrollRun = Object.assign(Root, {
|
|
|
3452
3573
|
Actions
|
|
3453
3574
|
});
|
|
3454
3575
|
//#endregion
|
|
3455
|
-
|
|
3576
|
+
//#region src/headless/client-connection/use-client-connection.ts
|
|
3577
|
+
function useClientConnection(input) {
|
|
3578
|
+
const [view, setView] = useState(null);
|
|
3579
|
+
const [pending, setPending] = useState(false);
|
|
3580
|
+
const [error, setError] = useState(null);
|
|
3581
|
+
const [resendReady, setResendReady] = useState(false);
|
|
3582
|
+
const identityRef = useRef({
|
|
3583
|
+
client: input.client,
|
|
3584
|
+
requestId: input.requestId
|
|
3585
|
+
});
|
|
3586
|
+
identityRef.current = {
|
|
3587
|
+
client: input.client,
|
|
3588
|
+
requestId: input.requestId
|
|
3589
|
+
};
|
|
3590
|
+
const belongsHere = useCallback((client, requestId) => identityRef.current.client === client && identityRef.current.requestId === requestId, []);
|
|
3591
|
+
const refresh = useCallback(async (signal) => {
|
|
3592
|
+
const { client, requestId } = identityRef.current;
|
|
3593
|
+
const result = await client.auth.clientRequests.read({ requestId }, signal === void 0 ? void 0 : { signal });
|
|
3594
|
+
if (signal?.aborted || !belongsHere(client, requestId)) return;
|
|
3595
|
+
if (result.ok) {
|
|
3596
|
+
setView(result.value);
|
|
3597
|
+
setError(null);
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
setError(result.error);
|
|
3601
|
+
}, [belongsHere]);
|
|
3602
|
+
useEffect(() => {
|
|
3603
|
+
setView(null);
|
|
3604
|
+
setError(null);
|
|
3605
|
+
setPending(false);
|
|
3606
|
+
setResendReady(false);
|
|
3607
|
+
const controller = new AbortController();
|
|
3608
|
+
refresh(controller.signal);
|
|
3609
|
+
return () => {
|
|
3610
|
+
controller.abort();
|
|
3611
|
+
};
|
|
3612
|
+
}, [
|
|
3613
|
+
input.client,
|
|
3614
|
+
input.requestId,
|
|
3615
|
+
refresh
|
|
3616
|
+
]);
|
|
3617
|
+
useEffect(() => {
|
|
3618
|
+
if (view?.phase !== "otp_required") {
|
|
3619
|
+
setResendReady(false);
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
const remaining = view.resendAt - Date.now();
|
|
3623
|
+
if (remaining <= 0) {
|
|
3624
|
+
setResendReady(true);
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
setResendReady(false);
|
|
3628
|
+
const timer = setTimeout(() => setResendReady(true), remaining);
|
|
3629
|
+
return () => clearTimeout(timer);
|
|
3630
|
+
}, [view]);
|
|
3631
|
+
const run = useCallback(async (work) => {
|
|
3632
|
+
const { client, requestId } = identityRef.current;
|
|
3633
|
+
setPending(true);
|
|
3634
|
+
try {
|
|
3635
|
+
const result = await work();
|
|
3636
|
+
if (!belongsHere(client, requestId)) return;
|
|
3637
|
+
if (!result.ok) {
|
|
3638
|
+
setError(result.error);
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
await refresh();
|
|
3642
|
+
} finally {
|
|
3643
|
+
if (belongsHere(client, requestId)) setPending(false);
|
|
3644
|
+
}
|
|
3645
|
+
}, [belongsHere, refresh]);
|
|
3646
|
+
return useMemo(() => {
|
|
3647
|
+
if (error !== null && (view === null || view.phase !== "refused")) return {
|
|
3648
|
+
phase: "failure",
|
|
3649
|
+
otp: null,
|
|
3650
|
+
consent: null,
|
|
3651
|
+
complete: null,
|
|
3652
|
+
failure: {
|
|
3653
|
+
code: error.code,
|
|
3654
|
+
retry: () => refresh(),
|
|
3655
|
+
cancel: async () => {
|
|
3656
|
+
if (view === null) return;
|
|
3657
|
+
const { client, requestId } = identityRef.current;
|
|
3658
|
+
await run(() => client.auth.clientRequests.cancel({
|
|
3659
|
+
requestId,
|
|
3660
|
+
revision: view.revision
|
|
3661
|
+
}));
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
if (view === null) return {
|
|
3666
|
+
phase: "checking",
|
|
3667
|
+
otp: null,
|
|
3668
|
+
consent: null,
|
|
3669
|
+
complete: null,
|
|
3670
|
+
failure: null
|
|
3671
|
+
};
|
|
3672
|
+
if (view.phase === "otp_required") return {
|
|
3673
|
+
phase: view.phase,
|
|
3674
|
+
otp: {
|
|
3675
|
+
email: view.email,
|
|
3676
|
+
pending,
|
|
3677
|
+
canResend: resendReady,
|
|
3678
|
+
verifyCode: async (code) => {
|
|
3679
|
+
const { client, requestId } = identityRef.current;
|
|
3680
|
+
await run(() => client.auth.clientRequests.verifyOtp({
|
|
3681
|
+
requestId,
|
|
3682
|
+
code
|
|
3683
|
+
}));
|
|
3684
|
+
},
|
|
3685
|
+
resend: async () => {
|
|
3686
|
+
const { client, requestId } = identityRef.current;
|
|
3687
|
+
await run(() => client.auth.clientRequests.sendOtp({ requestId }));
|
|
3688
|
+
}
|
|
3689
|
+
},
|
|
3690
|
+
consent: null,
|
|
3691
|
+
complete: null,
|
|
3692
|
+
failure: null
|
|
3693
|
+
};
|
|
3694
|
+
if (view.phase === "review") return {
|
|
3695
|
+
phase: view.phase,
|
|
3696
|
+
otp: null,
|
|
3697
|
+
consent: {
|
|
3698
|
+
verifiedEmail: view.email,
|
|
3699
|
+
clientLabel: view.clientLabel,
|
|
3700
|
+
scopeCodes: view.scopes,
|
|
3701
|
+
purpose: view.intent.mode,
|
|
3702
|
+
setupState: view.setupState,
|
|
3703
|
+
pending,
|
|
3704
|
+
accept: async () => {
|
|
3705
|
+
const { client, requestId } = identityRef.current;
|
|
3706
|
+
await run(() => client.auth.clientRequests.accept({
|
|
3707
|
+
requestId,
|
|
3708
|
+
revision: view.revision
|
|
3709
|
+
}));
|
|
3710
|
+
},
|
|
3711
|
+
cancel: async () => {
|
|
3712
|
+
const { client, requestId } = identityRef.current;
|
|
3713
|
+
await run(() => client.auth.clientRequests.cancel({
|
|
3714
|
+
requestId,
|
|
3715
|
+
revision: view.revision
|
|
3716
|
+
}));
|
|
3717
|
+
}
|
|
3718
|
+
},
|
|
3719
|
+
complete: null,
|
|
3720
|
+
failure: null
|
|
3721
|
+
};
|
|
3722
|
+
if (view.phase === "connected") return {
|
|
3723
|
+
phase: view.phase,
|
|
3724
|
+
otp: null,
|
|
3725
|
+
consent: null,
|
|
3726
|
+
complete: { outcome: "connected" },
|
|
3727
|
+
failure: null
|
|
3728
|
+
};
|
|
3729
|
+
if (view.phase === "refused") return {
|
|
3730
|
+
phase: view.phase,
|
|
3731
|
+
otp: null,
|
|
3732
|
+
consent: null,
|
|
3733
|
+
complete: null,
|
|
3734
|
+
failure: {
|
|
3735
|
+
code: view.code,
|
|
3736
|
+
...view.field === void 0 ? {} : { field: view.field },
|
|
3737
|
+
retry: () => refresh(),
|
|
3738
|
+
cancel: async () => void 0
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
return {
|
|
3742
|
+
phase: view.phase,
|
|
3743
|
+
otp: null,
|
|
3744
|
+
consent: null,
|
|
3745
|
+
complete: null,
|
|
3746
|
+
failure: null
|
|
3747
|
+
};
|
|
3748
|
+
}, [
|
|
3749
|
+
error,
|
|
3750
|
+
pending,
|
|
3751
|
+
refresh,
|
|
3752
|
+
resendReady,
|
|
3753
|
+
run,
|
|
3754
|
+
view
|
|
3755
|
+
]);
|
|
3756
|
+
}
|
|
3757
|
+
//#endregion
|
|
3758
|
+
//#region src/headless/client-connection/client-connection.tsx
|
|
3759
|
+
const ConnectionContext = createContext(null);
|
|
3760
|
+
function useConnection() {
|
|
3761
|
+
const engine = useContext(ConnectionContext);
|
|
3762
|
+
if (engine === null) throw new Error("CapxulClientConnection parts must be used inside <CapxulClientConnection>");
|
|
3763
|
+
return engine;
|
|
3764
|
+
}
|
|
3765
|
+
function Root({ client, requestId, children }) {
|
|
3766
|
+
const engine = useClientConnection({
|
|
3767
|
+
client,
|
|
3768
|
+
requestId
|
|
3769
|
+
});
|
|
3770
|
+
return /* @__PURE__ */ jsx(ConnectionContext.Provider, {
|
|
3771
|
+
value: engine,
|
|
3772
|
+
children
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
function Checking({ children }) {
|
|
3776
|
+
return useConnection().phase === "checking" ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
3777
|
+
}
|
|
3778
|
+
function Otp({ children }) {
|
|
3779
|
+
const otp = useConnection().otp;
|
|
3780
|
+
return otp === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(otp) });
|
|
3781
|
+
}
|
|
3782
|
+
function Consent({ children }) {
|
|
3783
|
+
const consent = useConnection().consent;
|
|
3784
|
+
return consent === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(consent) });
|
|
3785
|
+
}
|
|
3786
|
+
function Complete({ children }) {
|
|
3787
|
+
const complete = useConnection().complete;
|
|
3788
|
+
return complete === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(complete) });
|
|
3789
|
+
}
|
|
3790
|
+
function Failure({ children }) {
|
|
3791
|
+
const failure = useConnection().failure;
|
|
3792
|
+
return failure === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(failure) });
|
|
3793
|
+
}
|
|
3794
|
+
const CapxulClientConnection = Object.assign(Root, {
|
|
3795
|
+
Checking,
|
|
3796
|
+
Otp,
|
|
3797
|
+
Consent,
|
|
3798
|
+
Complete,
|
|
3799
|
+
Failure
|
|
3800
|
+
});
|
|
3801
|
+
//#endregion
|
|
3802
|
+
export { CapxulActivity, CapxulAuthenticationController, CapxulClientConnection, CapxulContacts, CapxulDashboardAccess, CapxulOnboardingController, CapxulOrgMember, CapxulPayroll, CapxulPayrollRun, CapxulProvider, CapxulSendMoney, acknowledgeOnboardingDestination, activeOnboardingRecovery, capxulKeys, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHandleAvailability, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUploadImage };
|