@solvapay/react 1.0.9-preview.1 → 1.0.9

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,361 @@
1
+ import {
2
+ AmountPicker,
3
+ PaymentForm,
4
+ PlanSelector,
5
+ Slot,
6
+ TopupForm,
7
+ interpolate,
8
+ useCopy,
9
+ usePaywallResolver,
10
+ usePlanSelector
11
+ } from "./chunk-37R5NZGF.js";
12
+
13
+ // src/primitives/PaywallNotice.tsx
14
+ import {
15
+ createContext,
16
+ forwardRef,
17
+ useContext,
18
+ useEffect,
19
+ useMemo,
20
+ useState
21
+ } from "react";
22
+
23
+ // src/utils/isPayg.ts
24
+ function isPaygPlan(plan) {
25
+ if (!plan) return false;
26
+ const type = plan.planType ?? plan.type;
27
+ return type === "usage-based" || type === "hybrid";
28
+ }
29
+
30
+ // src/primitives/PaywallNotice.tsx
31
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
32
+ var PaywallNoticeContext = createContext(null);
33
+ function usePaywallNoticeCtx(part) {
34
+ const ctx = useContext(PaywallNoticeContext);
35
+ if (!ctx) {
36
+ throw new Error(`PaywallNotice.${part} must be rendered inside <PaywallNotice.Root>.`);
37
+ }
38
+ return ctx;
39
+ }
40
+ var Root = forwardRef(function PaywallNoticeRoot({ content, onResolved, classNames, asChild, children, ...rest }, forwardedRef) {
41
+ const { resolved, refetch } = usePaywallResolver(content);
42
+ const classNamesResolved = useMemo(() => classNames ?? {}, [classNames]);
43
+ useEffect(() => {
44
+ if (resolved) onResolved?.();
45
+ }, [resolved, onResolved]);
46
+ const ctx = useMemo(
47
+ () => ({
48
+ content,
49
+ resolved,
50
+ refetch,
51
+ onResolved,
52
+ classNames: classNamesResolved
53
+ }),
54
+ [content, resolved, refetch, onResolved, classNamesResolved]
55
+ );
56
+ const Comp = asChild ? Slot : "div";
57
+ return /* @__PURE__ */ jsx(PaywallNoticeContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
58
+ Comp,
59
+ {
60
+ ref: forwardedRef,
61
+ "data-solvapay-paywall-notice": "",
62
+ "data-kind": content.kind,
63
+ "data-state": resolved ? "resolved" : "pending",
64
+ className: classNamesResolved.root,
65
+ ...rest,
66
+ children
67
+ }
68
+ ) });
69
+ });
70
+ var Heading = forwardRef(function PaywallNoticeHeading({ asChild, children, className, ...rest }, forwardedRef) {
71
+ const ctx = usePaywallNoticeCtx("Heading");
72
+ const copy = useCopy();
73
+ const defaultText = ctx.resolved ? copy.paywall.resolvedHeading : ctx.content.kind === "payment_required" ? copy.paywall.paymentRequiredHeading : copy.paywall.activationRequiredHeading;
74
+ const Comp = asChild ? Slot : "h2";
75
+ return /* @__PURE__ */ jsx(
76
+ Comp,
77
+ {
78
+ ref: forwardedRef,
79
+ "data-solvapay-paywall-heading": "",
80
+ className: className ?? ctx.classNames.heading,
81
+ ...rest,
82
+ children: children ?? defaultText
83
+ }
84
+ );
85
+ });
86
+ var Message = forwardRef(function PaywallNoticeMessage({ asChild, children, className, ...rest }, forwardedRef) {
87
+ const ctx = usePaywallNoticeCtx("Message");
88
+ const copy = useCopy();
89
+ const Comp = asChild ? Slot : "p";
90
+ const defaultText = resolvePaywallMessage(ctx.content, copy.paywall);
91
+ return /* @__PURE__ */ jsx(
92
+ Comp,
93
+ {
94
+ ref: forwardedRef,
95
+ "data-solvapay-paywall-message": "",
96
+ className: className ?? ctx.classNames.message,
97
+ ...rest,
98
+ children: children ?? defaultText
99
+ }
100
+ );
101
+ });
102
+ function resolvePaywallMessage(content, paywallCopy) {
103
+ if (content.kind !== "payment_required") {
104
+ return content.message;
105
+ }
106
+ const balance = content.balance;
107
+ if (!balance) return content.message;
108
+ const productName = content.productDetails?.name;
109
+ const forProduct = productName ? interpolate(paywallCopy.paymentRequiredProductSuffix, { product: productName }) : "";
110
+ const remaining = balance.remainingUnits ?? 0;
111
+ if (remaining <= 0) {
112
+ return interpolate(paywallCopy.paymentRequiredMessage, { forProduct });
113
+ }
114
+ return interpolate(paywallCopy.paymentRequiredMessageRemaining, {
115
+ remaining: String(remaining),
116
+ pluralSuffix: remaining === 1 ? "" : "s",
117
+ forProduct
118
+ });
119
+ }
120
+ var ProductContext = forwardRef(function PaywallNoticeProductContext({ asChild, children, className, ...rest }, forwardedRef) {
121
+ const ctx = usePaywallNoticeCtx("ProductContext");
122
+ const copy = useCopy();
123
+ const product = ctx.content.kind === "activation_required" ? ctx.content.productDetails : void 0;
124
+ if (!product?.name) return null;
125
+ const label = interpolate(copy.paywall.productContext, { product: product.name });
126
+ const Comp = asChild ? Slot : "div";
127
+ return /* @__PURE__ */ jsx(
128
+ Comp,
129
+ {
130
+ ref: forwardedRef,
131
+ "data-solvapay-paywall-product-context": "",
132
+ className: className ?? ctx.classNames.productContext,
133
+ ...rest,
134
+ children: children ?? label
135
+ }
136
+ );
137
+ });
138
+ var Balance = forwardRef(function PaywallNoticeBalance({ asChild, children, className, ...rest }, forwardedRef) {
139
+ const ctx = usePaywallNoticeCtx("Balance");
140
+ const copy = useCopy();
141
+ const balance = ctx.content.balance;
142
+ if (ctx.content.kind !== "activation_required" || !balance) return null;
143
+ const label = interpolate(copy.paywall.balanceLine, {
144
+ available: String(balance.remainingUnits ?? 0),
145
+ required: String(balance.creditsPerUnit ?? 1)
146
+ });
147
+ const Comp = asChild ? Slot : "div";
148
+ return /* @__PURE__ */ jsx(
149
+ Comp,
150
+ {
151
+ ref: forwardedRef,
152
+ "data-solvapay-paywall-balance": "",
153
+ className: className ?? ctx.classNames.balance,
154
+ ...rest,
155
+ children: children ?? label
156
+ }
157
+ );
158
+ });
159
+ function Plans({ className, children }) {
160
+ const ctx = usePaywallNoticeCtx("Plans");
161
+ if (ctx.content.kind !== "activation_required") return null;
162
+ if (!ctx.content.product) return null;
163
+ const defaultClass = className ?? ctx.classNames.plans ?? "solvapay-paywall-plans";
164
+ return /* @__PURE__ */ jsx("div", { "data-solvapay-paywall-plans": "", className: defaultClass, children: /* @__PURE__ */ jsx(PlanSelector.Root, { productRef: ctx.content.product, filter: hidesFreePlan, children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
165
+ /* @__PURE__ */ jsx(PlanSelector.Grid, { children: /* @__PURE__ */ jsxs(PlanSelector.Card, { children: [
166
+ /* @__PURE__ */ jsx(PlanSelector.CardBadge, {}),
167
+ /* @__PURE__ */ jsx(PlanSelector.CardName, {}),
168
+ /* @__PURE__ */ jsx(PlanSelector.CardPrice, {}),
169
+ /* @__PURE__ */ jsx(PlanSelector.CardInterval, {})
170
+ ] }) }),
171
+ /* @__PURE__ */ jsx(PlanSelector.Loading, {}),
172
+ /* @__PURE__ */ jsx(PlanSelector.Error, {})
173
+ ] }) }) });
174
+ }
175
+ function hidesFreePlan(plan) {
176
+ return plan.requiresPayment !== false;
177
+ }
178
+ var HostedCheckoutLink = forwardRef(function PaywallNoticeHostedCheckoutLink({ asChild, children, className, ...rest }, forwardedRef) {
179
+ const ctx = usePaywallNoticeCtx("HostedCheckoutLink");
180
+ const copy = useCopy();
181
+ const href = ctx.content.checkoutUrl;
182
+ if (!href) return null;
183
+ const Comp = asChild ? Slot : "a";
184
+ return /* @__PURE__ */ jsx(
185
+ Comp,
186
+ {
187
+ ref: forwardedRef,
188
+ href,
189
+ target: "_blank",
190
+ rel: "noopener noreferrer",
191
+ "data-solvapay-paywall-hosted-link": "",
192
+ className: className ?? ctx.classNames.hostedLink,
193
+ ...rest,
194
+ children: children ?? copy.paywall.hostedCheckoutButton
195
+ }
196
+ );
197
+ });
198
+ function EmbeddedCheckout({
199
+ returnUrl,
200
+ className,
201
+ children
202
+ }) {
203
+ const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
204
+ if (!ctx.content.product) return null;
205
+ const defaultClass = className ?? ctx.classNames.embeddedCheckout ?? "solvapay-paywall-embedded-checkout";
206
+ return /* @__PURE__ */ jsx("div", { "data-solvapay-paywall-embedded-checkout": "", className: defaultClass, children: /* @__PURE__ */ jsxs(PlanSelector.Root, { productRef: ctx.content.product, filter: hidesFreePlan, children: [
207
+ /* @__PURE__ */ jsx(PlanSelector.Grid, { children: /* @__PURE__ */ jsxs(PlanSelector.Card, { children: [
208
+ /* @__PURE__ */ jsx(PlanSelector.CardBadge, {}),
209
+ /* @__PURE__ */ jsx(PlanSelector.CardName, {}),
210
+ /* @__PURE__ */ jsx(PlanSelector.CardPrice, {}),
211
+ /* @__PURE__ */ jsx(PlanSelector.CardInterval, {})
212
+ ] }) }),
213
+ /* @__PURE__ */ jsx(PlanSelector.Loading, {}),
214
+ /* @__PURE__ */ jsx(PlanSelector.Error, {}),
215
+ /* @__PURE__ */ jsx(PaywallSelectedPlanGate, { productRef: ctx.content.product, returnUrl, children })
216
+ ] }) });
217
+ }
218
+ function PaywallSelectedPlanGate({
219
+ productRef,
220
+ returnUrl,
221
+ children
222
+ }) {
223
+ const { selectedPlan, selectedPlanRef } = usePlanSelector();
224
+ if (!selectedPlanRef || !selectedPlan) return null;
225
+ if (isPaygPlan(selectedPlan)) {
226
+ return /* @__PURE__ */ jsx(
227
+ PaywallPaygGate,
228
+ {
229
+ plan: selectedPlan,
230
+ returnUrl
231
+ },
232
+ selectedPlanRef
233
+ );
234
+ }
235
+ return /* @__PURE__ */ jsx(PaywallPaymentFormGate, { productRef, returnUrl, children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
236
+ /* @__PURE__ */ jsx(PaymentForm.Summary, {}),
237
+ /* @__PURE__ */ jsx(PaymentForm.Loading, {}),
238
+ /* @__PURE__ */ jsx(PaymentForm.PaymentElement, {}),
239
+ /* @__PURE__ */ jsx(PaymentForm.Error, {}),
240
+ /* @__PURE__ */ jsx(PaymentForm.MandateText, {}),
241
+ /* @__PURE__ */ jsx(PaymentForm.SubmitButton, {})
242
+ ] }) });
243
+ }
244
+ function PaywallPaygGate({
245
+ plan,
246
+ returnUrl
247
+ }) {
248
+ const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
249
+ const currency = (plan.currency ?? "USD").toUpperCase();
250
+ const [amountMinor, setAmountMinor] = useState(null);
251
+ if (amountMinor == null) {
252
+ return /* @__PURE__ */ jsx("div", { "data-solvapay-paywall-payg-amount": "", children: /* @__PURE__ */ jsxs(AmountPicker.Root, { currency, emit: "minor", children: [
253
+ /* @__PURE__ */ jsx(AmountPicker.Custom, {}),
254
+ /* @__PURE__ */ jsx(AmountPicker.Confirm, { onConfirm: (minor) => setAmountMinor(minor), children: "Continue" })
255
+ ] }) });
256
+ }
257
+ return /* @__PURE__ */ jsxs("div", { "data-solvapay-paywall-payg-topup": "", children: [
258
+ /* @__PURE__ */ jsx(
259
+ "button",
260
+ {
261
+ type: "button",
262
+ "data-solvapay-paywall-payg-change-amount": "",
263
+ onClick: () => setAmountMinor(null),
264
+ children: "Change amount"
265
+ }
266
+ ),
267
+ /* @__PURE__ */ jsxs(
268
+ TopupForm.Root,
269
+ {
270
+ amount: amountMinor,
271
+ currency,
272
+ returnUrl,
273
+ onSuccess: () => {
274
+ void ctx.refetch();
275
+ },
276
+ children: [
277
+ /* @__PURE__ */ jsx(TopupForm.Loading, {}),
278
+ /* @__PURE__ */ jsx(TopupForm.PaymentElement, {}),
279
+ /* @__PURE__ */ jsx(TopupForm.Error, {}),
280
+ /* @__PURE__ */ jsx(TopupForm.SubmitButton, {})
281
+ ]
282
+ }
283
+ )
284
+ ] });
285
+ }
286
+ function PaywallPaymentFormGate({
287
+ productRef,
288
+ returnUrl,
289
+ children
290
+ }) {
291
+ const { selectedPlanRef } = usePlanSelector();
292
+ const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
293
+ if (!selectedPlanRef) return null;
294
+ return /* @__PURE__ */ jsx(
295
+ PaymentForm.Root,
296
+ {
297
+ planRef: selectedPlanRef,
298
+ productRef,
299
+ returnUrl,
300
+ requireTermsAcceptance: false,
301
+ onSuccess: () => {
302
+ void ctx.refetch();
303
+ },
304
+ children
305
+ },
306
+ selectedPlanRef
307
+ );
308
+ }
309
+ var Retry = forwardRef(
310
+ function PaywallNoticeRetry({ asChild, children, className, onClick, ...rest }, forwardedRef) {
311
+ const ctx = usePaywallNoticeCtx("Retry");
312
+ const copy = useCopy();
313
+ const Comp = asChild ? Slot : "button";
314
+ return /* @__PURE__ */ jsx(
315
+ Comp,
316
+ {
317
+ ref: forwardedRef,
318
+ type: "button",
319
+ disabled: !ctx.resolved,
320
+ "data-solvapay-paywall-retry": "",
321
+ "data-state": ctx.resolved ? "ready" : "waiting",
322
+ className: className ?? ctx.classNames.retryButton,
323
+ onClick: (e) => {
324
+ onClick?.(e);
325
+ if (!e.defaultPrevented && ctx.resolved) ctx.onResolved?.();
326
+ },
327
+ ...rest,
328
+ children: children ?? copy.paywall.retryButton
329
+ }
330
+ );
331
+ }
332
+ );
333
+ var PaywallNotice = Object.assign(Root, {
334
+ Root,
335
+ Heading,
336
+ Message,
337
+ ProductContext,
338
+ Plans,
339
+ Balance,
340
+ HostedCheckoutLink,
341
+ EmbeddedCheckout,
342
+ Retry
343
+ });
344
+ function usePaywallNotice() {
345
+ return usePaywallNoticeCtx("usePaywallNotice");
346
+ }
347
+
348
+ export {
349
+ isPaygPlan,
350
+ Root,
351
+ Heading,
352
+ Message,
353
+ ProductContext,
354
+ Balance,
355
+ Plans,
356
+ HostedCheckoutLink,
357
+ EmbeddedCheckout,
358
+ Retry,
359
+ PaywallNotice,
360
+ usePaywallNotice
361
+ };