@naculus/connect-native 0.8.1 → 0.9.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.
@@ -0,0 +1,954 @@
1
+ // src/ui/AuthorizationConsentNative.tsx
2
+ import {
3
+ describeAuthorization
4
+ } from "@naculus/connect-appkit-core";
5
+ import { Image, ScrollView, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
6
+
7
+ // src/ui/parts.tsx
8
+ import { useState } from "react";
9
+ import { Pressable, StyleSheet, Text, View } from "react-native";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ function toneColors(theme, tone) {
12
+ if (tone === "success")
13
+ return {
14
+ backgroundColor: theme.successBackground,
15
+ borderColor: theme.successBorder,
16
+ color: theme.successForeground
17
+ };
18
+ if (tone === "danger")
19
+ return {
20
+ backgroundColor: theme.dangerBackground,
21
+ borderColor: theme.dangerBorder,
22
+ color: theme.dangerForeground
23
+ };
24
+ if (tone === "warning")
25
+ return {
26
+ backgroundColor: theme.warningBackground,
27
+ borderColor: theme.warningBorder,
28
+ color: theme.warningForeground
29
+ };
30
+ return {
31
+ backgroundColor: theme.muted,
32
+ borderColor: theme.border,
33
+ color: theme.foreground
34
+ };
35
+ }
36
+ function Glyph({
37
+ children,
38
+ color
39
+ }) {
40
+ return /* @__PURE__ */ jsx(
41
+ Text,
42
+ {
43
+ accessible: false,
44
+ importantForAccessibility: "no",
45
+ accessibilityElementsHidden: true,
46
+ style: [styles.glyph, { color }],
47
+ children
48
+ }
49
+ );
50
+ }
51
+ function ActionButton({
52
+ label,
53
+ onPress,
54
+ theme,
55
+ variant = "primary",
56
+ disabled = false,
57
+ busy = false
58
+ }) {
59
+ const primary = variant === "primary";
60
+ return /* @__PURE__ */ jsx(
61
+ Pressable,
62
+ {
63
+ accessibilityRole: "button",
64
+ accessibilityLabel: label,
65
+ accessibilityState: { disabled, busy },
66
+ disabled,
67
+ onPress,
68
+ style: ({ pressed }) => [
69
+ styles.button,
70
+ {
71
+ backgroundColor: primary ? theme.primary : theme.background,
72
+ borderColor: primary ? theme.primary : theme.border,
73
+ opacity: disabled ? 0.55 : pressed ? 0.8 : 1
74
+ }
75
+ ],
76
+ children: /* @__PURE__ */ jsx(
77
+ Text,
78
+ {
79
+ style: [
80
+ styles.buttonLabel,
81
+ { color: primary ? theme.primaryForeground : theme.foreground }
82
+ ],
83
+ children: label
84
+ }
85
+ )
86
+ }
87
+ );
88
+ }
89
+ function CopyableValue({
90
+ value,
91
+ display,
92
+ onCopy,
93
+ theme,
94
+ color = theme.foreground
95
+ }) {
96
+ const [copied, setCopied] = useState(false);
97
+ return /* @__PURE__ */ jsxs(View, { style: styles.copyRow, children: [
98
+ /* @__PURE__ */ jsx(
99
+ Text,
100
+ {
101
+ accessibilityLabel: value,
102
+ numberOfLines: 1,
103
+ ellipsizeMode: "middle",
104
+ style: [styles.mono, { color }],
105
+ children: display ?? value
106
+ }
107
+ ),
108
+ onCopy && /* @__PURE__ */ jsx(
109
+ Pressable,
110
+ {
111
+ accessibilityRole: "button",
112
+ accessibilityLabel: copied ? `Copied ${value}` : `Copy ${value}`,
113
+ hitSlop: 6,
114
+ onPress: () => {
115
+ onCopy(value);
116
+ setCopied(true);
117
+ },
118
+ style: ({ pressed }) => [
119
+ styles.iconButton,
120
+ { borderColor: theme.border, opacity: pressed ? 0.7 : 1 }
121
+ ],
122
+ children: /* @__PURE__ */ jsx(Glyph, { color: theme.foreground, children: copied ? "\u2713" : "\u29C9" })
123
+ }
124
+ )
125
+ ] });
126
+ }
127
+ function Callout({
128
+ tone,
129
+ glyph,
130
+ theme,
131
+ role,
132
+ children
133
+ }) {
134
+ const colors = toneColors(theme, tone);
135
+ return /* @__PURE__ */ jsxs(
136
+ View,
137
+ {
138
+ accessibilityRole: role,
139
+ style: [
140
+ styles.callout,
141
+ {
142
+ backgroundColor: colors.backgroundColor,
143
+ borderColor: colors.borderColor
144
+ }
145
+ ],
146
+ children: [
147
+ /* @__PURE__ */ jsx(Glyph, { color: colors.color, children: glyph }),
148
+ /* @__PURE__ */ jsx(Text, { style: [styles.calloutText, { color: colors.color }], children })
149
+ ]
150
+ }
151
+ );
152
+ }
153
+ var styles = StyleSheet.create({
154
+ glyph: { fontSize: 16, lineHeight: 20, fontWeight: "700" },
155
+ button: {
156
+ flex: 1,
157
+ minHeight: 48,
158
+ minWidth: 44,
159
+ borderRadius: 10,
160
+ borderWidth: 1,
161
+ alignItems: "center",
162
+ justifyContent: "center",
163
+ paddingHorizontal: 16
164
+ },
165
+ buttonLabel: { fontSize: 16, fontWeight: "600" },
166
+ copyRow: { flexDirection: "row", alignItems: "center", gap: 6 },
167
+ mono: { flexShrink: 1, fontFamily: "monospace", fontSize: 13 },
168
+ iconButton: {
169
+ width: 32,
170
+ height: 32,
171
+ borderRadius: 8,
172
+ borderWidth: 1,
173
+ alignItems: "center",
174
+ justifyContent: "center"
175
+ },
176
+ callout: {
177
+ flexDirection: "row",
178
+ gap: 8,
179
+ borderWidth: 1,
180
+ borderRadius: 10,
181
+ padding: 12
182
+ },
183
+ calloutText: { flex: 1, fontSize: 14, lineHeight: 20 },
184
+ label: { fontSize: 12, fontWeight: "500" },
185
+ heading: { fontSize: 18, fontWeight: "600" },
186
+ chip: {
187
+ borderWidth: 1,
188
+ borderRadius: 999,
189
+ paddingHorizontal: 10,
190
+ paddingVertical: 4
191
+ },
192
+ chipText: { fontSize: 12 }
193
+ });
194
+
195
+ // src/ui/theme.tsx
196
+ import { createContext, useContext } from "react";
197
+ import { useColorScheme } from "react-native";
198
+ import { jsx as jsx2 } from "react/jsx-runtime";
199
+ var lightPaymentTheme = {
200
+ scheme: "light",
201
+ background: "#ffffff",
202
+ // --background 0 0% 100%
203
+ foreground: "#020817",
204
+ // --foreground 222.2 84% 4.9%
205
+ card: "#ffffff",
206
+ // --card
207
+ border: "#e2e8f0",
208
+ // --border 214.3 31.8% 91.4%
209
+ muted: "#f1f5f9",
210
+ // --muted 210 40% 96.1%
211
+ mutedForeground: "#475569",
212
+ // --muted-foreground hue, one step darker
213
+ primary: "#0f172a",
214
+ // --primary 222.2 47.4% 11.2%
215
+ primaryForeground: "#f8fafc",
216
+ // --primary-foreground 210 40% 98%
217
+ successBackground: "#ecfdf5",
218
+ successBorder: "#047857",
219
+ successForeground: "#064e3b",
220
+ dangerBackground: "#fef2f2",
221
+ dangerBorder: "#ef4444",
222
+ // --destructive 0 84.2% 60.2%
223
+ dangerForeground: "#991b1b",
224
+ warningBackground: "#fffbeb",
225
+ warningBorder: "#b45309",
226
+ warningForeground: "#451a03"
227
+ };
228
+ var darkPaymentTheme = {
229
+ scheme: "dark",
230
+ background: "#020817",
231
+ // .dark --background
232
+ foreground: "#f8fafc",
233
+ // .dark --foreground
234
+ card: "#020817",
235
+ // .dark --card
236
+ border: "#1e293b",
237
+ // .dark --border 217.2 32.6% 17.5%
238
+ muted: "#1e293b",
239
+ // .dark --muted
240
+ mutedForeground: "#94a3b8",
241
+ // .dark --muted-foreground 215 20.2% 65.1%
242
+ primary: "#f8fafc",
243
+ // .dark --primary
244
+ primaryForeground: "#0f172a",
245
+ // .dark --primary-foreground
246
+ successBackground: "#022c22",
247
+ successBorder: "#10b981",
248
+ successForeground: "#d1fae5",
249
+ dangerBackground: "#450a0a",
250
+ dangerBorder: "#f87171",
251
+ dangerForeground: "#fecaca",
252
+ warningBackground: "#451a03",
253
+ warningBorder: "#f59e0b",
254
+ warningForeground: "#fef3c7"
255
+ };
256
+ var ThemeContext = createContext(void 0);
257
+ function NativePaymentThemeProvider({
258
+ theme,
259
+ children
260
+ }) {
261
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value: theme, children });
262
+ }
263
+ function useNativePaymentTheme(override) {
264
+ const provided = useContext(ThemeContext);
265
+ const scheme = useColorScheme();
266
+ return override ?? provided ?? (scheme === "dark" ? darkPaymentTheme : lightPaymentTheme);
267
+ }
268
+
269
+ // src/ui/AuthorizationConsentNative.tsx
270
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
271
+ function AuthorizationConsentNative({
272
+ authorization,
273
+ assets = {},
274
+ locale,
275
+ now,
276
+ requester,
277
+ preview,
278
+ onApprove,
279
+ onDecline,
280
+ busy = false,
281
+ onCopy,
282
+ theme: themeProp
283
+ }) {
284
+ const theme = useNativePaymentTheme(themeProp);
285
+ const description = "expiry" in authorization ? authorization : describeAuthorization(authorization, { assets, locale, now });
286
+ const muted = { color: theme.mutedForeground };
287
+ return /* @__PURE__ */ jsxs2(View2, { style: [styles2.root, { backgroundColor: theme.background }], children: [
288
+ /* @__PURE__ */ jsxs2(ScrollView, { contentContainerStyle: styles2.content, children: [
289
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.requester, children: [
290
+ requester.icon ? /* @__PURE__ */ jsx3(
291
+ Image,
292
+ {
293
+ accessibilityIgnoresInvertColors: true,
294
+ source: { uri: requester.icon },
295
+ style: styles2.avatar
296
+ }
297
+ ) : /* @__PURE__ */ jsx3(
298
+ View2,
299
+ {
300
+ importantForAccessibility: "no",
301
+ style: [styles2.avatar, { backgroundColor: theme.muted }],
302
+ children: /* @__PURE__ */ jsx3(Text2, { style: [styles2.initial, { color: theme.foreground }], children: requester.name.slice(0, 1).toUpperCase() })
303
+ }
304
+ ),
305
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.flexShrink, children: [
306
+ /* @__PURE__ */ jsxs2(
307
+ Text2,
308
+ {
309
+ accessibilityRole: "header",
310
+ style: [styles.heading, { color: theme.foreground }],
311
+ children: [
312
+ "Allow ",
313
+ requester.name,
314
+ " to pay?"
315
+ ]
316
+ }
317
+ ),
318
+ requester.origin && /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: requester.origin })
319
+ ] })
320
+ ] }),
321
+ description.warnings.map((warning) => /* @__PURE__ */ jsx3(
322
+ Callout,
323
+ {
324
+ tone: "warning",
325
+ glyph: "\u26A0",
326
+ role: "alert",
327
+ theme,
328
+ children: /* @__PURE__ */ jsx3(Text2, { style: styles2.strong, children: warning })
329
+ },
330
+ warning
331
+ )),
332
+ preview && /* @__PURE__ */ jsxs2(
333
+ Callout,
334
+ {
335
+ tone: preview.allowed ? "success" : "danger",
336
+ glyph: preview.allowed ? "\u2713" : "\u2715",
337
+ theme,
338
+ children: [
339
+ /* @__PURE__ */ jsxs2(Text2, { style: styles2.strong, children: [
340
+ preview.allowed ? "Allowed" : "Blocked",
341
+ "."
342
+ ] }),
343
+ " ",
344
+ preview.sentence
345
+ ]
346
+ }
347
+ ),
348
+ description.grants.map((grant) => /* @__PURE__ */ jsxs2(
349
+ View2,
350
+ {
351
+ style: [
352
+ styles2.card,
353
+ { backgroundColor: theme.card, borderColor: theme.border }
354
+ ],
355
+ children: [
356
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.assetLabel, { color: theme.foreground }], children: grant.assetLabel }),
357
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limits, children: [
358
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limit, children: [
359
+ /* @__PURE__ */ jsx3(Text2, { style: [styles.label, muted], children: "Per payment" }),
360
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.amount, { color: theme.foreground }], children: grant.perPayment.formatted })
361
+ ] }),
362
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limit, children: [
363
+ /* @__PURE__ */ jsx3(Text2, { style: [styles.label, muted], children: "Total limit" }),
364
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.amount, { color: theme.foreground }], children: grant.total.formatted })
365
+ ] })
366
+ ] }),
367
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Recipients" }),
368
+ grant.recipients.length ? grant.recipients.map((recipient) => /* @__PURE__ */ jsx3(
369
+ CopyableValue,
370
+ {
371
+ value: recipient.full,
372
+ display: recipient.shortened,
373
+ onCopy,
374
+ theme
375
+ },
376
+ recipient.full
377
+ )) : /* @__PURE__ */ jsx3(Text2, { style: [styles2.strong, { color: theme.dangerForeground }], children: "No recipient restriction is displayed" }),
378
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Payment count" }),
379
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: grant.count === null ? "No count limit" : `Up to ${grant.count}` }),
380
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Payment methods" }),
381
+ /* @__PURE__ */ jsx3(View2, { style: styles2.chips, children: grant.rails.map((rail) => /* @__PURE__ */ jsx3(
382
+ View2,
383
+ {
384
+ style: [
385
+ styles.chip,
386
+ { backgroundColor: theme.muted, borderColor: theme.border }
387
+ ],
388
+ children: /* @__PURE__ */ jsx3(Text2, { style: [styles.chipText, { color: theme.foreground }], children: rail })
389
+ },
390
+ rail
391
+ )) })
392
+ ]
393
+ },
394
+ `${grant.asset}:${grant.recipients.map((r) => r.full).join(",")}:${grant.perPayment.formatted}:${grant.total.formatted}`
395
+ )),
396
+ /* @__PURE__ */ jsxs2(View2, { style: [styles2.expiry, { backgroundColor: theme.muted }], children: [
397
+ /* @__PURE__ */ jsxs2(Text2, { style: [styles2.strong, { color: theme.foreground }], children: [
398
+ "Expires ",
399
+ description.expiry.relative
400
+ ] }),
401
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: description.expiry.absolute })
402
+ ] })
403
+ ] }),
404
+ /* @__PURE__ */ jsxs2(
405
+ View2,
406
+ {
407
+ testID: "payment-actions",
408
+ style: [
409
+ styles2.footer,
410
+ { borderTopColor: theme.border, backgroundColor: theme.background }
411
+ ],
412
+ children: [
413
+ /* @__PURE__ */ jsx3(
414
+ ActionButton,
415
+ {
416
+ label: "Decline",
417
+ variant: "secondary",
418
+ onPress: onDecline,
419
+ theme
420
+ }
421
+ ),
422
+ /* @__PURE__ */ jsx3(
423
+ ActionButton,
424
+ {
425
+ label: busy ? "Approving\u2026" : "Approve",
426
+ onPress: onApprove,
427
+ disabled: busy,
428
+ busy,
429
+ theme
430
+ }
431
+ )
432
+ ]
433
+ }
434
+ )
435
+ ] });
436
+ }
437
+ var styles2 = StyleSheet2.create({
438
+ root: { flex: 1 },
439
+ content: { padding: 16, gap: 12 },
440
+ requester: { flexDirection: "row", alignItems: "center", gap: 12 },
441
+ avatar: {
442
+ width: 44,
443
+ height: 44,
444
+ borderRadius: 12,
445
+ alignItems: "center",
446
+ justifyContent: "center"
447
+ },
448
+ initial: { fontSize: 18, fontWeight: "600" },
449
+ flexShrink: { flexShrink: 1 },
450
+ small: { fontSize: 12 },
451
+ strong: { fontWeight: "700" },
452
+ card: { borderWidth: 1, borderRadius: 12, padding: 16, gap: 6 },
453
+ assetLabel: { fontSize: 14, fontWeight: "600" },
454
+ limits: { flexDirection: "row", gap: 12, marginVertical: 6 },
455
+ limit: { flex: 1 },
456
+ amount: {
457
+ fontSize: 22,
458
+ fontWeight: "600",
459
+ fontVariant: ["tabular-nums"]
460
+ },
461
+ term: { fontSize: 14, fontWeight: "500", marginTop: 8 },
462
+ chips: { flexDirection: "row", flexWrap: "wrap", gap: 6 },
463
+ expiry: { borderRadius: 10, padding: 12, gap: 2 },
464
+ footer: {
465
+ flexDirection: "row",
466
+ gap: 12,
467
+ paddingHorizontal: 16,
468
+ paddingVertical: 12,
469
+ borderTopWidth: 1
470
+ }
471
+ });
472
+
473
+ // src/ui/AuthorizationListNative.tsx
474
+ import {
475
+ describeAuthorization as describeAuthorization2,
476
+ formatAuthorizationAmount
477
+ } from "@naculus/connect-appkit-core";
478
+ import { useState as useState2 } from "react";
479
+ import { FlatList, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
480
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
481
+ var ENFORCER_LABELS = {
482
+ "evm-session": "EVM session key",
483
+ "solana-session": "Solana session key",
484
+ "mpp-voucher": "MPP channel key"
485
+ };
486
+ var STATUS_GLYPHS = {
487
+ active: "\u25CF",
488
+ pending: "\u25D0",
489
+ revoked: "\u2715",
490
+ expired: "\u25CB"
491
+ };
492
+ function StatusBadge({
493
+ status,
494
+ theme
495
+ }) {
496
+ const colors = status === "active" ? {
497
+ backgroundColor: theme.primary,
498
+ borderColor: theme.primary,
499
+ color: theme.primaryForeground
500
+ } : status === "pending" ? {
501
+ backgroundColor: theme.background,
502
+ borderColor: theme.foreground,
503
+ color: theme.foreground
504
+ } : status === "revoked" ? toneColors(theme, "danger") : {
505
+ backgroundColor: theme.muted,
506
+ borderColor: theme.border,
507
+ color: theme.mutedForeground
508
+ };
509
+ return /* @__PURE__ */ jsxs3(
510
+ View3,
511
+ {
512
+ accessibilityLabel: `Status: ${status}`,
513
+ style: [
514
+ styles.chip,
515
+ styles3.badge,
516
+ {
517
+ backgroundColor: colors.backgroundColor,
518
+ borderColor: colors.borderColor
519
+ }
520
+ ],
521
+ children: [
522
+ /* @__PURE__ */ jsx4(Glyph, { color: colors.color, children: STATUS_GLYPHS[status] }),
523
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.badgeText, { color: colors.color }], children: status })
524
+ ]
525
+ }
526
+ );
527
+ }
528
+ function AuthorizationListNative({
529
+ entries: input,
530
+ assets = {},
531
+ onRevoke,
532
+ revokingId,
533
+ locale,
534
+ onCopy,
535
+ theme: themeProp
536
+ }) {
537
+ const theme = useNativePaymentTheme(themeProp);
538
+ const entries = Array.isArray(input) ? input : input.entries;
539
+ const [confirming, setConfirming] = useState2(null);
540
+ const [onChain, setOnChain] = useState2(/* @__PURE__ */ new Set());
541
+ const muted = { color: theme.mutedForeground };
542
+ const revoke = (entry) => void Promise.resolve(onRevoke(entry)).then((result) => {
543
+ setConfirming(null);
544
+ if (result.onChainRevocationRequired)
545
+ setOnChain((current) => new Set(current).add(entry.keyId));
546
+ });
547
+ const renderItem = ({ item: entry }) => {
548
+ const view = describeAuthorization2(entry, { assets, locale });
549
+ const busy = revokingId === entry.keyId;
550
+ const live = entry.status === "active" || entry.status === "pending";
551
+ return /* @__PURE__ */ jsxs3(
552
+ View3,
553
+ {
554
+ style: [
555
+ styles3.card,
556
+ { backgroundColor: theme.card, borderColor: theme.border }
557
+ ],
558
+ children: [
559
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.cardHeader, children: [
560
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
561
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.title, { color: theme.foreground }], children: view.label ?? view.grants.map((g) => g.assetLabel).join(", ") }),
562
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.small, muted], children: ENFORCER_LABELS[entry.enforcer] ?? entry.enforcer.replaceAll("-", " ") })
563
+ ] }),
564
+ /* @__PURE__ */ jsx4(StatusBadge, { status: entry.status, theme })
565
+ ] }),
566
+ view.grants.map((grant) => {
567
+ const spent = entry.spent?.[grant.asset];
568
+ const total = entry.grants.find(
569
+ (g) => g.asset === grant.asset
570
+ )?.maxTotal;
571
+ const percent = spent !== void 0 && total && total > 0n ? Math.min(Number(spent * 100n / total), 100) : 0;
572
+ return /* @__PURE__ */ jsxs3(
573
+ View3,
574
+ {
575
+ style: [styles3.grant, { backgroundColor: theme.muted }],
576
+ children: [
577
+ view.label && /* @__PURE__ */ jsx4(Text3, { style: [styles3.strong, { color: theme.foreground }], children: grant.assetLabel }),
578
+ grant.recipients.map((recipient) => /* @__PURE__ */ jsx4(
579
+ CopyableValue,
580
+ {
581
+ value: recipient.full,
582
+ display: recipient.shortened,
583
+ onCopy,
584
+ theme
585
+ },
586
+ recipient.full
587
+ )),
588
+ spent !== void 0 && /* @__PURE__ */ jsxs3(View3, { children: [
589
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.spentRow, children: [
590
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, { color: theme.foreground }], children: [
591
+ "Spent",
592
+ " ",
593
+ formatAuthorizationAmount(spent, assets[grant.asset]).formatted
594
+ ] }),
595
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, { color: theme.foreground }], children: [
596
+ "Total ",
597
+ grant.total.formatted
598
+ ] })
599
+ ] }),
600
+ /* @__PURE__ */ jsx4(
601
+ View3,
602
+ {
603
+ accessibilityRole: "progressbar",
604
+ accessibilityLabel: `${grant.assetLabel} spent`,
605
+ accessibilityValue: { min: 0, max: 100, now: percent },
606
+ style: [
607
+ styles3.track,
608
+ { backgroundColor: theme.background }
609
+ ],
610
+ children: /* @__PURE__ */ jsx4(
611
+ View3,
612
+ {
613
+ style: [
614
+ styles3.fill,
615
+ {
616
+ width: `${percent}%`,
617
+ backgroundColor: theme.primary
618
+ }
619
+ ]
620
+ }
621
+ )
622
+ }
623
+ )
624
+ ] })
625
+ ]
626
+ },
627
+ grant.asset
628
+ );
629
+ }),
630
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, muted], children: [
631
+ entry.status === "expired" ? "Expired" : entry.status === "revoked" ? "Revoked" : `Expires ${view.expiry.relative}`,
632
+ " ",
633
+ "\xB7 ",
634
+ view.expiry.absolute
635
+ ] }),
636
+ view.warnings.map((warning) => /* @__PURE__ */ jsx4(
637
+ Callout,
638
+ {
639
+ tone: "warning",
640
+ glyph: "\u26A0",
641
+ role: "alert",
642
+ theme,
643
+ children: warning
644
+ },
645
+ warning
646
+ )),
647
+ onChain.has(entry.keyId) && /* @__PURE__ */ jsx4(Callout, { tone: "warning", glyph: "\u26D3", theme, children: "Local access is revoked. Complete the on-chain revocation to stop this authorization on chain." }),
648
+ live && /* @__PURE__ */ jsx4(View3, { style: [styles3.actions, { borderTopColor: theme.border }], children: confirming === entry.keyId ? /* @__PURE__ */ jsxs3(Fragment, { children: [
649
+ /* @__PURE__ */ jsx4(
650
+ ActionButton,
651
+ {
652
+ label: "Cancel",
653
+ variant: "secondary",
654
+ onPress: () => setConfirming(null),
655
+ theme
656
+ }
657
+ ),
658
+ /* @__PURE__ */ jsx4(
659
+ ActionButton,
660
+ {
661
+ label: busy ? "Revoking\u2026" : "Confirm revoke",
662
+ disabled: busy,
663
+ busy,
664
+ onPress: () => revoke(entry),
665
+ theme
666
+ }
667
+ )
668
+ ] }) : /* @__PURE__ */ jsx4(
669
+ ActionButton,
670
+ {
671
+ label: "Revoke",
672
+ variant: "secondary",
673
+ onPress: () => setConfirming(entry.keyId),
674
+ theme
675
+ }
676
+ ) })
677
+ ]
678
+ }
679
+ );
680
+ };
681
+ return /* @__PURE__ */ jsx4(
682
+ FlatList,
683
+ {
684
+ data: entries,
685
+ keyExtractor: (entry) => entry.keyId,
686
+ renderItem,
687
+ extraData: [confirming, onChain, revokingId, theme],
688
+ contentContainerStyle: styles3.list,
689
+ style: { backgroundColor: theme.background },
690
+ ListHeaderComponent: entries.length ? /* @__PURE__ */ jsx4(
691
+ Text3,
692
+ {
693
+ accessibilityRole: "header",
694
+ style: [styles.heading, { color: theme.foreground }],
695
+ children: "Payment authorizations"
696
+ }
697
+ ) : void 0,
698
+ ListEmptyComponent: /* @__PURE__ */ jsxs3(View3, { style: [styles3.empty, { borderColor: theme.border }], children: [
699
+ /* @__PURE__ */ jsx4(
700
+ Text3,
701
+ {
702
+ accessibilityRole: "header",
703
+ style: [styles3.title, { color: theme.foreground }],
704
+ children: "No payment authorizations"
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.body, muted], children: "Approvals you create will appear here." })
708
+ ] })
709
+ }
710
+ );
711
+ }
712
+ var styles3 = StyleSheet3.create({
713
+ list: { padding: 16, gap: 12 },
714
+ card: { borderWidth: 1, borderRadius: 12, padding: 16, gap: 10 },
715
+ cardHeader: {
716
+ flexDirection: "row",
717
+ justifyContent: "space-between",
718
+ alignItems: "flex-start",
719
+ gap: 8
720
+ },
721
+ flexShrink: { flexShrink: 1 },
722
+ title: { fontSize: 16, fontWeight: "600" },
723
+ small: { fontSize: 12 },
724
+ body: { fontSize: 14, textAlign: "center" },
725
+ strong: { fontSize: 14, fontWeight: "500" },
726
+ badge: { flexDirection: "row", alignItems: "center", gap: 4 },
727
+ badgeText: { fontSize: 12, fontWeight: "600", textTransform: "capitalize" },
728
+ grant: { borderRadius: 10, padding: 12, gap: 6 },
729
+ spentRow: {
730
+ flexDirection: "row",
731
+ justifyContent: "space-between",
732
+ gap: 8
733
+ },
734
+ track: { height: 8, borderRadius: 4, overflow: "hidden", marginTop: 4 },
735
+ fill: { height: "100%" },
736
+ actions: {
737
+ flexDirection: "row",
738
+ justifyContent: "flex-end",
739
+ gap: 8,
740
+ borderTopWidth: 1,
741
+ paddingTop: 12
742
+ },
743
+ empty: {
744
+ borderWidth: 1,
745
+ borderStyle: "dashed",
746
+ borderRadius: 12,
747
+ padding: 32,
748
+ alignItems: "center",
749
+ gap: 4
750
+ }
751
+ });
752
+
753
+ // src/ui/PaymentReceiptNative.tsx
754
+ import {
755
+ formatAuthorizationAmount as formatAuthorizationAmount2
756
+ } from "@naculus/connect-appkit-core";
757
+ import { Pressable as Pressable2, StyleSheet as StyleSheet4, Text as Text4, View as View4 } from "react-native";
758
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
759
+ var VERIFICATION = {
760
+ unverified: {
761
+ title: "Unverified",
762
+ detail: "Reported by the server, not yet checked.",
763
+ glyph: "?",
764
+ tone: "neutral"
765
+ },
766
+ pending: {
767
+ title: "Checking",
768
+ detail: "Checking on chain.",
769
+ glyph: "\u2026",
770
+ tone: "neutral"
771
+ },
772
+ verified: {
773
+ title: "Verified",
774
+ detail: "Verified on chain.",
775
+ glyph: "\u2713",
776
+ tone: "success"
777
+ },
778
+ mismatch: {
779
+ title: "Mismatch",
780
+ detail: "The on-chain settlement does not match this payment.",
781
+ glyph: "!",
782
+ tone: "danger"
783
+ },
784
+ failed: {
785
+ title: "Verification failed",
786
+ detail: "The settlement could not be verified.",
787
+ glyph: "!",
788
+ tone: "danger"
789
+ },
790
+ unavailable: {
791
+ title: "Verification unavailable",
792
+ detail: "On-chain checking is unavailable. Retry later.",
793
+ glyph: "?",
794
+ tone: "neutral"
795
+ }
796
+ };
797
+ function PaymentReceiptNative({
798
+ record,
799
+ assets = {},
800
+ onOpenExplorer,
801
+ onCopy,
802
+ theme: themeProp
803
+ }) {
804
+ const theme = useNativePaymentTheme(themeProp);
805
+ const status = record.verification.status;
806
+ const state = VERIFICATION[status];
807
+ const colors = toneColors(theme, state.tone);
808
+ const muted = { color: theme.mutedForeground };
809
+ const amount = record.amount === null ? "Amount not reported" : /^\d+$/.test(record.amount) ? formatAuthorizationAmount2(
810
+ BigInt(record.amount),
811
+ record.asset ? assets[record.asset] : void 0
812
+ ).formatted : `${record.amount} base units`;
813
+ return /* @__PURE__ */ jsxs4(
814
+ View4,
815
+ {
816
+ testID: `verification-${status}`,
817
+ style: [
818
+ styles4.card,
819
+ { backgroundColor: theme.card, borderColor: theme.border }
820
+ ],
821
+ children: [
822
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.header, children: [
823
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.eyebrow, muted], children: "PAYMENT RECEIPT" }),
824
+ /* @__PURE__ */ jsx5(
825
+ Text4,
826
+ {
827
+ accessibilityRole: "header",
828
+ style: [styles4.amount, { color: theme.foreground }],
829
+ children: amount
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: record.asset ? assets[record.asset]?.symbol ?? record.asset : "Asset not reported" })
833
+ ] }),
834
+ /* @__PURE__ */ jsxs4(
835
+ View4,
836
+ {
837
+ testID: "verification-panel",
838
+ accessibilityLiveRegion: "polite",
839
+ accessible: true,
840
+ accessibilityLabel: `${state.title}. ${state.detail}${record.verification.reason ? ` Reason: ${record.verification.reason}` : ""}`,
841
+ style: [
842
+ styles4.panel,
843
+ {
844
+ backgroundColor: colors.backgroundColor,
845
+ borderColor: colors.borderColor
846
+ }
847
+ ],
848
+ children: [
849
+ /* @__PURE__ */ jsx5(Glyph, { color: colors.color, children: state.glyph }),
850
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.flexShrink, children: [
851
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.badge, { color: colors.color }], children: state.title }),
852
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: colors.color }], children: state.detail }),
853
+ record.verification.reason && /* @__PURE__ */ jsxs4(
854
+ Text4,
855
+ {
856
+ numberOfLines: 2,
857
+ style: [styles4.body, styles4.strong, { color: colors.color }],
858
+ children: [
859
+ "Reason: ",
860
+ record.verification.reason
861
+ ]
862
+ }
863
+ )
864
+ ] })
865
+ ]
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.field, children: [
869
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Recipient" }),
870
+ record.payTo ? /* @__PURE__ */ jsx5(CopyableValue, { value: record.payTo, onCopy, theme }) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not reported" })
871
+ ] }),
872
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.row, children: [
873
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.cell, children: [
874
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Protocol" }),
875
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: record.protocol === "mpp" ? "MPP" : record.protocol })
876
+ ] }),
877
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.cell, children: [
878
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Time" }),
879
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not recorded" })
880
+ ] })
881
+ ] }),
882
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.field, children: [
883
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Settlement reference" }),
884
+ record.reference ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
885
+ /* @__PURE__ */ jsx5(
886
+ CopyableValue,
887
+ {
888
+ value: record.reference,
889
+ onCopy,
890
+ theme
891
+ }
892
+ ),
893
+ onOpenExplorer && /* @__PURE__ */ jsx5(
894
+ Pressable2,
895
+ {
896
+ accessibilityRole: "link",
897
+ accessibilityLabel: "View on explorer",
898
+ onPress: () => onOpenExplorer(record),
899
+ style: styles4.link,
900
+ children: /* @__PURE__ */ jsx5(Text4, { style: [styles4.linkText, { color: theme.foreground }], children: "View on explorer \u2197" })
901
+ }
902
+ )
903
+ ] }) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not reported" })
904
+ ] })
905
+ ]
906
+ }
907
+ );
908
+ }
909
+ var styles4 = StyleSheet4.create({
910
+ card: { borderWidth: 1, borderRadius: 16, padding: 20, gap: 16 },
911
+ header: { alignItems: "center", gap: 4 },
912
+ eyebrow: { fontSize: 12, fontWeight: "500", letterSpacing: 0.6 },
913
+ amount: {
914
+ fontSize: 32,
915
+ fontWeight: "600",
916
+ fontVariant: ["tabular-nums"],
917
+ textAlign: "center"
918
+ },
919
+ small: { fontSize: 12 },
920
+ body: { fontSize: 14, lineHeight: 20 },
921
+ strong: { fontWeight: "600" },
922
+ // Fixed height: badge, explanation and a two-line reason fit, so the card
923
+ // does not jump as verification moves from pending to its final state.
924
+ panel: {
925
+ height: 128,
926
+ flexDirection: "row",
927
+ gap: 8,
928
+ borderWidth: 1,
929
+ borderRadius: 10,
930
+ padding: 12,
931
+ overflow: "hidden"
932
+ },
933
+ flexShrink: { flexShrink: 1 },
934
+ badge: { fontSize: 13, fontWeight: "700", marginBottom: 2 },
935
+ field: { gap: 4 },
936
+ row: { flexDirection: "row", gap: 12 },
937
+ cell: { flex: 1, gap: 4 },
938
+ link: { minHeight: 44, minWidth: 44, justifyContent: "center" },
939
+ linkText: {
940
+ fontSize: 13,
941
+ fontWeight: "500",
942
+ textDecorationLine: "underline"
943
+ }
944
+ });
945
+ export {
946
+ AuthorizationConsentNative,
947
+ AuthorizationListNative,
948
+ NativePaymentThemeProvider,
949
+ PaymentReceiptNative,
950
+ darkPaymentTheme,
951
+ lightPaymentTheme,
952
+ useNativePaymentTheme
953
+ };
954
+ //# sourceMappingURL=index.js.map