@monetize.software/sdk-react 3.0.0-alpha.5 → 3.0.0-alpha.7
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 +7 -2
- package/dist/components/PaywallButton.d.ts +5 -2
- package/dist/components/PaywallButton.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +43 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -117,13 +117,18 @@ the render prop:
|
|
|
117
117
|
)} />
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
`mode` switches between `open()` / `openSupport()` / `
|
|
120
|
+
`mode` switches between `open()` / `openSupport()` / `openSignin()` / `openSignup()`:
|
|
121
121
|
|
|
122
122
|
```tsx
|
|
123
123
|
<PaywallButton mode="support">Need help?</PaywallButton>
|
|
124
|
-
<PaywallButton mode="
|
|
124
|
+
<PaywallButton mode="signin">Sign in</PaywallButton>
|
|
125
|
+
<PaywallButton mode="signup">Create account</PaywallButton>
|
|
125
126
|
```
|
|
126
127
|
|
|
128
|
+
`mode="auth"` оставлен как алиас для `signin` (back-compat).
|
|
129
|
+
|
|
130
|
+
Для анонимного signin'а используй `usePaywall().signInAnonymously()` напрямую — он headless (без модалки), хост сам управляет loading-стейтом кнопки.
|
|
131
|
+
|
|
127
132
|
## SSR / Next.js
|
|
128
133
|
|
|
129
134
|
```tsx
|
|
@@ -7,8 +7,11 @@ import { OpenOptions } from '../../../sdk/src';
|
|
|
7
7
|
*/
|
|
8
8
|
type OpenProps = OpenOptions;
|
|
9
9
|
interface CommonProps extends OpenProps {
|
|
10
|
-
/** Что открывать: layout (default), support, auth-gate,
|
|
11
|
-
|
|
10
|
+
/** Что открывать: layout (default), support, auth-gate (signin),
|
|
11
|
+
* signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth
|
|
12
|
+
* дефолтит в signin-mode). Для анонимного signin используй
|
|
13
|
+
* `usePaywall().signInAnonymously()` напрямую — headless без модалки. */
|
|
14
|
+
mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';
|
|
12
15
|
/** Render-prop для полного контроля над элементом-триггером. Когда задан,
|
|
13
16
|
* все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */
|
|
14
17
|
render?: (args: PaywallButtonRenderArgs) => ReactElement;
|
|
@@ -1 +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
|
|
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;;;8EAG0E;IAC1E,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5D;+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;qDA0FrB,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react/jsx-runtime"),l=require("react"),m=require("@monetize.software/sdk"),b=l.createContext(null);b.displayName="PaywallContext";const g=l.createContext(!1);g.displayName="PaywallProviderMarker";function T(e){const n="instance"in e?e.instance:void 0,t="options"in e?e.options:void 0,[r,a]=l.useState(n??null);return l.useEffect(()=>{if(n){a(n);return}if(!t)return;const s=new m.PaywallUI(t);return a(s),()=>{s.destroy(),a(null)}},[n]),i.jsx(g.Provider,{value:!0,children:i.jsx(b.Provider,{value:r,children:e.children})})}function o(){const e=l.useContext(g),n=l.useContext(b);if(!e)throw new Error("[sdk-react] usePaywall() called outside <PaywallProvider>. Wrap your tree with <PaywallProvider options={...}> or pass an externally-created instance via <PaywallProvider instance={paywall}>.");return n}const h={open:!1,view:null,error:null};function j(){const e=o(),n=l.useCallback(r=>e?e.onStateChange(r,{immediate:"none"}):()=>{},[e]),t=l.useCallback(()=>e?e.getState():h,[e]);return l.useSyncExternalStore(n,t,()=>h)}function B(){const e=o(),n=l.useCallback(r=>e?e.on("userChange",()=>r()):()=>{},[e]),t=l.useCallback(()=>e?e.billing.getCachedUser():null,[e]);return l.useSyncExternalStore(n,t,A)}function A(){return null}function V(e,n){const t=o(),r=l.useRef(n);r.current=n,l.useEffect(()=>{if(t)return t.on(e,a=>{r.current(a)})},[t,e])}const k={status:"loading",result:null};function C(e={}){const n=o(),[t,r]=l.useState(k),a=e.skipTrial===!0,s=e.skipVisibility===!0;return l.useEffect(()=>{if(!n){r(k);return}const c=new AbortController;let y=!1;const u=()=>{n.getAccess({skipTrial:a,skipVisibility:s,signal:c.signal}).then(p=>{y||c.signal.aborted||r({status:"ready",result:p})}).catch(()=>{})};u();const d=n.on("userChange",u),w=n.on("purchase_completed",u);return()=>{y=!0,c.abort(),d(),w()}},[n,a,s]),t}function R(){const e=o(),[n,t]=l.useState(()=>({prices:e?.getCachedPrices()??null,loading:!0,error:null}));return l.useEffect(()=>{if(!e){t({prices:null,loading:!0,error:null});return}const r=e.getCachedPrices();t({prices:r,loading:r===null,error:null});const a=new AbortController;let s=!1;(()=>{e.getPrices({signal:a.signal}).then(u=>{s||t({prices:u,loading:!1,error:null})}).catch(u=>{s||a.signal.aborted||t(d=>({prices:d.prices,loading:!1,error:u instanceof Error?u:new Error(String(u))}))})})();const y=e.on("ready",()=>{const u=e.getCachedPrices();u&&t({prices:u,loading:!1,error:null})});return()=>{s=!0,a.abort(),y()}},[e]),n}function O(){const e=o(),[n,t]=l.useState(()=>e?.getTrialStatus()??null),r=l.useCallback(()=>{if(!e){t(null);return}t(e.getTrialStatus())},[e]);return l.useEffect(()=>{if(!e){t(null);return}r();const a=e.on("trial_blocked",r),s=e.on("trial_expired",r);return()=>{a(),s()}},[e,r]),n}function _(){const e=o(),[n,t]=l.useState(()=>e?.getVisibility()??null),r=l.useCallback(()=>{if(!e){t(null);return}t(e.getVisibility())},[e]);return l.useEffect(()=>{if(!e){t(null);return}r();const a=e.on("ready",r),s=e.on("visibility_blocked",r);return()=>{a(),s()}},[e,r]),n}function U(e){const n=o(),t=C(),r=t.status==="ready"&&t.result.access==="blocked",a=e.openOnBlocked===!0&&r;if(l.useEffect(()=>{a&&n&&n.open()},[a,n]),t.status==="loading")return i.jsx(i.Fragment,{children:e.loading??null});if(t.result.access==="granted")return i.jsx(i.Fragment,{children:e.children});const s=e.fallback;return typeof s=="function"?i.jsx(i.Fragment,{children:s({result:t.result,open:()=>n?.open()})}):i.jsx(i.Fragment,{children:s??null})}const v=l.forwardRef(function(n,t){const r=o(),{mode:a="paywall",identity:s,renew:c,skipTrial:y,skipVisibility:u,render:d,onClick:w,disabled:p,...x}=n,P=r!==null,f={identity:s,renew:c,skipTrial:y,skipVisibility:u},S=()=>{if(r)switch(a){case"support":r.openSupport(f);return;case"auth":case"signin":r.openSignin(f);return;case"signup":r.openSignup(f);return;default:r.open(f)}};return d?d({open:S,ready:P}):i.jsx("button",{ref:t,type:"button",disabled:p||!P,"aria-busy":P?void 0:!0,onClick:E=>{S(),w?.(E)},...x})}),F=l.forwardRef(function(n,t){return i.jsx(v,{...n,mode:"support",ref:t})});exports.PaywallButton=v;exports.PaywallGate=U;exports.PaywallProvider=T;exports.PaywallSupportButton=F;exports.usePaywall=o;exports.usePaywallAccess=C;exports.usePaywallEvent=V;exports.usePaywallPrices=R;exports.usePaywallState=j;exports.usePaywallTrial=O;exports.usePaywallUser=B;exports.usePaywallVisibility=_;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate, anon-gate. */\n mode?: 'paywall' | 'support' | 'auth' | 'anon';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n paywall.openAuth(openOpts);\n return;\n case 'anon':\n paywall.openAnonGate(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as i, Fragment as P } from "react/jsx-runtime";
|
|
3
|
-
import { createContext as
|
|
3
|
+
import { createContext as T, useState as p, useEffect as u, useContext as C, useCallback as f, useSyncExternalStore as A, useRef as _, forwardRef as B } from "react";
|
|
4
4
|
import { PaywallUI as O } from "@monetize.software/sdk";
|
|
5
|
-
const S =
|
|
5
|
+
const S = T(null);
|
|
6
6
|
S.displayName = "PaywallContext";
|
|
7
|
-
const k =
|
|
7
|
+
const k = T(!1);
|
|
8
8
|
k.displayName = "PaywallProviderMarker";
|
|
9
9
|
function j(e) {
|
|
10
10
|
const n = "instance" in e ? e.instance : void 0, t = "options" in e ? e.options : void 0, [r, l] = p(
|
|
@@ -16,9 +16,9 @@ function j(e) {
|
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
if (!t) return;
|
|
19
|
-
const
|
|
20
|
-
return l(
|
|
21
|
-
|
|
19
|
+
const s = new O(t);
|
|
20
|
+
return l(s), () => {
|
|
21
|
+
s.destroy(), l(null);
|
|
22
22
|
};
|
|
23
23
|
}, [n]), /* @__PURE__ */ i(k.Provider, { value: !0, children: /* @__PURE__ */ i(S.Provider, { value: r, children: e.children }) });
|
|
24
24
|
}
|
|
@@ -37,7 +37,7 @@ function D() {
|
|
|
37
37
|
},
|
|
38
38
|
[e]
|
|
39
39
|
), t = f(() => e ? e.getState() : m, [e]);
|
|
40
|
-
return
|
|
40
|
+
return A(n, t, () => m);
|
|
41
41
|
}
|
|
42
42
|
function F() {
|
|
43
43
|
const e = o(), n = f(
|
|
@@ -45,7 +45,7 @@ function F() {
|
|
|
45
45
|
},
|
|
46
46
|
[e]
|
|
47
47
|
), t = f(() => e ? e.billing.getCachedUser() : null, [e]);
|
|
48
|
-
return
|
|
48
|
+
return A(n, t, R);
|
|
49
49
|
}
|
|
50
50
|
function R() {
|
|
51
51
|
return null;
|
|
@@ -61,7 +61,7 @@ function H(e, n) {
|
|
|
61
61
|
}
|
|
62
62
|
const x = { status: "loading", result: null };
|
|
63
63
|
function N(e = {}) {
|
|
64
|
-
const n = o(), [t, r] = p(x), l = e.skipTrial === !0,
|
|
64
|
+
const n = o(), [t, r] = p(x), l = e.skipTrial === !0, s = e.skipVisibility === !0;
|
|
65
65
|
return u(() => {
|
|
66
66
|
if (!n) {
|
|
67
67
|
r(x);
|
|
@@ -69,18 +69,18 @@ function N(e = {}) {
|
|
|
69
69
|
}
|
|
70
70
|
const c = new AbortController();
|
|
71
71
|
let d = !1;
|
|
72
|
-
const
|
|
73
|
-
n.getAccess({ skipTrial: l, skipVisibility:
|
|
74
|
-
d || c.signal.aborted || r({ status: "ready", result:
|
|
72
|
+
const a = () => {
|
|
73
|
+
n.getAccess({ skipTrial: l, skipVisibility: s, signal: c.signal }).then((b) => {
|
|
74
|
+
d || c.signal.aborted || r({ status: "ready", result: b });
|
|
75
75
|
}).catch(() => {
|
|
76
76
|
});
|
|
77
77
|
};
|
|
78
|
-
|
|
79
|
-
const y = n.on("userChange",
|
|
78
|
+
a();
|
|
79
|
+
const y = n.on("userChange", a), g = n.on("purchase_completed", a);
|
|
80
80
|
return () => {
|
|
81
|
-
d = !0, c.abort(), y(),
|
|
81
|
+
d = !0, c.abort(), y(), g();
|
|
82
82
|
};
|
|
83
|
-
}, [n, l,
|
|
83
|
+
}, [n, l, s]), t;
|
|
84
84
|
}
|
|
85
85
|
function L() {
|
|
86
86
|
const e = o(), [n, t] = p(() => ({
|
|
@@ -96,24 +96,24 @@ function L() {
|
|
|
96
96
|
const r = e.getCachedPrices();
|
|
97
97
|
t({ prices: r, loading: r === null, error: null });
|
|
98
98
|
const l = new AbortController();
|
|
99
|
-
let
|
|
99
|
+
let s = !1;
|
|
100
100
|
(() => {
|
|
101
|
-
e.getPrices({ signal: l.signal }).then((
|
|
102
|
-
|
|
103
|
-
}).catch((
|
|
104
|
-
|
|
101
|
+
e.getPrices({ signal: l.signal }).then((a) => {
|
|
102
|
+
s || t({ prices: a, loading: !1, error: null });
|
|
103
|
+
}).catch((a) => {
|
|
104
|
+
s || l.signal.aborted || t((y) => ({
|
|
105
105
|
prices: y.prices,
|
|
106
106
|
loading: !1,
|
|
107
|
-
error:
|
|
107
|
+
error: a instanceof Error ? a : new Error(String(a))
|
|
108
108
|
}));
|
|
109
109
|
});
|
|
110
110
|
})();
|
|
111
111
|
const d = e.on("ready", () => {
|
|
112
|
-
const
|
|
113
|
-
|
|
112
|
+
const a = e.getCachedPrices();
|
|
113
|
+
a && t({ prices: a, loading: !1, error: null });
|
|
114
114
|
});
|
|
115
115
|
return () => {
|
|
116
|
-
|
|
116
|
+
s = !0, l.abort(), d();
|
|
117
117
|
};
|
|
118
118
|
}, [e]), n;
|
|
119
119
|
}
|
|
@@ -133,9 +133,9 @@ function W() {
|
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
r();
|
|
136
|
-
const l = e.on("trial_blocked", r),
|
|
136
|
+
const l = e.on("trial_blocked", r), s = e.on("trial_expired", r);
|
|
137
137
|
return () => {
|
|
138
|
-
l(),
|
|
138
|
+
l(), s();
|
|
139
139
|
};
|
|
140
140
|
}, [e, r]), n;
|
|
141
141
|
}
|
|
@@ -155,9 +155,9 @@ function q() {
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
r();
|
|
158
|
-
const l = e.on("ready", r),
|
|
158
|
+
const l = e.on("ready", r), s = e.on("visibility_blocked", r);
|
|
159
159
|
return () => {
|
|
160
|
-
l(),
|
|
160
|
+
l(), s();
|
|
161
161
|
};
|
|
162
162
|
}, [e, r]), n;
|
|
163
163
|
}
|
|
@@ -169,35 +169,36 @@ function z(e) {
|
|
|
169
169
|
return /* @__PURE__ */ i(P, { children: e.loading ?? null });
|
|
170
170
|
if (t.result.access === "granted")
|
|
171
171
|
return /* @__PURE__ */ i(P, { children: e.children });
|
|
172
|
-
const
|
|
173
|
-
return typeof
|
|
172
|
+
const s = e.fallback;
|
|
173
|
+
return typeof s == "function" ? /* @__PURE__ */ i(P, { children: s({
|
|
174
174
|
result: t.result,
|
|
175
175
|
open: () => n?.open()
|
|
176
|
-
}) }) : /* @__PURE__ */ i(P, { children:
|
|
176
|
+
}) }) : /* @__PURE__ */ i(P, { children: s ?? null });
|
|
177
177
|
}
|
|
178
178
|
const U = B(
|
|
179
179
|
function(n, t) {
|
|
180
180
|
const r = o(), {
|
|
181
181
|
mode: l = "paywall",
|
|
182
|
-
identity:
|
|
182
|
+
identity: s,
|
|
183
183
|
renew: c,
|
|
184
184
|
skipTrial: d,
|
|
185
|
-
skipVisibility:
|
|
185
|
+
skipVisibility: a,
|
|
186
186
|
render: y,
|
|
187
|
-
onClick:
|
|
188
|
-
disabled:
|
|
187
|
+
onClick: g,
|
|
188
|
+
disabled: b,
|
|
189
189
|
...E
|
|
190
|
-
} = n, h = r !== null, w = { identity:
|
|
190
|
+
} = n, h = r !== null, w = { identity: s, renew: c, skipTrial: d, skipVisibility: a }, v = () => {
|
|
191
191
|
if (r)
|
|
192
192
|
switch (l) {
|
|
193
193
|
case "support":
|
|
194
194
|
r.openSupport(w);
|
|
195
195
|
return;
|
|
196
196
|
case "auth":
|
|
197
|
-
|
|
197
|
+
case "signin":
|
|
198
|
+
r.openSignin(w);
|
|
198
199
|
return;
|
|
199
|
-
case "
|
|
200
|
-
r.
|
|
200
|
+
case "signup":
|
|
201
|
+
r.openSignup(w);
|
|
201
202
|
return;
|
|
202
203
|
default:
|
|
203
204
|
r.open(w);
|
|
@@ -208,10 +209,10 @@ const U = B(
|
|
|
208
209
|
{
|
|
209
210
|
ref: t,
|
|
210
211
|
type: "button",
|
|
211
|
-
disabled:
|
|
212
|
+
disabled: b || !h,
|
|
212
213
|
"aria-busy": h ? void 0 : !0,
|
|
213
214
|
onClick: (V) => {
|
|
214
|
-
v(),
|
|
215
|
+
v(), g?.(V);
|
|
215
216
|
},
|
|
216
217
|
...E
|
|
217
218
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate, anon-gate. */\n mode?: 'paywall' | 'support' | 'auth' | 'anon';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n paywall.openAuth(openOpts);\n return;\n case 'anon':\n paywall.openAnonGate(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monetize.software/sdk-react",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.7",
|
|
4
4
|
"description": "React bindings for @monetize.software/sdk — Provider, hooks and declarative components. Works with the web SDK and the extension SDK (any drop-in compatible PaywallUI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": ">=18",
|
|
29
|
-
"@monetize.software/sdk": "3.0.0-alpha.
|
|
29
|
+
"@monetize.software/sdk": "3.0.0-alpha.9"
|
|
30
30
|
},
|
|
31
31
|
"peerDependenciesMeta": {
|
|
32
32
|
"@monetize.software/sdk": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"vite": "^6.0.5",
|
|
49
49
|
"vite-plugin-dts": "^4.3.0",
|
|
50
50
|
"vitest": "^2.1.8",
|
|
51
|
-
"@monetize.software/sdk": "3.0.0-alpha.
|
|
51
|
+
"@monetize.software/sdk": "3.0.0-alpha.9"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"dev": "vite",
|