@openreceive/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenReceive contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,208 @@
1
+ import * as React from "react";
2
+ import { type OpenReceiveQrEncoder, type OpenReceiveBrowserLogContext, type OpenReceiveBrowserLogger, type CheckoutPhase, type CheckoutSnapshot, type CheckoutState, type CheckoutStatusModel, type OpenReceiveRefreshInvoiceResult, type OpenReceiveResolvedTheme, type OpenReceiveThemeModel, type OpenReceiveThemePreference, type Status } from "@openreceive/browser/internal";
3
+ export interface CheckoutData {
4
+ readonly invoice: CheckoutSnapshot;
5
+ }
6
+ export interface CheckoutViewModel {
7
+ readonly invoice_id?: string;
8
+ readonly invoice: string;
9
+ readonly payment_hash?: string;
10
+ readonly amount_msats?: number;
11
+ readonly fiat_quote?: CheckoutSnapshot["fiat_quote"];
12
+ readonly expires_at?: number;
13
+ readonly settled_at?: number;
14
+ readonly lightningUri: string;
15
+ readonly amountLabel?: string;
16
+ readonly fiatLabel?: string;
17
+ readonly paymentHashLabel?: string;
18
+ readonly status: Status;
19
+ }
20
+ export interface UseCheckoutOptions extends CheckoutData {
21
+ readonly clipboard?: Pick<Clipboard, "writeText">;
22
+ readonly open?: (uri: string) => void;
23
+ readonly logger?: OpenReceiveBrowserLogger;
24
+ readonly onError?: (error: unknown) => void;
25
+ readonly lookupInvoice?: (state: CheckoutState) => Promise<Partial<CheckoutSnapshot>>;
26
+ readonly lookupUrl?: string | false;
27
+ readonly refreshInvoice?: (state: CheckoutState) => Promise<CheckoutSnapshot | OpenReceiveRefreshInvoiceResult>;
28
+ readonly refreshUrl?: string | ((state: CheckoutState) => string);
29
+ readonly refreshHeaders?: Readonly<Record<string, string>>;
30
+ readonly refreshIdempotencyKey?: string | ((state: CheckoutState) => string);
31
+ readonly refreshReason?: string | ((state: CheckoutState) => string);
32
+ readonly onState?: (state: CheckoutState) => void;
33
+ readonly onPaid?: () => void;
34
+ readonly polling?: boolean;
35
+ readonly pollIntervalMs?: number;
36
+ }
37
+ export interface UseCheckoutResult extends CheckoutViewModel {
38
+ readonly copied: boolean;
39
+ readonly expiresInSeconds?: number;
40
+ readonly countdownLabel?: string;
41
+ readonly countdownPrefix?: string;
42
+ readonly statusTitle: string;
43
+ readonly statusDetail: string;
44
+ readonly waiting: boolean;
45
+ reloadState(): Promise<void>;
46
+ retry(): Promise<void>;
47
+ refreshExpiredInvoice(): Promise<void>;
48
+ cancel(): void;
49
+ copyInvoice(): Promise<void>;
50
+ openWallet(): string;
51
+ }
52
+ export interface CheckoutProviderProps extends UseCheckoutOptions {
53
+ readonly children?: React.ReactNode | ((checkout: UseCheckoutResult) => React.ReactNode);
54
+ }
55
+ export interface QRCodeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
56
+ readonly invoice: string;
57
+ readonly encoder?: OpenReceiveQrEncoder;
58
+ readonly width?: number;
59
+ readonly onError?: (error: unknown) => void;
60
+ }
61
+ export type ButtonComponent = React.ElementType<React.ButtonHTMLAttributes<HTMLButtonElement>>;
62
+ export interface CopyInvoiceButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
63
+ readonly invoice: string;
64
+ readonly copyInvoice?: () => Promise<void>;
65
+ readonly clipboard?: Pick<Clipboard, "writeText">;
66
+ readonly logger?: OpenReceiveBrowserLogger;
67
+ readonly onCopied?: () => void;
68
+ readonly onError?: (error: unknown) => void;
69
+ readonly copiedLabel?: React.ReactNode;
70
+ readonly ButtonComponent?: ButtonComponent;
71
+ }
72
+ export interface OpenWalletButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
73
+ readonly invoice: string;
74
+ readonly openWallet?: () => string;
75
+ readonly open?: (uri: string) => void;
76
+ readonly logger?: OpenReceiveBrowserLogger;
77
+ readonly onOpened?: (uri: string) => void;
78
+ readonly onError?: (error: unknown) => void;
79
+ readonly ButtonComponent?: ButtonComponent;
80
+ }
81
+ export interface PaymentStateProps extends React.HTMLAttributes<HTMLSpanElement> {
82
+ readonly state?: string;
83
+ }
84
+ export interface InvoiceSummaryClassNames {
85
+ readonly amount?: string;
86
+ readonly fiat?: string;
87
+ readonly paymentHash?: string;
88
+ readonly paymentState?: string;
89
+ }
90
+ export interface InvoiceSummaryProps extends React.HTMLAttributes<HTMLDivElement> {
91
+ readonly amountLabel?: string;
92
+ readonly fiatLabel?: string;
93
+ readonly paymentHashLabel?: string;
94
+ readonly status?: Status;
95
+ readonly PaymentStateComponent?: React.ComponentType<PaymentStateProps>;
96
+ readonly classNames?: InvoiceSummaryClassNames;
97
+ }
98
+ export interface CheckoutClassNames extends InvoiceSummaryClassNames {
99
+ readonly root?: string;
100
+ readonly qr?: string;
101
+ readonly details?: string;
102
+ readonly waiting?: string;
103
+ readonly countdown?: string;
104
+ readonly summary?: string;
105
+ readonly invoice?: string;
106
+ readonly actions?: string;
107
+ readonly copyButton?: string;
108
+ readonly openWalletButton?: string;
109
+ readonly wizard?: string;
110
+ readonly themeToggle?: string;
111
+ }
112
+ export interface CheckoutComponents {
113
+ readonly Button?: ButtonComponent;
114
+ readonly QRCode?: React.ComponentType<QRCodeProps>;
115
+ readonly InvoiceSummary?: React.ComponentType<InvoiceSummaryProps>;
116
+ readonly CopyButton?: React.ComponentType<CopyInvoiceButtonProps>;
117
+ readonly OpenWalletButton?: React.ComponentType<OpenWalletButtonProps>;
118
+ readonly PaymentState?: React.ComponentType<PaymentStateProps>;
119
+ }
120
+ export type CheckoutChildren = React.ReactNode | ((model: UseCheckoutResult) => React.ReactNode);
121
+ export interface CheckoutProps extends CheckoutData, Omit<React.HTMLAttributes<HTMLElement>, "children"> {
122
+ readonly qrEncoder?: OpenReceiveQrEncoder;
123
+ readonly logger?: OpenReceiveBrowserLogger;
124
+ readonly onError?: (error: unknown) => void;
125
+ readonly lookupInvoice?: (state: CheckoutState) => Promise<Partial<CheckoutSnapshot>>;
126
+ readonly lookupUrl?: string | false;
127
+ readonly refreshInvoice?: (state: CheckoutState) => Promise<CheckoutSnapshot | OpenReceiveRefreshInvoiceResult>;
128
+ readonly refreshUrl?: string | ((state: CheckoutState) => string);
129
+ readonly refreshHeaders?: Readonly<Record<string, string>>;
130
+ readonly refreshIdempotencyKey?: string | ((state: CheckoutState) => string);
131
+ readonly refreshReason?: string | ((state: CheckoutState) => string);
132
+ readonly onState?: (state: CheckoutState) => void;
133
+ readonly onPaid?: () => void;
134
+ readonly onStartOver?: () => void;
135
+ readonly polling?: boolean;
136
+ readonly paymentWizard?: boolean;
137
+ readonly themeSwitcher?: boolean;
138
+ readonly defaultTheme?: OpenReceiveThemePreference;
139
+ readonly themeStorageKey?: string;
140
+ readonly countryStorageKey?: string;
141
+ readonly components?: CheckoutComponents;
142
+ readonly classNames?: CheckoutClassNames;
143
+ readonly children?: CheckoutChildren;
144
+ }
145
+ export interface UseThemeOptions {
146
+ readonly defaultTheme?: OpenReceiveThemePreference;
147
+ readonly storageKey?: string;
148
+ readonly storage?: Storage;
149
+ }
150
+ export interface UseThemeResult {
151
+ readonly theme: OpenReceiveThemePreference;
152
+ readonly resolvedTheme: OpenReceiveResolvedTheme;
153
+ readonly model: OpenReceiveThemeModel;
154
+ readonly nextTheme: OpenReceiveThemePreference;
155
+ readonly toggleLabel: string;
156
+ readonly attributes: OpenReceiveThemeModel["attributes"];
157
+ readonly checkoutElementAttributes: OpenReceiveThemeModel["checkoutElementAttributes"];
158
+ setTheme(theme: OpenReceiveThemePreference): void;
159
+ toggleTheme(): void;
160
+ }
161
+ export interface ThemeToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
162
+ readonly theme?: OpenReceiveThemePreference;
163
+ readonly resolvedTheme?: OpenReceiveResolvedTheme;
164
+ readonly onThemeChange?: (theme: OpenReceiveThemePreference) => void;
165
+ readonly ButtonComponent?: ButtonComponent;
166
+ }
167
+ export type ThemeScopeChildren = React.ReactNode | ((theme: UseThemeResult) => React.ReactNode);
168
+ export interface ThemeScopeProps extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
169
+ readonly as?: keyof React.JSX.IntrinsicElements;
170
+ readonly defaultTheme?: OpenReceiveThemePreference;
171
+ readonly themeStorageKey?: string;
172
+ readonly storage?: Storage;
173
+ readonly themeToggle?: boolean;
174
+ readonly topbarClassName?: string;
175
+ readonly themeToggleClassName?: string;
176
+ readonly ButtonComponent?: ButtonComponent;
177
+ readonly children?: ThemeScopeChildren;
178
+ }
179
+ export interface PaymentWizardProps {
180
+ readonly invoice: string;
181
+ readonly className?: string;
182
+ readonly logger?: OpenReceiveBrowserLogger;
183
+ readonly logContext?: OpenReceiveBrowserLogContext;
184
+ readonly countryStorageKey?: string;
185
+ readonly onError?: (error: unknown) => void;
186
+ }
187
+ export declare function createCheckoutViewModel(data: CheckoutData): CheckoutViewModel;
188
+ export declare function useCheckout(options: UseCheckoutOptions): UseCheckoutResult;
189
+ export declare function useCheckoutContext(): UseCheckoutResult;
190
+ export declare function CheckoutProvider(props: CheckoutProviderProps): React.ReactElement;
191
+ export declare function QRCode(props: QRCodeProps): React.ReactElement;
192
+ export declare function CopyInvoiceButton(props: CopyInvoiceButtonProps): React.ReactElement;
193
+ export declare function OpenWalletButton(props: OpenWalletButtonProps): React.ReactElement;
194
+ export declare function PaymentState(props: PaymentStateProps): React.ReactElement;
195
+ export declare function useTheme(options?: UseThemeOptions): UseThemeResult;
196
+ export declare function ThemeToggle(props: ThemeToggleProps): React.ReactElement;
197
+ export declare function ThemeScope(props: ThemeScopeProps): React.ReactElement;
198
+ export declare function WaitingState(props: {
199
+ readonly waiting?: boolean;
200
+ readonly phase?: CheckoutPhase;
201
+ readonly status?: CheckoutStatusModel;
202
+ readonly statusTitle?: string;
203
+ readonly statusDetail?: string;
204
+ readonly className?: string;
205
+ }): React.ReactElement;
206
+ export declare function PaymentWizard(props: PaymentWizardProps): React.ReactElement;
207
+ export declare function InvoiceSummary(props: InvoiceSummaryProps): React.ReactElement;
208
+ export declare function Checkout(props: CheckoutProps): React.ReactElement;
package/dist/index.js ADDED
@@ -0,0 +1,919 @@
1
+ import * as React from "react";
2
+ import { OPENRECEIVE_COPY_FEEDBACK_MS, OPENRECEIVE_COUNTRY_STORAGE_KEY, OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, OPENRECEIVE_THEME_STORAGE_KEY, copyInvoice as copyInvoiceHelper, createCheckoutController, createCheckoutDisplayModel, createCheckoutSnapshotFromDisplayData, createCheckoutStatusModel, createCheckoutStateFromDisplayData, createOpenReceivePaymentWizardModel, createOpenReceivePaymentWizardController, createCheckoutProviderCopyEvent, createOpenReceiveWizardRouteAssetDisplays, createOpenReceiveWizardRouteDisplays, createQrSvg, createOpenReceiveThemeModel, createOpenReceiveTransientFeedbackController, getOpenReceiveDefaultCountryCode, getOpenReceivePaymentMethodIcon, getOpenReceiveWizardEmptyMessage, openReceiveCheckoutLabels, openReceivePaymentMethods, openWallet as openWalletHelper, readOpenReceiveThemePreference, status as deriveStatus, writeOpenReceiveThemePreference } from "@openreceive/browser/internal";
3
+ const DEFAULT_LOOKUP_URL = "/openreceive/v1/invoices/lookup";
4
+ function useOpenReceiveTransientValue(resetValue, delayMs = OPENRECEIVE_COPY_FEEDBACK_MS) {
5
+ const [value, setValue] = React.useState(resetValue);
6
+ const controller = React.useRef(null);
7
+ React.useEffect(() => {
8
+ controller.current?.clear();
9
+ controller.current = createOpenReceiveTransientFeedbackController({
10
+ resetValue,
11
+ delayMs,
12
+ onValue: setValue
13
+ });
14
+ return () => controller.current?.clear();
15
+ }, [resetValue, delayMs]);
16
+ const showValue = React.useCallback((nextValue) => {
17
+ if (controller.current === null) {
18
+ setValue(nextValue);
19
+ return;
20
+ }
21
+ controller.current.show(nextValue);
22
+ }, []);
23
+ return [value, showValue];
24
+ }
25
+ function toCheckoutDisplayData(invoice) {
26
+ return {
27
+ invoice_id: invoice.invoice_id,
28
+ invoice: invoice.invoice,
29
+ ...(invoice.payment_hash === undefined ? {} : { payment_hash: invoice.payment_hash }),
30
+ ...(invoice.amount_msats === undefined ? {} : { amount_msats: invoice.amount_msats }),
31
+ ...(invoice.fiat_quote === undefined ? {} : { fiat_quote: invoice.fiat_quote }),
32
+ ...(invoice.transaction_state === undefined
33
+ ? {}
34
+ : { transaction_state: invoice.transaction_state }),
35
+ ...(invoice.workflow_state === undefined ? {} : { workflow_state: invoice.workflow_state }),
36
+ ...(invoice.expires_at === undefined ? {} : { expires_at: invoice.expires_at }),
37
+ ...(invoice.settled_at === undefined ? {} : { settled_at: invoice.settled_at })
38
+ };
39
+ }
40
+ function toCheckoutViewModel(display, currentStatus) {
41
+ return {
42
+ invoice_id: display.invoice_id,
43
+ invoice: display.invoice,
44
+ ...(display.payment_hash === undefined ? {} : { payment_hash: display.payment_hash }),
45
+ ...(display.amount_msats === undefined ? {} : { amount_msats: display.amount_msats }),
46
+ ...(display.fiat_quote === undefined ? {} : { fiat_quote: display.fiat_quote }),
47
+ ...(display.expires_at === undefined ? {} : { expires_at: display.expires_at }),
48
+ ...(display.settled_at === undefined ? {} : { settled_at: display.settled_at }),
49
+ lightningUri: display.lightningUri,
50
+ ...(display.amountLabel === undefined ? {} : { amountLabel: display.amountLabel }),
51
+ ...(display.fiatLabel === undefined ? {} : { fiatLabel: display.fiatLabel }),
52
+ ...(display.paymentHashLabel === undefined ? {} : { paymentHashLabel: display.paymentHashLabel }),
53
+ status: currentStatus
54
+ };
55
+ }
56
+ export function createCheckoutViewModel(data) {
57
+ return toCheckoutViewModel(createCheckoutDisplayModel(toCheckoutDisplayData(data.invoice)), deriveStatus(data.invoice));
58
+ }
59
+ function resolveCheckoutLookupUrl(options) {
60
+ if (options.polling === false || options.lookupUrl === false)
61
+ return undefined;
62
+ return options.lookupUrl ?? DEFAULT_LOOKUP_URL;
63
+ }
64
+ export function useCheckout(options) {
65
+ const [copied, showCopied] = useOpenReceiveTransientValue(false);
66
+ const displayData = React.useMemo(() => toCheckoutDisplayData(options.invoice), [options.invoice]);
67
+ const model = React.useMemo(() => toCheckoutViewModel(createCheckoutDisplayModel(displayData), deriveStatus(options.invoice)), [
68
+ displayData,
69
+ options.invoice
70
+ ]);
71
+ const snapshot = React.useMemo(() => createCheckoutSnapshotFromDisplayData(displayData), [
72
+ displayData
73
+ ]);
74
+ const [state, setState] = React.useState(() => createCheckoutStateFromDisplayData(displayData, {
75
+ logger: options.logger
76
+ }));
77
+ const controllerRef = React.useRef(null);
78
+ const onStateRef = React.useRef(options.onState);
79
+ onStateRef.current = options.onState;
80
+ const onPaidRef = React.useRef(options.onPaid);
81
+ onPaidRef.current = options.onPaid;
82
+ const paidAnnouncementRef = React.useRef({
83
+ invoiceId: snapshot.invoice_id,
84
+ fired: false
85
+ });
86
+ const logContext = React.useMemo(() => getCheckoutLogContext(displayData), [
87
+ displayData
88
+ ]);
89
+ React.useEffect(() => {
90
+ const lookupInvoice = options.polling === false ? undefined : options.lookupInvoice;
91
+ const lookupUrl = resolveCheckoutLookupUrl(options);
92
+ const controller = createCheckoutController({
93
+ snapshot,
94
+ ...(lookupInvoice === undefined ? {} : { lookupInvoice }),
95
+ ...(lookupUrl === undefined ? {} : { lookupUrl }),
96
+ ...(options.refreshInvoice === undefined ? {} : { refreshInvoice: options.refreshInvoice }),
97
+ ...(options.refreshUrl === undefined ? {} : { refreshUrl: options.refreshUrl }),
98
+ ...(options.refreshHeaders === undefined ? {} : { refreshHeaders: options.refreshHeaders }),
99
+ ...(options.refreshIdempotencyKey === undefined
100
+ ? {}
101
+ : { refreshIdempotencyKey: options.refreshIdempotencyKey }),
102
+ ...(options.refreshReason === undefined ? {} : { refreshReason: options.refreshReason }),
103
+ pollIntervalMs: options.pollIntervalMs,
104
+ logger: options.logger,
105
+ onError: options.onError,
106
+ clipboard: options.clipboard,
107
+ open: options.open,
108
+ onState: (nextState) => {
109
+ setState(nextState);
110
+ onStateRef.current?.(nextState);
111
+ }
112
+ });
113
+ controllerRef.current = controller;
114
+ controller.start();
115
+ return () => {
116
+ controller.stop();
117
+ if (controllerRef.current === controller)
118
+ controllerRef.current = null;
119
+ };
120
+ }, [
121
+ snapshot,
122
+ options.lookupInvoice,
123
+ options.lookupUrl,
124
+ options.polling,
125
+ options.refreshInvoice,
126
+ options.refreshUrl,
127
+ options.refreshHeaders,
128
+ options.refreshIdempotencyKey,
129
+ options.refreshReason,
130
+ options.pollIntervalMs,
131
+ options.logger,
132
+ options.onError,
133
+ options.clipboard,
134
+ options.open
135
+ ]);
136
+ const publicStatus = deriveStatus(state);
137
+ const richStatus = createCheckoutStatusModel(state);
138
+ React.useEffect(() => {
139
+ const announced = paidAnnouncementRef.current;
140
+ if (announced.invoiceId !== snapshot.invoice_id) {
141
+ paidAnnouncementRef.current = {
142
+ invoiceId: snapshot.invoice_id,
143
+ fired: false
144
+ };
145
+ }
146
+ }, [snapshot.invoice_id]);
147
+ React.useEffect(() => {
148
+ const announced = paidAnnouncementRef.current;
149
+ if (publicStatus !== "paid" || announced.fired)
150
+ return;
151
+ paidAnnouncementRef.current = {
152
+ invoiceId: snapshot.invoice_id,
153
+ fired: true
154
+ };
155
+ // UI hint only; server-side fulfillment must use the backend onPaid hook.
156
+ onPaidRef.current?.();
157
+ }, [publicStatus, snapshot.invoice_id]);
158
+ const copyInvoice = React.useCallback(async () => {
159
+ try {
160
+ const controller = controllerRef.current;
161
+ if (controller === null) {
162
+ await copyInvoiceHelper({
163
+ invoice: displayData.invoice,
164
+ clipboard: options.clipboard,
165
+ logger: options.logger,
166
+ logContext
167
+ });
168
+ }
169
+ else {
170
+ await controller.copyInvoice();
171
+ }
172
+ showCopied(true);
173
+ }
174
+ catch (error) {
175
+ options.onError?.(error);
176
+ throw error;
177
+ }
178
+ }, [logContext, displayData.invoice, options.clipboard, options.logger, options.onError, showCopied]);
179
+ const openWallet = React.useCallback(() => {
180
+ try {
181
+ const controller = controllerRef.current;
182
+ return controller === null
183
+ ? openWalletHelper({
184
+ invoice: displayData.invoice,
185
+ open: options.open,
186
+ logger: options.logger,
187
+ logContext
188
+ })
189
+ : controller.openWallet();
190
+ }
191
+ catch (error) {
192
+ options.onError?.(error);
193
+ throw error;
194
+ }
195
+ }, [logContext, displayData.invoice, options.open, options.logger, options.onError]);
196
+ const reloadState = React.useCallback(async () => {
197
+ try {
198
+ const next = await controllerRef.current?.reloadState();
199
+ if (next !== undefined)
200
+ setState(next);
201
+ }
202
+ catch (error) {
203
+ options.onError?.(error);
204
+ throw error;
205
+ }
206
+ }, [options.onError]);
207
+ const retry = React.useCallback(async () => {
208
+ try {
209
+ const next = await controllerRef.current?.retry();
210
+ if (next !== undefined)
211
+ setState(next);
212
+ }
213
+ catch (error) {
214
+ options.onError?.(error);
215
+ throw error;
216
+ }
217
+ }, [options.onError]);
218
+ const refreshExpiredInvoice = React.useCallback(async () => {
219
+ try {
220
+ const next = await controllerRef.current?.refreshExpiredInvoice();
221
+ if (next !== undefined)
222
+ setState(next);
223
+ }
224
+ catch (error) {
225
+ options.onError?.(error);
226
+ throw error;
227
+ }
228
+ }, [options.onError]);
229
+ const cancel = React.useCallback(() => {
230
+ const next = controllerRef.current?.cancel();
231
+ if (next !== undefined)
232
+ setState(next);
233
+ }, []);
234
+ return {
235
+ ...model,
236
+ copied,
237
+ status: publicStatus,
238
+ expiresInSeconds: richStatus.expiresInSeconds,
239
+ countdownLabel: richStatus.countdownLabel,
240
+ countdownPrefix: richStatus.countdownPrefix,
241
+ statusTitle: richStatus.title,
242
+ statusDetail: richStatus.detail,
243
+ waiting: richStatus.waiting,
244
+ reloadState,
245
+ retry,
246
+ refreshExpiredInvoice,
247
+ cancel,
248
+ copyInvoice,
249
+ openWallet
250
+ };
251
+ }
252
+ const CheckoutContext = React.createContext(null);
253
+ export function useCheckoutContext() {
254
+ const checkout = React.useContext(CheckoutContext);
255
+ if (checkout === null) {
256
+ throw new Error("useCheckoutContext must be used within CheckoutProvider.");
257
+ }
258
+ return checkout;
259
+ }
260
+ export function CheckoutProvider(props) {
261
+ const { children, ...options } = props;
262
+ const checkout = useCheckout(options);
263
+ const content = typeof children === "function" ? children(checkout) : children;
264
+ return React.createElement(CheckoutContext.Provider, { value: checkout }, content);
265
+ }
266
+ export function QRCode(props) {
267
+ const { invoice, encoder, width = 256, onError, ...divProps } = props;
268
+ const [svg, setSvg] = React.useState("");
269
+ React.useEffect(() => {
270
+ let cancelled = false;
271
+ createQrSvg(invoice, { encoder, width })
272
+ .then((nextSvg) => {
273
+ if (!cancelled)
274
+ setSvg(nextSvg);
275
+ })
276
+ .catch((error) => {
277
+ if (!cancelled)
278
+ onError?.(error);
279
+ });
280
+ return () => {
281
+ cancelled = true;
282
+ };
283
+ }, [invoice, encoder, width, onError]);
284
+ return React.createElement("div", {
285
+ ...divProps,
286
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.qr]: "",
287
+ dangerouslySetInnerHTML: {
288
+ __html: svg
289
+ }
290
+ });
291
+ }
292
+ export function CopyInvoiceButton(props) {
293
+ const { invoice, copyInvoice, clipboard, logger, onCopied, onError, onClick, copiedLabel = openReceiveCheckoutLabels.copied, ButtonComponent = "button", children, type = "button", ...buttonProps } = props;
294
+ const [copied, showCopied] = useOpenReceiveTransientValue(false);
295
+ return React.createElement(ButtonComponent, {
296
+ ...buttonProps,
297
+ type,
298
+ onClick: async (event) => {
299
+ onClick?.(event);
300
+ if (event.defaultPrevented)
301
+ return;
302
+ try {
303
+ if (copyInvoice === undefined) {
304
+ await copyInvoiceHelper({ invoice, clipboard, logger });
305
+ }
306
+ else {
307
+ await copyInvoice();
308
+ }
309
+ showCopied(true);
310
+ onCopied?.();
311
+ }
312
+ catch (error) {
313
+ onError?.(error);
314
+ }
315
+ }
316
+ }, children ?? (copied ? copiedLabel : openReceiveCheckoutLabels.copyInvoice));
317
+ }
318
+ export function OpenWalletButton(props) {
319
+ const { invoice, openWallet, open, logger, onOpened, onError, onClick, ButtonComponent = "button", children = openReceiveCheckoutLabels.openWallet, type = "button", ...buttonProps } = props;
320
+ return React.createElement(ButtonComponent, {
321
+ ...buttonProps,
322
+ type,
323
+ onClick: (event) => {
324
+ onClick?.(event);
325
+ if (event.defaultPrevented)
326
+ return;
327
+ try {
328
+ const uri = openWallet === undefined
329
+ ? openWalletHelper({ invoice, open, logger })
330
+ : openWallet();
331
+ onOpened?.(uri);
332
+ }
333
+ catch (error) {
334
+ onError?.(error);
335
+ }
336
+ }
337
+ }, children);
338
+ }
339
+ export function PaymentState(props) {
340
+ const { state = "pending", ...spanProps } = props;
341
+ return React.createElement("span", {
342
+ ...spanProps,
343
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.state]: state
344
+ }, state);
345
+ }
346
+ export function useTheme(options = {}) {
347
+ const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
348
+ const [theme, setThemeState] = React.useState(() => readOpenReceiveThemePreference({
349
+ storage: options.storage,
350
+ storageKey,
351
+ defaultTheme: options.defaultTheme
352
+ }));
353
+ const [systemDark, setSystemDark] = React.useState(() => globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false);
354
+ React.useEffect(() => {
355
+ const media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
356
+ if (media === undefined)
357
+ return undefined;
358
+ const update = () => setSystemDark(media.matches);
359
+ media.addEventListener?.("change", update);
360
+ return () => media.removeEventListener?.("change", update);
361
+ }, []);
362
+ const themeModel = createOpenReceiveThemeModel(theme, { systemDark });
363
+ const setTheme = React.useCallback((nextTheme) => {
364
+ setThemeState(nextTheme);
365
+ writeOpenReceiveThemePreference(nextTheme, {
366
+ storage: options.storage,
367
+ storageKey
368
+ });
369
+ }, [options.storage, storageKey]);
370
+ const toggleTheme = React.useCallback(() => {
371
+ setTheme(themeModel.nextTheme);
372
+ }, [setTheme, themeModel.nextTheme]);
373
+ return {
374
+ theme,
375
+ resolvedTheme: themeModel.resolvedTheme,
376
+ model: themeModel,
377
+ nextTheme: themeModel.nextTheme,
378
+ toggleLabel: themeModel.toggleLabel,
379
+ attributes: themeModel.attributes,
380
+ checkoutElementAttributes: themeModel.checkoutElementAttributes,
381
+ setTheme,
382
+ toggleTheme
383
+ };
384
+ }
385
+ export function ThemeToggle(props) {
386
+ const { theme, resolvedTheme, onThemeChange, ButtonComponent = "button", children, type = "button", onClick, ...buttonProps } = props;
387
+ const fallback = useTheme({
388
+ defaultTheme: theme
389
+ });
390
+ const activeTheme = resolvedTheme ?? fallback.resolvedTheme;
391
+ const themeModel = createOpenReceiveThemeModel(activeTheme);
392
+ const componentProps = {
393
+ ...buttonProps,
394
+ "aria-label": themeModel.toggleLabel,
395
+ className: joinClassNames("or-theme-toggle-button", `or-theme-toggle-${themeModel.resolvedTheme}`, buttonProps.className),
396
+ title: themeModel.toggleLabel,
397
+ type,
398
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.themeToggle]: "",
399
+ onClick: (event) => {
400
+ onClick?.(event);
401
+ if (event.defaultPrevented)
402
+ return;
403
+ onThemeChange?.(themeModel.nextTheme);
404
+ if (onThemeChange === undefined)
405
+ fallback.setTheme(themeModel.nextTheme);
406
+ }
407
+ };
408
+ const defaultChildren = React.createElement(React.Fragment, null, React.createElement("span", {
409
+ "aria-hidden": true,
410
+ className: "or-theme-toggle-track"
411
+ }, React.createElement("span", {
412
+ className: "or-theme-toggle-icon or-theme-toggle-icon-light"
413
+ })), React.createElement("span", {
414
+ className: "or-theme-toggle-label"
415
+ }, themeModel.toggleLabel));
416
+ return React.createElement(ButtonComponent, componentProps, children ?? defaultChildren);
417
+ }
418
+ export function ThemeScope(props) {
419
+ const { as: Element = "div", defaultTheme, themeStorageKey, storage, themeToggle = false, topbarClassName, themeToggleClassName, ButtonComponent, children, ...elementProps } = props;
420
+ const theme = useTheme({
421
+ defaultTheme,
422
+ storageKey: themeStorageKey,
423
+ storage
424
+ });
425
+ const scopedChildren = typeof children === "function" ? children(theme) : children;
426
+ return React.createElement(Element, {
427
+ ...elementProps,
428
+ ...theme.attributes
429
+ }, [
430
+ themeToggle
431
+ ? React.createElement("div", {
432
+ className: topbarClassName,
433
+ key: "openreceive-theme-scope-toggle"
434
+ }, React.createElement(ThemeToggle, {
435
+ className: themeToggleClassName,
436
+ theme: theme.theme,
437
+ resolvedTheme: theme.resolvedTheme,
438
+ onThemeChange: theme.setTheme,
439
+ ButtonComponent
440
+ }))
441
+ : null,
442
+ scopedChildren
443
+ ]);
444
+ }
445
+ export function WaitingState(props) {
446
+ const status = props.status ??
447
+ createCheckoutStatusModel({
448
+ phase: props.phase,
449
+ waiting: props.waiting ?? false
450
+ });
451
+ return React.createElement("div", {
452
+ className: joinClassNames("or-payment-status", props.className)
453
+ }, status.waiting
454
+ ? React.createElement("span", {
455
+ className: "or-spinner",
456
+ "aria-hidden": "true"
457
+ })
458
+ : null, React.createElement("div", null, React.createElement("strong", null, props.statusTitle ?? status.title), React.createElement("span", null, props.statusDetail ?? status.detail)));
459
+ }
460
+ export function PaymentWizard(props) {
461
+ const countryStorageKey = props.countryStorageKey ?? OPENRECEIVE_COUNTRY_STORAGE_KEY;
462
+ const [selection, setSelection] = React.useState(() => createOpenReceivePaymentWizardController({
463
+ storageKey: countryStorageKey,
464
+ defaultCountryCode: getOpenReceiveDefaultCountryCode()
465
+ }).getSelection());
466
+ const [activeTutorial, setActiveTutorial] = React.useState(null);
467
+ const updateWizardSelection = React.useCallback((apply) => {
468
+ setSelection((current) => apply(createOpenReceivePaymentWizardController({
469
+ selection: current,
470
+ storageKey: countryStorageKey
471
+ })));
472
+ }, [countryStorageKey]);
473
+ const model = createOpenReceivePaymentWizardModel(selection);
474
+ const { wizard } = model;
475
+ const routeAssetDisplays = createOpenReceiveWizardRouteAssetDisplays(model.routeAssets, {
476
+ selectedRoute: model.selectedRoute
477
+ });
478
+ const routeDisplays = createOpenReceiveWizardRouteDisplays(wizard.routes);
479
+ const showRoutePicker = routeAssetDisplays.length > 0 &&
480
+ (model.selectedRoute === null || routeDisplays.length === 0);
481
+ const activeTutorialProvider = activeTutorial === null
482
+ ? undefined
483
+ : routeDisplays
484
+ .flatMap((route) => route.providers)
485
+ .find((provider) => provider.id === activeTutorial.providerId);
486
+ return React.createElement("div", {
487
+ className: joinClassNames("or-wizard", props.className)
488
+ }, selection.selectedMethod === null
489
+ ? React.createElement(React.Fragment, null, React.createElement("div", {
490
+ className: "or-wizard-header"
491
+ }, React.createElement("div", null, React.createElement("h2", null, openReceiveCheckoutLabels.wizardTitle), React.createElement("p", null, openReceiveCheckoutLabels.wizardSubtitle))), React.createElement("div", {
492
+ className: "or-method-grid"
493
+ }, openReceivePaymentMethods.map((method) => React.createElement("button", {
494
+ key: method.id,
495
+ onClick: () => {
496
+ updateWizardSelection((controller) => controller.selectMethod(method.id));
497
+ },
498
+ type: "button"
499
+ }, React.createElement("img", { alt: "", src: getOpenReceivePaymentMethodIcon(method.id) }), React.createElement("span", null, method.title), React.createElement("small", null, method.detail)))))
500
+ : null, selection.selectedMethod === null
501
+ ? null
502
+ : renderWizardBreadcrumbs({
503
+ method: selection.selectedMethod,
504
+ selectedRoute: model.selectedRoute,
505
+ routeAssets: routeAssetDisplays,
506
+ onChangeMethod: () => {
507
+ updateWizardSelection((controller) => controller.changeMethod());
508
+ },
509
+ onChangeRoute: () => {
510
+ updateWizardSelection((controller) => controller.update({ type: "change_route" }));
511
+ }
512
+ }), showRoutePicker && selection.selectedMethod === "bitcoin"
513
+ ? renderRoutePicker({
514
+ assets: routeAssetDisplays,
515
+ method: "bitcoin",
516
+ onSelectRoute: (route) => {
517
+ updateWizardSelection((controller) => controller.selectRoute(route));
518
+ }
519
+ })
520
+ : null, showRoutePicker && selection.selectedMethod === "crypto"
521
+ ? renderRoutePicker({
522
+ assets: routeAssetDisplays,
523
+ method: "crypto",
524
+ onSelectRoute: (route) => {
525
+ updateWizardSelection((controller) => controller.selectRoute(route));
526
+ }
527
+ })
528
+ : null, selection.selectedMethod === null
529
+ ? null
530
+ : React.createElement("div", {
531
+ className: "or-wizard-results"
532
+ }, routeDisplays.length === 0
533
+ ? React.createElement("p", {
534
+ className: "or-wizard-empty"
535
+ }, getOpenReceiveWizardEmptyMessage(selection.selectedMethod))
536
+ : routeDisplays.map((route) => React.createElement("section", {
537
+ className: "or-wizard-route",
538
+ key: route.key
539
+ }, React.createElement("div", {
540
+ className: "or-wizard-route-heading"
541
+ }, React.createElement("div", null, React.createElement("h3", null, route.title, wizard.selectedRail === null
542
+ ? null
543
+ : renderCountrySelect({
544
+ countries: model.countryDisplays,
545
+ selectedCountryCode: selection.selectedCountryCode,
546
+ onSelectCountry: (countryCode) => {
547
+ updateWizardSelection((controller) => controller.selectCountry(countryCode));
548
+ }
549
+ })))), React.createElement("div", {
550
+ className: "or-provider-grid"
551
+ }, route.providers.map((provider) => React.createElement("article", {
552
+ className: provider.recommended
553
+ ? "or-provider-card recommended"
554
+ : "or-provider-card",
555
+ key: provider.id
556
+ }, React.createElement("div", {
557
+ className: "or-provider-heading"
558
+ }, React.createElement("img", {
559
+ alt: "",
560
+ src: provider.icon
561
+ }), React.createElement("h4", null, provider.name), provider.recommendedLabel === null
562
+ ? null
563
+ : React.createElement("span", null, provider.recommendedLabel)), React.createElement("p", {
564
+ className: "or-provider-kind"
565
+ }, provider.kind), React.createElement("div", {
566
+ className: "or-provider-actions"
567
+ }, renderProviderOpenAction(provider, () => setActiveTutorial({
568
+ providerId: provider.id,
569
+ index: 0,
570
+ copied: false
571
+ }))))))))), activeTutorialProvider === undefined || activeTutorial === null
572
+ ? null
573
+ : renderProviderTutorialModal({
574
+ provider: activeTutorialProvider,
575
+ index: activeTutorial.index,
576
+ copied: activeTutorial.copied,
577
+ onClose: () => setActiveTutorial(null),
578
+ onCopy: async () => {
579
+ try {
580
+ await copyInvoiceHelper({
581
+ invoice: props.invoice,
582
+ logger: props.logger,
583
+ logContext: props.logContext
584
+ });
585
+ globalThis.dispatchEvent?.(createCheckoutProviderCopyEvent(activeTutorialProvider.id));
586
+ setActiveTutorial({
587
+ providerId: activeTutorialProvider.id,
588
+ index: 0,
589
+ copied: true
590
+ });
591
+ }
592
+ catch (error) {
593
+ props.onError?.(error);
594
+ }
595
+ },
596
+ onStep: (index) => setActiveTutorial({
597
+ providerId: activeTutorialProvider.id,
598
+ index,
599
+ copied: activeTutorial.copied
600
+ })
601
+ }));
602
+ }
603
+ function renderWizardBreadcrumbs(options) {
604
+ const method = openReceivePaymentMethods.find((candidate) => candidate.id === options.method);
605
+ const methodLabel = method?.title ?? openReceiveCheckoutLabels.paymentMethod;
606
+ const routeLabel = options.selectedRoute === null || options.routeAssets.length <= 1
607
+ ? null
608
+ : options.routeAssets.find((asset) => asset.id === options.selectedRoute)?.label ?? options.selectedRoute;
609
+ return React.createElement("nav", {
610
+ "aria-label": "Payment path",
611
+ className: "or-wizard-breadcrumbs"
612
+ }, React.createElement("button", {
613
+ className: "or-wizard-breadcrumb",
614
+ onClick: options.onChangeMethod,
615
+ type: "button"
616
+ }, openReceiveCheckoutLabels.paymentMethod), React.createElement("span", { "aria-hidden": "true" }, "/"), routeLabel === null
617
+ ? React.createElement("span", { className: "or-wizard-breadcrumb-current" }, methodLabel)
618
+ : React.createElement(React.Fragment, null, React.createElement("button", {
619
+ className: "or-wizard-breadcrumb",
620
+ onClick: options.onChangeRoute,
621
+ type: "button"
622
+ }, methodLabel), React.createElement("span", { "aria-hidden": "true" }, "/"), React.createElement("span", { className: "or-wizard-breadcrumb-current" }, routeLabel)));
623
+ }
624
+ function renderProviderOpenAction(provider, onOpenTutorial) {
625
+ if (provider.tutorials.length === 0) {
626
+ return React.createElement("a", {
627
+ href: provider.url,
628
+ rel: "noreferrer",
629
+ target: "_blank"
630
+ }, provider.openLabel);
631
+ }
632
+ return React.createElement("button", {
633
+ className: "or-provider-open",
634
+ onClick: onOpenTutorial,
635
+ type: "button"
636
+ }, provider.openLabel);
637
+ }
638
+ function renderProviderTutorialModal(options) {
639
+ const { provider } = options;
640
+ if (provider.tutorials.length === 0)
641
+ return null;
642
+ const totalSteps = provider.tutorials.length + 1;
643
+ const stepIndex = Math.max(0, Math.min(provider.tutorials.length, options.index));
644
+ const tutorial = stepIndex === 0 ? undefined : provider.tutorials[stepIndex - 1];
645
+ const previousIndex = Math.max(0, stepIndex - 1);
646
+ const nextIndex = Math.min(provider.tutorials.length, stepIndex + 1);
647
+ const isFinalStep = stepIndex === provider.tutorials.length;
648
+ return React.createElement("div", {
649
+ "aria-label": `${openReceiveCheckoutLabels.tutorialTitlePrefix} ${provider.name}`,
650
+ "aria-modal": true,
651
+ className: "or-tutorial-modal",
652
+ onClick: (event) => {
653
+ if (event.target === event.currentTarget)
654
+ options.onClose();
655
+ },
656
+ onKeyDown: (event) => {
657
+ if (event.key === "Escape")
658
+ options.onClose();
659
+ },
660
+ role: "dialog",
661
+ tabIndex: -1
662
+ }, React.createElement("div", {
663
+ className: "or-tutorial-dialog"
664
+ }, React.createElement("div", {
665
+ className: "or-tutorial-header"
666
+ }, React.createElement("div", {
667
+ className: "or-tutorial-title"
668
+ }, React.createElement("img", {
669
+ alt: "",
670
+ className: "or-tutorial-header-logo",
671
+ src: provider.icon
672
+ }), React.createElement("h3", null, `${openReceiveCheckoutLabels.tutorialTitlePrefix} ${provider.name}`)), React.createElement("button", {
673
+ "aria-label": "Close",
674
+ className: "or-tutorial-close",
675
+ onClick: options.onClose,
676
+ type: "button"
677
+ }, "X")), stepIndex === 0
678
+ ? React.createElement("div", {
679
+ className: "or-tutorial-intro"
680
+ }, React.createElement("img", {
681
+ alt: "",
682
+ className: "or-tutorial-provider-logo",
683
+ src: provider.icon
684
+ }), React.createElement("p", null, `${openReceiveCheckoutLabels.tutorialIntroPrefix} ${provider.name}.`), React.createElement("p", null, openReceiveCheckoutLabels.tutorialIntroCopy), React.createElement("button", {
685
+ className: "or-tutorial-copy",
686
+ onClick: () => void options.onCopy(),
687
+ type: "button"
688
+ }, openReceiveCheckoutLabels.copyInvoice), options.copied
689
+ ? React.createElement("p", {
690
+ className: "or-tutorial-copy-message"
691
+ }, openReceiveCheckoutLabels.tutorialCopiedContinue)
692
+ : null)
693
+ : React.createElement(React.Fragment, null, React.createElement("div", {
694
+ className: "or-tutorial-frame"
695
+ }, React.createElement("img", {
696
+ alt: tutorial?.caption ?? "",
697
+ className: "or-tutorial-image",
698
+ src: tutorial?.image ?? ""
699
+ })), React.createElement("p", {
700
+ className: "or-tutorial-caption"
701
+ }, tutorial?.caption ?? "")), React.createElement("div", {
702
+ "aria-hidden": "true",
703
+ className: "or-tutorial-steps"
704
+ }, Array.from({ length: totalSteps }, (_, index) => React.createElement("span", {
705
+ className: index === stepIndex
706
+ ? "or-tutorial-step active"
707
+ : "or-tutorial-step",
708
+ key: index
709
+ }))), React.createElement("p", {
710
+ className: "or-tutorial-progress"
711
+ }, `Step ${stepIndex + 1} of ${totalSteps}`), React.createElement("div", {
712
+ className: "or-tutorial-controls"
713
+ }, React.createElement("button", {
714
+ disabled: stepIndex === 0,
715
+ onClick: () => options.onStep(previousIndex),
716
+ type: "button"
717
+ }, "Back"), React.createElement("button", {
718
+ onClick: () => {
719
+ if (isFinalStep) {
720
+ options.onClose();
721
+ return;
722
+ }
723
+ options.onStep(nextIndex);
724
+ },
725
+ type: "button"
726
+ }, isFinalStep ? openReceiveCheckoutLabels.tutorialExit : "Next"))));
727
+ }
728
+ export function InvoiceSummary(props) {
729
+ const { amountLabel, fiatLabel, status, PaymentStateComponent = PaymentState, classNames, className, ...divProps } = props;
730
+ return React.createElement("div", {
731
+ ...divProps,
732
+ className,
733
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.meta]: ""
734
+ }, amountLabel === undefined
735
+ ? null
736
+ : React.createElement("span", {
737
+ className: classNames?.amount
738
+ }, amountLabel), fiatLabel === undefined
739
+ ? null
740
+ : React.createElement("span", {
741
+ className: classNames?.fiat
742
+ }, fiatLabel), status === undefined
743
+ ? null
744
+ : React.createElement(PaymentStateComponent, {
745
+ state: status,
746
+ className: classNames?.paymentState
747
+ }));
748
+ }
749
+ export function Checkout(props) {
750
+ const { invoice, qrEncoder, logger, onError, lookupInvoice, lookupUrl, refreshInvoice, refreshUrl, refreshHeaders, refreshIdempotencyKey, refreshReason, onState, onPaid, onStartOver, polling, paymentWizard = true, themeSwitcher = false, defaultTheme, themeStorageKey, countryStorageKey, components, classNames, children, className, ...sectionProps } = props;
751
+ const checkoutModel = useCheckout({
752
+ invoice,
753
+ logger,
754
+ onError,
755
+ lookupInvoice,
756
+ lookupUrl,
757
+ refreshInvoice,
758
+ refreshUrl,
759
+ refreshHeaders,
760
+ refreshIdempotencyKey,
761
+ refreshReason,
762
+ onState,
763
+ onPaid,
764
+ polling
765
+ });
766
+ const theme = useTheme({
767
+ defaultTheme,
768
+ storageKey: themeStorageKey
769
+ });
770
+ const QRCodeComponent = components?.QRCode ?? QRCode;
771
+ const InvoiceSummaryComponent = components?.InvoiceSummary ?? InvoiceSummary;
772
+ const CopyButton = components?.CopyButton ?? CopyInvoiceButton;
773
+ const ButtonComponent = components?.Button;
774
+ const PaymentStateComponent = components?.PaymentState ?? PaymentState;
775
+ const customChildren = typeof children === "function" ? children(checkoutModel) : children;
776
+ const expired = checkoutModel.status === "expired";
777
+ const startOver = () => {
778
+ if (onStartOver !== undefined) {
779
+ onStartOver();
780
+ return;
781
+ }
782
+ void checkoutModel.refreshExpiredInvoice().catch((error) => {
783
+ onError?.(error);
784
+ });
785
+ };
786
+ return React.createElement("section", {
787
+ ...sectionProps,
788
+ className: joinClassNames(className, classNames?.root),
789
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.root]: "",
790
+ ...theme.attributes
791
+ }, customChildren === undefined
792
+ ? [
793
+ themeSwitcher
794
+ ? React.createElement(ThemeToggle, {
795
+ key: "theme",
796
+ className: classNames?.themeToggle,
797
+ theme: theme.theme,
798
+ resolvedTheme: theme.resolvedTheme,
799
+ onThemeChange: theme.setTheme,
800
+ ButtonComponent
801
+ })
802
+ : null,
803
+ expired
804
+ ? null
805
+ : React.createElement(QRCodeComponent, {
806
+ key: "qr",
807
+ invoice: checkoutModel.invoice,
808
+ encoder: qrEncoder,
809
+ onError,
810
+ className: classNames?.qr,
811
+ style: {
812
+ aspectRatio: "1",
813
+ justifySelf: "center",
814
+ maxWidth: 420,
815
+ width: "min(100%, 420px)"
816
+ }
817
+ }),
818
+ React.createElement(WaitingState, {
819
+ key: "waiting",
820
+ waiting: checkoutModel.waiting,
821
+ statusTitle: checkoutModel.statusTitle,
822
+ statusDetail: checkoutModel.statusDetail,
823
+ className: classNames?.waiting
824
+ }),
825
+ checkoutModel.countdownLabel === undefined
826
+ ? null
827
+ : React.createElement("div", {
828
+ key: "countdown",
829
+ className: joinClassNames("or-countdown", classNames?.countdown)
830
+ }, checkoutModel.countdownPrefix, " ", React.createElement("strong", null, checkoutModel.countdownLabel)),
831
+ React.createElement(InvoiceSummaryComponent, {
832
+ key: "summary",
833
+ amountLabel: checkoutModel.amountLabel,
834
+ fiatLabel: checkoutModel.fiatLabel,
835
+ status: checkoutModel.status,
836
+ PaymentStateComponent,
837
+ className: classNames?.summary,
838
+ classNames
839
+ }),
840
+ React.createElement("div", {
841
+ key: "actions",
842
+ className: classNames?.actions,
843
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.actions]: ""
844
+ }, expired
845
+ ? React.createElement(ButtonComponent ?? "button", {
846
+ type: "button",
847
+ onClick: startOver
848
+ }, openReceiveCheckoutLabels.startOver)
849
+ : React.createElement(CopyButton, {
850
+ invoice: checkoutModel.invoice,
851
+ copyInvoice: checkoutModel.copyInvoice,
852
+ onError,
853
+ logger,
854
+ ButtonComponent,
855
+ className: classNames?.copyButton
856
+ })),
857
+ paymentWizard && !expired
858
+ ? React.createElement(PaymentWizard, {
859
+ key: "wizard",
860
+ invoice: checkoutModel.invoice,
861
+ className: classNames?.wizard,
862
+ logger,
863
+ onError,
864
+ countryStorageKey,
865
+ logContext: getCheckoutLogContext({
866
+ invoice_id: checkoutModel.invoice_id,
867
+ payment_hash: checkoutModel.payment_hash,
868
+ amount_msats: checkoutModel.amount_msats
869
+ })
870
+ })
871
+ : null
872
+ ]
873
+ : customChildren);
874
+ }
875
+ function getCheckoutLogContext(data) {
876
+ return {
877
+ ...(data.invoice_id === undefined ? {} : { invoice_id: data.invoice_id }),
878
+ ...(data.payment_hash === undefined ? {} : { payment_hash: data.payment_hash }),
879
+ ...(data.amount_msats === undefined ? {} : { amount_msats: data.amount_msats }),
880
+ ...(data.transaction_state === undefined
881
+ ? {}
882
+ : { transaction_state: data.transaction_state }),
883
+ ...(data.workflow_state === undefined
884
+ ? {}
885
+ : { workflow_state: data.workflow_state })
886
+ };
887
+ }
888
+ function renderCountrySelect(options) {
889
+ return React.createElement("label", {
890
+ className: "or-country-select"
891
+ }, React.createElement("span", null, openReceiveCheckoutLabels.chooseCountry), React.createElement("select", {
892
+ value: options.selectedCountryCode,
893
+ onChange: (event) => {
894
+ options.onSelectCountry(event.currentTarget.value);
895
+ }
896
+ }, options.countries.map((country) => React.createElement("option", {
897
+ key: country.code,
898
+ value: country.code
899
+ }, country.label))));
900
+ }
901
+ function renderRoutePicker(options) {
902
+ return React.createElement("div", {
903
+ className: `or-route-picker or-route-picker-${options.method}`
904
+ }, options.assets.map((asset) => {
905
+ return React.createElement("button", {
906
+ className: asset.selected ? "selected" : "",
907
+ key: asset.id,
908
+ onClick: () => options.onSelectRoute(asset.id),
909
+ type: "button"
910
+ }, React.createElement("img", {
911
+ alt: "",
912
+ src: asset.icon
913
+ }), React.createElement("span", null, asset.label), React.createElement("small", null, asset.subtitle));
914
+ }));
915
+ }
916
+ function joinClassNames(...values) {
917
+ const joined = values.filter(Boolean).join(" ");
918
+ return joined === "" ? undefined : joined;
919
+ }
@@ -0,0 +1 @@
1
+ @import "@openreceive/browser/styles.css";
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@openreceive/react",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@openreceive/browser": "0.1.0"
7
+ },
8
+ "peerDependencies": {
9
+ "react": "*"
10
+ },
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./styles.css": "./dist/styles.css"
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ]
21
+ }