@monetize.software/sdk-react 3.0.0-alpha.1

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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/PaywallProvider.d.ts +58 -0
  4. package/dist/PaywallProvider.d.ts.map +1 -0
  5. package/dist/components/PaywallButton.d.ts +61 -0
  6. package/dist/components/PaywallButton.d.ts.map +1 -0
  7. package/dist/components/PaywallGate.d.ts +61 -0
  8. package/dist/components/PaywallGate.d.ts.map +1 -0
  9. package/dist/components/PaywallSupportButton.d.ts +13 -0
  10. package/dist/components/PaywallSupportButton.d.ts.map +1 -0
  11. package/dist/context.d.ts +26 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/contract.d.ts +29 -0
  14. package/dist/contract.d.ts.map +1 -0
  15. package/dist/hooks/usePaywall.d.ts +23 -0
  16. package/dist/hooks/usePaywall.d.ts.map +1 -0
  17. package/dist/hooks/usePaywallAccess.d.ts +47 -0
  18. package/dist/hooks/usePaywallAccess.d.ts.map +1 -0
  19. package/dist/hooks/usePaywallEvent.d.ts +27 -0
  20. package/dist/hooks/usePaywallEvent.d.ts.map +1 -0
  21. package/dist/hooks/usePaywallPrices.d.ts +38 -0
  22. package/dist/hooks/usePaywallPrices.d.ts.map +1 -0
  23. package/dist/hooks/usePaywallState.d.ts +23 -0
  24. package/dist/hooks/usePaywallState.d.ts.map +1 -0
  25. package/dist/hooks/usePaywallTrial.d.ts +27 -0
  26. package/dist/hooks/usePaywallTrial.d.ts.map +1 -0
  27. package/dist/hooks/usePaywallUser.d.ts +30 -0
  28. package/dist/hooks/usePaywallUser.d.ts.map +1 -0
  29. package/dist/hooks/usePaywallVisibility.d.ts +26 -0
  30. package/dist/hooks/usePaywallVisibility.d.ts.map +1 -0
  31. package/dist/index.cjs +2 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +237 -0
  36. package/dist/index.js.map +1 -0
  37. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 monetize.software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # @monetize.software/sdk-react
