@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.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/PaywallProvider.d.ts +58 -0
- package/dist/PaywallProvider.d.ts.map +1 -0
- package/dist/components/PaywallButton.d.ts +61 -0
- package/dist/components/PaywallButton.d.ts.map +1 -0
- package/dist/components/PaywallGate.d.ts +61 -0
- package/dist/components/PaywallGate.d.ts.map +1 -0
- package/dist/components/PaywallSupportButton.d.ts +13 -0
- package/dist/components/PaywallSupportButton.d.ts.map +1 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/contract.d.ts +29 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/hooks/usePaywall.d.ts +23 -0
- package/dist/hooks/usePaywall.d.ts.map +1 -0
- package/dist/hooks/usePaywallAccess.d.ts +47 -0
- package/dist/hooks/usePaywallAccess.d.ts.map +1 -0
- package/dist/hooks/usePaywallEvent.d.ts +27 -0
- package/dist/hooks/usePaywallEvent.d.ts.map +1 -0
- package/dist/hooks/usePaywallPrices.d.ts +38 -0
- package/dist/hooks/usePaywallPrices.d.ts.map +1 -0
- package/dist/hooks/usePaywallState.d.ts +23 -0
- package/dist/hooks/usePaywallState.d.ts.map +1 -0
- package/dist/hooks/usePaywallTrial.d.ts +27 -0
- package/dist/hooks/usePaywallTrial.d.ts.map +1 -0
- package/dist/hooks/usePaywallUser.d.ts +30 -0
- package/dist/hooks/usePaywallUser.d.ts.map +1 -0
- package/dist/hooks/usePaywallVisibility.d.ts +26 -0
- package/dist/hooks/usePaywallVisibility.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +237 -0
- package/dist/index.js.map +1 -0
- 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"}
|