@naculus/connect-native 0.8.1 → 0.10.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,970 @@
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
+ trustedAssets,
275
+ locale,
276
+ now,
277
+ requester,
278
+ preview,
279
+ onApprove,
280
+ onDecline,
281
+ busy = false,
282
+ onCopy,
283
+ theme: themeProp
284
+ }) {
285
+ const theme = useNativePaymentTheme(themeProp);
286
+ const description = "expiry" in authorization ? authorization : describeAuthorization(authorization, {
287
+ assets,
288
+ trustedAssets,
289
+ locale,
290
+ now
291
+ });
292
+ const muted = { color: theme.mutedForeground };
293
+ return /* @__PURE__ */ jsxs2(View2, { style: [styles2.root, { backgroundColor: theme.background }], children: [
294
+ /* @__PURE__ */ jsxs2(ScrollView, { contentContainerStyle: styles2.content, children: [
295
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.requester, children: [
296
+ requester.icon ? /* @__PURE__ */ jsx3(
297
+ Image,
298
+ {
299
+ accessibilityIgnoresInvertColors: true,
300
+ source: { uri: requester.icon },
301
+ style: styles2.avatar
302
+ }
303
+ ) : /* @__PURE__ */ jsx3(
304
+ View2,
305
+ {
306
+ importantForAccessibility: "no",
307
+ style: [styles2.avatar, { backgroundColor: theme.muted }],
308
+ children: /* @__PURE__ */ jsx3(Text2, { style: [styles2.initial, { color: theme.foreground }], children: requester.name.slice(0, 1).toUpperCase() })
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.flexShrink, children: [
312
+ /* @__PURE__ */ jsxs2(
313
+ Text2,
314
+ {
315
+ accessibilityRole: "header",
316
+ style: [styles.heading, { color: theme.foreground }],
317
+ children: [
318
+ "Allow ",
319
+ requester.name,
320
+ " to pay?"
321
+ ]
322
+ }
323
+ ),
324
+ requester.origin && /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: requester.origin })
325
+ ] })
326
+ ] }),
327
+ description.warnings.map((warning) => /* @__PURE__ */ jsx3(
328
+ Callout,
329
+ {
330
+ tone: "warning",
331
+ glyph: "\u26A0",
332
+ role: "alert",
333
+ theme,
334
+ children: /* @__PURE__ */ jsx3(Text2, { style: styles2.strong, children: warning })
335
+ },
336
+ warning
337
+ )),
338
+ preview && /* @__PURE__ */ jsxs2(
339
+ Callout,
340
+ {
341
+ tone: preview.allowed ? "success" : "danger",
342
+ glyph: preview.allowed ? "\u2713" : "\u2715",
343
+ theme,
344
+ children: [
345
+ /* @__PURE__ */ jsxs2(Text2, { style: styles2.strong, children: [
346
+ preview.allowed ? "Allowed" : "Blocked",
347
+ "."
348
+ ] }),
349
+ " ",
350
+ preview.sentence
351
+ ]
352
+ }
353
+ ),
354
+ description.grants.map((grant) => /* @__PURE__ */ jsxs2(
355
+ View2,
356
+ {
357
+ style: [
358
+ styles2.card,
359
+ { backgroundColor: theme.card, borderColor: theme.border }
360
+ ],
361
+ children: [
362
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.assetLabel, { color: theme.foreground }], children: grant.assetLabel }),
363
+ grant.assetTrust === "unverified" && /* @__PURE__ */ jsx3(Callout, { tone: "warning", glyph: "\u26A0", role: "alert", theme, children: /* @__PURE__ */ jsx3(Text2, { style: styles2.strong, children: "Unverified token \u2014 not on the trusted list" }) }),
364
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limits, children: [
365
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limit, children: [
366
+ /* @__PURE__ */ jsx3(Text2, { style: [styles.label, muted], children: "Per payment" }),
367
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.amount, { color: theme.foreground }], children: grant.perPayment.formatted })
368
+ ] }),
369
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.limit, children: [
370
+ /* @__PURE__ */ jsx3(Text2, { style: [styles.label, muted], children: "Total limit" }),
371
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.amount, { color: theme.foreground }], children: grant.total.formatted })
372
+ ] })
373
+ ] }),
374
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Recipients" }),
375
+ grant.recipients.length ? grant.recipients.map((recipient) => /* @__PURE__ */ jsx3(
376
+ CopyableValue,
377
+ {
378
+ value: recipient.full,
379
+ display: recipient.shortened,
380
+ onCopy,
381
+ theme
382
+ },
383
+ recipient.full
384
+ )) : /* @__PURE__ */ jsx3(Text2, { style: [styles2.strong, { color: theme.dangerForeground }], children: "No recipient restriction is displayed" }),
385
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Payment count" }),
386
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: grant.count === null ? "No count limit" : `Up to ${grant.count}` }),
387
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.term, { color: theme.foreground }], children: "Payment methods" }),
388
+ /* @__PURE__ */ jsx3(View2, { style: styles2.chips, children: grant.rails.map((rail) => /* @__PURE__ */ jsx3(
389
+ View2,
390
+ {
391
+ style: [
392
+ styles.chip,
393
+ { backgroundColor: theme.muted, borderColor: theme.border }
394
+ ],
395
+ children: /* @__PURE__ */ jsx3(Text2, { style: [styles.chipText, { color: theme.foreground }], children: rail })
396
+ },
397
+ rail
398
+ )) })
399
+ ]
400
+ },
401
+ `${grant.asset}:${grant.recipients.map((r) => r.full).join(",")}:${grant.perPayment.formatted}:${grant.total.formatted}`
402
+ )),
403
+ /* @__PURE__ */ jsxs2(View2, { style: [styles2.expiry, { backgroundColor: theme.muted }], children: [
404
+ /* @__PURE__ */ jsxs2(Text2, { style: [styles2.strong, { color: theme.foreground }], children: [
405
+ "Expires ",
406
+ description.expiry.relative
407
+ ] }),
408
+ /* @__PURE__ */ jsx3(Text2, { style: [styles2.small, muted], children: description.expiry.absolute })
409
+ ] })
410
+ ] }),
411
+ /* @__PURE__ */ jsxs2(
412
+ View2,
413
+ {
414
+ testID: "payment-actions",
415
+ style: [
416
+ styles2.footer,
417
+ { borderTopColor: theme.border, backgroundColor: theme.background }
418
+ ],
419
+ children: [
420
+ /* @__PURE__ */ jsx3(
421
+ ActionButton,
422
+ {
423
+ label: "Decline",
424
+ variant: "secondary",
425
+ onPress: onDecline,
426
+ theme
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx3(
430
+ ActionButton,
431
+ {
432
+ label: busy ? "Approving\u2026" : "Approve",
433
+ onPress: onApprove,
434
+ disabled: busy,
435
+ busy,
436
+ theme
437
+ }
438
+ )
439
+ ]
440
+ }
441
+ )
442
+ ] });
443
+ }
444
+ var styles2 = StyleSheet2.create({
445
+ root: { flex: 1 },
446
+ content: { padding: 16, gap: 12 },
447
+ requester: { flexDirection: "row", alignItems: "center", gap: 12 },
448
+ avatar: {
449
+ width: 44,
450
+ height: 44,
451
+ borderRadius: 12,
452
+ alignItems: "center",
453
+ justifyContent: "center"
454
+ },
455
+ initial: { fontSize: 18, fontWeight: "600" },
456
+ flexShrink: { flexShrink: 1 },
457
+ small: { fontSize: 12 },
458
+ strong: { fontWeight: "700" },
459
+ card: { borderWidth: 1, borderRadius: 12, padding: 16, gap: 6 },
460
+ assetLabel: { fontSize: 14, fontWeight: "600" },
461
+ limits: { flexDirection: "row", gap: 12, marginVertical: 6 },
462
+ limit: { flex: 1 },
463
+ amount: {
464
+ fontSize: 22,
465
+ fontWeight: "600",
466
+ fontVariant: ["tabular-nums"]
467
+ },
468
+ term: { fontSize: 14, fontWeight: "500", marginTop: 8 },
469
+ chips: { flexDirection: "row", flexWrap: "wrap", gap: 6 },
470
+ expiry: { borderRadius: 10, padding: 12, gap: 2 },
471
+ footer: {
472
+ flexDirection: "row",
473
+ gap: 12,
474
+ paddingHorizontal: 16,
475
+ paddingVertical: 12,
476
+ borderTopWidth: 1
477
+ }
478
+ });
479
+
480
+ // src/ui/AuthorizationListNative.tsx
481
+ import {
482
+ describeAuthorization as describeAuthorization2,
483
+ formatAuthorizationAmount
484
+ } from "@naculus/connect-appkit-core";
485
+ import { useState as useState2 } from "react";
486
+ import { FlatList, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
487
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
488
+ var ENFORCER_LABELS = {
489
+ "evm-session": "EVM session key",
490
+ "solana-session": "Solana session key",
491
+ "mpp-voucher": "MPP channel key"
492
+ };
493
+ var STATUS_GLYPHS = {
494
+ active: "\u25CF",
495
+ pending: "\u25D0",
496
+ revoked: "\u2715",
497
+ expired: "\u25CB"
498
+ };
499
+ function StatusBadge({
500
+ status,
501
+ theme
502
+ }) {
503
+ const colors = status === "active" ? {
504
+ backgroundColor: theme.primary,
505
+ borderColor: theme.primary,
506
+ color: theme.primaryForeground
507
+ } : status === "pending" ? {
508
+ backgroundColor: theme.background,
509
+ borderColor: theme.foreground,
510
+ color: theme.foreground
511
+ } : status === "revoked" ? toneColors(theme, "danger") : {
512
+ backgroundColor: theme.muted,
513
+ borderColor: theme.border,
514
+ color: theme.mutedForeground
515
+ };
516
+ return /* @__PURE__ */ jsxs3(
517
+ View3,
518
+ {
519
+ accessibilityLabel: `Status: ${status}`,
520
+ style: [
521
+ styles.chip,
522
+ styles3.badge,
523
+ {
524
+ backgroundColor: colors.backgroundColor,
525
+ borderColor: colors.borderColor
526
+ }
527
+ ],
528
+ children: [
529
+ /* @__PURE__ */ jsx4(Glyph, { color: colors.color, children: STATUS_GLYPHS[status] }),
530
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.badgeText, { color: colors.color }], children: status })
531
+ ]
532
+ }
533
+ );
534
+ }
535
+ function AuthorizationListNative({
536
+ entries: input,
537
+ assets = {},
538
+ trustedAssets,
539
+ onRevoke,
540
+ revokingId,
541
+ locale,
542
+ onCopy,
543
+ theme: themeProp
544
+ }) {
545
+ const theme = useNativePaymentTheme(themeProp);
546
+ const entries = Array.isArray(input) ? input : input.entries;
547
+ const [confirming, setConfirming] = useState2(null);
548
+ const [onChain, setOnChain] = useState2(/* @__PURE__ */ new Set());
549
+ const muted = { color: theme.mutedForeground };
550
+ const revoke = (entry) => void Promise.resolve(onRevoke(entry)).then((result) => {
551
+ setConfirming(null);
552
+ if (result.onChainRevocationRequired)
553
+ setOnChain((current) => new Set(current).add(entry.keyId));
554
+ });
555
+ const renderItem = ({ item: entry }) => {
556
+ const view = describeAuthorization2(entry, {
557
+ assets,
558
+ trustedAssets,
559
+ locale
560
+ });
561
+ const busy = revokingId === entry.keyId;
562
+ const live = entry.status === "active" || entry.status === "pending";
563
+ return /* @__PURE__ */ jsxs3(
564
+ View3,
565
+ {
566
+ style: [
567
+ styles3.card,
568
+ { backgroundColor: theme.card, borderColor: theme.border }
569
+ ],
570
+ children: [
571
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.cardHeader, children: [
572
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
573
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.title, { color: theme.foreground }], children: view.label ?? view.grants.map((g) => g.assetLabel).join(", ") }),
574
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.small, muted], children: ENFORCER_LABELS[entry.enforcer] ?? entry.enforcer.replaceAll("-", " ") })
575
+ ] }),
576
+ /* @__PURE__ */ jsx4(StatusBadge, { status: entry.status, theme })
577
+ ] }),
578
+ view.grants.map((grant) => {
579
+ const spent = entry.spent?.[grant.asset];
580
+ const total = entry.grants.find(
581
+ (g) => g.asset === grant.asset
582
+ )?.maxTotal;
583
+ const percent = spent !== void 0 && total && total > 0n ? Math.min(Number(spent * 100n / total), 100) : 0;
584
+ return /* @__PURE__ */ jsxs3(
585
+ View3,
586
+ {
587
+ style: [styles3.grant, { backgroundColor: theme.muted }],
588
+ children: [
589
+ view.label && /* @__PURE__ */ jsx4(Text3, { style: [styles3.strong, { color: theme.foreground }], children: grant.assetLabel }),
590
+ grant.assetTrust === "unverified" && /* @__PURE__ */ jsx4(Callout, { tone: "warning", glyph: "\u26A0", role: "alert", theme, children: "Unverified token \u2014 not on the trusted list" }),
591
+ grant.recipients.map((recipient) => /* @__PURE__ */ jsx4(
592
+ CopyableValue,
593
+ {
594
+ value: recipient.full,
595
+ display: recipient.shortened,
596
+ onCopy,
597
+ theme
598
+ },
599
+ recipient.full
600
+ )),
601
+ spent !== void 0 && /* @__PURE__ */ jsxs3(View3, { children: [
602
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.spentRow, children: [
603
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, { color: theme.foreground }], children: [
604
+ "Spent",
605
+ " ",
606
+ formatAuthorizationAmount(
607
+ spent,
608
+ grant.assetTrust === "unverified" ? void 0 : assets[grant.asset]
609
+ ).formatted
610
+ ] }),
611
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, { color: theme.foreground }], children: [
612
+ "Total ",
613
+ grant.total.formatted
614
+ ] })
615
+ ] }),
616
+ /* @__PURE__ */ jsx4(
617
+ View3,
618
+ {
619
+ accessibilityRole: "progressbar",
620
+ accessibilityLabel: `${grant.assetLabel} spent`,
621
+ accessibilityValue: { min: 0, max: 100, now: percent },
622
+ style: [
623
+ styles3.track,
624
+ { backgroundColor: theme.background }
625
+ ],
626
+ children: /* @__PURE__ */ jsx4(
627
+ View3,
628
+ {
629
+ style: [
630
+ styles3.fill,
631
+ {
632
+ width: `${percent}%`,
633
+ backgroundColor: theme.primary
634
+ }
635
+ ]
636
+ }
637
+ )
638
+ }
639
+ )
640
+ ] })
641
+ ]
642
+ },
643
+ grant.asset
644
+ );
645
+ }),
646
+ /* @__PURE__ */ jsxs3(Text3, { style: [styles3.small, muted], children: [
647
+ entry.status === "expired" ? "Expired" : entry.status === "revoked" ? "Revoked" : `Expires ${view.expiry.relative}`,
648
+ " ",
649
+ "\xB7 ",
650
+ view.expiry.absolute
651
+ ] }),
652
+ view.warnings.map((warning) => /* @__PURE__ */ jsx4(
653
+ Callout,
654
+ {
655
+ tone: "warning",
656
+ glyph: "\u26A0",
657
+ role: "alert",
658
+ theme,
659
+ children: warning
660
+ },
661
+ warning
662
+ )),
663
+ 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." }),
664
+ live && /* @__PURE__ */ jsx4(View3, { style: [styles3.actions, { borderTopColor: theme.border }], children: confirming === entry.keyId ? /* @__PURE__ */ jsxs3(Fragment, { children: [
665
+ /* @__PURE__ */ jsx4(
666
+ ActionButton,
667
+ {
668
+ label: "Cancel",
669
+ variant: "secondary",
670
+ onPress: () => setConfirming(null),
671
+ theme
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx4(
675
+ ActionButton,
676
+ {
677
+ label: busy ? "Revoking\u2026" : "Confirm revoke",
678
+ disabled: busy,
679
+ busy,
680
+ onPress: () => revoke(entry),
681
+ theme
682
+ }
683
+ )
684
+ ] }) : /* @__PURE__ */ jsx4(
685
+ ActionButton,
686
+ {
687
+ label: "Revoke",
688
+ variant: "secondary",
689
+ onPress: () => setConfirming(entry.keyId),
690
+ theme
691
+ }
692
+ ) })
693
+ ]
694
+ }
695
+ );
696
+ };
697
+ return /* @__PURE__ */ jsx4(
698
+ FlatList,
699
+ {
700
+ data: entries,
701
+ keyExtractor: (entry) => entry.keyId,
702
+ renderItem,
703
+ extraData: [confirming, onChain, revokingId, theme],
704
+ contentContainerStyle: styles3.list,
705
+ style: { backgroundColor: theme.background },
706
+ ListHeaderComponent: entries.length ? /* @__PURE__ */ jsx4(
707
+ Text3,
708
+ {
709
+ accessibilityRole: "header",
710
+ style: [styles.heading, { color: theme.foreground }],
711
+ children: "Payment authorizations"
712
+ }
713
+ ) : void 0,
714
+ ListEmptyComponent: /* @__PURE__ */ jsxs3(View3, { style: [styles3.empty, { borderColor: theme.border }], children: [
715
+ /* @__PURE__ */ jsx4(
716
+ Text3,
717
+ {
718
+ accessibilityRole: "header",
719
+ style: [styles3.title, { color: theme.foreground }],
720
+ children: "No payment authorizations"
721
+ }
722
+ ),
723
+ /* @__PURE__ */ jsx4(Text3, { style: [styles3.body, muted], children: "Approvals you create will appear here." })
724
+ ] })
725
+ }
726
+ );
727
+ }
728
+ var styles3 = StyleSheet3.create({
729
+ list: { padding: 16, gap: 12 },
730
+ card: { borderWidth: 1, borderRadius: 12, padding: 16, gap: 10 },
731
+ cardHeader: {
732
+ flexDirection: "row",
733
+ justifyContent: "space-between",
734
+ alignItems: "flex-start",
735
+ gap: 8
736
+ },
737
+ flexShrink: { flexShrink: 1 },
738
+ title: { fontSize: 16, fontWeight: "600" },
739
+ small: { fontSize: 12 },
740
+ body: { fontSize: 14, textAlign: "center" },
741
+ strong: { fontSize: 14, fontWeight: "500" },
742
+ badge: { flexDirection: "row", alignItems: "center", gap: 4 },
743
+ badgeText: { fontSize: 12, fontWeight: "600", textTransform: "capitalize" },
744
+ grant: { borderRadius: 10, padding: 12, gap: 6 },
745
+ spentRow: {
746
+ flexDirection: "row",
747
+ justifyContent: "space-between",
748
+ gap: 8
749
+ },
750
+ track: { height: 8, borderRadius: 4, overflow: "hidden", marginTop: 4 },
751
+ fill: { height: "100%" },
752
+ actions: {
753
+ flexDirection: "row",
754
+ justifyContent: "flex-end",
755
+ gap: 8,
756
+ borderTopWidth: 1,
757
+ paddingTop: 12
758
+ },
759
+ empty: {
760
+ borderWidth: 1,
761
+ borderStyle: "dashed",
762
+ borderRadius: 12,
763
+ padding: 32,
764
+ alignItems: "center",
765
+ gap: 4
766
+ }
767
+ });
768
+
769
+ // src/ui/PaymentReceiptNative.tsx
770
+ import {
771
+ formatAuthorizationAmount as formatAuthorizationAmount2
772
+ } from "@naculus/connect-appkit-core";
773
+ import { Pressable as Pressable2, StyleSheet as StyleSheet4, Text as Text4, View as View4 } from "react-native";
774
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
775
+ var VERIFICATION = {
776
+ unverified: {
777
+ title: "Unverified",
778
+ detail: "Reported by the server, not yet checked.",
779
+ glyph: "?",
780
+ tone: "neutral"
781
+ },
782
+ pending: {
783
+ title: "Checking",
784
+ detail: "Checking on chain.",
785
+ glyph: "\u2026",
786
+ tone: "neutral"
787
+ },
788
+ verified: {
789
+ title: "Verified",
790
+ detail: "Verified on chain.",
791
+ glyph: "\u2713",
792
+ tone: "success"
793
+ },
794
+ mismatch: {
795
+ title: "Mismatch",
796
+ detail: "The on-chain settlement does not match this payment.",
797
+ glyph: "!",
798
+ tone: "danger"
799
+ },
800
+ failed: {
801
+ title: "Verification failed",
802
+ detail: "The settlement could not be verified.",
803
+ glyph: "!",
804
+ tone: "danger"
805
+ },
806
+ unavailable: {
807
+ title: "Verification unavailable",
808
+ detail: "On-chain checking is unavailable. Retry later.",
809
+ glyph: "?",
810
+ tone: "neutral"
811
+ }
812
+ };
813
+ function PaymentReceiptNative({
814
+ record,
815
+ assets = {},
816
+ onOpenExplorer,
817
+ onCopy,
818
+ theme: themeProp
819
+ }) {
820
+ const theme = useNativePaymentTheme(themeProp);
821
+ const status = record.verification.status;
822
+ const state = VERIFICATION[status];
823
+ const colors = toneColors(theme, state.tone);
824
+ const muted = { color: theme.mutedForeground };
825
+ const amount = record.amount === null ? "Amount not reported" : /^\d+$/.test(record.amount) ? formatAuthorizationAmount2(
826
+ BigInt(record.amount),
827
+ record.asset ? assets[record.asset] : void 0
828
+ ).formatted : `${record.amount} base units`;
829
+ return /* @__PURE__ */ jsxs4(
830
+ View4,
831
+ {
832
+ testID: `verification-${status}`,
833
+ style: [
834
+ styles4.card,
835
+ { backgroundColor: theme.card, borderColor: theme.border }
836
+ ],
837
+ children: [
838
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.header, children: [
839
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.eyebrow, muted], children: "PAYMENT RECEIPT" }),
840
+ /* @__PURE__ */ jsx5(
841
+ Text4,
842
+ {
843
+ accessibilityRole: "header",
844
+ style: [styles4.amount, { color: theme.foreground }],
845
+ children: amount
846
+ }
847
+ ),
848
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: record.asset ? assets[record.asset]?.symbol ?? record.asset : "Asset not reported" })
849
+ ] }),
850
+ /* @__PURE__ */ jsxs4(
851
+ View4,
852
+ {
853
+ testID: "verification-panel",
854
+ accessibilityLiveRegion: "polite",
855
+ accessible: true,
856
+ accessibilityLabel: `${state.title}. ${state.detail}${record.verification.reason ? ` Reason: ${record.verification.reason}` : ""}`,
857
+ style: [
858
+ styles4.panel,
859
+ {
860
+ backgroundColor: colors.backgroundColor,
861
+ borderColor: colors.borderColor
862
+ }
863
+ ],
864
+ children: [
865
+ /* @__PURE__ */ jsx5(Glyph, { color: colors.color, children: state.glyph }),
866
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.flexShrink, children: [
867
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.badge, { color: colors.color }], children: state.title }),
868
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: colors.color }], children: state.detail }),
869
+ record.verification.reason && /* @__PURE__ */ jsxs4(
870
+ Text4,
871
+ {
872
+ numberOfLines: 2,
873
+ style: [styles4.body, styles4.strong, { color: colors.color }],
874
+ children: [
875
+ "Reason: ",
876
+ record.verification.reason
877
+ ]
878
+ }
879
+ )
880
+ ] })
881
+ ]
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.field, children: [
885
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Recipient" }),
886
+ record.payTo ? /* @__PURE__ */ jsx5(CopyableValue, { value: record.payTo, onCopy, theme }) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not reported" })
887
+ ] }),
888
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.row, children: [
889
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.cell, children: [
890
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Protocol" }),
891
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: record.protocol === "mpp" ? "MPP" : record.protocol })
892
+ ] }),
893
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.cell, children: [
894
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Time" }),
895
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not recorded" })
896
+ ] })
897
+ ] }),
898
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.field, children: [
899
+ /* @__PURE__ */ jsx5(Text4, { style: [styles4.small, muted], children: "Settlement reference" }),
900
+ record.reference ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
901
+ /* @__PURE__ */ jsx5(
902
+ CopyableValue,
903
+ {
904
+ value: record.reference,
905
+ onCopy,
906
+ theme
907
+ }
908
+ ),
909
+ onOpenExplorer && /* @__PURE__ */ jsx5(
910
+ Pressable2,
911
+ {
912
+ accessibilityRole: "link",
913
+ accessibilityLabel: "View on explorer",
914
+ onPress: () => onOpenExplorer(record),
915
+ style: styles4.link,
916
+ children: /* @__PURE__ */ jsx5(Text4, { style: [styles4.linkText, { color: theme.foreground }], children: "View on explorer \u2197" })
917
+ }
918
+ )
919
+ ] }) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.body, { color: theme.foreground }], children: "Not reported" })
920
+ ] })
921
+ ]
922
+ }
923
+ );
924
+ }
925
+ var styles4 = StyleSheet4.create({
926
+ card: { borderWidth: 1, borderRadius: 16, padding: 20, gap: 16 },
927
+ header: { alignItems: "center", gap: 4 },
928
+ eyebrow: { fontSize: 12, fontWeight: "500", letterSpacing: 0.6 },
929
+ amount: {
930
+ fontSize: 32,
931
+ fontWeight: "600",
932
+ fontVariant: ["tabular-nums"],
933
+ textAlign: "center"
934
+ },
935
+ small: { fontSize: 12 },
936
+ body: { fontSize: 14, lineHeight: 20 },
937
+ strong: { fontWeight: "600" },
938
+ // Fixed height: badge, explanation and a two-line reason fit, so the card
939
+ // does not jump as verification moves from pending to its final state.
940
+ panel: {
941
+ height: 128,
942
+ flexDirection: "row",
943
+ gap: 8,
944
+ borderWidth: 1,
945
+ borderRadius: 10,
946
+ padding: 12,
947
+ overflow: "hidden"
948
+ },
949
+ flexShrink: { flexShrink: 1 },
950
+ badge: { fontSize: 13, fontWeight: "700", marginBottom: 2 },
951
+ field: { gap: 4 },
952
+ row: { flexDirection: "row", gap: 12 },
953
+ cell: { flex: 1, gap: 4 },
954
+ link: { minHeight: 44, minWidth: 44, justifyContent: "center" },
955
+ linkText: {
956
+ fontSize: 13,
957
+ fontWeight: "500",
958
+ textDecorationLine: "underline"
959
+ }
960
+ });
961
+ export {
962
+ AuthorizationConsentNative,
963
+ AuthorizationListNative,
964
+ NativePaymentThemeProvider,
965
+ PaymentReceiptNative,
966
+ darkPaymentTheme,
967
+ lightPaymentTheme,
968
+ useNativePaymentTheme
969
+ };
970
+ //# sourceMappingURL=index.js.map