@capxul/sdk-react 2.1.0 → 2.2.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-Chqyy3HR.mjs → controllers-ClhmseFL.mjs} +11 -0
- package/dist/index.d.mts +214 -2
- package/dist/index.mjs +451 -224
- package/dist/testing/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,6 +35,19 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
35
35
|
- Rich-data hooks such as `useCapxulProfile`, `useCapxulOrgs`, members, roles,
|
|
36
36
|
treasury, and money hooks remain TanStack Query projections. Personal
|
|
37
37
|
holdings and activity reads start only after the Account is claimed.
|
|
38
|
+
- `CapxulSendMoney` is the compound send component. One root owns the send
|
|
39
|
+
behaviour and draws nothing. The `.Asset`, `.Amount`, `.Recipient`, and
|
|
40
|
+
`.Actions` region parts each hand the screen one finished slice. The engine
|
|
41
|
+
hook stays module-scoped. This release ships the personal actor path;
|
|
42
|
+
`.Source` and the organization path follow the Access & session budgets
|
|
43
|
+
read.
|
|
44
|
+
- `CapxulContacts` is the compound address-book component. One root owns the
|
|
45
|
+
book behaviour and draws nothing. The `.Summary`, `.List`, and `.Add` region
|
|
46
|
+
parts each hand the screen one finished slice, and a `.List` row carries its
|
|
47
|
+
own `rename`, `hide`, and `unhide` verbs. A screen places any subset. Both
|
|
48
|
+
actor paths ship: a personal actor reads the account book, and an
|
|
49
|
+
organization actor reads the organization book. The `managePeople` capability
|
|
50
|
+
gate stays in the app, on `CapxulOrgMember.Can`.
|
|
38
51
|
|
|
39
52
|
The packed npm package exposes `@capxul/sdk-react` and
|
|
40
53
|
`@capxul/sdk-react/testing`. The workspace-only `@capxul/sdk-react/headless`
|
|
@@ -80,6 +80,17 @@ const capxulKeys = {
|
|
|
80
80
|
orgId ?? "pending",
|
|
81
81
|
"permissions"
|
|
82
82
|
],
|
|
83
|
+
contacts: (scope) => [
|
|
84
|
+
"capxul",
|
|
85
|
+
"contacts",
|
|
86
|
+
scope
|
|
87
|
+
],
|
|
88
|
+
contactsList: (scope, includeHidden) => [
|
|
89
|
+
"capxul",
|
|
90
|
+
"contacts",
|
|
91
|
+
scope,
|
|
92
|
+
includeHidden ? "all" : "visible"
|
|
93
|
+
],
|
|
83
94
|
activity: ["capxul", "activity"],
|
|
84
95
|
activityDetail: (reference) => [
|
|
85
96
|
"capxul",
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { A as useCapxulAuth, C as CreateOrganizationSubmission, D as ProfileDetails, E as OrganizationDetails, M as useCapxulIdentity, N as useCapxulSend, O as SendResult, P as useCapxulTransitions, S as CapxulSend, T as InvocationOptions, _ as ReadyDestination, a as AuthenticationSlots, b as Slot, c as ControllerAction, d as OnboardingControllerProps, f as OrgFailure, g as ProfileSlotProps, h as PendingAuthState, i as AuthenticatedState, j as useCapxulDestination, k as entered, l as FaultedState, m as OtpPendingState, n as AccountProgress, o as CapxulAuthenticationController, p as OrgProgress, r as ActionResult, s as CapxulOnboardingController, t as AccountFailure, u as NavigationAction, v as RetryAction, w as Destination, x as CapxulAuth, y as SignedOutState } from "./controllers-D3_Q5d2t.mjs";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import { QueryClient, UseMutationResult, UseQueryResult } from "@tanstack/react-query";
|
|
4
|
-
import { Account, AccountRequirement, ActivityAnnotationInput, ActivityDetail, ActivityListParams, ActivityPage, ActivityReference, CapxulClient, CapxulError, CapxulSigner, CreateOrgInput, CurrentHoldings, HostObservability, InviteMemberInput, MemberView, Money, MovementAnnotation, OrgId, OrgView, OrganizationPaymentBatchInput, OrganizationPaymentInput, Payment, PaymentTiming, PaymentsPayInput, Permission, PermissionReadResult, Profile, RoleView } from "@capxul/sdk";
|
|
4
|
+
import { Account, AccountRequirement, ActivityAnnotationInput, ActivityDetail, ActivityListParams, ActivityPage, ActivityReference, AddressBookEntry, CapxulClient, CapxulError, CapxulSigner, CreateOrgInput, CurrentHoldings, HostObservability, InviteMemberInput, MemberView, Money, MoneyParseErrorReason, MovementAnnotation, OrgId, OrgView, OrganizationPaymentBatchInput, OrganizationPaymentInput, PartyId, Payment, PaymentTiming, PaymentsPayInput, Permission, PermissionReadResult, Profile, RoleView } from "@capxul/sdk";
|
|
5
5
|
|
|
6
6
|
//#region src/provider.d.ts
|
|
7
7
|
type CapxulProviderSharedProps = {
|
|
@@ -112,6 +112,7 @@ type Address = Brand<string, "Address">;
|
|
|
112
112
|
type PermissionId = Brand<string, "PermissionId">;
|
|
113
113
|
type PermissionAssignmentId = Brand<string, "PermissionAssignmentId">;
|
|
114
114
|
type PaymentCommandId = Brand<string, "PaymentCommandId">;
|
|
115
|
+
type OrgId$1 = Brand<string, "OrgId">;
|
|
115
116
|
type WeiAmount = Brand<string, "WeiAmount">;
|
|
116
117
|
type RoleKey = Brand<string, "RoleKey">;
|
|
117
118
|
type AllowanceKey = Brand<string, "AllowanceKey">;
|
|
@@ -517,4 +518,215 @@ type UseCapxulImageUploadReturn = UseMutationResult<{
|
|
|
517
518
|
*/
|
|
518
519
|
declare function useCapxulImageUpload(): UseCapxulImageUploadReturn;
|
|
519
520
|
//#endregion
|
|
520
|
-
|
|
521
|
+
//#region src/headless/contacts/use-contacts.d.ts
|
|
522
|
+
type ContactsActor = {
|
|
523
|
+
readonly kind: "personal";
|
|
524
|
+
} | {
|
|
525
|
+
readonly kind: "organization";
|
|
526
|
+
readonly orgId: OrgId$1 | undefined;
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* How a contact entered the book. Taken from the shipped surface rather than
|
|
530
|
+
* re-declared, so the component can never advertise a provenance the wire
|
|
531
|
+
* cannot carry (ADR-0022 R2; #1532 owns the definition site).
|
|
532
|
+
*/
|
|
533
|
+
type ContactRelationship = AddressBookEntry["relationship"][number];
|
|
534
|
+
interface Contact {
|
|
535
|
+
/**
|
|
536
|
+
* The counterparty's `PartyId` — created once and claimed at signup, never
|
|
537
|
+
* re-keyed (ADR-0022 R1/R3). No email, no prefix, nothing to parse.
|
|
538
|
+
*/
|
|
539
|
+
readonly id: PartyId;
|
|
540
|
+
/** The saved label when there is one, else the party's display name. */
|
|
541
|
+
readonly name: string;
|
|
542
|
+
readonly relationship: readonly ContactRelationship[];
|
|
543
|
+
readonly hidden: boolean;
|
|
544
|
+
/** Epoch milliseconds. The seam never formats a date; the app writes the words. */
|
|
545
|
+
readonly lastActivityAt: number;
|
|
546
|
+
}
|
|
547
|
+
interface ContactRow extends Contact {
|
|
548
|
+
/** Ours: one initials rule, not one copy per screen. */
|
|
549
|
+
readonly initials: string;
|
|
550
|
+
readonly rename: (name: string) => void;
|
|
551
|
+
readonly hide: () => void;
|
|
552
|
+
readonly unhide: () => void;
|
|
553
|
+
}
|
|
554
|
+
/** ADR-0023 R1: refusal CODES, never sentences — the app owns the words. */
|
|
555
|
+
type ContactsRefusal = "loading" | "org-unavailable" | "unavailable";
|
|
556
|
+
/**
|
|
557
|
+
* The blocked facts, in the fixed order `.Add.blockedReason` names them.
|
|
558
|
+
* `"no-permission"` is absent by ruling, not by omission: the org screen places
|
|
559
|
+
* `CapxulOrgMember.Can do="managePeople"` around its own Add control.
|
|
560
|
+
*/
|
|
561
|
+
type ContactsAddBlockedReason = "org-unavailable" | "empty";
|
|
562
|
+
/**
|
|
563
|
+
* Why an add did not land, as a code. ADR-0023 R1 supersedes the contract's
|
|
564
|
+
* field-message carve-out: `"unresolved"` is the backend refusing to resolve
|
|
565
|
+
* what was typed (`INVALID_INPUT`), and every other code — offline, signed
|
|
566
|
+
* out, refused — is `"failed"`. A person can fix only one of them.
|
|
567
|
+
*/
|
|
568
|
+
type ContactsAddError = "unresolved" | "failed";
|
|
569
|
+
interface ContactsSummarySlice {
|
|
570
|
+
readonly total: number | null;
|
|
571
|
+
readonly activeCount: number | null;
|
|
572
|
+
readonly isLoading: boolean;
|
|
573
|
+
readonly reason: ContactsRefusal | null;
|
|
574
|
+
}
|
|
575
|
+
interface ContactsListSlice {
|
|
576
|
+
readonly rows: readonly ContactRow[];
|
|
577
|
+
readonly isLoading: boolean;
|
|
578
|
+
readonly reason: ContactsRefusal | null;
|
|
579
|
+
}
|
|
580
|
+
interface ContactsListOptions {
|
|
581
|
+
readonly limit?: number | undefined;
|
|
582
|
+
/** `"all"` forwards `includeHidden` to the backend, which already accepts it. */
|
|
583
|
+
readonly show?: "visible" | "all" | undefined;
|
|
584
|
+
}
|
|
585
|
+
interface ContactsAddSlice {
|
|
586
|
+
readonly value: string;
|
|
587
|
+
readonly change: (text: string) => void;
|
|
588
|
+
readonly submit: () => void;
|
|
589
|
+
readonly isSubmitting: boolean;
|
|
590
|
+
readonly blocked: boolean;
|
|
591
|
+
readonly blockedReason: ContactsAddBlockedReason | null;
|
|
592
|
+
readonly error: ContactsAddError | null;
|
|
593
|
+
}
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/headless/contacts/contacts.d.ts
|
|
596
|
+
interface CapxulContactsProps {
|
|
597
|
+
readonly actor: ContactsActor;
|
|
598
|
+
readonly onAdded: (contact: Contact) => void;
|
|
599
|
+
/** ADR-0023 R1: the app maps `error.code` to its own copy; no SDK sentence. */
|
|
600
|
+
readonly onFailed: (error: CapxulError) => void;
|
|
601
|
+
readonly children: ReactNode;
|
|
602
|
+
}
|
|
603
|
+
declare function Root$1({
|
|
604
|
+
actor,
|
|
605
|
+
onAdded,
|
|
606
|
+
onFailed,
|
|
607
|
+
children
|
|
608
|
+
}: CapxulContactsProps): import("react/jsx-runtime").JSX.Element;
|
|
609
|
+
declare function Summary({
|
|
610
|
+
children
|
|
611
|
+
}: {
|
|
612
|
+
readonly children: (slice: ContactsSummarySlice) => ReactNode;
|
|
613
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
614
|
+
declare function List({
|
|
615
|
+
limit,
|
|
616
|
+
show,
|
|
617
|
+
children
|
|
618
|
+
}: ContactsListOptions & {
|
|
619
|
+
readonly children: (slice: ContactsListSlice) => ReactNode;
|
|
620
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
621
|
+
declare function Add({
|
|
622
|
+
children
|
|
623
|
+
}: {
|
|
624
|
+
readonly children: (slice: ContactsAddSlice) => ReactNode;
|
|
625
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
626
|
+
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
627
|
+
declare const CapxulContacts: typeof Root$1 & {
|
|
628
|
+
Summary: typeof Summary;
|
|
629
|
+
List: typeof List;
|
|
630
|
+
Add: typeof Add;
|
|
631
|
+
};
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/headless/send-money/use-recipient-resolution.d.ts
|
|
634
|
+
type SendMoneyRecipientMethod = "username" | "email-address";
|
|
635
|
+
type SendMoneyRecipientStatus = "empty" | "checking" | "found" | "not-found" | "failed";
|
|
636
|
+
interface SendMoneyRecipientSlice {
|
|
637
|
+
readonly method: SendMoneyRecipientMethod;
|
|
638
|
+
readonly setMethod: (method: SendMoneyRecipientMethod) => void;
|
|
639
|
+
readonly value: string;
|
|
640
|
+
readonly change: (text: string) => void;
|
|
641
|
+
/** ADR-0023 R1: the status IS the failure code; the app owns the sentence. */
|
|
642
|
+
readonly status: SendMoneyRecipientStatus;
|
|
643
|
+
readonly label: string | null;
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region src/headless/send-money/use-send-money.d.ts
|
|
647
|
+
type SendActor = {
|
|
648
|
+
readonly kind: "personal";
|
|
649
|
+
} | {
|
|
650
|
+
readonly kind: "organization";
|
|
651
|
+
readonly orgId: OrgId$1 | undefined;
|
|
652
|
+
};
|
|
653
|
+
type SentPayment = {
|
|
654
|
+
/** The backend payment row. */readonly payment: Payment; /** What we parsed and sent — `.value` is the comma-stripped major-unit string. */
|
|
655
|
+
readonly amount: Money; /** The trimmed text the person typed — the app's success toast interpolates it. */
|
|
656
|
+
readonly recipient: string;
|
|
657
|
+
};
|
|
658
|
+
/**
|
|
659
|
+
* Refusal CODES, never sentences (#1447 ruling 8). The app maps each code to
|
|
660
|
+
* the words it already prints; no English refusal string ships from here.
|
|
661
|
+
*/
|
|
662
|
+
type CapxulSendMoneyBlockedReason = "signer-not-ready" | "amount-invalid" | "recipient-unresolved" | "no-source" | "no-asset";
|
|
663
|
+
interface SendMoneyAssetOption {
|
|
664
|
+
readonly id: string;
|
|
665
|
+
readonly symbol: string;
|
|
666
|
+
readonly availableDisplay: string;
|
|
667
|
+
}
|
|
668
|
+
/** ADR-0023 R1: a code, never a sentence — the app owns the words. */
|
|
669
|
+
type SendMoneyAssetError = "read-failed";
|
|
670
|
+
interface SendMoneyAssetSlice {
|
|
671
|
+
readonly options: ReadonlyArray<SendMoneyAssetOption>;
|
|
672
|
+
readonly selected: SendMoneyAssetOption | null;
|
|
673
|
+
readonly select: (id: string) => void;
|
|
674
|
+
readonly error: SendMoneyAssetError | null;
|
|
675
|
+
}
|
|
676
|
+
interface SendMoneyAmountSlice {
|
|
677
|
+
readonly value: string;
|
|
678
|
+
readonly change: (text: string) => void;
|
|
679
|
+
/** ADR-0023 R1: the parse verdict as a closed code; the app maps it to copy. */
|
|
680
|
+
readonly error: MoneyParseErrorReason | null;
|
|
681
|
+
readonly availableDisplay: string | null;
|
|
682
|
+
}
|
|
683
|
+
interface SendMoneyActionsSlice {
|
|
684
|
+
readonly submit: () => void;
|
|
685
|
+
readonly isSubmitting: boolean;
|
|
686
|
+
readonly blocked: boolean;
|
|
687
|
+
readonly blockedReason: CapxulSendMoneyBlockedReason | null;
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/headless/send-money/send-money.d.ts
|
|
691
|
+
interface CapxulSendMoneyProps {
|
|
692
|
+
readonly actor: SendActor;
|
|
693
|
+
readonly onSent: (sent: SentPayment) => void;
|
|
694
|
+
/** ADR-0023 R1: the app maps `error.code` to its own copy; no SDK sentence. */
|
|
695
|
+
readonly onFailed: (error: CapxulError) => void;
|
|
696
|
+
readonly children: ReactNode;
|
|
697
|
+
}
|
|
698
|
+
declare function Root({
|
|
699
|
+
actor,
|
|
700
|
+
onSent,
|
|
701
|
+
onFailed,
|
|
702
|
+
children
|
|
703
|
+
}: CapxulSendMoneyProps): import("react/jsx-runtime").JSX.Element;
|
|
704
|
+
declare function Asset({
|
|
705
|
+
children
|
|
706
|
+
}: {
|
|
707
|
+
readonly children: (slice: SendMoneyAssetSlice) => ReactNode;
|
|
708
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
709
|
+
declare function Amount({
|
|
710
|
+
children
|
|
711
|
+
}: {
|
|
712
|
+
readonly children: (slice: SendMoneyAmountSlice) => ReactNode;
|
|
713
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
714
|
+
declare function Recipient({
|
|
715
|
+
children
|
|
716
|
+
}: {
|
|
717
|
+
readonly children: (slice: SendMoneyRecipientSlice) => ReactNode;
|
|
718
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
719
|
+
declare function Actions({
|
|
720
|
+
children
|
|
721
|
+
}: {
|
|
722
|
+
readonly children: (slice: SendMoneyActionsSlice) => ReactNode;
|
|
723
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
724
|
+
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
725
|
+
declare const CapxulSendMoney: typeof Root & {
|
|
726
|
+
Asset: typeof Asset;
|
|
727
|
+
Amount: typeof Amount;
|
|
728
|
+
Recipient: typeof Recipient;
|
|
729
|
+
Actions: typeof Actions;
|
|
730
|
+
};
|
|
731
|
+
//#endregion
|
|
732
|
+
export { type AccountFailure, type AccountProgress, type ActionResult, type AuthenticatedState, type AuthenticationSlots, type CapxulAuth, CapxulAuthenticationController, type CapxulBootstrapState, type CapxulBootstrapStatus, CapxulContacts, type CapxulContactsProps, CapxulOnboardingController, CapxulProvider, type CapxulProviderProps, type CapxulSend, CapxulSendMoney, type CapxulSendMoneyBlockedReason, type CapxulSendMoneyProps, type Contact, type ContactRelationship, type ContactRow, type ContactsActor, type ContactsAddBlockedReason, type ContactsAddError, type ContactsAddSlice, type ContactsListOptions, type ContactsListSlice, type ContactsRefusal, type ContactsSummarySlice, type ControllerAction, type CreateOrganizationSubmission, type Destination, type FaultedState, type ImageUploadInput, type ImageUploadTarget, type InvocationOptions, type NavigationAction, type OnboardingControllerProps, type OnboardingIntent, type OnboardingJourney, type OnboardingJourneyPosition, type OnboardingOrigin, type OnboardingStep, type OrgFailure, type OrgProgress, type OrganizationDetails, type OrganizationDraft, type OtpPendingState, type PayoutDraftChain, type PayoutDraftEntry, type PendingAuthState, type ProfileDetails, type ProfileDraft, type ProfileSlotProps, type ReadyDestination, type RetryAction, type SendActor, type SendMoneyActionsSlice, type SendMoneyAmountSlice, type SendMoneyAssetError, type SendMoneyAssetOption, type SendMoneyAssetSlice, type SendMoneyRecipientMethod, type SendMoneyRecipientSlice, type SendMoneyRecipientStatus, type SendResult, type SentPayment, type SignedOutState, type Slot, type UseCapxulAccountBalanceOptions, type UseCapxulAccountBalanceReturn, type UseCapxulAccountFundReturn, type UseCapxulCreateCommitmentReturn, type UseCapxulCreateOrgReturn, type UseCapxulImageUploadReturn, type UseCapxulInviteMemberReturn, type UseCapxulOrgMembersOptions, type UseCapxulOrgMembersReturn, type UseCapxulOrgRolesOptions, type UseCapxulOrgRolesReturn, type UseCapxulOrgTreasuryOptions, type UseCapxulOrgTreasuryReturn, type UseCapxulOrgsReturn, type UseCapxulPayReturn, type UseCapxulPaymentReturn, type UseCapxulPaymentsReturn, type UseCapxulProfileReturn, type UseCapxulRedirectPaymentReturn, type UseCapxulUsernameAvailabilityReturn, type UsernameAvailability, acknowledgeOnboardingDestination, activeOnboardingRecovery, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUsernameAvailability };
|
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-ClhmseFL.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 { Errors, fingerprintPaymentIntent, formatMoney, isMoneyParseError, parseMoney } 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,8 +304,7 @@ 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
309
|
enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed" && (options?.enabled ?? true)
|
|
475
310
|
});
|
|
@@ -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
|
});
|
|
@@ -566,8 +399,7 @@ function useCapxulPermissions(orgId, options) {
|
|
|
566
399
|
return useQuery({
|
|
567
400
|
queryKey: capxulKeys.orgPermissions(orgId),
|
|
568
401
|
queryFn: async () => {
|
|
569
|
-
|
|
570
|
-
return unwrapCapxulResult(await bootstrapped.org(orgId).permissions.list(), bootstrapped._internal.telemetry);
|
|
402
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "permissions.list").org(orgId).permissions.list());
|
|
571
403
|
},
|
|
572
404
|
enabled: client !== null && (options?.enabled ?? true)
|
|
573
405
|
});
|
|
@@ -577,9 +409,8 @@ function useCapxulPermission(orgId, permissionId) {
|
|
|
577
409
|
return useQuery({
|
|
578
410
|
queryKey: [...capxulKeys.orgPermissions(orgId), permissionId ?? "pending"],
|
|
579
411
|
queryFn: async () => {
|
|
580
|
-
if (permissionId === void 0) throw
|
|
581
|
-
|
|
582
|
-
return unwrapCapxulResult(await bootstrapped.org(orgId).permissions.get(permissionId), bootstrapped._internal.telemetry);
|
|
412
|
+
if (permissionId === void 0) throw Errors.invalidInput("permissionId", "required for permissions.get");
|
|
413
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "permissions.get").org(orgId).permissions.get(permissionId));
|
|
583
414
|
},
|
|
584
415
|
enabled: client !== null && permissionId !== void 0
|
|
585
416
|
});
|
|
@@ -592,8 +423,7 @@ function useCapxulActivity(params) {
|
|
|
592
423
|
return useQuery({
|
|
593
424
|
queryKey: [...capxulKeys.activity, params ?? {}],
|
|
594
425
|
queryFn: async () => {
|
|
595
|
-
|
|
596
|
-
return unwrapCapxulResult(await bootstrapped.activity.list(params), bootstrapped._internal.telemetry);
|
|
426
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.list").activity.list(params));
|
|
597
427
|
},
|
|
598
428
|
enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed"
|
|
599
429
|
});
|
|
@@ -604,9 +434,8 @@ function useCapxulActivityDetail(reference) {
|
|
|
604
434
|
return useQuery({
|
|
605
435
|
queryKey: capxulKeys.activityDetail(reference),
|
|
606
436
|
queryFn: async () => {
|
|
607
|
-
if (reference === void 0) throw
|
|
608
|
-
|
|
609
|
-
return unwrapCapxulResult(await bootstrapped.activity.get(reference), bootstrapped._internal.telemetry);
|
|
437
|
+
if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
|
|
438
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.get").activity.get(reference));
|
|
610
439
|
},
|
|
611
440
|
enabled: client !== null && reference !== void 0 && identity?.phase === "authenticated" && identity.account.at === "claimed"
|
|
612
441
|
});
|
|
@@ -616,8 +445,7 @@ function useCapxulAnnotateMovement() {
|
|
|
616
445
|
const queryClient = useQueryClient();
|
|
617
446
|
return useMutation({
|
|
618
447
|
mutationFn: async (input) => {
|
|
619
|
-
|
|
620
|
-
return unwrapCapxulResult(await bootstrapped.activity.annotate(input), bootstrapped._internal.telemetry, "mutation");
|
|
448
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "activity.annotate").activity.annotate(input));
|
|
621
449
|
},
|
|
622
450
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: capxulKeys.activity })
|
|
623
451
|
});
|
|
@@ -630,8 +458,7 @@ function useCapxulHoldings() {
|
|
|
630
458
|
return useQuery({
|
|
631
459
|
queryKey: capxulKeys.holdings,
|
|
632
460
|
queryFn: async () => {
|
|
633
|
-
|
|
634
|
-
return unwrapCapxulResult(await bootstrapped.holdings.current(), bootstrapped._internal.telemetry);
|
|
461
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "holdings.current").holdings.current());
|
|
635
462
|
},
|
|
636
463
|
enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed"
|
|
637
464
|
});
|
|
@@ -643,8 +470,7 @@ function useCapxulOrgs(options) {
|
|
|
643
470
|
return useQuery({
|
|
644
471
|
queryKey: capxulKeys.orgs,
|
|
645
472
|
queryFn: async () => {
|
|
646
|
-
|
|
647
|
-
return unwrapCapxulResult(await bootstrappedClient.orgs(), bootstrappedClient._internal.telemetry);
|
|
473
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "orgs").orgs());
|
|
648
474
|
},
|
|
649
475
|
enabled: client !== null && (options?.enabled ?? true)
|
|
650
476
|
});
|
|
@@ -658,8 +484,7 @@ function useCapxulOrgMembers(orgId, options) {
|
|
|
658
484
|
queryKey: capxulKeys.orgMembers(orgId),
|
|
659
485
|
queryFn: async () => {
|
|
660
486
|
if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgMembers");
|
|
661
|
-
|
|
662
|
-
return unwrapCapxulResult(await bootstrappedClient.org(orgId).members(), bootstrappedClient._internal.telemetry);
|
|
487
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "org.members").org(orgId).members());
|
|
663
488
|
},
|
|
664
489
|
enabled: client !== null && enabled && orgId !== void 0
|
|
665
490
|
});
|
|
@@ -673,8 +498,7 @@ function useCapxulOrgRoles(orgId, options) {
|
|
|
673
498
|
queryKey: capxulKeys.orgRoles(orgId),
|
|
674
499
|
queryFn: async () => {
|
|
675
500
|
if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgRoles");
|
|
676
|
-
|
|
677
|
-
return unwrapCapxulResult(await bootstrappedClient.org(orgId).roles(), bootstrappedClient._internal.telemetry);
|
|
501
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "org.roles").org(orgId).roles());
|
|
678
502
|
},
|
|
679
503
|
enabled: client !== null && enabled && orgId !== void 0
|
|
680
504
|
});
|
|
@@ -688,8 +512,7 @@ function useCapxulOrgTreasury(orgId, options) {
|
|
|
688
512
|
queryKey: capxulKeys.orgTreasury(orgId),
|
|
689
513
|
queryFn: async () => {
|
|
690
514
|
if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulOrgTreasury");
|
|
691
|
-
|
|
692
|
-
return unwrapCapxulResult(await bootstrappedClient.org(orgId).treasury(), bootstrappedClient._internal.telemetry);
|
|
515
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "org.treasury").org(orgId).treasury());
|
|
693
516
|
},
|
|
694
517
|
enabled: client !== null && enabled && orgId !== void 0
|
|
695
518
|
});
|
|
@@ -701,8 +524,7 @@ function useCapxulCreateOrg() {
|
|
|
701
524
|
const queryClient = useQueryClient();
|
|
702
525
|
return useMutation({
|
|
703
526
|
mutationFn: async (input) => {
|
|
704
|
-
|
|
705
|
-
return unwrapCapxulResult(await bootstrappedClient.createOrg(input), bootstrappedClient._internal.telemetry, "mutation");
|
|
527
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "createOrg").createOrg(input));
|
|
706
528
|
},
|
|
707
529
|
onSuccess: async () => {
|
|
708
530
|
await queryClient.invalidateQueries({ queryKey: capxulKeys.orgs });
|
|
@@ -717,8 +539,7 @@ function useCapxulInviteMember(orgId) {
|
|
|
717
539
|
return useMutation({
|
|
718
540
|
mutationFn: async (input) => {
|
|
719
541
|
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");
|
|
542
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "org.invite").org(orgId).invite(input));
|
|
722
543
|
},
|
|
723
544
|
onSuccess: async () => {
|
|
724
545
|
if (orgId === void 0) return;
|
|
@@ -973,8 +794,7 @@ function useCapxulUsernameAvailability(username, options) {
|
|
|
973
794
|
return useQuery({
|
|
974
795
|
queryKey: capxulKeys.usernameAvailability(candidate),
|
|
975
796
|
queryFn: async () => {
|
|
976
|
-
|
|
977
|
-
return unwrapCapxulResult(await bootstrappedClient.identity.usernameAvailable(candidate), bootstrappedClient._internal.telemetry);
|
|
797
|
+
return unwrapCapxulResult(await requireBootstrappedClient(client, "identity.usernameAvailable").identity.usernameAvailable(candidate));
|
|
978
798
|
},
|
|
979
799
|
enabled: client !== null && candidate.length >= 3 && (options?.enabled ?? true)
|
|
980
800
|
});
|
|
@@ -992,15 +812,13 @@ function useCapxulImageUpload() {
|
|
|
992
812
|
const queryClient = useQueryClient();
|
|
993
813
|
return useMutation({
|
|
994
814
|
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 };
|
|
815
|
+
const media = requireBootstrappedClient(client, "media.uploadImage").media;
|
|
816
|
+
const uploaded = unwrapCapxulResult(await media.uploadImage(blob));
|
|
817
|
+
if (target.kind === "profile") return { url: unwrapCapxulResult(await media.setProfileImage({ storageId: uploaded.storageId })).imageUrl };
|
|
1000
818
|
return { url: unwrapCapxulResult(await media.setOrgLogo({
|
|
1001
819
|
orgId: target.orgId,
|
|
1002
820
|
storageId: uploaded.storageId
|
|
1003
|
-
})
|
|
821
|
+
})).logoUrl };
|
|
1004
822
|
},
|
|
1005
823
|
onSuccess: async (_value, input) => {
|
|
1006
824
|
await queryClient.invalidateQueries({ queryKey: input.target.kind === "profile" ? capxulKeys.profile : capxulKeys.orgs });
|
|
@@ -1008,4 +826,413 @@ function useCapxulImageUpload() {
|
|
|
1008
826
|
});
|
|
1009
827
|
}
|
|
1010
828
|
//#endregion
|
|
1011
|
-
|
|
829
|
+
//#region src/headless/contacts/use-contacts.ts
|
|
830
|
+
function useContacts(input) {
|
|
831
|
+
const { actor, onAdded, onFailed } = input;
|
|
832
|
+
const client = useCapxulClientOrNull();
|
|
833
|
+
const queryClient = useQueryClient();
|
|
834
|
+
const orgId = actor.kind === "organization" ? actor.orgId : void 0;
|
|
835
|
+
const scope = actor.kind === "personal" ? "personal" : orgId ?? "pending";
|
|
836
|
+
const refusal = actor.kind === "organization" && orgId === void 0 ? "org-unavailable" : null;
|
|
837
|
+
const book = useMemo(() => addressBookOf(client, actor.kind, orgId), [
|
|
838
|
+
client,
|
|
839
|
+
actor.kind,
|
|
840
|
+
orgId
|
|
841
|
+
]);
|
|
842
|
+
const invalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: capxulKeys.contacts(scope) }), [queryClient, scope]);
|
|
843
|
+
const writeMutate = useMutation({
|
|
844
|
+
mutationFn: async (command) => {
|
|
845
|
+
const target = requireBook(book, `addressBook.${command.kind}`);
|
|
846
|
+
return unwrapCapxulResult(command.kind === "rename" ? await target.label({
|
|
847
|
+
entryId: command.id,
|
|
848
|
+
label: command.name
|
|
849
|
+
}) : command.kind === "hide" ? await target.hide(command.id) : await target.unhide(command.id));
|
|
850
|
+
},
|
|
851
|
+
onSettled: invalidate,
|
|
852
|
+
onError: onFailed
|
|
853
|
+
}).mutate;
|
|
854
|
+
const [value, setValue] = useState("");
|
|
855
|
+
const [addError, setAddError] = useState(null);
|
|
856
|
+
const addContact = useMutation({
|
|
857
|
+
mutationFn: async (ref) => unwrapCapxulResult(await requireBook(book, "addressBook.add").add({ ref })),
|
|
858
|
+
onSettled: invalidate
|
|
859
|
+
});
|
|
860
|
+
const addMutate = addContact.mutate;
|
|
861
|
+
const blockedReason = refusal !== null ? "org-unavailable" : value.trim() === "" ? "empty" : null;
|
|
862
|
+
const submitGate = useRef(false);
|
|
863
|
+
const submit = useCallback(() => {
|
|
864
|
+
if (blockedReason !== null || submitGate.current) return;
|
|
865
|
+
submitGate.current = true;
|
|
866
|
+
setAddError(null);
|
|
867
|
+
addMutate(refFromTypedText(value), {
|
|
868
|
+
onSuccess: (entry) => {
|
|
869
|
+
setValue("");
|
|
870
|
+
onAdded(toContact(entry));
|
|
871
|
+
},
|
|
872
|
+
onError: (error) => {
|
|
873
|
+
setAddError(error.code === "INVALID_INPUT" ? "unresolved" : "failed");
|
|
874
|
+
onFailed(error);
|
|
875
|
+
},
|
|
876
|
+
onSettled: () => {
|
|
877
|
+
submitGate.current = false;
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
}, [
|
|
881
|
+
addMutate,
|
|
882
|
+
blockedReason,
|
|
883
|
+
onAdded,
|
|
884
|
+
onFailed,
|
|
885
|
+
value
|
|
886
|
+
]);
|
|
887
|
+
const change = useCallback((text) => {
|
|
888
|
+
setValue(text);
|
|
889
|
+
setAddError(null);
|
|
890
|
+
}, []);
|
|
891
|
+
return {
|
|
892
|
+
book,
|
|
893
|
+
scope,
|
|
894
|
+
refusal,
|
|
895
|
+
rename: useCallback((id, name) => writeMutate({
|
|
896
|
+
kind: "rename",
|
|
897
|
+
id,
|
|
898
|
+
name
|
|
899
|
+
}), [writeMutate]),
|
|
900
|
+
hide: useCallback((id) => writeMutate({
|
|
901
|
+
kind: "hide",
|
|
902
|
+
id
|
|
903
|
+
}), [writeMutate]),
|
|
904
|
+
unhide: useCallback((id) => writeMutate({
|
|
905
|
+
kind: "unhide",
|
|
906
|
+
id
|
|
907
|
+
}), [writeMutate]),
|
|
908
|
+
add: {
|
|
909
|
+
value,
|
|
910
|
+
change,
|
|
911
|
+
submit,
|
|
912
|
+
isSubmitting: addContact.isPending,
|
|
913
|
+
blocked: blockedReason !== null,
|
|
914
|
+
blockedReason,
|
|
915
|
+
error: addError
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* The read every part stands on. Two placements with the same `show` share one
|
|
921
|
+
* query key, so React Query issues ONE request for them.
|
|
922
|
+
*/
|
|
923
|
+
function useContactsList(engine, options) {
|
|
924
|
+
const includeHidden = options.show === "all";
|
|
925
|
+
const { book, refusal } = engine;
|
|
926
|
+
const query = useQuery({
|
|
927
|
+
queryKey: capxulKeys.contactsList(engine.scope, includeHidden),
|
|
928
|
+
queryFn: async () => unwrapCapxulResult(await requireBook(book, "addressBook.list").list({ includeHidden })),
|
|
929
|
+
enabled: book !== null
|
|
930
|
+
});
|
|
931
|
+
const reason = refusal ?? (query.isError ? "unavailable" : query.data === void 0 ? "loading" : null);
|
|
932
|
+
const entries = query.data;
|
|
933
|
+
const { limit } = options;
|
|
934
|
+
const { rename, hide, unhide } = engine;
|
|
935
|
+
return {
|
|
936
|
+
rows: useMemo(() => {
|
|
937
|
+
if (entries === void 0) return [];
|
|
938
|
+
return (limit === void 0 ? entries : entries.slice(0, limit)).map((entry) => Object.assign(toContact(entry), {
|
|
939
|
+
initials: initialsOf(entry.label),
|
|
940
|
+
rename: (name) => rename(entry.id, name),
|
|
941
|
+
hide: () => hide(entry.id),
|
|
942
|
+
unhide: () => unhide(entry.id)
|
|
943
|
+
}));
|
|
944
|
+
}, [
|
|
945
|
+
entries,
|
|
946
|
+
hide,
|
|
947
|
+
limit,
|
|
948
|
+
rename,
|
|
949
|
+
unhide
|
|
950
|
+
]),
|
|
951
|
+
isLoading: reason === "loading",
|
|
952
|
+
reason
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
/** Counts over the same visible read `.List` uses — one request, two parts. */
|
|
956
|
+
function useContactsSummary(engine) {
|
|
957
|
+
const { rows, isLoading, reason } = useContactsList(engine, {});
|
|
958
|
+
const settled = reason === null;
|
|
959
|
+
return {
|
|
960
|
+
total: settled ? rows.length : null,
|
|
961
|
+
activeCount: settled ? rows.filter((row) => row.relationship.includes("paid")).length : null,
|
|
962
|
+
isLoading,
|
|
963
|
+
reason
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function addressBookOf(client, kind, orgId) {
|
|
967
|
+
if (client === null) return null;
|
|
968
|
+
if (kind === "personal") return client.account.addressBook;
|
|
969
|
+
return orgId === void 0 ? null : client.org(orgId).addressBook;
|
|
970
|
+
}
|
|
971
|
+
function requireBook(book, method) {
|
|
972
|
+
if (book === null) throw Errors.wrongState({
|
|
973
|
+
method,
|
|
974
|
+
currentState: "bootstrapping",
|
|
975
|
+
validStates: ["ready"]
|
|
976
|
+
});
|
|
977
|
+
return book;
|
|
978
|
+
}
|
|
979
|
+
function toContact(entry) {
|
|
980
|
+
return {
|
|
981
|
+
id: entry.id,
|
|
982
|
+
name: entry.label,
|
|
983
|
+
relationship: entry.relationship,
|
|
984
|
+
hidden: entry.hidden,
|
|
985
|
+
lastActivityAt: entry.lastActivityAt
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* One typed field, both shapes. A leading `@` names a handle; an `@` anywhere
|
|
990
|
+
* else names an email — what the app already builds for Quick Pay, and what the
|
|
991
|
+
* backend resolver already accepts.
|
|
992
|
+
*/
|
|
993
|
+
function refFromTypedText(text) {
|
|
994
|
+
const trimmed = text.trim();
|
|
995
|
+
return trimmed.includes("@") && !trimmed.startsWith("@") ? {
|
|
996
|
+
kind: "email",
|
|
997
|
+
email: trimmed
|
|
998
|
+
} : {
|
|
999
|
+
kind: "handle",
|
|
1000
|
+
handle: trimmed.replace(/^@/, "")
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
/** First letters of the first two words, uppercased. */
|
|
1004
|
+
function initialsOf(name) {
|
|
1005
|
+
return name.trim().split(/\s+/).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
|
|
1006
|
+
}
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/headless/contacts/contacts.tsx
|
|
1009
|
+
const ContactsContext = createContext(null);
|
|
1010
|
+
function useContactsContext() {
|
|
1011
|
+
const engine = useContext(ContactsContext);
|
|
1012
|
+
if (engine === null) throw new Error("CapxulContacts parts must be used inside <CapxulContacts>");
|
|
1013
|
+
return engine;
|
|
1014
|
+
}
|
|
1015
|
+
function Root$1({ actor, onAdded, onFailed, children }) {
|
|
1016
|
+
const engine = useContacts({
|
|
1017
|
+
actor,
|
|
1018
|
+
onAdded,
|
|
1019
|
+
onFailed
|
|
1020
|
+
});
|
|
1021
|
+
return /* @__PURE__ */ jsx(ContactsContext.Provider, {
|
|
1022
|
+
value: engine,
|
|
1023
|
+
children
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function Summary({ children }) {
|
|
1027
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsSummary(useContactsContext())) });
|
|
1028
|
+
}
|
|
1029
|
+
function List({ limit, show, children }) {
|
|
1030
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsList(useContactsContext(), {
|
|
1031
|
+
limit,
|
|
1032
|
+
show
|
|
1033
|
+
})) });
|
|
1034
|
+
}
|
|
1035
|
+
function Add({ children }) {
|
|
1036
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsContext().add) });
|
|
1037
|
+
}
|
|
1038
|
+
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
1039
|
+
const CapxulContacts = Object.assign(Root$1, {
|
|
1040
|
+
Summary,
|
|
1041
|
+
List,
|
|
1042
|
+
Add
|
|
1043
|
+
});
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/headless/send-money/signer-status.ts
|
|
1046
|
+
const NO_SUBSCRIPTION = () => () => {};
|
|
1047
|
+
function useSignerStatus(client) {
|
|
1048
|
+
const signer = client?.signer;
|
|
1049
|
+
return useSyncExternalStore(useMemo(() => signer === void 0 ? NO_SUBSCRIPTION : (listener) => signer.subscribe(listener), [signer]), () => signer === void 0 ? "unknown" : signer.status(), () => "unknown");
|
|
1050
|
+
}
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/headless/send-money/use-recipient-resolution.ts
|
|
1053
|
+
function toTargetReference(method, text) {
|
|
1054
|
+
const trimmed = text.trim();
|
|
1055
|
+
if (trimmed === "") return null;
|
|
1056
|
+
return method === "username" ? {
|
|
1057
|
+
kind: "handle",
|
|
1058
|
+
handle: trimmed.replace(/^@/, "")
|
|
1059
|
+
} : {
|
|
1060
|
+
kind: "email",
|
|
1061
|
+
email: trimmed
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/** How long the engine waits after the last keystroke before resolving. */
|
|
1065
|
+
const RESOLVE_DEBOUNCE_MS = 400;
|
|
1066
|
+
function useRecipientResolution(client) {
|
|
1067
|
+
const [method, setMethodState] = useState("email-address");
|
|
1068
|
+
const [text, setText] = useState("");
|
|
1069
|
+
const reference = useMemo(() => toTargetReference(method, text), [method, text]);
|
|
1070
|
+
const debouncedReference = useDebouncedValue(reference, RESOLVE_DEBOUNCE_MS);
|
|
1071
|
+
const [outcome, setOutcome] = useState(null);
|
|
1072
|
+
useEffect(() => {
|
|
1073
|
+
if (debouncedReference === null || client === null) {
|
|
1074
|
+
setOutcome(null);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const controller = new AbortController();
|
|
1078
|
+
setOutcome({
|
|
1079
|
+
reference: debouncedReference,
|
|
1080
|
+
status: "checking",
|
|
1081
|
+
target: null
|
|
1082
|
+
});
|
|
1083
|
+
client.targets.resolve(debouncedReference, { signal: controller.signal }).then((result) => {
|
|
1084
|
+
if (controller.signal.aborted) return;
|
|
1085
|
+
setOutcome(result.ok ? {
|
|
1086
|
+
reference: debouncedReference,
|
|
1087
|
+
status: "found",
|
|
1088
|
+
target: result.value
|
|
1089
|
+
} : {
|
|
1090
|
+
reference: debouncedReference,
|
|
1091
|
+
status: result.error.code === "INVALID_INPUT" ? "not-found" : "failed",
|
|
1092
|
+
target: null
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
return () => controller.abort();
|
|
1096
|
+
}, [client, debouncedReference]);
|
|
1097
|
+
const setMethod = useCallback((next) => {
|
|
1098
|
+
setMethodState(next);
|
|
1099
|
+
setText("");
|
|
1100
|
+
}, []);
|
|
1101
|
+
const settled = outcome !== null && outcome.reference === debouncedReference ? outcome : null;
|
|
1102
|
+
const status = reference === null ? "empty" : reference !== debouncedReference || settled === null ? "checking" : settled.status;
|
|
1103
|
+
const resolvedTarget = status === "found" ? settled?.target ?? null : null;
|
|
1104
|
+
return {
|
|
1105
|
+
slice: {
|
|
1106
|
+
method,
|
|
1107
|
+
setMethod,
|
|
1108
|
+
value: text,
|
|
1109
|
+
change: setText,
|
|
1110
|
+
status,
|
|
1111
|
+
label: resolvedTarget?.label ?? null
|
|
1112
|
+
},
|
|
1113
|
+
resolvedTarget,
|
|
1114
|
+
trimmed: text.trim()
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/headless/send-money/use-send-money.ts
|
|
1119
|
+
function useSendMoney(input) {
|
|
1120
|
+
const { actor, onSent, onFailed } = input;
|
|
1121
|
+
const client = useCapxulClientOrNull();
|
|
1122
|
+
const signerStatus = useSignerStatus(client);
|
|
1123
|
+
const balance = useCapxulAccountBalance({ enabled: actor.kind === "personal" });
|
|
1124
|
+
const account = actor.kind === "personal" ? balance.data ?? null : null;
|
|
1125
|
+
const assetOptions = useMemo(() => account === null ? [] : [{
|
|
1126
|
+
id: account.id,
|
|
1127
|
+
symbol: account.available.currency,
|
|
1128
|
+
availableDisplay: formatMoney(account.available, { grammar: "code" })
|
|
1129
|
+
}], [account]);
|
|
1130
|
+
const [selectedAssetId, setSelectedAssetId] = useState(null);
|
|
1131
|
+
useEffect(() => {
|
|
1132
|
+
if (!assetOptions.some((option) => option.id === selectedAssetId)) setSelectedAssetId(assetOptions[0]?.id ?? null);
|
|
1133
|
+
}, [assetOptions, selectedAssetId]);
|
|
1134
|
+
const selectedAssetOption = assetOptions.find((option) => option.id === selectedAssetId) ?? null;
|
|
1135
|
+
const selectedAssetMoney = selectedAssetOption !== null && account !== null ? account.available : null;
|
|
1136
|
+
const selectAsset = useCallback((id) => {
|
|
1137
|
+
setSelectedAssetId((current) => assetOptions.some((option) => option.id === id) ? id : current);
|
|
1138
|
+
}, [assetOptions]);
|
|
1139
|
+
const [amountText, setAmountText] = useState("");
|
|
1140
|
+
const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null : parseMoney(amountText, selectedAssetMoney), [amountText, selectedAssetMoney]);
|
|
1141
|
+
const parsedMoney = parsed !== null && !isMoneyParseError(parsed) ? parsed : null;
|
|
1142
|
+
const amountError = parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null;
|
|
1143
|
+
const recipient = useRecipientResolution(client);
|
|
1144
|
+
const resolvedTarget = recipient.resolvedTarget;
|
|
1145
|
+
const pay = useCapxulPay();
|
|
1146
|
+
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;
|
|
1147
|
+
const submitGate = useRef(false);
|
|
1148
|
+
const submit = useCallback(() => {
|
|
1149
|
+
if (blockedReason !== null || submitGate.current) return;
|
|
1150
|
+
if (parsedMoney === null || resolvedTarget === null) return;
|
|
1151
|
+
submitGate.current = true;
|
|
1152
|
+
const amount = parsedMoney;
|
|
1153
|
+
const sentRecipient = recipient.trimmed;
|
|
1154
|
+
pay.mutate({
|
|
1155
|
+
to: resolvedTarget.reference,
|
|
1156
|
+
amount
|
|
1157
|
+
}, {
|
|
1158
|
+
onSuccess: (payment) => onSent({
|
|
1159
|
+
payment,
|
|
1160
|
+
amount,
|
|
1161
|
+
recipient: sentRecipient
|
|
1162
|
+
}),
|
|
1163
|
+
onError: (error) => onFailed(error),
|
|
1164
|
+
onSettled: () => {
|
|
1165
|
+
submitGate.current = false;
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
}, [
|
|
1169
|
+
blockedReason,
|
|
1170
|
+
onFailed,
|
|
1171
|
+
onSent,
|
|
1172
|
+
parsedMoney,
|
|
1173
|
+
pay,
|
|
1174
|
+
recipient.trimmed,
|
|
1175
|
+
resolvedTarget
|
|
1176
|
+
]);
|
|
1177
|
+
return {
|
|
1178
|
+
asset: {
|
|
1179
|
+
options: assetOptions,
|
|
1180
|
+
selected: selectedAssetOption,
|
|
1181
|
+
select: selectAsset,
|
|
1182
|
+
error: actor.kind === "personal" && balance.error !== null ? "read-failed" : null
|
|
1183
|
+
},
|
|
1184
|
+
amount: {
|
|
1185
|
+
value: amountText,
|
|
1186
|
+
change: setAmountText,
|
|
1187
|
+
error: amountError,
|
|
1188
|
+
availableDisplay: selectedAssetOption?.availableDisplay ?? null
|
|
1189
|
+
},
|
|
1190
|
+
recipient: recipient.slice,
|
|
1191
|
+
actions: {
|
|
1192
|
+
submit,
|
|
1193
|
+
isSubmitting: pay.isPending,
|
|
1194
|
+
blocked: blockedReason !== null,
|
|
1195
|
+
blockedReason
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region src/headless/send-money/send-money.tsx
|
|
1201
|
+
const SendMoneyContext = createContext(null);
|
|
1202
|
+
function useSendMoneyContext() {
|
|
1203
|
+
const engine = useContext(SendMoneyContext);
|
|
1204
|
+
if (engine === null) throw new Error("CapxulSendMoney parts must be used inside <CapxulSendMoney>");
|
|
1205
|
+
return engine;
|
|
1206
|
+
}
|
|
1207
|
+
function Root({ actor, onSent, onFailed, children }) {
|
|
1208
|
+
const engine = useSendMoney({
|
|
1209
|
+
actor,
|
|
1210
|
+
onSent,
|
|
1211
|
+
onFailed
|
|
1212
|
+
});
|
|
1213
|
+
return /* @__PURE__ */ jsx(SendMoneyContext.Provider, {
|
|
1214
|
+
value: engine,
|
|
1215
|
+
children
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
function Asset({ children }) {
|
|
1219
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().asset) });
|
|
1220
|
+
}
|
|
1221
|
+
function Amount({ children }) {
|
|
1222
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().amount) });
|
|
1223
|
+
}
|
|
1224
|
+
function Recipient({ children }) {
|
|
1225
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().recipient) });
|
|
1226
|
+
}
|
|
1227
|
+
function Actions({ children }) {
|
|
1228
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().actions) });
|
|
1229
|
+
}
|
|
1230
|
+
/** The compound shape the app's own design system already uses (`StatusTabs`). */
|
|
1231
|
+
const CapxulSendMoney = Object.assign(Root, {
|
|
1232
|
+
Asset,
|
|
1233
|
+
Amount,
|
|
1234
|
+
Recipient,
|
|
1235
|
+
Actions
|
|
1236
|
+
});
|
|
1237
|
+
//#endregion
|
|
1238
|
+
export { CapxulAuthenticationController, CapxulContacts, CapxulOnboardingController, CapxulProvider, CapxulSendMoney, acknowledgeOnboardingDestination, activeOnboardingRecovery, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUsernameAvailability };
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-
|
|
1
|
+
import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-ClhmseFL.mjs";
|
|
2
2
|
import "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createCapxulTestClient } from "@capxul/sdk/testing";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capxul/sdk-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Xelmar-tech/infrastructure.git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@capxul/sdk": "2.
|
|
29
|
+
"@capxul/sdk": "2.2.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@tanstack/react-query": "^5.66.9",
|