2
+
3
+ React bindings для [`@monetize.software/sdk`](../sdk) — Provider, хуки и декларативные компоненты для пейвола. Работает с web SDK и extension SDK (любой drop-in-совместимый `PaywallUI`).
4
+
5
+ - **Bundle**: < 2 KB gzip (только bindings, никакого UI — он внутри SDK).
6
+ - **React**: >= 18, использует `useSyncExternalStore` для concurrent-safe чтения снимков.
7
+ - **SSR**: безопасно. На сервере хуки отдают `null` / `{ status: 'loading' }`, инстанс PaywallUI создаётся только на клиенте.
8
+ - **TypeScript**: полный тип-уровень контракт ([`src/contract.ts`](src/contract.ts)) — если в основном SDK поедет публичная поверхность, сборка sdk-react падает на этапе `tsc`.
9
+
10
+ ## Установка
11
+
12
+ ```bash
13
+ pnpm add @monetize.software/sdk-react @monetize.software/sdk react
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```tsx
19
+ import {
20
+ PaywallProvider,
21
+ PaywallGate,
22
+ PaywallButton,
23
+ usePaywallUser
24
+ } from '@monetize.software/sdk-react';
25
+
26
+ function App() {
27
+ return (
28
+ <PaywallProvider options={{ paywallId: 'YOUR_ID', auth: true }}>
29
+ <PaywallGate fallback={<UpgradeCTA />}>
30
+ <PremiumFeature />
31
+ </PaywallGate>
32
+
33
+ <PaywallButton>Upgrade</PaywallButton>
34
+ </PaywallProvider>
35
+ );
36
+ }
37
+
38
+ function UpgradeCTA() {
39
+ const user = usePaywallUser();
40
+ return <p>Привет, {user?.email ?? 'гость'}! Открой полный доступ.</p>;
41
+ }
42
+ ```
43
+
44
+ ## Provider
45
+
46
+ `<PaywallProvider>` принимает один из двух пропсов:
47
+
48
+ ```tsx
49
+ // Вариант 1 — Provider сам создаёт инстанс
50
+ <PaywallProvider options={{ paywallId, apiOrigin, auth: true }}>
51
+
52
+ // Вариант 2 — готовый инстанс снаружи (extension / shared / тесты)
53
+ import { createPaywallUI } from '@monetize.software/sdk-extension';
54
+ const paywall = createPaywallUI({ paywallId });
55
+
56
+ <PaywallProvider instance={paywall}>
57
+ ```
58
+
59
+ Если `paywallId` динамически меняется, перемонтируй Provider через `<PaywallProvider key={paywallId} options={...}>` — реактивная пересборка опций намеренно не делается.
60
+
61
+ ## Хуки
62
+
63
+ | Хук | Возвращает | Когда триггерит rerender |
64
+ |---|---|---|
65
+ | `usePaywall()` | `PaywallUI \| null` | смена инстанса (редко) |
66
+ | `usePaywallState()` | `{ open, view, error }` | любое изменение state-машины |
67
+ | `usePaywallUser()` | `PaywallUser \| null` | event `userChange` |
68
+ | `usePaywallAccess(opts?)` | `{ status, result }` | `userChange` / `purchase_completed` |
69
+ | `usePaywallPrices()` | `{ prices, loading, error }` | bootstrap refresh |
70
+ | `usePaywallTrial()` | `TrialStatus \| null` | `trial_blocked` / `trial_expired` |
71
+ | `usePaywallVisibility()` | `VisibilityStatus \| null` | `ready` / `visibility_blocked` |
72
+ | `usePaywallEvent(event, handler)` | — | подписка с stable-handler-ref |
73
+
74
+ Все хуки безопасны до mount-а Provider'а (отдают `null` / loading) — можно использовать в SSR без `'use client'`-обёрток на ветке дерева.
75
+
76
+ ## Компоненты
77
+
78
+ ### `<PaywallGate>`
79
+
80
+ Декларативный гейт: loading → fallback → children.
81
+
82
+ ```tsx
83
+ <PaywallGate
84
+ loading={<Skeleton />}
85
+ fallback={({ open }) => <button onClick={open}>Upgrade</button>}
86
+ openOnBlocked={false} // если true — автоматом дёргает paywall.open()
87
+ >
88
+ <PremiumFeature />
89
+ </PaywallGate>
90
+ ```
91
+
92
+ ### `<PaywallButton>` / `<PaywallSupportButton>`
93
+
94
+ Сахар над `paywall.open()`. По умолчанию рендерится как нативный `<button>` со всеми твоими `className`/`disabled`/`aria-*`. Для кастомного элемента — render-prop:
95
+
96
+ ```tsx
97
+ <PaywallButton render={({ open, ready }) => (
98
+ <MyButton onClick={open} disabled={!ready}>Upgrade</MyButton>
99
+ )} />
100
+ ```
101
+
102
+ `mode` переключает между `open()` / `openSupport()` / `openAuth()` / `openAnonGate()`:
103
+
104
+ ```tsx
105
+ <PaywallButton mode="support">Need help?</PaywallButton>
106
+ <PaywallButton mode="auth">Sign in</PaywallButton>
107
+ ```
108
+
109
+ ## SSR / Next.js
110
+
111
+ ```tsx
112
+ 'use client'; // на Provider, не на дерево потомков
113
+
114
+ import { PaywallProvider } from '@monetize.software/sdk-react';
115
+
116
+ export function PaywallProviders({ children }) {
117
+ return (
118
+ <PaywallProvider options={{ paywallId: process.env.NEXT_PUBLIC_PAYWALL_ID! }}>
119
+ {children}
120
+ </PaywallProvider>
121
+ );
122
+ }
123
+ ```
124
+
125
+ Хуки можно вызывать из server components только при типизированных-null-сценариях (всё равно вернётся `null`/`loading`). Рекомендация — выносить хук-логику в client component.
126
+
127
+ ## Защита от изменений в SDK
128
+
129
+ `pnpm typecheck` проверяет [`src/contract.ts`](src/contract.ts) — там перечислены все точки опоры на public API SDK (методы PaywallUI, поля snapshot'ов, имена событий). Любое разъезжание в `../sdk` ловится здесь раньше, чем в проде.
130
+
131
+ После изменений в SDK обнови dist для типов:
132
+
133
+ ```bash
134
+ cd ../sdk && pnpm build
135
+ cd ../sdk-react && pnpm typecheck
136
+ ```
137
+
138
+ ## Разработка
139
+
140
+ ```bash
141
+ pnpm install
142
+ pnpm dev # → http://localhost:5080/demo/
143
+ pnpm typecheck # TS-валидация + контракт
144
+ pnpm test # vitest + @testing-library/react
145
+ pnpm test:e2e # playwright против демо
146
+ pnpm build # ESM + CJS + d.ts → dist/
147
+ ```
148
+
149
+ ## API reference
150
+
151
+ Полные JSDoc-комментарии на каждый публичный экспорт смотри в исходниках:
152
+
153
+ - [`src/PaywallProvider.tsx`](src/PaywallProvider.tsx) — Provider, lifecycle
154
+ - [`src/hooks/`](src/hooks/) — все хуки
155
+ - [`src/components/`](src/components/) — декларативные компоненты
156
+ - [`src/contract.ts`](src/contract.ts) — точки опоры на SDK
@@ -0,0 +1,58 @@
1
+ import { ReactNode } from 'react';
2
+ import { PaywallUI, PaywallUIOptions } from '../../sdk/src';
3
+ /**
4
+ * Два взаимоисключающих режима использования:
5
+ *
6
+ * - `options` — Provider сам конструирует `PaywallUI` в useEffect и
7
+ * прибирает в cleanup. Самый частый кейс — обычный сайт.
8
+ * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для
9
+ * extension'ов (`@monetize.software/sdk-extension` поставляет structurally
10
+ * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между
11
+ * несколькими React-деревьями и для тестов.
12
+ *
13
+ * Discriminated union на уровне типов — TS не даст передать оба сразу.
14
+ */
15
+ export type PaywallProviderProps = {
16
+ options: PaywallUIOptions;
17
+ instance?: never;
18
+ children: ReactNode;
19
+ } | {
20
+ instance: PaywallUI;
21
+ options?: never;
22
+ children: ReactNode;
23
+ };
24
+ /**
25
+ * Корневой Provider для всех React-биндингов SDK.
26
+ *
27
+ * ```tsx
28
+ * // вариант 1: Provider сам создаёт инстанс
29
+ * <PaywallProvider options={{ paywallId: '...', auth: true }}>
30
+ * <App />
31
+ * </PaywallProvider>
32
+ *
33
+ * // вариант 2: готовый инстанс снаружи (extension / shared)
34
+ * const paywall = createPaywallUI({ paywallId: '...' });
35
+ * <PaywallProvider instance={paywall}>
36
+ * <App />
37
+ * </PaywallProvider>
38
+ * ```
39
+ *
40
+ * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все
41
+ * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что
42
+ * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-
43
+ * ограничений на дерево потомков.
44
+ *
45
+ * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не
46
+ * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-
47
+ * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op
48
+ * после destroy.
49
+ *
50
+ * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс
51
+ * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),
52
+ * следует менять `key` у Provider'а — это идиоматичный React-способ форсить
53
+ * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:
54
+ * структурный equality глубоких options всегда ломается на функциях-колбеках
55
+ * и live-обновлениях storage'а.
56
+ */
57
+ export declare function PaywallProvider(props: PaywallProviderProps): JSX.Element;
58
+ //# sourceMappingURL=PaywallProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaywallProvider.d.ts","sourceRoot":"","sources":["../src/PaywallProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,OAAO,EAAE,gBAAgB,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,GACD;IACE,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CAgDxE"}
@@ -0,0 +1,61 @@
1
+ import { ButtonHTMLAttributes, ReactElement, ReactNode } from 'react';
2
+ import { OpenOptions } from '../../../sdk/src';
3
+ /**
4
+ * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.
5
+ * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,
6
+ * `skipVisibility`.
7
+ */
8
+ type OpenProps = OpenOptions;
9
+ interface CommonProps extends OpenProps {
10
+ /** Что открывать: layout (default), support, auth-gate, anon-gate. */
11
+ mode?: 'paywall' | 'support' | 'auth' | 'anon';
12
+ /** Render-prop для полного контроля над элементом-триггером. Когда задан,
13
+ * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */
14
+ render?: (args: PaywallButtonRenderArgs) => ReactElement;
15
+ }
16
+ export interface PaywallButtonRenderArgs {
17
+ /** Открыть пейвол согласно `mode` + переданным opts. */
18
+ open: () => void;
19
+ /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */
20
+ ready: boolean;
21
+ }
22
+ /**
23
+ * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,
24
+ * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный
25
+ * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем
26
+ * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).
27
+ */
28
+ type ButtonRenderProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof OpenProps | 'children'> & {
29
+ children?: ReactNode;
30
+ };
31
+ export type PaywallButtonProps = CommonProps & ButtonRenderProps;
32
+ /**
33
+ * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как
34
+ * нативный `<button>` со всеми твоими className/style/disabled, но при нужде
35
+ * можно передать `render` для произвольного элемента (Radix-style asChild
36
+ * паттерн через render-prop).
37
+ *
38
+ * ```tsx
39
+ * // обычный кейс
40
+ * <PaywallButton className="btn-primary" renew>
41
+ * Renew subscription
42
+ * </PaywallButton>
43
+ *
44
+ * // custom-элемент
45
+ * <PaywallButton render={({ open, ready }) => (
46
+ * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>
47
+ * )} />
48
+ *
49
+ * // саппорт-форма вместо тарифов
50
+ * <PaywallButton mode="support">Need help?</PaywallButton>
51
+ * ```
52
+ *
53
+ * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`
54
+ * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —
55
+ * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.
56
+ */
57
+ export declare const PaywallButton: import('react').ForwardRefExoticComponent<CommonProps & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | keyof OpenOptions> & {
58
+ children?: ReactNode;
59
+ } & import('react').RefAttributes<HTMLButtonElement>>;
60
+ export {};
61
+ //# sourceMappingURL=PaywallButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaywallButton.d.ts","sourceRoot":"","sources":["../../src/components/PaywallButton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;GAIG;AACH,KAAK,SAAS,GAAG,WAAW,CAAC;AAE7B,UAAU,WAAY,SAAQ,SAAS;IACrC,sEAAsE;IACtE,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/C;+EAC2E;IAC3E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,YAAY,CAAC;CAC1D;AAED,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,KAAK,iBAAiB,GAAG,IAAI,CAC3B,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,SAAS,GAAG,UAAU,CAC7B,GAAG;IACF,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa;eA9Bb,SAAS;qDAyFrB,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { ReactNode } from 'react';
2
+ import { PaywallAccessResult } from '../../../sdk/src';
3
+ export interface PaywallGateProps {
4
+ /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */
5
+ loading?: ReactNode;
6
+ /**
7
+ * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо
8
+ * статичный ReactNode, либо render-функцию, получающую callback
9
+ * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:
10
+ *
11
+ * ```tsx
12
+ * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}
13
+ * ```
14
+ *
15
+ * Если не передан — компонент рендерит `null` для blocked (host
16
+ * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).
17
+ */
18
+ fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);
19
+ /**
20
+ * Автоматически дёргать `paywall.open()` сразу как только access перешёл в
21
+ * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на
22
+ * paywall»: компонент сам открывает модалку, не нужно писать onClick.
23
+ *
24
+ * По умолчанию `false` — большинство хостов хотят сначала показать
25
+ * объясняющий CTA, а модалку открывать по клику. Включать осознанно.
26
+ */
27
+ openOnBlocked?: boolean;
28
+ /** Премиум-контент. Рендерится только когда access=granted. */
29
+ children: ReactNode;
30
+ }
31
+ export interface BlockedRenderArgs {
32
+ result: Extract<PaywallAccessResult, {
33
+ access: 'blocked';
34
+ }>;
35
+ open: () => void;
36
+ }
37
+ /**
38
+ * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().
39
+ *
40
+ * Три состояния:
41
+ * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;
42
+ * - `granted` (есть подписка / visibility / trial) — рендерим `children`;
43
+ * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем
44
+ * `paywall.open()` при `openOnBlocked={true}`.
45
+ *
46
+ * ```tsx
47
+ * <PaywallGate
48
+ * loading={<Skeleton />}
49
+ * fallback={({ open }) => <button onClick={open}>Upgrade</button>}
50
+ * >
51
+ * <PremiumFeature />
52
+ * </PaywallGate>
53
+ * ```
54
+ *
55
+ * Для нестандартных сценариев (показать "Try free trial" вместо upgrade,
56
+ * комбинировать с собственным auth-flow'ом) использовать
57
+ * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь
58
+ * стать конфигурируемым на каждый чих.
59
+ */
60
+ export declare function PaywallGate(props: PaywallGateProps): JSX.Element | null;
61
+ //# sourceMappingURL=PaywallGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaywallGate.d.ts","sourceRoot":"","sources":["../../src/components/PaywallGate.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,iBAAiB,KAAK,SAAS,CAAC,CAAC;IAChE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IAC5D,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAoCvE"}
@@ -0,0 +1,13 @@
1
+ import { PaywallButtonProps } from './PaywallButton';
2
+ export type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;
3
+ /**
4
+ * Сахар над `<PaywallButton mode="support">`. Самостоятельная компонента, а
5
+ * не пресет prop'а, для discoverability — название говорит за себя, и в
6
+ * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.
7
+ *
8
+ * ```tsx
9
+ * <PaywallSupportButton className="link">Help</PaywallSupportButton>
10
+ * ```
11
+ */
12
+ export declare const PaywallSupportButton: import('react').ForwardRefExoticComponent<PaywallSupportButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
13
+ //# sourceMappingURL=PaywallSupportButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaywallSupportButton.d.ts","sourceRoot":"","sources":["../../src/components/PaywallSupportButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;AAEzE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,yHAK/B,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { PaywallUI } from '../../sdk/src';
2
+ /**
3
+ * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.
4
+ *
5
+ * value === null до того, как Provider успел смонтировать инстанс (SSR,
6
+ * первый render до useEffect, дев double-mount в StrictMode после cleanup).
7
+ * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,
8
+ * а не падать.
9
+ *
10
+ * defaultValue intentionally `null`, а не `undefined` — это позволяет
11
+ * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция
12
+ * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,
13
+ * но инстанс ещё не создан» (null).
14
+ */
15
+ export declare const PaywallContext: import('react').Context<PaywallUI>;
16
+ /**
17
+ * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».
18
+ *
19
+ * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.
20
+ * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /
21
+ * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда
22
+ * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall
23
+ * проверяет первым.
24
+ */
25
+ export declare const PaywallProviderMarker: import('react').Context<boolean>;
26
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,oCAAwC,CAAC;AAGpE;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,kCAAgC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Type-level контракт между sdk-react и @monetize.software/sdk.
3
+ *
4
+ * Этот файл компилируется в TS-сборке (`pnpm typecheck`), но не экспортируется
5
+ * наружу — это «assertion'ы», а не runtime-код. Каждая проверка фиксирует
6
+ * конкретное ожидание относительно публичной поверхности SDK, на которое
7
+ * полагаются Provider / hooks / components.
8
+ *
9
+ * Если в SDK кто-то:
10
+ * - переименует/удалит публичный метод PaywallUI,
11
+ * - поменяет сигнатуру конструктора `new PaywallUI(opts)`,
12
+ * - выкинет поле из `PaywallStateSnapshot` / `PaywallAccessResult`,
13
+ * - изменит payload-тип события (например, `purchase_completed`),
14
+ *
15
+ * — `tsc --noEmit` в sdk-react падает на этом файле раньше, чем кто-то
16
+ * заметит расхождение в проде. Это и есть «контракт»: эксплицитно
17
+ * перечисленные точки опоры.
18
+ *
19
+ * Дисциплина: при добавлении нового хука/компонента, который тянет что-то
20
+ * новое из SDK, добавлять проверку сюда. Контракт должен быть проверяем
21
+ * сам по себе, а не косвенно через успешный typecheck бизнес-кода (бизнес-
22
+ * код может незаметно переехать на другую API-точку, и контракт по
23
+ * фактическому использованию потеряет смысл).
24
+ *
25
+ * Тип-имена с префиксом `_` намеренно — сигнализирует, что это assertion'ы
26
+ * для компилятора, а не общедоступные алиасы.
27
+ */
28
+ export {};
29
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1,23 @@
1
+ import { PaywallUI } from '../../../sdk/src';
2
+ /**
3
+ * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.
4
+ *
5
+ * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,
6
+ * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`
7
+ * (Provider есть, но инстанс ещё не смонтирован).
8
+ *
9
+ * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,
10
+ * `paywall.openSupport()`, подписки на события — для всего этого
11
+ * usePaywall() самый прямой путь:
12
+ *
13
+ * ```tsx
14
+ * const paywall = usePaywall();
15
+ * <button onClick={() => paywall?.open()}>Upgrade</button>
16
+ * ```
17
+ *
18
+ * Для типичных кейсов (gating, state-driven UI) обычно удобнее
19
+ * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},
20
+ * {@link usePaywallUser}.
21
+ */
22
+ export declare function usePaywall(): PaywallUI | null;
23
+ //# sourceMappingURL=usePaywall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaywall.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywall.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,IAAI,SAAS,GAAG,IAAI,CAa7C"}
@@ -0,0 +1,47 @@
1
+ import { GetAccessOptions, PaywallAccessResult } from '../../../sdk/src';
2
+ /**
3
+ * `loading` — первый fetch ещё в полёте (или Provider не готов).
4
+ * `ready` — есть свежий ответ; `result` гарантированно non-null.
5
+ *
6
+ * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:
7
+ *
8
+ * `if (access.status === 'ready') access.result.access === 'granted'`
9
+ */
10
+ export type PaywallAccessState = {
11
+ status: 'loading';
12
+ result: null;
13
+ } | {
14
+ status: 'ready';
15
+ result: PaywallAccessResult;
16
+ };
17
+ /**
18
+ * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».
19
+ *
20
+ * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не
21
+ * монтируется, trial-storage не двигается). На каждый `userChange` событие
22
+ * автоматически рефетчится — после успешной покупки `has_subscription`
23
+ * сработает мгновенно, и хост перерендерит UI без feature-lock'а.
24
+ *
25
+ * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать
26
+ * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,
27
+ * если кеш свежий).
28
+ *
29
+ * ```tsx
30
+ * const access = usePaywallAccess();
31
+ * const paywall = usePaywall();
32
+ *
33
+ * if (access.status === 'loading') return <Skeleton />;
34
+ * if (access.result.access === 'blocked') {
35
+ * return <button onClick={() => paywall?.open()}>Upgrade</button>;
36
+ * }
37
+ * return <PremiumFeature />;
38
+ * ```
39
+ *
40
+ * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность
41
+ * ссылки `opts` не требуется, эффект перезапустится только при реальном
42
+ * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него
43
+ * новый ref) — отмена inflight-запроса делается локально через AbortController
44
+ * в cleanup-эффекте.
45
+ */
46
+ export declare function usePaywallAccess(opts?: GetAccessOptions): PaywallAccessState;
47
+ //# sourceMappingURL=usePaywallAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaywallAccess.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallAccess.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,gBAAqB,GAAG,kBAAkB,CAyDhF"}
@@ -0,0 +1,27 @@
1
+ import { PaywallEvent, PaywallEventHandler } from '../../../sdk/src';
2
+ /**
3
+ * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`
4
+ * с двумя важными отличиями от ручного useEffect:
5
+ *
6
+ * 1. handler не нужно мемоизировать через `useCallback` — внутри храним
7
+ * последнюю версию в `useRef`, само subscription пересоздаётся только
8
+ * при смене `event` или инстанса paywall'а. Это убирает класс багов с
9
+ * «забыл useCallback → подписка отписывается-переподписывается на каждый
10
+ * рендер → события теряются».
11
+ *
12
+ * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):
13
+ * подписка просто не создаётся, ждёт пока инстанс появится.
14
+ *
15
+ * ```tsx
16
+ * usePaywallEvent('purchase_completed', (payload) => {
17
+ * toast.success(`Покупка ${payload.priceId} прошла`);
18
+ * queryClient.invalidateQueries(['user']);
19
+ * });
20
+ * ```
21
+ *
22
+ * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации
23
+ * локального стейта) это самый прямой паттерн — компонент гарантированно
24
+ * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.
25
+ */
26
+ export declare function usePaywallEvent<E extends PaywallEvent>(event: E, handler: PaywallEventHandler<E>): void;
27
+ //# sourceMappingURL=usePaywallEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaywallEvent.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallEvent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAUhF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,EACpD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI,CAkBN"}
@@ -0,0 +1,38 @@
1
+ import { PaywallPrice } from '../../../sdk/src';
2
+ /**
3
+ * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а
4
+ * или когда инстанс ещё не готов).
5
+ * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.
6
+ * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).
7
+ *
8
+ * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`
9
+ * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три
10
+ * независимые величины одновременно (показать предыдущий список + skeleton +
11
+ * сообщение об ошибке поверх) — discriminated union тут только усложняет.
12
+ */
13
+ export interface PaywallPricesState {
14
+ prices: PaywallPrice[] | null;
15
+ loading: boolean;
16
+ error: Error | null;
17
+ }
18
+ /**
19
+ * Загружает и подписывается на цены пейвола. Подходит для отдельной
20
+ * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,
21
+ * что и в модалке, без открытия paywall'а.
22
+ *
23
+ * Реализация:
24
+ * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше
25
+ * BillingClient'а — например, после `paywall.preload()` или предыдущего
26
+ * open'а — цены доступны мгновенно);
27
+ * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;
28
+ * - subscription на `ready` event — рефетч bootstrap'а на новом open()
29
+ * может принести обновлённые цены, мы обновляем snapshot.
30
+ *
31
+ * ```tsx
32
+ * const { prices, loading } = usePaywallPrices();
33
+ * if (loading && !prices) return <Skeleton />;
34
+ * return prices?.map((p) => <PriceCard key={p.id} price={p} />);
35
+ * ```
36
+ */
37
+ export declare function usePaywallPrices(): PaywallPricesState;
38
+ //# sourceMappingURL=usePaywallPrices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaywallPrices.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallPrices.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,IAAI,kBAAkB,CA0DrD"}
@@ -0,0 +1,23 @@
1
+ import { PaywallStateSnapshot } from '../../../sdk/src';
2
+ /**
3
+ * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,
4
+ * последняя ошибка.
5
+ *
6
+ * Реализована поверх `paywall.onStateChange` + `paywall.getState` через
7
+ * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику
8
+ * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и
9
+ * минимум re-render'ов (snapshot равенство по `Object.is`).
10
+ *
11
+ * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,
12
+ * error: null }` — это та же форма, что PaywallUI кладёт во внутренний
13
+ * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».
14
+ *
15
+ * ```tsx
16
+ * const { open, view } = usePaywallState();
17
+ * useEffect(() => {
18
+ * if (open) analytics.track('paywall_seen');
19
+ * }, [open]);
20
+ * ```
21
+ */
22
+ export declare function usePaywallState(): PaywallStateSnapshot;
23
+ //# sourceMappingURL=usePaywallState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaywallState.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAYnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,IAAI,oBAAoB,CAmBtD"}