@solvapay/react 2.1.1 → 2.2.0-preview-e3e546693c002c32f128d83bf6c5fe76ac182a79
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/CHANGELOG.md +23 -2
- package/dist/UsageMeter-BhxNHeRM.d.ts +74 -0
- package/dist/UsageMeter-rga6cyfB.d.cts +74 -0
- package/dist/chunk-FWT62B56.js +316 -0
- package/dist/{chunk-LL6AYSTC.js → chunk-VTGYZIE2.js} +1734 -688
- package/dist/{chunk-HQH6KSXJ.js → chunk-X65JBC2A.js} +419 -810
- package/dist/{index-CBWGkBqF.d.ts → index-BJGjKUtC.d.cts} +46 -39
- package/dist/{index-SylJb9Rh.d.cts → index-PsjS_d7V.d.ts} +46 -39
- package/dist/index.cjs +2284 -1580
- package/dist/index.d.cts +110 -25
- package/dist/index.d.ts +110 -25
- package/dist/index.js +388 -46
- package/dist/mcp/index.cjs +6537 -3396
- package/dist/mcp/index.d.cts +637 -158
- package/dist/mcp/index.d.ts +637 -158
- package/dist/mcp/index.js +3421 -1004
- package/dist/mcp/styles.css +1422 -500
- package/dist/primitives/index.cjs +1635 -1031
- package/dist/primitives/index.d.cts +19 -76
- package/dist/primitives/index.d.ts +19 -76
- package/dist/primitives/index.js +3 -3
- package/dist/styles.css +60 -10
- package/dist/{shared-CUl6mTC1.d.ts → useUsage-CMqxEF9d.d.ts} +387 -53
- package/dist/{shared-D0hmT1qU.d.cts → useUsage-CTgRBvFK.d.cts} +387 -53
- package/package.json +5 -5
- package/dist/chunk-QXNNMNCV.js +0 -579
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
# @solvapay/react changelog
|
|
2
2
|
|
|
3
|
-
## 2.
|
|
3
|
+
## 2.2.0-preview-e3e546693c002c32f128d83bf6c5fe76ac182a79
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f994f1a: Unify MCP commerce surfaces and ship the v3 account widget (states A–J).
|
|
8
|
+
|
|
9
|
+
**Layout and chrome.** One layout law across checkout, top-up, and account: primary content leads, seller and your-account trails, identity rail visible at every width. `<McpApp>` reads host `displayMode` and advertises `inline` + `fullscreen`. Inline chrome is a centered `36rem` block; fullscreen is a hosted 1000px column (340 payment / 300 management rails) that falls back to the stacked widget under 1000px host width. Drop the shell sidebar — identity moves to `McpPayingAs` inside the payment form. Blend the widget into the host canvas (transparent root, hairline card, `color-scheme` meta in scaffolder and examples).
|
|
10
|
+
|
|
11
|
+
**Account widget.** StatusPill, FactBand, and UsageMeter (warning at 80%, critical at 100%) with v3 usage captions. Route each plan family through its own panel; ladder rows use per-row Activate/Switch (in-place when no payment, checkout when payment is required). Fullscreen extras: plan consequence lines, credit-activity and charges tables, merchant/buyer footer. Dedicated auto-recharge view from the account panel; shared fields in top-up. Seed bootstrap `limits` into `useLimits`/`useUsage`. `useHistory` fetches charges and credit activity on section mount.
|
|
12
|
+
|
|
13
|
+
**Checkout and payment.** Theme Stripe Payment Element from `--solvapay-*` tokens; tabs layout by default. Own billing country in the widget (not PaymentElement). Move mandate text below the pay button. Remove decorative save-card checkbox on PAYG; pass deferred auto-recharge into top-up so the card is saved for off-session reuse. `planPricingShape` so usage-based $0 plans are not labelled Free and one-time vs monthly render correctly.
|
|
14
|
+
|
|
15
|
+
**Links and polish.** Outbound links route through host `ui/open-link` when `openLinks` is declared (`useExternalLinkClick`, `useOpenExternal`, `<ExternalLinkProvider>`; `<McpApp>` mounts automatically). Drop fullscreen `CloseButton` export — host owns window chrome. Portal history links render as text; shell centres at 1144px. Seller-card email/support render as plain text (no iframe navigation).
|
|
16
|
+
|
|
17
|
+
**Breaking (MCP-only exports).** Removed `McpSellerDetailsCard`, `McpCustomerDetailsCard`, `McpAccountView.hideDetailCards`, `McpLimitReached`, and `CloseButton`. Turnkey `<McpApp>` integrators are unaffected; custom shells that imported these must migrate.
|
|
18
|
+
|
|
19
|
+
**Templates.** Example and `create-solvapay` pass `SOLVAPAY_MCP_APP_CAPABILITIES` into `new App()`.
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
6
22
|
|
|
23
|
+
- 4771b85: Widen the `@solvapay/mcp-core` peer range to accept `^0.4.0`. Existing installs pinned to `^0.3.0` keep resolving to 0.3.x and are unaffected; moving to mcp-core 0.4.x is now an explicit opt-in that no longer forces a peer conflict.
|
|
7
24
|
- Updated dependencies [a98faa9]
|
|
8
|
-
|
|
25
|
+
- Updated dependencies [f994f1a]
|
|
26
|
+
- Updated dependencies [f994f1a]
|
|
27
|
+
- Updated dependencies [5d37370]
|
|
28
|
+
- @solvapay/core@1.6.0-preview-e3e546693c002c32f128d83bf6c5fe76ac182a79
|
|
29
|
+
- @solvapay/mcp-core@0.4.0-preview-e3e546693c002c32f128d83bf6c5fe76ac182a79
|
|
9
30
|
|
|
10
31
|
## 2.1.0
|
|
11
32
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { _ as UsageSnapshot } from './useUsage-CMqxEF9d.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `<UsageMeter>` compound primitive — renders the authenticated customer's
|
|
6
|
+
* usage against their plan quota.
|
|
7
|
+
*
|
|
8
|
+
* Composes `useUsage()` internally so integrators only render the leaf
|
|
9
|
+
* primitives they care about. Leaves render `null` when the active plan
|
|
10
|
+
* isn't usage-based, so dropping the compound into any account page is
|
|
11
|
+
* safe.
|
|
12
|
+
*
|
|
13
|
+
* State:
|
|
14
|
+
* - root / bar `data-state="safe" | "warning" | "critical" | "loading"`
|
|
15
|
+
* - root `data-solvapay-usage-meter`
|
|
16
|
+
* - bar `data-solvapay-usage-meter-bar`
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type UsageMeterState = 'safe' | 'warning' | 'critical' | 'loading';
|
|
20
|
+
interface UsageMeterContextValue {
|
|
21
|
+
usage: UsageSnapshot | null;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
percentUsed: number | null;
|
|
25
|
+
isApproachingLimit: boolean;
|
|
26
|
+
isAtLimit: boolean;
|
|
27
|
+
isUnlimited: boolean;
|
|
28
|
+
state: UsageMeterState;
|
|
29
|
+
warningAt: number;
|
|
30
|
+
criticalAt: number;
|
|
31
|
+
}
|
|
32
|
+
interface UsageMeterClassNames {
|
|
33
|
+
root?: string;
|
|
34
|
+
bar?: string;
|
|
35
|
+
label?: string;
|
|
36
|
+
percentage?: string;
|
|
37
|
+
resetsIn?: string;
|
|
38
|
+
loading?: string;
|
|
39
|
+
empty?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
interface UsageMeterRootProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
43
|
+
/** Percent at which the bar flips to `data-state="warning"`. Default 75. */
|
|
44
|
+
warningAt?: number;
|
|
45
|
+
/** Percent at which the bar flips to `data-state="critical"`. Default 90. */
|
|
46
|
+
criticalAt?: number;
|
|
47
|
+
classNames?: UsageMeterClassNames;
|
|
48
|
+
asChild?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Override the usage snapshot rather than reading from `useUsage`.
|
|
51
|
+
* Mostly useful for tests / Storybook demos.
|
|
52
|
+
*/
|
|
53
|
+
usageOverride?: UsageSnapshot | null;
|
|
54
|
+
}
|
|
55
|
+
declare const Root: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
56
|
+
declare const Bar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
57
|
+
declare const Label: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
58
|
+
declare const Percentage: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
59
|
+
declare const ResetsIn: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
60
|
+
declare const Loading: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
61
|
+
declare const Empty: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
62
|
+
declare const UsageMeter: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>> & {
|
|
63
|
+
Root: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
64
|
+
Bar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
65
|
+
Label: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
66
|
+
Percentage: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
67
|
+
ResetsIn: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
68
|
+
Loading: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
69
|
+
Empty: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
declare function useUsageMeter(): UsageMeterContextValue;
|
|
73
|
+
|
|
74
|
+
export { Bar as B, Empty as E, Label as L, Percentage as P, ResetsIn as R, type UsageMeterRootProps as U, UsageMeter as a, type UsageMeterClassNames as b, Loading as c, Root as d, useUsageMeter as u };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { _ as UsageSnapshot } from './useUsage-CTgRBvFK.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `<UsageMeter>` compound primitive — renders the authenticated customer's
|
|
6
|
+
* usage against their plan quota.
|
|
7
|
+
*
|
|
8
|
+
* Composes `useUsage()` internally so integrators only render the leaf
|
|
9
|
+
* primitives they care about. Leaves render `null` when the active plan
|
|
10
|
+
* isn't usage-based, so dropping the compound into any account page is
|
|
11
|
+
* safe.
|
|
12
|
+
*
|
|
13
|
+
* State:
|
|
14
|
+
* - root / bar `data-state="safe" | "warning" | "critical" | "loading"`
|
|
15
|
+
* - root `data-solvapay-usage-meter`
|
|
16
|
+
* - bar `data-solvapay-usage-meter-bar`
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type UsageMeterState = 'safe' | 'warning' | 'critical' | 'loading';
|
|
20
|
+
interface UsageMeterContextValue {
|
|
21
|
+
usage: UsageSnapshot | null;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
percentUsed: number | null;
|
|
25
|
+
isApproachingLimit: boolean;
|
|
26
|
+
isAtLimit: boolean;
|
|
27
|
+
isUnlimited: boolean;
|
|
28
|
+
state: UsageMeterState;
|
|
29
|
+
warningAt: number;
|
|
30
|
+
criticalAt: number;
|
|
31
|
+
}
|
|
32
|
+
interface UsageMeterClassNames {
|
|
33
|
+
root?: string;
|
|
34
|
+
bar?: string;
|
|
35
|
+
label?: string;
|
|
36
|
+
percentage?: string;
|
|
37
|
+
resetsIn?: string;
|
|
38
|
+
loading?: string;
|
|
39
|
+
empty?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
interface UsageMeterRootProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
43
|
+
/** Percent at which the bar flips to `data-state="warning"`. Default 75. */
|
|
44
|
+
warningAt?: number;
|
|
45
|
+
/** Percent at which the bar flips to `data-state="critical"`. Default 90. */
|
|
46
|
+
criticalAt?: number;
|
|
47
|
+
classNames?: UsageMeterClassNames;
|
|
48
|
+
asChild?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Override the usage snapshot rather than reading from `useUsage`.
|
|
51
|
+
* Mostly useful for tests / Storybook demos.
|
|
52
|
+
*/
|
|
53
|
+
usageOverride?: UsageSnapshot | null;
|
|
54
|
+
}
|
|
55
|
+
declare const Root: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
56
|
+
declare const Bar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
57
|
+
declare const Label: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
58
|
+
declare const Percentage: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
59
|
+
declare const ResetsIn: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
60
|
+
declare const Loading: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
61
|
+
declare const Empty: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
62
|
+
declare const UsageMeter: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>> & {
|
|
63
|
+
Root: React.ForwardRefExoticComponent<UsageMeterRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
64
|
+
Bar: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
65
|
+
Label: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
66
|
+
Percentage: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
67
|
+
ResetsIn: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & React.RefAttributes<HTMLSpanElement>>;
|
|
68
|
+
Loading: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
69
|
+
Empty: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
declare function useUsageMeter(): UsageMeterContextValue;
|
|
73
|
+
|
|
74
|
+
export { Bar as B, Empty as E, Label as L, Percentage as P, ResetsIn as R, type UsageMeterRootProps as U, UsageMeter as a, type UsageMeterClassNames as b, Loading as c, Root as d, useUsageMeter as u };
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Slot,
|
|
3
|
+
composeEventHandlers,
|
|
4
|
+
createHttpTransport,
|
|
5
|
+
createTransportCacheKey,
|
|
6
|
+
useCopy,
|
|
7
|
+
useExternalLinkClick,
|
|
8
|
+
useOpenExternal,
|
|
9
|
+
useSolvaPay,
|
|
10
|
+
useTransport
|
|
11
|
+
} from "./chunk-VTGYZIE2.js";
|
|
12
|
+
|
|
13
|
+
// src/hooks/usePaymentMethod.ts
|
|
14
|
+
import { useCallback, useEffect, useState } from "react";
|
|
15
|
+
var paymentMethodCache = /* @__PURE__ */ new Map();
|
|
16
|
+
var CACHE_DURATION = 5 * 60 * 1e3;
|
|
17
|
+
function cacheKeyFor(config) {
|
|
18
|
+
return createTransportCacheKey(
|
|
19
|
+
config,
|
|
20
|
+
config?.api?.getPaymentMethod || "/api/payment-method"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
async function fetchPaymentMethod(config) {
|
|
24
|
+
const transport = config?.transport ?? createHttpTransport(config);
|
|
25
|
+
if (!transport.getPaymentMethod) return null;
|
|
26
|
+
return transport.getPaymentMethod();
|
|
27
|
+
}
|
|
28
|
+
function usePaymentMethod() {
|
|
29
|
+
const { _config } = useSolvaPay();
|
|
30
|
+
const key = cacheKeyFor(_config);
|
|
31
|
+
const [paymentMethod, setPaymentMethod] = useState(
|
|
32
|
+
() => paymentMethodCache.get(key)?.paymentMethod ?? null
|
|
33
|
+
);
|
|
34
|
+
const [loading, setLoading] = useState(() => {
|
|
35
|
+
const cached = paymentMethodCache.get(key);
|
|
36
|
+
return !cached || !cached.paymentMethod && !cached.promise;
|
|
37
|
+
});
|
|
38
|
+
const [error, setError] = useState(null);
|
|
39
|
+
const load = useCallback(
|
|
40
|
+
async (force = false) => {
|
|
41
|
+
const cached = paymentMethodCache.get(key);
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (!force && cached?.paymentMethod && now - cached.timestamp < CACHE_DURATION) {
|
|
44
|
+
setPaymentMethod(cached.paymentMethod);
|
|
45
|
+
setLoading(false);
|
|
46
|
+
setError(null);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!force && cached?.promise) {
|
|
50
|
+
try {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
const pm = await cached.promise;
|
|
53
|
+
setPaymentMethod(pm);
|
|
54
|
+
setError(null);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
setError(err instanceof Error ? err : new Error("Failed to load payment method"));
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
setLoading(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
const promise = fetchPaymentMethod(_config);
|
|
66
|
+
paymentMethodCache.set(key, {
|
|
67
|
+
paymentMethod: cached?.paymentMethod ?? null,
|
|
68
|
+
promise,
|
|
69
|
+
timestamp: now
|
|
70
|
+
});
|
|
71
|
+
const pm = await promise;
|
|
72
|
+
if (pm === null) {
|
|
73
|
+
paymentMethodCache.set(key, {
|
|
74
|
+
paymentMethod: cached?.paymentMethod ?? null,
|
|
75
|
+
promise: null,
|
|
76
|
+
timestamp: cached?.timestamp ?? now
|
|
77
|
+
});
|
|
78
|
+
setPaymentMethod(cached?.paymentMethod ?? null);
|
|
79
|
+
setLoading(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
paymentMethodCache.set(key, { paymentMethod: pm, promise: null, timestamp: now });
|
|
83
|
+
setPaymentMethod(pm);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
paymentMethodCache.delete(key);
|
|
86
|
+
setError(err instanceof Error ? err : new Error("Failed to load payment method"));
|
|
87
|
+
setPaymentMethod(null);
|
|
88
|
+
} finally {
|
|
89
|
+
setLoading(false);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
[_config, key]
|
|
93
|
+
);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
load();
|
|
96
|
+
}, [load]);
|
|
97
|
+
return {
|
|
98
|
+
paymentMethod,
|
|
99
|
+
loading,
|
|
100
|
+
error,
|
|
101
|
+
refetch: () => load(true)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/components/LaunchCustomerPortalButton.tsx
|
|
106
|
+
import { forwardRef, useState as useState2 } from "react";
|
|
107
|
+
|
|
108
|
+
// src/hooks/useCustomerSessionUrl.ts
|
|
109
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useSyncExternalStore } from "react";
|
|
110
|
+
var SessionStore = class {
|
|
111
|
+
constructor(transport) {
|
|
112
|
+
this.transport = transport;
|
|
113
|
+
}
|
|
114
|
+
// Snapshot is replaced (not mutated) on every transition so
|
|
115
|
+
// `useSyncExternalStore` sees a fresh reference and schedules
|
|
116
|
+
// a re-render for every subscriber.
|
|
117
|
+
state = { status: "idle" };
|
|
118
|
+
inflight = null;
|
|
119
|
+
listeners = /* @__PURE__ */ new Set();
|
|
120
|
+
subscribe = (listener) => {
|
|
121
|
+
this.listeners.add(listener);
|
|
122
|
+
return () => {
|
|
123
|
+
this.listeners.delete(listener);
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
getSnapshot = () => this.state;
|
|
127
|
+
ensure = () => {
|
|
128
|
+
if (this.state.status === "ready" && this.state.url) {
|
|
129
|
+
return Promise.resolve(this.state.url);
|
|
130
|
+
}
|
|
131
|
+
return this.start();
|
|
132
|
+
};
|
|
133
|
+
refresh = () => {
|
|
134
|
+
this.inflight = null;
|
|
135
|
+
this.state = { status: "idle" };
|
|
136
|
+
this.notify();
|
|
137
|
+
return this.start();
|
|
138
|
+
};
|
|
139
|
+
start() {
|
|
140
|
+
if (this.inflight) return this.inflight;
|
|
141
|
+
this.state = { status: "loading" };
|
|
142
|
+
this.notify();
|
|
143
|
+
const promise = this.transport.createCustomerSession().then(({ customerUrl }) => {
|
|
144
|
+
this.state = { status: "ready", url: customerUrl };
|
|
145
|
+
this.inflight = null;
|
|
146
|
+
this.notify();
|
|
147
|
+
return customerUrl;
|
|
148
|
+
}).catch((err) => {
|
|
149
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
150
|
+
this.state = { status: "error", error };
|
|
151
|
+
this.inflight = null;
|
|
152
|
+
this.notify();
|
|
153
|
+
throw error;
|
|
154
|
+
});
|
|
155
|
+
this.inflight = promise;
|
|
156
|
+
return promise;
|
|
157
|
+
}
|
|
158
|
+
notify() {
|
|
159
|
+
this.listeners.forEach((listener) => listener());
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
163
|
+
function getStore(transport) {
|
|
164
|
+
let store = stores.get(transport);
|
|
165
|
+
if (!store) {
|
|
166
|
+
store = new SessionStore(transport);
|
|
167
|
+
stores.set(transport, store);
|
|
168
|
+
}
|
|
169
|
+
return store;
|
|
170
|
+
}
|
|
171
|
+
function useCustomerSessionUrl() {
|
|
172
|
+
const transport = useTransport();
|
|
173
|
+
const store = getStore(transport);
|
|
174
|
+
const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
175
|
+
useEffect2(() => {
|
|
176
|
+
const current = getStore(transport);
|
|
177
|
+
if (current.state.status === "idle") {
|
|
178
|
+
current.ensure().catch(() => {
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}, [transport]);
|
|
182
|
+
const ensure = useCallback2(() => getStore(transport).ensure(), [transport]);
|
|
183
|
+
const refresh = useCallback2(() => getStore(transport).refresh(), [transport]);
|
|
184
|
+
return {
|
|
185
|
+
status: snapshot.status,
|
|
186
|
+
url: snapshot.url,
|
|
187
|
+
error: snapshot.error,
|
|
188
|
+
ensure,
|
|
189
|
+
refresh
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/components/ExternalLinkGlyph.tsx
|
|
194
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
195
|
+
var ExternalLinkGlyph = ({ className }) => {
|
|
196
|
+
return /* @__PURE__ */ jsxs(
|
|
197
|
+
"svg",
|
|
198
|
+
{
|
|
199
|
+
className: `solvapay-mcp-external-glyph${className ? ` ${className}` : ""}`,
|
|
200
|
+
width: "0.9em",
|
|
201
|
+
height: "0.9em",
|
|
202
|
+
viewBox: "0 0 24 24",
|
|
203
|
+
fill: "none",
|
|
204
|
+
stroke: "currentColor",
|
|
205
|
+
strokeWidth: "2",
|
|
206
|
+
strokeLinecap: "round",
|
|
207
|
+
strokeLinejoin: "round",
|
|
208
|
+
"aria-hidden": "true",
|
|
209
|
+
focusable: "false",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsx("path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }),
|
|
212
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
213
|
+
/* @__PURE__ */ jsx("path", { d: "m10 14 11-11" })
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/components/LaunchCustomerPortalButton.tsx
|
|
220
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
221
|
+
var LaunchCustomerPortalButton = forwardRef(function LaunchCustomerPortalButton2({
|
|
222
|
+
children,
|
|
223
|
+
onLaunch,
|
|
224
|
+
onError,
|
|
225
|
+
onClick,
|
|
226
|
+
loadingClassName,
|
|
227
|
+
errorClassName,
|
|
228
|
+
asChild,
|
|
229
|
+
className,
|
|
230
|
+
...rest
|
|
231
|
+
}, forwardedRef) {
|
|
232
|
+
const { status, url, ensure } = useCustomerSessionUrl();
|
|
233
|
+
const copy = useCopy();
|
|
234
|
+
const handleExternalClick = useExternalLinkClick();
|
|
235
|
+
const openExternal = useOpenExternal();
|
|
236
|
+
const [clickState, setClickState] = useState2("idle");
|
|
237
|
+
const isReady = status === "ready" && typeof url === "string";
|
|
238
|
+
const label = children ?? copy.customerPortal.launchButton;
|
|
239
|
+
const labelText = typeof label === "string" ? label : copy.customerPortal.launchButton;
|
|
240
|
+
const composedClassName = [
|
|
241
|
+
className,
|
|
242
|
+
clickState === "pending" ? loadingClassName : null,
|
|
243
|
+
clickState === "error" ? errorClassName : null
|
|
244
|
+
].filter(Boolean).join(" ") || void 0;
|
|
245
|
+
const handleReadyClick = (event) => {
|
|
246
|
+
if (!isReady || !url) return;
|
|
247
|
+
onLaunch?.(url);
|
|
248
|
+
handleExternalClick(event);
|
|
249
|
+
};
|
|
250
|
+
const handlePendingClick = async (event) => {
|
|
251
|
+
event.preventDefault();
|
|
252
|
+
setClickState("pending");
|
|
253
|
+
try {
|
|
254
|
+
const resolved = await ensure();
|
|
255
|
+
if (!await openExternal(resolved)) {
|
|
256
|
+
setClickState("error");
|
|
257
|
+
onError?.(new Error(`Host declined to open the customer portal: ${resolved}`));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
setClickState("idle");
|
|
261
|
+
onLaunch?.(resolved);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
264
|
+
setClickState("error");
|
|
265
|
+
onError?.(error);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const sharedProps = {
|
|
269
|
+
"data-solvapay-launch-customer-portal": "",
|
|
270
|
+
"data-state": clickState === "pending" ? "pending" : isReady ? "ready" : "idle",
|
|
271
|
+
"aria-label": `${labelText} (opens in a new tab)`,
|
|
272
|
+
className: composedClassName,
|
|
273
|
+
...rest
|
|
274
|
+
};
|
|
275
|
+
if (isReady && url) {
|
|
276
|
+
const readyProps = {
|
|
277
|
+
...sharedProps,
|
|
278
|
+
href: url,
|
|
279
|
+
target: "_blank",
|
|
280
|
+
rel: "noopener noreferrer",
|
|
281
|
+
onClick: composeEventHandlers(onClick, handleReadyClick)
|
|
282
|
+
};
|
|
283
|
+
if (asChild) {
|
|
284
|
+
return (
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
|
+
/* @__PURE__ */ jsx2(Slot, { ref: forwardedRef, ...readyProps, children: label })
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return /* @__PURE__ */ jsxs2("a", { ref: forwardedRef, ...readyProps, children: [
|
|
290
|
+
label,
|
|
291
|
+
/* @__PURE__ */ jsx2(ExternalLinkGlyph, {})
|
|
292
|
+
] });
|
|
293
|
+
}
|
|
294
|
+
const pendingProps = {
|
|
295
|
+
...sharedProps,
|
|
296
|
+
role: "link",
|
|
297
|
+
onClick: composeEventHandlers(onClick, handlePendingClick)
|
|
298
|
+
};
|
|
299
|
+
if (asChild) {
|
|
300
|
+
return (
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
302
|
+
/* @__PURE__ */ jsx2(Slot, { ref: forwardedRef, ...pendingProps, children: label })
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return /* @__PURE__ */ jsxs2("a", { ref: forwardedRef, ...pendingProps, children: [
|
|
306
|
+
label,
|
|
307
|
+
/* @__PURE__ */ jsx2(ExternalLinkGlyph, {})
|
|
308
|
+
] });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
export {
|
|
312
|
+
paymentMethodCache,
|
|
313
|
+
usePaymentMethod,
|
|
314
|
+
ExternalLinkGlyph,
|
|
315
|
+
LaunchCustomerPortalButton
|
|
316
|
+
};
|