@owlmeans/web-payment 0.1.18-rc.20 → 0.1.18-rc.21

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.
Files changed (136) hide show
  1. package/README.md +1 -1
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/web-payment/SKILL.md +171 -31
  4. package/build/@/components/ui/checkbox.d.ts +5 -0
  5. package/build/@/components/ui/checkbox.d.ts.map +1 -0
  6. package/build/@/components/ui/checkbox.js +6 -0
  7. package/build/@/components/ui/checkbox.js.map +1 -0
  8. package/build/checkout-limit.d.ts +11 -0
  9. package/build/checkout-limit.d.ts.map +1 -0
  10. package/build/checkout-limit.js +34 -0
  11. package/build/checkout-limit.js.map +1 -0
  12. package/build/consumer/cancellation.d.ts +20 -0
  13. package/build/consumer/cancellation.d.ts.map +1 -0
  14. package/build/consumer/cancellation.js +115 -0
  15. package/build/consumer/cancellation.js.map +1 -0
  16. package/build/consumer/copy.d.ts +23 -0
  17. package/build/consumer/copy.d.ts.map +1 -0
  18. package/build/consumer/copy.js +75 -0
  19. package/build/consumer/copy.js.map +1 -0
  20. package/build/consumer/ensure.d.ts +56 -0
  21. package/build/consumer/ensure.d.ts.map +1 -0
  22. package/build/consumer/ensure.js +71 -0
  23. package/build/consumer/ensure.js.map +1 -0
  24. package/build/consumer/fields.d.ts +43 -0
  25. package/build/consumer/fields.d.ts.map +1 -0
  26. package/build/consumer/fields.js +19 -0
  27. package/build/consumer/fields.js.map +1 -0
  28. package/build/consumer/hooks.d.ts +113 -0
  29. package/build/consumer/hooks.d.ts.map +1 -0
  30. package/build/consumer/hooks.js +225 -0
  31. package/build/consumer/hooks.js.map +1 -0
  32. package/build/consumer/index.d.ts +14 -0
  33. package/build/consumer/index.d.ts.map +1 -0
  34. package/build/consumer/index.js +12 -0
  35. package/build/consumer/index.js.map +1 -0
  36. package/build/consumer/legal.d.ts +53 -0
  37. package/build/consumer/legal.d.ts.map +1 -0
  38. package/build/consumer/legal.js +77 -0
  39. package/build/consumer/legal.js.map +1 -0
  40. package/build/consumer/performance-consent-dialog.d.ts +18 -0
  41. package/build/consumer/performance-consent-dialog.d.ts.map +1 -0
  42. package/build/consumer/performance-consent-dialog.js +71 -0
  43. package/build/consumer/performance-consent-dialog.js.map +1 -0
  44. package/build/consumer/provider.d.ts +33 -0
  45. package/build/consumer/provider.d.ts.map +1 -0
  46. package/build/consumer/provider.js +26 -0
  47. package/build/consumer/provider.js.map +1 -0
  48. package/build/consumer/refusal.d.ts +35 -0
  49. package/build/consumer/refusal.d.ts.map +1 -0
  50. package/build/consumer/refusal.js +98 -0
  51. package/build/consumer/refusal.js.map +1 -0
  52. package/build/consumer/subscription-start-dialog.d.ts +16 -0
  53. package/build/consumer/subscription-start-dialog.d.ts.map +1 -0
  54. package/build/consumer/subscription-start-dialog.js +43 -0
  55. package/build/consumer/subscription-start-dialog.js.map +1 -0
  56. package/build/consumer/types.d.ts +114 -0
  57. package/build/consumer/types.d.ts.map +1 -0
  58. package/build/consumer/types.js +2 -0
  59. package/build/consumer/types.js.map +1 -0
  60. package/build/consumer/withdrawal.d.ts +29 -0
  61. package/build/consumer/withdrawal.d.ts.map +1 -0
  62. package/build/consumer/withdrawal.js +140 -0
  63. package/build/consumer/withdrawal.js.map +1 -0
  64. package/build/dialog.d.ts +1 -1
  65. package/build/dialog.d.ts.map +1 -1
  66. package/build/dialog.js +42 -16
  67. package/build/dialog.js.map +1 -1
  68. package/build/estimate-summary.d.ts +9 -1
  69. package/build/estimate-summary.d.ts.map +1 -1
  70. package/build/estimate-summary.js +10 -7
  71. package/build/estimate-summary.js.map +1 -1
  72. package/build/estimate.d.ts +6 -0
  73. package/build/estimate.d.ts.map +1 -1
  74. package/build/estimate.js +26 -1
  75. package/build/estimate.js.map +1 -1
  76. package/build/format.d.ts +15 -0
  77. package/build/format.d.ts.map +1 -0
  78. package/build/format.js +68 -0
  79. package/build/format.js.map +1 -0
  80. package/build/i18n/be.json +1 -1
  81. package/build/i18n/de.json +1 -1
  82. package/build/i18n/en.json +1 -1
  83. package/build/i18n/es.json +1 -1
  84. package/build/i18n/fr.json +110 -15
  85. package/build/i18n/pl.json +1 -1
  86. package/build/i18n/ru.json +1 -1
  87. package/build/i18n/uk.json +1 -1
  88. package/build/i18n.d.ts +2 -1
  89. package/build/i18n.d.ts.map +1 -1
  90. package/build/i18n.js +10 -8
  91. package/build/i18n.js.map +1 -1
  92. package/build/index.d.ts +2 -0
  93. package/build/index.d.ts.map +1 -1
  94. package/build/index.js +2 -0
  95. package/build/index.js.map +1 -1
  96. package/build/types.d.ts +22 -1
  97. package/build/types.d.ts.map +1 -1
  98. package/package.json +18 -7
  99. package/src/@/components/ui/checkbox.tsx +23 -0
  100. package/src/checkout-limit.tsx +48 -0
  101. package/src/consumer/cancellation.tsx +214 -0
  102. package/src/consumer/copy.ts +93 -0
  103. package/src/consumer/ensure.ts +124 -0
  104. package/src/consumer/fields.tsx +89 -0
  105. package/src/consumer/hooks.ts +358 -0
  106. package/src/consumer/index.ts +13 -0
  107. package/src/consumer/legal.tsx +129 -0
  108. package/src/consumer/performance-consent-dialog.tsx +132 -0
  109. package/src/consumer/provider.tsx +49 -0
  110. package/src/consumer/refusal.ts +110 -0
  111. package/src/consumer/subscription-start-dialog.tsx +87 -0
  112. package/src/consumer/types.ts +121 -0
  113. package/src/consumer/withdrawal.tsx +279 -0
  114. package/src/dialog.tsx +53 -23
  115. package/src/estimate-summary.tsx +21 -11
  116. package/src/estimate.ts +27 -1
  117. package/src/format.ts +71 -0
  118. package/src/i18n/be.json +1 -1
  119. package/src/i18n/de.json +1 -1
  120. package/src/i18n/en.json +1 -1
  121. package/src/i18n/es.json +1 -1
  122. package/src/i18n/fr.json +110 -15
  123. package/src/i18n/pl.json +1 -1
  124. package/src/i18n/ru.json +1 -1
  125. package/src/i18n/uk.json +1 -1
  126. package/src/i18n.ts +11 -8
  127. package/src/index.ts +2 -0
  128. package/src/types.ts +23 -1
  129. package/tests/consent-refusal.spec.ts +83 -0
  130. package/tests/consumer-hooks.spec.ts +205 -0
  131. package/tests/consumer.spec.ts +343 -0
  132. package/tests/context.ts +1 -1
  133. package/tests/harness/consumer-fixtures.ts +105 -0
  134. package/tests/harness/consumer.tsx +197 -0
  135. package/tests/harness/mount.tsx +9 -2
  136. package/tests/i18n.spec.ts +41 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../../src/consumer/copy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAwCnD,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,KAAK,MAAM,CAAA;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QAAS,MAAM,KAAG,SAQ3C,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,SAUzC,CAAA;AAED,uDAAuD;AACvD,eAAO,MAAM,cAAc,QAAS,MAAM,KAAG,SAAqD,CAAA;AAElG,qDAAqD;AACrD,eAAO,MAAM,YAAY,QAAS,MAAM,KAAG,SAAmD,CAAA;AAE9F,kGAAkG;AAClG,eAAO,MAAM,cAAc,QAAS,MAAM,cAAc,MAAM,KAAG,MAMhE,CAAA"}
