@owlmeans/web-payment 0.1.18-rc.2 → 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.
- package/README.md +30 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-payment/SKILL.md +287 -20
- package/build/@/components/ui/card.d.ts +10 -0
- package/build/@/components/ui/card.d.ts.map +1 -0
- package/build/@/components/ui/card.js +25 -0
- package/build/@/components/ui/card.js.map +1 -0
- package/build/@/components/ui/checkbox.d.ts +5 -0
- package/build/@/components/ui/checkbox.d.ts.map +1 -0
- package/build/@/components/ui/checkbox.js +6 -0
- package/build/@/components/ui/checkbox.js.map +1 -0
- package/build/@/components/ui/progress.d.ts +5 -0
- package/build/@/components/ui/progress.d.ts.map +1 -0
- package/build/@/components/ui/progress.js +11 -0
- package/build/@/components/ui/progress.js.map +1 -0
- package/build/@/components/ui/select.d.ts +8 -0
- package/build/@/components/ui/select.d.ts.map +1 -0
- package/build/@/components/ui/select.js +9 -0
- package/build/@/components/ui/select.js.map +1 -0
- package/build/capability-list.d.ts +7 -0
- package/build/capability-list.d.ts.map +1 -0
- package/build/capability-list.js +24 -0
- package/build/capability-list.js.map +1 -0
- package/build/checkout-limit.d.ts +11 -0
- package/build/checkout-limit.d.ts.map +1 -0
- package/build/checkout-limit.js +34 -0
- package/build/checkout-limit.js.map +1 -0
- package/build/consumer/cancellation.d.ts +20 -0
- package/build/consumer/cancellation.d.ts.map +1 -0
- package/build/consumer/cancellation.js +115 -0
- package/build/consumer/cancellation.js.map +1 -0
- package/build/consumer/copy.d.ts +23 -0
- package/build/consumer/copy.d.ts.map +1 -0
- package/build/consumer/copy.js +75 -0
- package/build/consumer/copy.js.map +1 -0
- package/build/consumer/ensure.d.ts +56 -0
- package/build/consumer/ensure.d.ts.map +1 -0
- package/build/consumer/ensure.js +71 -0
- package/build/consumer/ensure.js.map +1 -0
- package/build/consumer/fields.d.ts +43 -0
- package/build/consumer/fields.d.ts.map +1 -0
- package/build/consumer/fields.js +19 -0
- package/build/consumer/fields.js.map +1 -0
- package/build/consumer/hooks.d.ts +113 -0
- package/build/consumer/hooks.d.ts.map +1 -0
- package/build/consumer/hooks.js +225 -0
- package/build/consumer/hooks.js.map +1 -0
- package/build/consumer/index.d.ts +14 -0
- package/build/consumer/index.d.ts.map +1 -0
- package/build/consumer/index.js +12 -0
- package/build/consumer/index.js.map +1 -0
- package/build/consumer/legal.d.ts +53 -0
- package/build/consumer/legal.d.ts.map +1 -0
- package/build/consumer/legal.js +77 -0
- package/build/consumer/legal.js.map +1 -0
- package/build/consumer/performance-consent-dialog.d.ts +18 -0
- package/build/consumer/performance-consent-dialog.d.ts.map +1 -0
- package/build/consumer/performance-consent-dialog.js +71 -0
- package/build/consumer/performance-consent-dialog.js.map +1 -0
- package/build/consumer/provider.d.ts +33 -0
- package/build/consumer/provider.d.ts.map +1 -0
- package/build/consumer/provider.js +26 -0
- package/build/consumer/provider.js.map +1 -0
- package/build/consumer/refusal.d.ts +35 -0
- package/build/consumer/refusal.d.ts.map +1 -0
- package/build/consumer/refusal.js +98 -0
- package/build/consumer/refusal.js.map +1 -0
- package/build/consumer/subscription-start-dialog.d.ts +16 -0
- package/build/consumer/subscription-start-dialog.d.ts.map +1 -0
- package/build/consumer/subscription-start-dialog.js +43 -0
- package/build/consumer/subscription-start-dialog.js.map +1 -0
- package/build/consumer/types.d.ts +114 -0
- package/build/consumer/types.d.ts.map +1 -0
- package/build/consumer/types.js +2 -0
- package/build/consumer/types.js.map +1 -0
- package/build/consumer/withdrawal.d.ts +29 -0
- package/build/consumer/withdrawal.d.ts.map +1 -0
- package/build/consumer/withdrawal.js +140 -0
- package/build/consumer/withdrawal.js.map +1 -0
- package/build/copy.d.ts +14 -0
- package/build/copy.d.ts.map +1 -0
- package/build/copy.js +51 -0
- package/build/copy.js.map +1 -0
- package/build/dialog.d.ts +1 -1
- package/build/dialog.d.ts.map +1 -1
- package/build/dialog.js +46 -17
- package/build/dialog.js.map +1 -1
- package/build/entitlement.d.ts +26 -0
- package/build/entitlement.d.ts.map +1 -0
- package/build/entitlement.js +42 -0
- package/build/entitlement.js.map +1 -0
- package/build/estimate-cache.d.ts +14 -0
- package/build/estimate-cache.d.ts.map +1 -0
- package/build/estimate-cache.js +39 -0
- package/build/estimate-cache.js.map +1 -0
- package/build/estimate-summary.d.ts +64 -0
- package/build/estimate-summary.d.ts.map +1 -0
- package/build/estimate-summary.js +94 -0
- package/build/estimate-summary.js.map +1 -0
- package/build/estimate.d.ts +34 -0
- package/build/estimate.d.ts.map +1 -0
- package/build/estimate.js +92 -0
- package/build/estimate.js.map +1 -0
- package/build/format.d.ts +15 -0
- package/build/format.d.ts.map +1 -0
- package/build/format.js +68 -0
- package/build/format.js.map +1 -0
- package/build/hooks.d.ts +0 -4
- package/build/hooks.d.ts.map +1 -1
- package/build/hooks.js +3 -30
- package/build/hooks.js.map +1 -1
- package/build/i18n/be.json +1 -1
- package/build/i18n/de.json +1 -1
- package/build/i18n/en.json +1 -1
- package/build/i18n/es.json +1 -1
- package/build/i18n/fr.json +145 -0
- package/build/i18n/pl.json +1 -1
- package/build/i18n/ru.json +1 -1
- package/build/i18n/uk.json +1 -1
- package/build/i18n.d.ts +2 -1
- package/build/i18n.d.ts.map +1 -1
- package/build/i18n.js +11 -7
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +9 -0
- package/build/index.js.map +1 -1
- package/build/limit-meter.d.ts +7 -0
- package/build/limit-meter.d.ts.map +1 -0
- package/build/limit-meter.js +26 -0
- package/build/limit-meter.js.map +1 -0
- package/build/plan-card.d.ts +7 -0
- package/build/plan-card.d.ts.map +1 -0
- package/build/plan-card.js +29 -0
- package/build/plan-card.js.map +1 -0
- package/build/poll.d.ts +7 -0
- package/build/poll.d.ts.map +1 -0
- package/build/poll.js +28 -0
- package/build/poll.js.map +1 -0
- package/build/selectors.d.ts +18 -0
- package/build/selectors.d.ts.map +1 -0
- package/build/selectors.js +78 -0
- package/build/selectors.js.map +1 -0
- package/build/types.d.ts +120 -2
- package/build/types.d.ts.map +1 -1
- package/package.json +22 -7
- package/src/@/components/ui/card.tsx +93 -0
- package/src/@/components/ui/checkbox.tsx +23 -0
- package/src/@/components/ui/progress.tsx +44 -0
- package/src/@/components/ui/select.tsx +62 -0
- package/src/@/globals.css +10 -0
- package/src/capability-list.tsx +50 -0
- package/src/checkout-limit.tsx +48 -0
- package/src/consumer/cancellation.tsx +214 -0
- package/src/consumer/copy.ts +93 -0
- package/src/consumer/ensure.ts +124 -0
- package/src/consumer/fields.tsx +89 -0
- package/src/consumer/hooks.ts +358 -0
- package/src/consumer/index.ts +13 -0
- package/src/consumer/legal.tsx +129 -0
- package/src/consumer/performance-consent-dialog.tsx +132 -0
- package/src/consumer/provider.tsx +49 -0
- package/src/consumer/refusal.ts +110 -0
- package/src/consumer/subscription-start-dialog.tsx +87 -0
- package/src/consumer/types.ts +121 -0
- package/src/consumer/withdrawal.tsx +279 -0
- package/src/copy.tsx +62 -0
- package/src/dialog.tsx +58 -25
- package/src/entitlement.ts +56 -0
- package/src/estimate-cache.ts +40 -0
- package/src/estimate-summary.tsx +172 -0
- package/src/estimate.ts +119 -0
- package/src/format.ts +71 -0
- package/src/hooks.ts +3 -35
- package/src/i18n/be.json +1 -1
- package/src/i18n/de.json +1 -1
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +1 -1
- package/src/i18n/fr.json +145 -0
- package/src/i18n/pl.json +1 -1
- package/src/i18n/ru.json +1 -1
- package/src/i18n/uk.json +1 -1
- package/src/i18n.ts +12 -7
- package/src/index.ts +9 -0
- package/src/limit-meter.tsx +55 -0
- package/src/plan-card.tsx +67 -0
- package/src/poll.ts +27 -0
- package/src/selectors.ts +93 -0
- package/src/types.ts +142 -2
- package/tests/consent-refusal.spec.ts +83 -0
- package/tests/consumer-hooks.spec.ts +205 -0
- package/tests/consumer.spec.ts +343 -0
- package/tests/context.ts +9 -0
- package/tests/dialog.spec.ts +49 -0
- package/tests/entitlement.spec.ts +117 -0
- package/tests/estimate-cache.spec.ts +46 -0
- package/tests/harness/consumer-fixtures.ts +105 -0
- package/tests/harness/consumer.tsx +197 -0
- package/tests/harness/entitlement.tsx +159 -0
- package/tests/harness/estimate-fixtures.ts +48 -0
- package/tests/harness/mount.tsx +19 -3
- package/tests/i18n.spec.ts +78 -8
- package/tests/pieces.spec.ts +134 -0
package/build/types.d.ts
CHANGED
|
@@ -1,10 +1,128 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { CallOptions, RequestShape } from '@owlmeans/entrypoint';
|
|
3
|
+
import type { AmountCheckoutPolicy, CapabilityView, CheckoutLimitView, CreateCheckoutResponse, EntitlementPlanView, LimitView, PortalLinkBody, PriceEstimate, PriceEstimateBody } from '@owlmeans/payment';
|
|
2
4
|
export type CheckoutResult = CreateCheckoutResponse;
|
|
3
5
|
export interface AmountCheckoutDialogProps {
|
|
4
6
|
open: boolean;
|
|
5
7
|
onOpenChange: (open: boolean) => void;
|
|
8
|
+
/** The plan's policy — or one already narrowed for the entity (`AmountPolicyView.policy`). */
|
|
6
9
|
policy: AmountCheckoutPolicy;
|
|
7
10
|
pending?: boolean;
|
|
8
|
-
|
|
11
|
+
/** Chosen amount and, where tax estimation collected it, the billing country. */
|
|
12
|
+
onConfirm: (amountMinor: number, country?: string) => Promise<void> | void;
|
|
13
|
+
/** A live tax/currency estimate for the credit line — absent: the plain "tax at checkout" note. */
|
|
14
|
+
estimate?: PriceEstimateControl;
|
|
15
|
+
/**
|
|
16
|
+
* What the entity may buy now (`AmountPolicyView.limit`). The dialog narrows `policy` with it
|
|
17
|
+
* through `narrowAmountPolicy` — presets above the maximum disappear, the default is clamped —
|
|
18
|
+
* shows a `CheckoutLimitNote`, and on `blocked` disables the input and the confirm button.
|
|
19
|
+
*/
|
|
20
|
+
limit?: CheckoutLimitView | null;
|
|
21
|
+
/** Legal text the application places right above the buttons (a withdrawal note, terms links). */
|
|
22
|
+
legalNote?: ReactNode;
|
|
9
23
|
}
|
|
24
|
+
/** What `usePriceEstimate` returns: the latest answer, the chosen country, and its lifecycle. */
|
|
25
|
+
export interface PriceEstimateControl {
|
|
26
|
+
estimate: PriceEstimate | null;
|
|
27
|
+
/** ISO 3166-1 alpha-2, or `''` before a country is known. */
|
|
28
|
+
country: string;
|
|
29
|
+
/** A request is in flight — including the first, silent one. */
|
|
30
|
+
loading: boolean;
|
|
31
|
+
/** The last request failed; `estimate` is the previous good answer, if any. */
|
|
32
|
+
failed: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The country is the entity's locked billing country (an answer with `locked` or `source:
|
|
35
|
+
* 'profile'`): a picker shows it disabled, and `onCountryChange` does nothing.
|
|
36
|
+
*/
|
|
37
|
+
locked?: boolean;
|
|
38
|
+
onCountryChange: (country: string) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface CheckoutLimitNoteProps {
|
|
41
|
+
/** `null`, or a limit that narrowed nothing, renders nothing. */
|
|
42
|
+
limit: CheckoutLimitView | null | undefined;
|
|
43
|
+
/** Replaces the package's sentence for the limit's `reason` (`per-purchase`, `window`, `hold`). */
|
|
44
|
+
reasonLabel?: string | ((reason: string) => string | null | undefined);
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
/** The request of a price-estimate protocol: its body is a `PriceEstimateBody`. */
|
|
48
|
+
export type PriceEstimateRequest = RequestShape & {
|
|
49
|
+
body: PriceEstimateBody;
|
|
50
|
+
};
|
|
51
|
+
type PriceEstimateCall<Request extends PriceEstimateRequest> = Omit<Request, 'body'> & {
|
|
52
|
+
body?: Omit<Request['body'], 'country'>;
|
|
53
|
+
};
|
|
54
|
+
/** A price-estimate protocol's call arguments with `body.country` left to `usePriceEstimate`. */
|
|
55
|
+
export type PriceEstimateArguments<Request extends PriceEstimateRequest> = {} extends PriceEstimateCall<Request> ? [request?: PriceEstimateCall<Request> & CallOptions] : [request: PriceEstimateCall<Request> & CallOptions];
|
|
56
|
+
/** A limit row plus what a UI derives from it. */
|
|
57
|
+
export interface LimitStatus extends LimitView {
|
|
58
|
+
/** No room for one more unit — including a limit that is not included (`limit: 0`). */
|
|
59
|
+
exhausted: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* How full the limit is, clamped to `[0, 1]`. A `0` ceiling reads `1` once anything is used and
|
|
62
|
+
* `0` otherwise: the ratio says how full, `exhausted` says whether anything is left.
|
|
63
|
+
*/
|
|
64
|
+
ratio: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What a plan's status line says, in precedence order: an inactive or blocked status, paused,
|
|
68
|
+
* suspended, trial, past due, cancellation scheduled, free, renews, active.
|
|
69
|
+
*/
|
|
70
|
+
export type PlanStatusLineKind = 'created' | 'canceled' | 'expired' | 'ended' | 'blocked' | 'paused' | 'suspended' | 'trial' | 'past-due' | 'cancel-scheduled' | 'free' | 'renews' | 'active';
|
|
71
|
+
/** How a status line should be coloured: entitled and fine, needs attention, revoked, or over. */
|
|
72
|
+
export type PlanStatusTone = 'ok' | 'warning' | 'critical' | 'inactive';
|
|
73
|
+
export interface PlanStatusLine {
|
|
74
|
+
kind: PlanStatusLineKind;
|
|
75
|
+
tone: PlanStatusTone;
|
|
76
|
+
/** The date the line names — trial end, cancellation, pause start or renewal. */
|
|
77
|
+
date?: Date;
|
|
78
|
+
}
|
|
79
|
+
/** How a promo is inscribed: free until its end, kept by a grandfathered plan, or over. */
|
|
80
|
+
export type PromoInscription = 'free-until' | 'grandfathered' | 'ended';
|
|
81
|
+
/** The request of a portal protocol: its body is a `PortalLinkBody`. */
|
|
82
|
+
export type PortalRequest = RequestShape & {
|
|
83
|
+
body: PortalLinkBody;
|
|
84
|
+
};
|
|
85
|
+
type PortalCall<Request extends PortalRequest> = Omit<Request, 'body'> & {
|
|
86
|
+
body?: Omit<Request['body'], 'flow'>;
|
|
87
|
+
};
|
|
88
|
+
/** A portal protocol's call arguments with `body.flow` left to `usePortal`. */
|
|
89
|
+
export type PortalArguments<Request extends PortalRequest> = {} extends PortalCall<Request> ? [request?: PortalCall<Request> & CallOptions] : [request: PortalCall<Request> & CallOptions];
|
|
90
|
+
/** A plan offered next to the entity's own — an upgrade, a downgrade, a plan comparison column. */
|
|
91
|
+
export interface PlanOffer {
|
|
92
|
+
sku: string;
|
|
93
|
+
title: string;
|
|
94
|
+
/** The price as the application formats it (`$20 / month`); the package never formats prices. */
|
|
95
|
+
priceLabel: string;
|
|
96
|
+
highlight?: boolean;
|
|
97
|
+
}
|
|
98
|
+
export interface PlanCardProps {
|
|
99
|
+
/** The entity's effective plan. */
|
|
100
|
+
plan: EntitlementPlanView;
|
|
101
|
+
/** Render an offered plan instead; the status line then shows only when the offer is current. */
|
|
102
|
+
offer?: PlanOffer;
|
|
103
|
+
/** Defaults to `offer.sku === plan.sku` when an offer is given. */
|
|
104
|
+
current?: boolean;
|
|
105
|
+
pending?: boolean;
|
|
106
|
+
actionLabel?: string;
|
|
107
|
+
/** Called with the rendered sku (`offer.sku`, else `plan.sku`). */
|
|
108
|
+
onAction?: (sku: string) => Promise<void> | void;
|
|
109
|
+
className?: string;
|
|
110
|
+
children?: ReactNode;
|
|
111
|
+
}
|
|
112
|
+
export interface LimitMeterProps {
|
|
113
|
+
limit: LimitView;
|
|
114
|
+
label: string;
|
|
115
|
+
/** Show when a window limit resets. Default `true`. */
|
|
116
|
+
showReset?: boolean;
|
|
117
|
+
compact?: boolean;
|
|
118
|
+
className?: string;
|
|
119
|
+
}
|
|
120
|
+
export interface CapabilityListProps {
|
|
121
|
+
capabilities: CapabilityView[];
|
|
122
|
+
/** Product copy by capability `param`; a capability without a label is not rendered. */
|
|
123
|
+
labels: Record<string, string>;
|
|
124
|
+
onlyGranted?: boolean;
|
|
125
|
+
className?: string;
|
|
126
|
+
}
|
|
127
|
+
export {};
|
|
10
128
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-payment",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Protocol-bound payment hooks and themed amount checkout UI for OwlMeans web applications.",
|
|
6
6
|
"type": "module",
|
|
@@ -20,18 +20,30 @@
|
|
|
20
20
|
"default": "./build/index.js",
|
|
21
21
|
"module": "./build/index.js",
|
|
22
22
|
"types": "./build/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./consumer": {
|
|
25
|
+
"import": "./build/consumer/index.js",
|
|
26
|
+
"require": "./build/consumer/index.js",
|
|
27
|
+
"default": "./build/consumer/index.js",
|
|
28
|
+
"module": "./build/consumer/index.js",
|
|
29
|
+
"types": "./build/consumer/index.d.ts"
|
|
23
30
|
}
|
|
24
31
|
},
|
|
25
32
|
"dependencies": {
|
|
26
|
-
"@owlmeans/
|
|
27
|
-
"@owlmeans/client-
|
|
28
|
-
"@owlmeans/
|
|
29
|
-
"@owlmeans/
|
|
30
|
-
"@owlmeans/
|
|
33
|
+
"@owlmeans/api": "^0.1.18-rc.37",
|
|
34
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.39",
|
|
35
|
+
"@owlmeans/client-payment": "^0.1.18-rc.46",
|
|
36
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.36",
|
|
37
|
+
"@owlmeans/error": "^0.1.18-rc.35",
|
|
38
|
+
"@owlmeans/i18n": "^0.1.18-rc.36",
|
|
39
|
+
"@owlmeans/payment": "^0.1.18-rc.38"
|
|
31
40
|
},
|
|
32
41
|
"peerDependencies": {
|
|
42
|
+
"@radix-ui/react-checkbox": "*",
|
|
33
43
|
"@radix-ui/react-dialog": "*",
|
|
34
44
|
"@radix-ui/react-label": "*",
|
|
45
|
+
"@radix-ui/react-progress": "*",
|
|
46
|
+
"@radix-ui/react-select": "*",
|
|
35
47
|
"@radix-ui/react-slot": "*",
|
|
36
48
|
"class-variance-authority": "*",
|
|
37
49
|
"clsx": "*",
|
|
@@ -42,9 +54,12 @@
|
|
|
42
54
|
},
|
|
43
55
|
"devDependencies": {
|
|
44
56
|
"@owlmeans/dep-config": "workspace:*",
|
|
45
|
-
"@owlmeans/test-ui": "^0.1.18-rc.
|
|
57
|
+
"@owlmeans/test-ui": "^0.1.18-rc.38",
|
|
58
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
46
59
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
47
60
|
"@radix-ui/react-label": "^2.1.0",
|
|
61
|
+
"@radix-ui/react-progress": "^1.1.0",
|
|
62
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
48
63
|
"@radix-ui/react-slot": "^1.1.0",
|
|
49
64
|
"@vitejs/plugin-react": "^6.0.3",
|
|
50
65
|
"@types/bun": "^1.4.0",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// shadcn card — sourced from shadcn (new-york) 2026-05-23
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
data-slot="card"
|
|
10
|
+
className={cn(
|
|
11
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="card-header"
|
|
23
|
+
className={cn(
|
|
24
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
data-slot="card-title"
|
|
36
|
+
className={cn("leading-none font-semibold", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
data-slot="card-description"
|
|
46
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
data-slot="card-action"
|
|
56
|
+
className={cn(
|
|
57
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
data-slot="card-content"
|
|
69
|
+
className={cn("px-6", className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
data-slot="card-footer"
|
|
79
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
Card,
|
|
87
|
+
CardHeader,
|
|
88
|
+
CardFooter,
|
|
89
|
+
CardTitle,
|
|
90
|
+
CardAction,
|
|
91
|
+
CardDescription,
|
|
92
|
+
CardContent,
|
|
93
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
/** The shadcn checkbox, with an inline check mark instead of an icon library. */
|
|
6
|
+
export const Checkbox = ({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) =>
|
|
7
|
+
<CheckboxPrimitive.Root
|
|
8
|
+
data-slot="checkbox"
|
|
9
|
+
className={cn(
|
|
10
|
+
'peer border-input size-4 shrink-0 cursor-pointer rounded-[4px] border shadow-xs outline-none transition-shadow',
|
|
11
|
+
'data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary',
|
|
12
|
+
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="flex items-center justify-center text-current">
|
|
18
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" className="size-3.5" fill="none" stroke="currentColor"
|
|
19
|
+
strokeWidth={3} strokeLinecap="round" strokeLinejoin="round">
|
|
20
|
+
<path d="M20 6 9 17l-5-5" />
|
|
21
|
+
</svg>
|
|
22
|
+
</CheckboxPrimitive.Indicator>
|
|
23
|
+
</CheckboxPrimitive.Root>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// shadcn progress — sourced from shadcn (new-york) 2026-05-23
|
|
2
|
+
// Extended with `indeterminate` mode: when `value === undefined` (or not passed),
|
|
3
|
+
// the indicator animates left-to-right continuously via the
|
|
4
|
+
// `--animate-progress-indeterminate` token defined in globals.css.
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress'
|
|
7
|
+
|
|
8
|
+
import { cn } from '@/lib/utils'
|
|
9
|
+
|
|
10
|
+
function Progress({
|
|
11
|
+
className,
|
|
12
|
+
value,
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
|
15
|
+
const indeterminate = value == null
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ProgressPrimitive.Root
|
|
19
|
+
data-slot="progress"
|
|
20
|
+
className={cn(
|
|
21
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
value={indeterminate ? undefined : value}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
<ProgressPrimitive.Indicator
|
|
28
|
+
data-slot="progress-indicator"
|
|
29
|
+
data-state={indeterminate ? 'indeterminate' : 'determinate'}
|
|
30
|
+
className={cn(
|
|
31
|
+
"bg-primary h-full w-full flex-1 transition-all",
|
|
32
|
+
indeterminate && "absolute inset-y-0 left-0 w-1/3 animate-[progress-indeterminate_1.5s_linear_infinite]"
|
|
33
|
+
)}
|
|
34
|
+
style={
|
|
35
|
+
indeterminate
|
|
36
|
+
? undefined
|
|
37
|
+
: { transform: `translateX(-${100 - (value || 0)}%)` }
|
|
38
|
+
}
|
|
39
|
+
/>
|
|
40
|
+
</ProgressPrimitive.Root>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { Progress }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
export const Select = (props: React.ComponentProps<typeof SelectPrimitive.Root>) => <SelectPrimitive.Root {...props} />
|
|
6
|
+
export const SelectValue = (props: React.ComponentProps<typeof SelectPrimitive.Value>) => <SelectPrimitive.Value {...props} />
|
|
7
|
+
|
|
8
|
+
export const SelectTrigger = ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger>) => (
|
|
9
|
+
<SelectPrimitive.Trigger
|
|
10
|
+
className={cn(
|
|
11
|
+
'border-input data-[placeholder]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50',
|
|
12
|
+
'flex h-9 w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs',
|
|
13
|
+
'outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
<SelectPrimitive.Icon asChild>
|
|
20
|
+
<span aria-hidden="true" className="text-muted-foreground shrink-0">▾</span>
|
|
21
|
+
</SelectPrimitive.Icon>
|
|
22
|
+
</SelectPrimitive.Trigger>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
export const SelectContent = ({ className, children, position = 'popper', ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) => (
|
|
26
|
+
<SelectPrimitive.Portal>
|
|
27
|
+
<SelectPrimitive.Content
|
|
28
|
+
position={position}
|
|
29
|
+
className={cn(
|
|
30
|
+
'bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-y-auto rounded-md border shadow-md',
|
|
31
|
+
position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<SelectPrimitive.ScrollUpButton className="flex items-center justify-center py-1">
|
|
37
|
+
<span aria-hidden="true">▴</span>
|
|
38
|
+
</SelectPrimitive.ScrollUpButton>
|
|
39
|
+
<SelectPrimitive.Viewport className={cn('p-1', position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]')}>
|
|
40
|
+
{children}
|
|
41
|
+
</SelectPrimitive.Viewport>
|
|
42
|
+
<SelectPrimitive.ScrollDownButton className="flex items-center justify-center py-1">
|
|
43
|
+
<span aria-hidden="true">▾</span>
|
|
44
|
+
</SelectPrimitive.ScrollDownButton>
|
|
45
|
+
</SelectPrimitive.Content>
|
|
46
|
+
</SelectPrimitive.Portal>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export const SelectItem = ({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) => (
|
|
50
|
+
<SelectPrimitive.Item
|
|
51
|
+
className={cn(
|
|
52
|
+
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
58
|
+
<SelectPrimitive.ItemIndicator aria-hidden="true">✓</SelectPrimitive.ItemIndicator>
|
|
59
|
+
</span>
|
|
60
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
61
|
+
</SelectPrimitive.Item>
|
|
62
|
+
)
|
package/src/@/globals.css
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
/* The indeterminate mode of the private `progress` primitive (a `value`-less bar). */
|
|
5
|
+
--animate-progress-indeterminate: progress-indeterminate 1.5s linear infinite;
|
|
6
|
+
|
|
7
|
+
@keyframes progress-indeterminate {
|
|
8
|
+
0% { transform: translateX(-100%); }
|
|
9
|
+
100% { transform: translateX(100%); }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CapabilityView } from '@owlmeans/payment'
|
|
2
|
+
import { PromoNote, useEntitlementCopy } from './copy.js'
|
|
3
|
+
import type { CapabilityListProps } from './types.js'
|
|
4
|
+
|
|
5
|
+
const classes = (...names: Array<string | false | null | undefined>): string =>
|
|
6
|
+
names.filter(Boolean).join(' ')
|
|
7
|
+
|
|
8
|
+
/** One row per param, in view order: granted when any row grants it, the promo of that row. */
|
|
9
|
+
const collapse = (capabilities: CapabilityView[]): CapabilityView[] => {
|
|
10
|
+
const rows = new Map<string, CapabilityView>()
|
|
11
|
+
for (const capability of capabilities) {
|
|
12
|
+
const seen = rows.get(capability.param)
|
|
13
|
+
if (seen == null || (!seen.granted && capability.granted)) {
|
|
14
|
+
rows.set(capability.param, capability)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return [...rows.values()]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The capabilities of a view, labelled by the application. A capability without a label is not
|
|
23
|
+
* rendered, so a view may carry grants a screen has nothing to say about.
|
|
24
|
+
*/
|
|
25
|
+
export const CapabilityList = ({ capabilities, labels, onlyGranted = false, className }: CapabilityListProps) => {
|
|
26
|
+
const copy = useEntitlementCopy()
|
|
27
|
+
const rows = collapse(capabilities).filter(capability =>
|
|
28
|
+
(labels[capability.param] ?? '') !== '' && (!onlyGranted || capability.granted))
|
|
29
|
+
|
|
30
|
+
return <ul data-capability-list="" className={classes('grid gap-2 text-sm', className)}>
|
|
31
|
+
{rows.map(capability => <li
|
|
32
|
+
key={capability.param}
|
|
33
|
+
data-capability={capability.param}
|
|
34
|
+
data-granted={capability.granted ? 'true' : 'false'}
|
|
35
|
+
className="flex items-start gap-2"
|
|
36
|
+
>
|
|
37
|
+
<span aria-hidden="true" className={classes(
|
|
38
|
+
'mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-xs font-semibold',
|
|
39
|
+
capability.granted ? 'text-primary' : 'text-muted-foreground',
|
|
40
|
+
)}>{capability.granted ? '✓' : '–'}</span>
|
|
41
|
+
<span className="grid gap-0.5">
|
|
42
|
+
<span className={capability.granted ? undefined : 'text-muted-foreground'}>
|
|
43
|
+
{labels[capability.param]}
|
|
44
|
+
<span className="sr-only">{` — ${copy.text(capability.granted ? 'capability.included' : 'capability.not-included')}`}</span>
|
|
45
|
+
</span>
|
|
46
|
+
{capability.promo != null && <span className="text-xs"><PromoNote promo={capability.promo} copy={copy} /></span>}
|
|
47
|
+
</span>
|
|
48
|
+
</li>)}
|
|
49
|
+
</ul>
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useI18nLib, useLanguage } from '@owlmeans/client-i18n'
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
import { money, shortMomentUtc } from './format.js'
|
|
4
|
+
import type { CheckoutLimitNoteProps } from './types.js'
|
|
5
|
+
|
|
6
|
+
/** The reasons the package phrases itself; any other reads the generic sentence. */
|
|
7
|
+
const KNOWN_REASONS = ['per-purchase', 'window', 'hold']
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What an amount checkout is narrowed to for this entity now: the largest amount (or that nothing
|
|
11
|
+
* may be bought), why, and when it rises again — the same `CheckoutLimitView` the server refuses
|
|
12
|
+
* with, so the note and a refusal never disagree.
|
|
13
|
+
*
|
|
14
|
+
* `[data-checkout-limit]` carries `data-max-minor`, `data-blocked`, `data-reason` and, when known,
|
|
15
|
+
* `data-resets-at` (ISO).
|
|
16
|
+
*/
|
|
17
|
+
export const CheckoutLimitNote = ({ limit, reasonLabel, className }: CheckoutLimitNoteProps) => {
|
|
18
|
+
const t = useI18nLib('web-payment', 'checkout-limit')
|
|
19
|
+
const [locale] = useLanguage()
|
|
20
|
+
if (limit == null || (!limit.narrowed && !limit.blocked)) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const reason = limit.reason ?? ''
|
|
25
|
+
const custom = typeof reasonLabel === 'function' ? reasonLabel(reason) : reasonLabel
|
|
26
|
+
const explained = custom != null && custom !== ''
|
|
27
|
+
? custom
|
|
28
|
+
: t(`reason.${KNOWN_REASONS.includes(reason) ? reason : 'other'}`)
|
|
29
|
+
const resetsAt = limit.resetsAt != null ? new Date(limit.resetsAt) : null
|
|
30
|
+
const resets = resetsAt != null && !Number.isNaN(resetsAt.getTime())
|
|
31
|
+
? t('resets', { date: shortMomentUtc(resetsAt, locale), interpolation: { escapeValue: false } })
|
|
32
|
+
: null
|
|
33
|
+
|
|
34
|
+
return <div
|
|
35
|
+
data-checkout-limit="" data-max-minor={limit.maximumMinor} data-blocked={limit.blocked ? 'true' : 'false'}
|
|
36
|
+
data-reason={reason} data-resets-at={resetsAt?.toISOString()}
|
|
37
|
+
role={limit.blocked ? 'alert' : 'note'}
|
|
38
|
+
className={cn('grid gap-1 rounded-lg border p-3 text-sm', limit.blocked ? 'border-destructive/50' : 'bg-muted/30', className)}
|
|
39
|
+
>
|
|
40
|
+
<p className={limit.blocked ? 'text-destructive font-medium' : 'font-medium'} data-checkout-limit-headline="">
|
|
41
|
+
{limit.blocked
|
|
42
|
+
? t('blocked')
|
|
43
|
+
: t('note', { amount: money(limit.maximumMinor, limit.currency, locale), interpolation: { escapeValue: false } })}
|
|
44
|
+
</p>
|
|
45
|
+
<p className="text-muted-foreground text-xs" data-checkout-limit-reason="">{explained}</p>
|
|
46
|
+
{resets != null && <p className="text-muted-foreground text-xs" data-checkout-limit-resets="">{resets}</p>}
|
|
47
|
+
</div>
|
|
48
|
+
}
|