@@ -0,0 +1,75 @@
1
+ import { useMemo } from 'react';
2
+ import { LIB_NAMESPACE, resolveI18nResource } from '@owlmeans/i18n';
3
+ import { baseLanguageOf, consumerText } from '@owlmeans/payment';
4
+ import { WEB_PAYMENT_RESOURCE } from '../i18n.js';
5
+ const isTree = (value) => value != null && typeof value === 'object' && !Array.isArray(value);
6
+ const merge = (base, over) => {
7
+ const result = { ...base };
8
+ for (const [key, value] of Object.entries(over ?? {})) {
9
+ if (isTree(value)) {
10
+ const current = result[key];
11
+ result[key] = merge(isTree(current) ? current : {}, value);
12
+ }
13
+ else if (typeof value === 'string') {
14
+ result[key] = value;
15
+ }
16
+ }
17
+ return result;
18
+ };
19
+ const bundleOf = (lng) => {
20
+ const base = baseLanguageOf(lng);
21
+ let tree = merge({}, resolveI18nResource('en', WEB_PAYMENT_RESOURCE, LIB_NAMESPACE));
22
+ if (base !== '' && base !== 'en') {
23
+ tree = merge(tree, resolveI18nResource(base, WEB_PAYMENT_RESOURCE, LIB_NAMESPACE));
24
+ }
25
+ if (lng !== base && lng.trim() !== '') {
26
+ tree = merge(tree, resolveI18nResource(lng, WEB_PAYMENT_RESOURCE, LIB_NAMESPACE));
27
+ }
28
+ return tree;
29
+ };
30
+ const fill = (text, values = {}) => text.replace(/\{\{\s*([\w-]+)\s*\}\}/g, (_, name) => values[name] != null ? String(values[name]) : '');
31
+ /**
32
+ * The package's own interface strings (`web-payment`, library tier, any application override
33
+ * included) in a FIXED language, merged key by key over English — no i18next instance and no
34
+ * draining (`resolveI18nResource`). What lets a consumer-rights dialog render wholly in the
35
+ * contract language while the rest of the page stays in the interface language.
36
+ */
37
+ export const paymentTextOf = (lng) => {
38
+ const tree = bundleOf(lng);
39
+ return (key, values) => {
40
+ const value = key.split('.').reduce((node, part) => isTree(node) ? node[part] : undefined, tree);
41
+ return typeof value === 'string' ? fill(value, values) : key;
42
+ };
43
+ };
44
+ /**
45
+ * The legal copy of `@owlmeans/payment` (`payment-consumer-rights`) in a fixed language. A text
46
+ * that cannot be rendered in the language (a broken application override) falls back to English
47
+ * rather than blanking the page; one that cannot be rendered at all is `''`.
48
+ */
49
+ export const legalTextOf = (lng) => (path, values) => {
50
+ try {
51
+ return consumerText(lng, path, values);
52
+ }
53
+ catch {
54
+ try {
55
+ return consumerText('en', path, values);
56
+ }
57
+ catch {
58
+ return '';
59
+ }
60
+ }
61
+ };
62
+ /** `paymentTextOf(lng)`, memoised for the language. */
63
+ export const usePaymentText = (lng) => useMemo(() => paymentTextOf(lng), [lng]);
64
+ /** `legalTextOf(lng)`, memoised for the language. */
65
+ export const useLegalText = (lng) => useMemo(() => legalTextOf(lng), [lng]);
66
+ /** A language's own name in another language (`de` in `en` → "German"); the code when unknown. */
67
+ export const languageNameOf = (lng, inLanguage) => {
68
+ try {
69
+ return new Intl.DisplayNames([inLanguage], { type: 'language' }).of(lng) ?? lng;
70
+ }
71
+ catch {
72
+ return lng;
73
+ }
74
+ };
75
+ //# sourceMappingURL=copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.js","sourceRoot":"","sources":["../../src/consumer/copy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export interface AskerOptions<Args extends unknown[], View, Answer> {
2
+ /** Read the current view. */
3
+ load: (...args: Args) => Promise<View>;
4
+ /** Whether the view asks the person anything. */
5
+ required: (view: View) => boolean;
6
+ /** Show the view to the person (open the dialog). */
7
+ show: (view: View) => void;
8
+ /**
9
+ * The answer when nothing needs asking — and when the read FAILS: the server's refusal stays
10
+ * the authority, and a flaky read must never block work that needs no consent.
11
+ */
12
+ skip: Answer;
13
+ }
14
+ export interface Asker<Args extends unknown[], Answer> {
15
+ /** Read, and ask only when the view requires it. Concurrent calls share ONE pending answer. */
16
+ ask: (...args: Args) => Promise<Answer>;
17
+ /** Answer the pending `ask` (the dialog confirmed or was declined); nothing when none waits. */
18
+ settle: (answer: Answer) => void;
19
+ /** Whether an `ask` is waiting for the person. */
20
+ waiting: () => boolean;
21
+ }
22
+ /**
23
+ * Read a view, show it when it asks for something, and answer once the person has — the state
24
+ * machine behind `usePerformanceConsent().ensure` and `useSubscriptionStart().ensure`, free of
25
+ * React so it is testable on its own. A burst of calls opens one dialog: they all get its answer.
26
+ */
27
+ export declare const makeAsker: <Args extends unknown[], View, Answer>(opts: AskerOptions<Args, View, Answer>) => Asker<Args, Answer>;
28
+ export interface EnsureOptions {
29
+ /**
30
+ * Asked after the server refused with a 428 although the last answer said nothing was needed.
31
+ * The view is always read fresh; `force` records why the caller asks.
32
+ */
33
+ force?: boolean;
34
+ }
35
+ export interface ConsentGate {
36
+ /**
37
+ * `true` when nothing needs consent or the person confirmed and it was recorded; `false` when
38
+ * they declined or closed the dialog. A failed read answers `true`. One pending answer is shared.
39
+ */
40
+ ensure: (opts?: EnsureOptions) => Promise<boolean>;
41
+ /**
42
+ * Run `action`; when it is refused for the spend consent (`isPerformanceConsentRefusal` — the
43
+ * class, its marker, or a bare 428, also wrapped inside another error), ask with
44
+ * `ensure({ force: true })` and run it again exactly ONCE. A decline throws `ConsentDeclined`;
45
+ * a second refusal, or any other failure, propagates as it is.
46
+ */
47
+ withConsent: <T>(action: () => Promise<T>) => Promise<T>;
48
+ }
49
+ /** A gate over an `ensure` — what `useConsentGate` answers inside a `PerformanceConsentProvider`. */
50
+ export declare const makeConsentGate: (ensure: ConsentGate['ensure']) => ConsentGate;
51
+ /**
52
+ * The gate outside any provider: nothing can be asked, so `ensure` answers `true` and a refusal
53
+ * propagates unchanged.
54
+ */
55
+ export declare const passThroughGate: ConsentGate;
56
+ //# sourceMappingURL=ensure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure.d.ts","sourceRoot":"","sources":["../../src/consumer/ensure.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM;IAChE,6BAA6B;IAC7B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,iDAAiD;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;IACjC,qDAAqD;IACrD,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,KAAK,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM;IACnD,+FAA+F;IAC/F,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,gGAAgG;IAChG,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,kDAAkD;IAClD,OAAO,EAAE,MAAM,OAAO,CAAA;CACvB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,IAAI,SAAS,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,QACtD,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,KACrC,KAAK,CAAC,IAAI,EAAE,MAAM,CAuCpB,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAClD;;;;;OAKG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CACzD;AAED,qGAAqG;AACrG,eAAO,MAAM,eAAe,WAAY,WAAW,CAAC,QAAQ,CAAC,KAAG,WAgB9D,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,WAG7B,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { ConsentKind } from '@owlmeans/payment';
2
+ import { ConsentDeclined, isPerformanceConsentRefusal } from './refusal.js';
3
+ /**
4
+ * Read a view, show it when it asks for something, and answer once the person has — the state
5
+ * machine behind `usePerformanceConsent().ensure` and `useSubscriptionStart().ensure`, free of
6
+ * React so it is testable on its own. A burst of calls opens one dialog: they all get its answer.
7
+ */
8
+ export const makeAsker = (opts) => {
9
+ let inflight = null;
10
+ let resolver = null;
11
+ const run = async (args) => {
12
+ let view;
13
+ try {
14
+ view = await opts.load(...args);
15
+ }
16
+ catch {
17
+ return opts.skip;
18
+ }
19
+ if (!opts.required(view)) {
20
+ return opts.skip;
21
+ }
22
+ return await new Promise(resolve => {
23
+ resolver = resolve;
24
+ opts.show(view);
25
+ });
26
+ };
27
+ return {
28
+ ask: async (...args) => {
29
+ if (inflight == null) {
30
+ inflight = run(args).finally(() => {
31
+ inflight = null;
32
+ resolver = null;
33
+ });
34
+ }
35
+ return await inflight;
36
+ },
37
+ settle: answer => {
38
+ const resolve = resolver;
39
+ resolver = null;
40
+ resolve?.(answer);
41
+ },
42
+ waiting: () => resolver != null,
43
+ };
44
+ };
45
+ /** A gate over an `ensure` — what `useConsentGate` answers inside a `PerformanceConsentProvider`. */
46
+ export const makeConsentGate = (ensure) => ({
47
+ ensure,
48
+ withConsent: async (action) => {
49
+ try {
50
+ return await action();
51
+ }
52
+ catch (e) {
53
+ if (!isPerformanceConsentRefusal(e)) {
54
+ throw e;
55
+ }
56
+ if (!await ensure({ force: true })) {
57
+ throw new ConsentDeclined(ConsentKind.Performance);
58
+ }
59
+ return await action();
60
+ }
61
+ },
62
+ });
63
+ /**
64
+ * The gate outside any provider: nothing can be asked, so `ensure` answers `true` and a refusal
65
+ * propagates unchanged.
66
+ */
67
+ export const passThroughGate = {
68
+ ensure: async () => true,
69
+ withConsent: async (action) => await action(),
70
+ };
71
+ //# sourceMappingURL=ensure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure.js","sourceRoot":"","sources":["../../src/consumer/ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAyB3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAsC,EACjB,EAAE;IACvB,IAAI,QAAQ,GAA2B,IAAI,CAAA;IAC3C,IAAI,QAAQ,GAAsC,IAAI,CAAA;IAEtD,MAAM,GAAG,GAAG,KAAK,EAAE,IAAU,EAAmB,EAAE;QAChD,IAAI,IAAU,CAAA;QACd,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,IAAI,CAAA;QAClB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,CAAA;QAClB,CAAC;QAED,OAAO,MAAM,IAAI,OAAO,CAAS,OAAO,CAAC,EAAE;YACzC,QAAQ,GAAG,OAAO,CAAA;YAClB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE;YACrB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;gBACrB,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;oBAChC,QAAQ,GAAG,IAAI,CAAA;oBACf,QAAQ,GAAG,IAAI,CAAA;gBACjB,CAAC,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,MAAM,QAAQ,CAAA;QACvB,CAAC;QACD,MAAM,EAAE,MAAM,CAAC,EAAE;YACf,MAAM,OAAO,GAAG,QAAQ,CAAA;YACxB,QAAQ,GAAG,IAAI,CAAA;YACf,OAAO,EAAE,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,IAAI,IAAI;KAChC,CAAA;AACH,CAAC,CAAA;AAyBD,qGAAqG;AACrG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAA6B,EAAe,EAAE,CAAC,CAAC;IAC9E,MAAM;IACN,WAAW,EAAE,KAAK,EAAK,MAAwB,EAAc,EAAE;QAC7D,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,EAAE,CAAA;QACvB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpC,MAAM,CAAC,CAAA;YACT,CAAC;YACD,IAAI,CAAC,MAAM,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,eAAe,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;YACpD,CAAC;YAED,OAAO,MAAM,MAAM,EAAE,CAAA;QACvB,CAAC;IACH,CAAC;CACF,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAgB;IAC1C,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;IACxB,WAAW,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE,CAAC,MAAM,MAAM,EAAE;CAC5C,CAAA"}
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from 'react';
2
+ /** The shape the platform validates an e-mail address with (`@owlmeans/payment` `EmailSchema`). */
3
+ export declare const EMAIL_PATTERN: RegExp;
4
+ export declare const isEmail: (value: string) => boolean;
5
+ export interface FieldProps {
6
+ id: string;
7
+ label: string;
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ error?: string | null;
11
+ type?: 'text' | 'email' | 'date';
12
+ autoComplete?: string;
13
+ disabled?: boolean;
14
+ min?: string;
15
+ /** A `data-*` hook for tests (`data-withdrawal-field="name"`). */
16
+ hook?: Record<string, string>;
17
+ multiline?: boolean;
18
+ }
19
+ export declare const Field: ({ id, label, value, onChange, error, type, autoComplete, disabled, min, hook, multiline, }: FieldProps) => import("react").JSX.Element;
20
+ export interface ChoiceProps {
21
+ id: string;
22
+ name: string;
23
+ checked: boolean;
24
+ onSelect: () => void;
25
+ disabled?: boolean;
26
+ hook?: Record<string, string>;
27
+ children: ReactNode;
28
+ }
29
+ /** One option of a radio group — a native radio, so the group needs no extra primitive. */
30
+ export declare const Choice: ({ id, name, checked, onSelect, disabled, hook, children }: ChoiceProps) => import("react").JSX.Element;
31
+ export interface HoneypotProps {
32
+ id: string;
33
+ label: string;
34
+ value: string;
35
+ onChange: (value: string) => void;
36
+ }
37
+ /**
38
+ * A field a person never sees or reaches: off screen, out of the tab order and hidden from
39
+ * assistive technology, with a plausible name a form-filling bot completes. A filled one marks the
40
+ * declaration as a bot's, which the server answers like any other (it never says so).
41
+ */
42
+ export declare const Honeypot: ({ id, label, value, onChange }: HoneypotProps) => import("react").JSX.Element;
43
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/consumer/fields.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,mGAAmG;AACnG,eAAO,MAAM,aAAa,QAAsB,CAAA;AAEhD,eAAO,MAAM,OAAO,UAAW,MAAM,KAAG,OAAgF,CAAA;AAExH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,eAAO,MAAM,KAAK,+FAEf,UAAU,gCAkBP,CAAA;AAEN,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,2FAA2F;AAC3F,eAAO,MAAM,MAAM,8DAA+D,WAAW,gCASrF,CAAA;AAER,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,mCAAoC,aAAa,gCAOhE,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '@/lib/utils';
3
+ import { Input } from '@/components/ui/input';
4
+ import { Label } from '@/components/ui/label';
5
+ /** The shape the platform validates an e-mail address with (`@owlmeans/payment` `EmailSchema`). */
6
+ export const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+$/;
7
+ export const isEmail = (value) => value.length >= 3 && value.length <= 254 && EMAIL_PATTERN.test(value);
8
+ export const Field = ({ id, label, value, onChange, error, type = 'text', autoComplete, disabled, min, hook, multiline = false, }) => _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: id, children: label }), multiline
9
+ ? _jsx("textarea", { id: id, value: value, disabled: disabled, rows: 3, maxLength: 2000, "aria-invalid": error != null, "aria-describedby": error != null ? `${id}-error` : undefined, onChange: event => onChange(event.target.value), className: cn('border-input bg-background placeholder:text-muted-foreground w-full min-w-0 rounded-md border px-3 py-2 text-sm shadow-xs outline-none', 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:opacity-50 aria-invalid:border-destructive'), ...hook })
10
+ : _jsx(Input, { id: id, type: type, value: value, disabled: disabled, autoComplete: autoComplete, min: min, "aria-invalid": error != null, "aria-describedby": error != null ? `${id}-error` : undefined, onChange: event => onChange(event.target.value), ...hook }), error != null && _jsx("p", { id: `${id}-error`, className: "text-destructive text-xs", children: error })] });
11
+ /** One option of a radio group — a native radio, so the group needs no extra primitive. */
12
+ export const Choice = ({ id, name, checked, onSelect, disabled, hook, children }) => _jsxs("label", { htmlFor: id, className: cn('flex cursor-pointer items-start gap-3 rounded-md border p-3 text-sm', checked && 'border-primary bg-muted/30'), children: [_jsx("input", { id: id, type: "radio", name: name, checked: checked, disabled: disabled, onChange: onSelect, className: "accent-primary mt-0.5 size-4 shrink-0", ...hook }), _jsx("span", { className: "grid gap-0.5", children: children })] });
13
+ /**
14
+ * A field a person never sees or reaches: off screen, out of the tab order and hidden from
15
+ * assistive technology, with a plausible name a form-filling bot completes. A filled one marks the
16
+ * declaration as a bot's, which the server answers like any other (it never says so).
17
+ */
18
+ export const Honeypot = ({ id, label, value, onChange }) => _jsxs("div", { "aria-hidden": "true", "data-honeypot": "", style: { position: 'absolute', left: '-10000px', top: 'auto', width: 1, height: 1, overflow: 'hidden' }, children: [_jsx("label", { htmlFor: id, children: label }), _jsx("input", { id: id, name: "website", type: "text", tabIndex: -1, autoComplete: "off", value: value, onChange: event => onChange(event.target.value) })] });
19
+ //# sourceMappingURL=fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/consumer/fields.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { RegisteredEntrypoint, RequestShape } from '@owlmeans/entrypoint';
3
+ import type { CancellationBody, DeclarationReceipt, PerformanceConsentBody, PerformanceConsentResponse, PerformanceConsentView, SubscriptionStartBody, SubscriptionStartQuery, SubscriptionStartResponse, SubscriptionStartView, WithdrawalBody, WithdrawalCandidateList } from '@owlmeans/payment';
4
+ import { type EnsureOptions } from './ensure.js';
5
+ import type { LegalLinksSource, PerformanceConsentDialogProps, SubscriptionStartDialogProps } from './types.js';
6
+ /** A protocol whose request carries `body`: the record, withdraw and cancel routes. */
7
+ type BodyRequest<Body> = RequestShape & {
8
+ body: Body;
9
+ };
10
+ export interface UsePerformanceConsentOptions {
11
+ /** Read the view on mount, so `required` is known before anything asks. Default `false`. */
12
+ enabled?: boolean;
13
+ uiLanguage?: string;
14
+ links?: LegalLinksSource;
15
+ /** Open the withdrawal function; the consent dialog closes (declined) first. */
16
+ onWithdraw?: () => void;
17
+ /** After the consent was recorded — refresh an account feed, say. */
18
+ onRecorded?: (response: PerformanceConsentResponse) => void;
19
+ }
20
+ export interface PerformanceConsentControl {
21
+ view: PerformanceConsentView | null;
22
+ /** From the last read; `null` before any. */
23
+ required: boolean | null;
24
+ /**
25
+ * `true` when no consent is needed or it was confirmed and recorded, `false` on a decline or a
26
+ * closed dialog. Reads the view fresh; a failed read answers `true`. Concurrent calls share one
27
+ * pending answer — one dialog.
28
+ */
29
+ ensure: (opts?: EnsureOptions) => Promise<boolean>;
30
+ refresh: () => Promise<PerformanceConsentView | null>;
31
+ /** Spread into ONE `PerformanceConsentDialog`. */
32
+ dialog: PerformanceConsentDialogProps;
33
+ }
34
+ /**
35
+ * The spend consent over two protocols — the view (`consent` GET) and the record (`giveConsent`
36
+ * POST) of `makeConsumerRightsProtocols` — bound with `ctx.entrypoint(protocol)`.
37
+ */
38
+ export declare const usePerformanceConsent: <RecordRequest extends BodyRequest<PerformanceConsentBody>>(view: RegisteredEntrypoint<{}, PerformanceConsentView>, record: RegisteredEntrypoint<RecordRequest, PerformanceConsentResponse>, opts?: UsePerformanceConsentOptions) => PerformanceConsentControl;
39
+ export interface UseSubscriptionStartOptions {
40
+ uiLanguage?: string;
41
+ links?: LegalLinksSource;
42
+ onWithdraw?: () => void;
43
+ }
44
+ export interface SubscriptionStartParams {
45
+ /** The plan's title as the application names it — part of the statement. */
46
+ planTitle: string;
47
+ /** The price line the dialog shows above the statement. */
48
+ price?: ReactNode;
49
+ }
50
+ export interface SubscriptionStartControl {
51
+ /**
52
+ * The start request id to send with the subscription checkout (`CreateCheckoutBody.
53
+ * startRequestId`); `''` when none is needed (or the view could not be read — the checkout's
54
+ * own refusal then decides); `null` when the person declined or closed the dialog.
55
+ */
56
+ ensure: (planSku: string, params: SubscriptionStartParams) => Promise<string | null>;
57
+ view: SubscriptionStartView | null;
58
+ /** Spread into ONE `SubscriptionStartDialog`. */
59
+ dialog: SubscriptionStartDialogProps;
60
+ }
61
+ /**
62
+ * The subscription start request over the `start` GET (`?planSku`) and `requestStart` POST
63
+ * protocols, asked right before a subscription checkout.
64
+ */
65
+ export declare const useSubscriptionStart: <ViewRequest extends RequestShape & {
66
+ query: SubscriptionStartQuery;
67
+ }, RecordRequest extends BodyRequest<SubscriptionStartBody>>(view: RegisteredEntrypoint<ViewRequest, SubscriptionStartView>, record: RegisteredEntrypoint<RecordRequest, SubscriptionStartResponse>, opts?: UseSubscriptionStartOptions) => SubscriptionStartControl;
68
+ export interface DeclarationControl<Body, Receipt> {
69
+ /** Send the declaration; the revived receipt, or `null` when it failed (`error` holds why). */
70
+ submit: (body: Body) => Promise<Receipt | null>;
71
+ receipt: Receipt | null;
72
+ pending: boolean;
73
+ error: unknown;
74
+ /** Forget the receipt and the error — before the form is shown again. */
75
+ reset: () => void;
76
+ }
77
+ export interface WithdrawalControl<Receipt extends DeclarationReceipt> extends DeclarationControl<WithdrawalBody, Receipt> {
78
+ /** In-app: the contracts that can still be withdrawn from; `null` before the first read. */
79
+ list: WithdrawalCandidateList | null;
80
+ load: () => Promise<WithdrawalCandidateList | null>;
81
+ /** Spread into a `WithdrawalForm` / `WithdrawalDialog` (with `mode` and `language`). */
82
+ form: {
83
+ list: WithdrawalCandidateList | null;
84
+ pending: boolean;
85
+ error: boolean;
86
+ receipt: Receipt | null;
87
+ onSubmit: (body: WithdrawalBody) => Promise<Receipt | null>;
88
+ };
89
+ }
90
+ /**
91
+ * The withdrawal function over the `withdrawals` GET (in-app; `null` on the public page) and a
92
+ * `withdraw` POST — the account one answers a `WithdrawalReceipt`, the public one a
93
+ * `DeclarationReceipt`.
94
+ */
95
+ export declare const useWithdrawal: <Request extends BodyRequest<WithdrawalBody>, Receipt extends DeclarationReceipt>(list: RegisteredEntrypoint<{}, WithdrawalCandidateList> | null, withdraw: RegisteredEntrypoint<Request, Receipt>, opts?: {
96
+ enabled?: boolean;
97
+ }) => WithdrawalControl<Receipt>;
98
+ export interface CancellationControl<Receipt extends DeclarationReceipt> extends DeclarationControl<CancellationBody, Receipt> {
99
+ /** Spread into a `CancellationForm` (with `mode` and `language`). */
100
+ form: {
101
+ pending: boolean;
102
+ error: boolean;
103
+ receipt: Receipt | null;
104
+ onSubmit: (body: CancellationBody) => Promise<Receipt | null>;
105
+ };
106
+ }
107
+ /**
108
+ * The cancellation function over a `cancel` POST — the account one answers a
109
+ * `CancellationReceipt`, the public one a `DeclarationReceipt`.
110
+ */
111
+ export declare const useCancellation: <Request extends BodyRequest<CancellationBody>, Receipt extends DeclarationReceipt>(cancel: RegisteredEntrypoint<Request, Receipt>) => CancellationControl<Receipt>;
112
+ export {};
113
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/consumer/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { reviveConsentResponse, reviveConsentView, reviveReceipt, reviveStartResponse, reviveWithdrawalList, } from '@owlmeans/payment';
3
+ import { makeAsker } from './ensure.js';
4
+ import { isConsentRefusal } from './refusal.js';
5
+ /** One call argument built by the hook — the protocol's own `CallArguments`, completed here. */
6
+ const argsOf = (request) => [request];
7
+ /**
8
+ * The spend consent over two protocols — the view (`consent` GET) and the record (`giveConsent`
9
+ * POST) of `makeConsumerRightsProtocols` — bound with `ctx.entrypoint(protocol)`.
10
+ */
11
+ export const usePerformanceConsent = (view, record, opts = {}) => {
12
+ const viewRef = useRef(view);
13
+ viewRef.current = view;
14
+ const recordRef = useRef(record);
15
+ recordRef.current = record;
16
+ const optsRef = useRef(opts);
17
+ optsRef.current = opts;
18
+ const [current, setCurrent] = useState(null);
19
+ const [open, setOpen] = useState(false);
20
+ const [pending, setPending] = useState(false);
21
+ const [error, setError] = useState(false);
22
+ const load = useCallback(async () => {
23
+ const next = reviveConsentView(await viewRef.current.call());
24
+ setCurrent(next);
25
+ return next;
26
+ }, []);
27
+ const asker = useMemo(() => makeAsker({
28
+ load, required: next => next.required, skip: true,
29
+ show: () => { setError(false); setOpen(true); },
30
+ }), [load]);
31
+ useEffect(() => {
32
+ if (opts.enabled === true)
33
+ void load().catch(() => undefined);
34
+ }, [opts.enabled, load]);
35
+ const decline = useCallback(() => {
36
+ setOpen(false);
37
+ asker.settle(false);
38
+ }, [asker]);
39
+ const onConfirm = useCallback(async (body) => {
40
+ setPending(true);
41
+ setError(false);
42
+ try {
43
+ const response = reviveConsentResponse(await recordRef.current.call(...argsOf({ body })));
44
+ setCurrent(held => held == null ? held : { ...held, required: false, purchases: [] });
45
+ setOpen(false);
46
+ asker.settle(true);
47
+ optsRef.current.onRecorded?.(response);
48
+ }
49
+ catch (e) {
50
+ setError(true);
51
+ // A stale text version is refused with a 428: show the current wording to confirm again.
52
+ if (isConsentRefusal(e))
53
+ void load().catch(() => undefined);
54
+ }
55
+ finally {
56
+ setPending(false);
57
+ }
58
+ }, [asker, load]);
59
+ const refresh = useCallback(async () => {
60
+ try {
61
+ return await load();
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }, [load]);
67
+ const ensure = useCallback(async () => await asker.ask(), [asker]);
68
+ const onWithdraw = opts.onWithdraw;
69
+ const dialog = {
70
+ open, view: current, pending, error, onConfirm, onDecline: decline,
71
+ onOpenChange: next => { if (next)
72
+ setOpen(true);
73
+ else
74
+ decline(); },
75
+ uiLanguage: opts.uiLanguage,
76
+ links: opts.links,
77
+ onWithdraw: onWithdraw != null ? () => { decline(); onWithdraw(); } : undefined,
78
+ };
79
+ return { view: current, required: current?.required ?? null, ensure, refresh, dialog };
80
+ };
81
+ /**
82
+ * The subscription start request over the `start` GET (`?planSku`) and `requestStart` POST
83
+ * protocols, asked right before a subscription checkout.
84
+ */
85
+ export const useSubscriptionStart = (view, record, opts = {}) => {
86
+ const viewRef = useRef(view);
87
+ viewRef.current = view;
88
+ const recordRef = useRef(record);
89
+ recordRef.current = record;
90
+ const [current, setCurrent] = useState(null);
91
+ const [params, setParams] = useState({ planTitle: '' });
92
+ const [open, setOpen] = useState(false);
93
+ const [pending, setPending] = useState(false);
94
+ const [error, setError] = useState(false);
95
+ const asker = useMemo(() => makeAsker({
96
+ load: async (planSku, next) => {
97
+ const answer = await viewRef.current.call(...argsOf({ query: { planSku } }));
98
+ setCurrent(answer);
99
+ setParams(next);
100
+ return answer;
101
+ },
102
+ required: next => next.required,
103
+ skip: '',
104
+ show: () => { setError(false); setOpen(true); },
105
+ }), []);
106
+ const decline = useCallback(() => {
107
+ setOpen(false);
108
+ asker.settle(null);
109
+ }, [asker]);
110
+ const onConfirm = useCallback(async (body) => {
111
+ setPending(true);
112
+ setError(false);
113
+ try {
114
+ const response = reviveStartResponse(await recordRef.current.call(...argsOf({ body })));
115
+ setOpen(false);
116
+ asker.settle(response.startRequestId);
117
+ }
118
+ catch {
119
+ setError(true);
120
+ }
121
+ finally {
122
+ setPending(false);
123
+ }
124
+ }, [asker]);
125
+ const ensure = useCallback(async (planSku, next) => await asker.ask(planSku, next), [asker]);
126
+ const onWithdraw = opts.onWithdraw;
127
+ const dialog = {
128
+ open, view: current, planTitle: params.planTitle, price: params.price, pending, error, onConfirm,
129
+ onDecline: decline,
130
+ onOpenChange: next => { if (next)
131
+ setOpen(true);
132
+ else
133
+ decline(); },
134
+ uiLanguage: opts.uiLanguage,
135
+ links: opts.links,
136
+ onWithdraw: onWithdraw != null ? () => { decline(); onWithdraw(); } : undefined,
137
+ };
138
+ return { ensure, view: current, dialog };
139
+ };
140
+ /** The submit half both statutory functions share: one protocol, one receipt. */
141
+ const useDeclaration = (entry) => {
142
+ const entryRef = useRef(entry);
143
+ entryRef.current = entry;
144
+ const [receipt, setReceipt] = useState(null);
145
+ const [pending, setPending] = useState(false);
146
+ const [error, setError] = useState(null);
147
+ const submit = useCallback(async (body) => {
148
+ setPending(true);
149
+ setError(null);
150
+ try {
151
+ const answer = reviveReceipt(await entryRef.current.call(...argsOf({ body })));
152
+ setReceipt(answer);
153
+ return answer;
154
+ }
155
+ catch (e) {
156
+ setError(e);
157
+ return null;
158
+ }
159
+ finally {
160
+ setPending(false);
161
+ }
162
+ }, []);
163
+ const reset = useCallback(() => {
164
+ setReceipt(null);
165
+ setError(null);
166
+ }, []);
167
+ return { submit, receipt, pending, error, reset };
168
+ };
169
+ /**
170
+ * The withdrawal function over the `withdrawals` GET (in-app; `null` on the public page) and a
171
+ * `withdraw` POST — the account one answers a `WithdrawalReceipt`, the public one a
172
+ * `DeclarationReceipt`.
173
+ */
174
+ export const useWithdrawal = (list, withdraw, opts = {}) => {
175
+ const listRef = useRef(list);
176
+ listRef.current = list;
177
+ const [candidates, setCandidates] = useState(null);
178
+ const declaration = useDeclaration(withdraw);
179
+ const load = useCallback(async () => {
180
+ if (listRef.current == null) {
181
+ return null;
182
+ }
183
+ try {
184
+ const next = reviveWithdrawalList(await listRef.current.call());
185
+ setCandidates(next);
186
+ return next;
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }, []);
192
+ useEffect(() => {
193
+ if (opts.enabled === true)
194
+ void load();
195
+ }, [opts.enabled, load]);
196
+ return {
197
+ ...declaration,
198
+ list: candidates,
199
+ load,
200
+ form: {
201
+ list: candidates,
202
+ pending: declaration.pending,
203
+ error: declaration.error != null,
204
+ receipt: declaration.receipt,
205
+ onSubmit: declaration.submit,
206
+ },
207
+ };
208
+ };
209
+ /**
210
+ * The cancellation function over a `cancel` POST — the account one answers a
211
+ * `CancellationReceipt`, the public one a `DeclarationReceipt`.
212
+ */
213
+ export const useCancellation = (cancel) => {
214
+ const declaration = useDeclaration(cancel);
215
+ return {
216
+ ...declaration,
217
+ form: {
218
+ pending: declaration.pending,
219
+ error: declaration.error != null,
220
+ receipt: declaration.receipt,
221
+ onSubmit: declaration.submit,
222
+ },
223
+ };
224
+ };
225
+ //# sourceMappingURL=hooks.js.map