@hook-sdk/template 0.5.0 → 0.7.0

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/dist/index.d.cts CHANGED
@@ -2,7 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType, Component } from 'react';
3
3
  import { z } from 'zod';
4
4
  import * as _hook_sdk_sdk from '@hook-sdk/sdk';
5
- import { ReminderSlot } from '@hook-sdk/sdk';
5
+ import { PlanState, ReminderSlot } from '@hook-sdk/sdk';
6
+ export { PlanInfo, PlanState } from '@hook-sdk/sdk';
6
7
 
7
8
  declare const AppConfigSchema: z.ZodObject<{
8
9
  $schema: z.ZodOptional<z.ZodString>;
@@ -266,14 +267,29 @@ interface UseResetFormResult {
266
267
  declare function useResetForm(): UseResetFormResult;
267
268
 
268
269
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
270
+ type PaymentMethod = 'card' | 'pix-auto' | 'pix-once';
271
+ interface PixPending {
272
+ method: 'pix-auto' | 'pix-once';
273
+ qrCodePayload: string | null;
274
+ qrCodeBase64: string | null;
275
+ expiresAt: string | null;
276
+ }
269
277
  /**
270
278
  * Hook headless pro Paywall. Expõe status atual da subscription + ação
271
- * `checkout(cpf)` que chama `POST /payments/checkout/card` e redireciona
272
- * o browser pro `invoiceUrl` hosted do Asaas. Após o checkout, Asaas
273
- * redireciona de volta com query string indicativa; subscription real
274
- * é atualizada via webhook (`PAYMENT_RECEIVED` / `SUBSCRIPTION_ACTIVATED`).
279
+ * `checkout({cpf, cycle, method})` com 3 métodos:
280
+ *
281
+ * - **card** (default): cria subscription Asaas recorrente, redireciona pro
282
+ * invoiceUrl hosted. Após pagar, Asaas redireciona de volta e webhook
283
+ * atualiza DB.
284
+ * - **pix-auto**: Pix Automático Jornada 3 BACEN. User escaneia QR 1x no app
285
+ * do banco, autoriza débito recorrente. Retorna QR via `pixPending`
286
+ * (hook fica populado até user confirmar no banco).
287
+ * - **pix-once**: Pix avulso. User paga QR único, ganha 1 ciclo. Sem
288
+ * recorrência automática — renova voltando no app. Retorna QR via
289
+ * `pixPending`.
275
290
  *
276
- * MVP: checkout cartão. Pix vem depois (gatilho documentado em P10.7).
291
+ * Subscription real é atualizada via webhook (`PAYMENT_RECEIVED` ou
292
+ * `PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED`).
277
293
  */
278
294
  declare function usePaywallState(): {
279
295
  status: SubscriptionStatus;
@@ -282,12 +298,106 @@ declare function usePaywallState(): {
282
298
  checkout: (args: {
283
299
  cpf: string;
284
300
  cycle?: "MONTHLY" | "YEARLY";
301
+ method?: PaymentMethod;
285
302
  }) => Promise<void>;
286
303
  cancel: () => Promise<void>;
287
304
  opening: boolean;
288
305
  error: Error | null;
306
+ pixPending: PixPending | null;
307
+ dismissPix: () => void;
289
308
  };
290
309
 
310
+ /**
311
+ * Thin wrapper sobre `useHook().plan`. Existe pra apps importarem "tudo
312
+ * que precisam" via `@hook-sdk/template` sem mudar de package pra cada
313
+ * primitive, e pra manter espaço pra evoluir a API (ex: expor campos
314
+ * derivados como `monthlyEquivalent()`) sem mexer no SDK.
315
+ *
316
+ * Retorna o PlanState bruto. Use junto com helpers de `@hook-sdk/template`:
317
+ *
318
+ * const { data, initialLoadComplete } = usePlan();
319
+ * if (!initialLoadComplete) return <Skeleton />;
320
+ * if (!data) return null;
321
+ * return <span>{formatBRL(data.priceCents)}</span>;
322
+ */
323
+ declare function usePlan(): PlanState;
324
+
325
+ /**
326
+ * Helpers de formatação de preço pro Hook.
327
+ *
328
+ * Preço vive no banco em CENTAVOS (int), sempre. Apps que renderizam preço
329
+ * leem do hook `usePlan()` e passam por `formatBRL(cents)` — nunca formatam
330
+ * inline. Isso evita os dois patterns errados que já vimos em conversões
331
+ * Lovable→Hook: (a) strings hardcoded "R$ 19,90" no JSX, (b) divisões ad-hoc
332
+ * por 100 + `toFixed(2)` que ignoram locale.
333
+ *
334
+ * Ver G72 em .claude/skills/lovable-to-hook/catalog/known-gotchas.md.
335
+ */
336
+ /**
337
+ * Formata centavos como BRL no locale pt-BR. Aceita null pra conveniência
338
+ * (callers que lidam com `yearlyPriceCents: number | null`).
339
+ *
340
+ * Examples:
341
+ * formatBRL(1990) → "R$ 19,90"
342
+ * formatBRL(4999) → "R$ 49,99"
343
+ * formatBRL(23988) → "R$ 239,88"
344
+ * formatBRL(0) → "R$ 0,00"
345
+ * formatBRL(null) → ""
346
+ */
347
+ declare function formatBRL(cents: number | null | undefined): string;
348
+ /**
349
+ * Deriva "preço mensal" a partir do preço anual (em centavos). Round half-up
350
+ * pra evitar R$ 19,899... → R$ 19,89 quando o usuário esperava R$ 19,90.
351
+ *
352
+ * Não assume que o creator configurou yearlyPriceCents "redondo": se ele
353
+ * setou 23988 (= R$ 239,88), 23988/12 = 1999 (= R$ 19,99). Se setou 23880
354
+ * (= R$ 238,80), 23880/12 = 1990 (= R$ 19,90).
355
+ *
356
+ * Example (papomaterno):
357
+ * monthlyFromYearly(23988) = 1999 → formatBRL = "R$ 19,99"
358
+ * monthlyFromYearly(23880) = 1990 → formatBRL = "R$ 19,90"
359
+ */
360
+ declare function monthlyFromYearly(yearlyCents: number | null | undefined): number;
361
+ /**
362
+ * Deriva "preço por dia" (365 dias) a partir do anual, em centavos. Útil
363
+ * pra copy tipo "apenas R$ 0,66 por dia, menos que um café".
364
+ *
365
+ * Example:
366
+ * dailyFromYearly(23988) = 66 → formatBRL = "R$ 0,66"
367
+ */
368
+ declare function dailyFromYearly(yearlyCents: number | null | undefined): number;
369
+ /**
370
+ * Deriva o preço-ancoragem (valor "cheio" riscado) como múltiplo do preço
371
+ * real. Serve pra ancoragem psicológica em telas de paywall: mostra um valor
372
+ * maior riscado antes do preço real pra o usuário sentir desconto.
373
+ *
374
+ * Com `anchorMultiplier` no `paywall_config`, o creator mantém só o preço
375
+ * real no DB; a ancoragem acompanha automaticamente se o preço muda.
376
+ *
377
+ * Retorna `null` quando o multiplier não é utilizável (falta, ≤ 1, NaN,
378
+ * Infinity). Caller deve cair no fallback (valor absoluto em
379
+ * `paywall_config.anchorPriceCents`) ou pular a renderização da ancoragem.
380
+ *
381
+ * Examples:
382
+ * computeAnchorCents(1999, 2.5) = 4998
383
+ * computeAnchorCents(1999, undefined) = null
384
+ * computeAnchorCents(1999, 1) = null // 1× não ancora nada
385
+ * computeAnchorCents(1999, 0) = null
386
+ * computeAnchorCents(1999, NaN) = null
387
+ */
388
+ declare function computeAnchorCents(baseCents: number, multiplier: number | null | undefined): number | null;
389
+ /**
390
+ * Percentual de desconto (anchor → real), arredondado pra baixo pra nunca
391
+ * overstate. Retorna 0 quando anchor ≤ real (sem desconto a mostrar).
392
+ *
393
+ * Examples:
394
+ * discountPercent(4998, 1999) = 60 // (4998-1999)/4998 = 0.60...
395
+ * discountPercent(3998, 1999) = 50
396
+ * discountPercent(1000, 2000) = 0 // real > anchor
397
+ * discountPercent(2000, 2000) = 0
398
+ */
399
+ declare function discountPercent(anchorCents: number, realCents: number): number;
400
+
291
401
  /**
292
402
  * Escape hatch pra screens FORA do fluxo de auth (ex: settings/trocar-senha).
293
403
  * Pra LoginScreen/SignupScreen/ForgotScreen custom, use useLoginForm/useSignupForm/useForgotForm.
@@ -493,4 +603,4 @@ declare function shouldBlockInstall(state: InstallState, now?: number): boolean;
493
603
  */
494
604
  declare function shouldShowPermanentOption(state: InstallState): boolean;
495
605
 
496
- export { type AndroidBrowser, AppRoot, type AppRootProps, type AuthFormError, type AuthFormErrorCode, type AuthScreen, type AuthScreenProps, DefaultForgotScreen, DefaultLoginScreen, DefaultPaywall, DefaultResetScreen, DefaultSignupScreen, EmptyState, ErrorBoundary, type IOSBrowser, type InAppApp, type InstallActions, InstallGate, InstallSplash, type InstallState, type InstallVariant, LoadingState, type Platform, PushPrompt, type PushPromptProps, type PushPromptTexts, type PushUiState, type SubscriptionStatus, type ToastItem, type UseLoginFormResult, type UseResetFormResult, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, shouldBlockInstall, shouldShowPermanentOption, useAuth, useAuthPrimitives, useForgotForm, useInstallPrompt, useLoginForm, usePaywallState, usePush, useReminders, useResetForm, useSignupForm, useSubscription, useToast };
606
+ export { type AndroidBrowser, AppRoot, type AppRootProps, type AuthFormError, type AuthFormErrorCode, type AuthScreen, type AuthScreenProps, DefaultForgotScreen, DefaultLoginScreen, DefaultPaywall, DefaultResetScreen, DefaultSignupScreen, EmptyState, ErrorBoundary, type IOSBrowser, type InAppApp, type InstallActions, InstallGate, InstallSplash, type InstallState, type InstallVariant, LoadingState, type Platform, PushPrompt, type PushPromptProps, type PushPromptTexts, type PushUiState, type SubscriptionStatus, type ToastItem, type UseLoginFormResult, type UseResetFormResult, computeAnchorCents, dailyFromYearly, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, discountPercent, formatBRL, monthlyFromYearly, shouldBlockInstall, shouldShowPermanentOption, useAuth, useAuthPrimitives, useForgotForm, useInstallPrompt, useLoginForm, usePaywallState, usePlan, usePush, useReminders, useResetForm, useSignupForm, useSubscription, useToast };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType, Component } from 'react';
3
3
  import { z } from 'zod';
4
4
  import * as _hook_sdk_sdk from '@hook-sdk/sdk';
5
- import { ReminderSlot } from '@hook-sdk/sdk';
5
+ import { PlanState, ReminderSlot } from '@hook-sdk/sdk';
6
+ export { PlanInfo, PlanState } from '@hook-sdk/sdk';
6
7
 
7
8
  declare const AppConfigSchema: z.ZodObject<{
8
9
  $schema: z.ZodOptional<z.ZodString>;
@@ -266,14 +267,29 @@ interface UseResetFormResult {
266
267
  declare function useResetForm(): UseResetFormResult;
267
268
 
268
269
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
270
+ type PaymentMethod = 'card' | 'pix-auto' | 'pix-once';
271
+ interface PixPending {
272
+ method: 'pix-auto' | 'pix-once';
273
+ qrCodePayload: string | null;
274
+ qrCodeBase64: string | null;
275
+ expiresAt: string | null;
276
+ }
269
277
  /**
270
278
  * Hook headless pro Paywall. Expõe status atual da subscription + ação
271
- * `checkout(cpf)` que chama `POST /payments/checkout/card` e redireciona
272
- * o browser pro `invoiceUrl` hosted do Asaas. Após o checkout, Asaas
273
- * redireciona de volta com query string indicativa; subscription real
274
- * é atualizada via webhook (`PAYMENT_RECEIVED` / `SUBSCRIPTION_ACTIVATED`).
279
+ * `checkout({cpf, cycle, method})` com 3 métodos:
280
+ *
281
+ * - **card** (default): cria subscription Asaas recorrente, redireciona pro
282
+ * invoiceUrl hosted. Após pagar, Asaas redireciona de volta e webhook
283
+ * atualiza DB.
284
+ * - **pix-auto**: Pix Automático Jornada 3 BACEN. User escaneia QR 1x no app
285
+ * do banco, autoriza débito recorrente. Retorna QR via `pixPending`
286
+ * (hook fica populado até user confirmar no banco).
287
+ * - **pix-once**: Pix avulso. User paga QR único, ganha 1 ciclo. Sem
288
+ * recorrência automática — renova voltando no app. Retorna QR via
289
+ * `pixPending`.
275
290
  *
276
- * MVP: checkout cartão. Pix vem depois (gatilho documentado em P10.7).
291
+ * Subscription real é atualizada via webhook (`PAYMENT_RECEIVED` ou
292
+ * `PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED`).
277
293
  */
278
294
  declare function usePaywallState(): {
279
295
  status: SubscriptionStatus;
@@ -282,12 +298,106 @@ declare function usePaywallState(): {
282
298
  checkout: (args: {
283
299
  cpf: string;
284
300
  cycle?: "MONTHLY" | "YEARLY";
301
+ method?: PaymentMethod;
285
302
  }) => Promise<void>;
286
303
  cancel: () => Promise<void>;
287
304
  opening: boolean;
288
305
  error: Error | null;
306
+ pixPending: PixPending | null;
307
+ dismissPix: () => void;
289
308
  };
290
309
 
310
+ /**
311
+ * Thin wrapper sobre `useHook().plan`. Existe pra apps importarem "tudo
312
+ * que precisam" via `@hook-sdk/template` sem mudar de package pra cada
313
+ * primitive, e pra manter espaço pra evoluir a API (ex: expor campos
314
+ * derivados como `monthlyEquivalent()`) sem mexer no SDK.
315
+ *
316
+ * Retorna o PlanState bruto. Use junto com helpers de `@hook-sdk/template`:
317
+ *
318
+ * const { data, initialLoadComplete } = usePlan();
319
+ * if (!initialLoadComplete) return <Skeleton />;
320
+ * if (!data) return null;
321
+ * return <span>{formatBRL(data.priceCents)}</span>;
322
+ */
323
+ declare function usePlan(): PlanState;
324
+
325
+ /**
326
+ * Helpers de formatação de preço pro Hook.
327
+ *
328
+ * Preço vive no banco em CENTAVOS (int), sempre. Apps que renderizam preço
329
+ * leem do hook `usePlan()` e passam por `formatBRL(cents)` — nunca formatam
330
+ * inline. Isso evita os dois patterns errados que já vimos em conversões
331
+ * Lovable→Hook: (a) strings hardcoded "R$ 19,90" no JSX, (b) divisões ad-hoc
332
+ * por 100 + `toFixed(2)` que ignoram locale.
333
+ *
334
+ * Ver G72 em .claude/skills/lovable-to-hook/catalog/known-gotchas.md.
335
+ */
336
+ /**
337
+ * Formata centavos como BRL no locale pt-BR. Aceita null pra conveniência
338
+ * (callers que lidam com `yearlyPriceCents: number | null`).
339
+ *
340
+ * Examples:
341
+ * formatBRL(1990) → "R$ 19,90"
342
+ * formatBRL(4999) → "R$ 49,99"
343
+ * formatBRL(23988) → "R$ 239,88"
344
+ * formatBRL(0) → "R$ 0,00"
345
+ * formatBRL(null) → ""
346
+ */
347
+ declare function formatBRL(cents: number | null | undefined): string;
348
+ /**
349
+ * Deriva "preço mensal" a partir do preço anual (em centavos). Round half-up
350
+ * pra evitar R$ 19,899... → R$ 19,89 quando o usuário esperava R$ 19,90.
351
+ *
352
+ * Não assume que o creator configurou yearlyPriceCents "redondo": se ele
353
+ * setou 23988 (= R$ 239,88), 23988/12 = 1999 (= R$ 19,99). Se setou 23880
354
+ * (= R$ 238,80), 23880/12 = 1990 (= R$ 19,90).
355
+ *
356
+ * Example (papomaterno):
357
+ * monthlyFromYearly(23988) = 1999 → formatBRL = "R$ 19,99"
358
+ * monthlyFromYearly(23880) = 1990 → formatBRL = "R$ 19,90"
359
+ */
360
+ declare function monthlyFromYearly(yearlyCents: number | null | undefined): number;
361
+ /**
362
+ * Deriva "preço por dia" (365 dias) a partir do anual, em centavos. Útil
363
+ * pra copy tipo "apenas R$ 0,66 por dia, menos que um café".
364
+ *
365
+ * Example:
366
+ * dailyFromYearly(23988) = 66 → formatBRL = "R$ 0,66"
367
+ */
368
+ declare function dailyFromYearly(yearlyCents: number | null | undefined): number;
369
+ /**
370
+ * Deriva o preço-ancoragem (valor "cheio" riscado) como múltiplo do preço
371
+ * real. Serve pra ancoragem psicológica em telas de paywall: mostra um valor
372
+ * maior riscado antes do preço real pra o usuário sentir desconto.
373
+ *
374
+ * Com `anchorMultiplier` no `paywall_config`, o creator mantém só o preço
375
+ * real no DB; a ancoragem acompanha automaticamente se o preço muda.
376
+ *
377
+ * Retorna `null` quando o multiplier não é utilizável (falta, ≤ 1, NaN,
378
+ * Infinity). Caller deve cair no fallback (valor absoluto em
379
+ * `paywall_config.anchorPriceCents`) ou pular a renderização da ancoragem.
380
+ *
381
+ * Examples:
382
+ * computeAnchorCents(1999, 2.5) = 4998
383
+ * computeAnchorCents(1999, undefined) = null
384
+ * computeAnchorCents(1999, 1) = null // 1× não ancora nada
385
+ * computeAnchorCents(1999, 0) = null
386
+ * computeAnchorCents(1999, NaN) = null
387
+ */
388
+ declare function computeAnchorCents(baseCents: number, multiplier: number | null | undefined): number | null;
389
+ /**
390
+ * Percentual de desconto (anchor → real), arredondado pra baixo pra nunca
391
+ * overstate. Retorna 0 quando anchor ≤ real (sem desconto a mostrar).
392
+ *
393
+ * Examples:
394
+ * discountPercent(4998, 1999) = 60 // (4998-1999)/4998 = 0.60...
395
+ * discountPercent(3998, 1999) = 50
396
+ * discountPercent(1000, 2000) = 0 // real > anchor
397
+ * discountPercent(2000, 2000) = 0
398
+ */
399
+ declare function discountPercent(anchorCents: number, realCents: number): number;
400
+
291
401
  /**
292
402
  * Escape hatch pra screens FORA do fluxo de auth (ex: settings/trocar-senha).
293
403
  * Pra LoginScreen/SignupScreen/ForgotScreen custom, use useLoginForm/useSignupForm/useForgotForm.
@@ -493,4 +603,4 @@ declare function shouldBlockInstall(state: InstallState, now?: number): boolean;
493
603
  */
494
604
  declare function shouldShowPermanentOption(state: InstallState): boolean;
495
605
 
496
- export { type AndroidBrowser, AppRoot, type AppRootProps, type AuthFormError, type AuthFormErrorCode, type AuthScreen, type AuthScreenProps, DefaultForgotScreen, DefaultLoginScreen, DefaultPaywall, DefaultResetScreen, DefaultSignupScreen, EmptyState, ErrorBoundary, type IOSBrowser, type InAppApp, type InstallActions, InstallGate, InstallSplash, type InstallState, type InstallVariant, LoadingState, type Platform, PushPrompt, type PushPromptProps, type PushPromptTexts, type PushUiState, type SubscriptionStatus, type ToastItem, type UseLoginFormResult, type UseResetFormResult, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, shouldBlockInstall, shouldShowPermanentOption, useAuth, useAuthPrimitives, useForgotForm, useInstallPrompt, useLoginForm, usePaywallState, usePush, useReminders, useResetForm, useSignupForm, useSubscription, useToast };
606
+ export { type AndroidBrowser, AppRoot, type AppRootProps, type AuthFormError, type AuthFormErrorCode, type AuthScreen, type AuthScreenProps, DefaultForgotScreen, DefaultLoginScreen, DefaultPaywall, DefaultResetScreen, DefaultSignupScreen, EmptyState, ErrorBoundary, type IOSBrowser, type InAppApp, type InstallActions, InstallGate, InstallSplash, type InstallState, type InstallVariant, LoadingState, type Platform, PushPrompt, type PushPromptProps, type PushPromptTexts, type PushUiState, type SubscriptionStatus, type ToastItem, type UseLoginFormResult, type UseResetFormResult, computeAnchorCents, dailyFromYearly, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, discountPercent, formatBRL, monthlyFromYearly, shouldBlockInstall, shouldShowPermanentOption, useAuth, useAuthPrimitives, useForgotForm, useInstallPrompt, useLoginForm, usePaywallState, usePlan, usePush, useReminders, useResetForm, useSignupForm, useSubscription, useToast };
package/dist/index.js CHANGED
@@ -98,6 +98,7 @@ function usePaywallState() {
98
98
  const { subscription } = useHook2();
99
99
  const [opening, setOpening] = useState2(false);
100
100
  const [error, setError] = useState2(null);
101
+ const [pixPending, setPixPending] = useState2(null);
101
102
  const status = subscription.status();
102
103
  const daysLeftInTrial = subscription.daysLeftInTrial();
103
104
  const initialLoadComplete = subscription.initialLoadComplete;
@@ -105,12 +106,37 @@ function usePaywallState() {
105
106
  async (args) => {
106
107
  setOpening(true);
107
108
  setError(null);
109
+ setPixPending(null);
110
+ const method = args.method ?? "card";
111
+ const cycle = args.cycle ?? "MONTHLY";
108
112
  try {
109
- const result = await subscription.checkoutCard({
110
- cpf: args.cpf,
111
- cycle: args.cycle ?? "MONTHLY"
112
- });
113
- window.location.href = result.invoiceUrl;
113
+ if (method === "card") {
114
+ const result = await subscription.checkoutCard({ cpf: args.cpf, cycle });
115
+ window.location.href = result.invoiceUrl;
116
+ return;
117
+ }
118
+ if (method === "pix-auto") {
119
+ const result = await subscription.checkoutPixAuto({ cpf: args.cpf, cycle });
120
+ setPixPending({
121
+ method: "pix-auto",
122
+ qrCodePayload: result.qrCodePayload,
123
+ qrCodeBase64: result.qrCodeBase64,
124
+ expiresAt: null
125
+ });
126
+ setOpening(false);
127
+ return;
128
+ }
129
+ if (method === "pix-once") {
130
+ const result = await subscription.checkoutPixOnce({ cpf: args.cpf, cycle });
131
+ setPixPending({
132
+ method: "pix-once",
133
+ qrCodePayload: result.qrCodePayload,
134
+ qrCodeBase64: result.qrCodeBase64,
135
+ expiresAt: result.expiresAt
136
+ });
137
+ setOpening(false);
138
+ return;
139
+ }
114
140
  } catch (err) {
115
141
  setError(err);
116
142
  setOpening(false);
@@ -126,7 +152,18 @@ function usePaywallState() {
126
152
  setError(err);
127
153
  }
128
154
  }, [subscription]);
129
- return { status, daysLeftInTrial, initialLoadComplete, checkout, cancel, opening, error };
155
+ const dismissPix = useCallback(() => setPixPending(null), []);
156
+ return {
157
+ status,
158
+ daysLeftInTrial,
159
+ initialLoadComplete,
160
+ checkout,
161
+ cancel,
162
+ opening,
163
+ error,
164
+ pixPending,
165
+ dismissPix
166
+ };
130
167
  }
131
168
 
132
169
  // src/internal/SubscriptionGate.tsx
@@ -2327,12 +2364,47 @@ function EmptyState({ title, description, action }) {
2327
2364
  ] });
2328
2365
  }
2329
2366
 
2367
+ // src/hooks/usePlan.ts
2368
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
2369
+ function usePlan() {
2370
+ const { plan } = useHook10();
2371
+ return plan;
2372
+ }
2373
+
2374
+ // src/utils/price.ts
2375
+ function formatBRL(cents) {
2376
+ if (cents === null || cents === void 0) return "";
2377
+ const reais = cents / 100;
2378
+ return new Intl.NumberFormat("pt-BR", {
2379
+ style: "currency",
2380
+ currency: "BRL"
2381
+ }).format(reais);
2382
+ }
2383
+ function monthlyFromYearly(yearlyCents) {
2384
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2385
+ return Math.round(yearlyCents / 12);
2386
+ }
2387
+ function dailyFromYearly(yearlyCents) {
2388
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2389
+ return Math.round(yearlyCents / 365);
2390
+ }
2391
+ function computeAnchorCents(baseCents, multiplier) {
2392
+ if (multiplier === null || multiplier === void 0) return null;
2393
+ if (!Number.isFinite(multiplier)) return null;
2394
+ if (multiplier <= 1) return null;
2395
+ return Math.round(baseCents * multiplier);
2396
+ }
2397
+ function discountPercent(anchorCents, realCents) {
2398
+ if (anchorCents <= realCents) return 0;
2399
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2400
+ }
2401
+
2330
2402
  // src/hooks/useAuthPrimitives.ts
2331
2403
  import { useEffect as useEffect9 } from "react";
2332
- import { useHook as useHook10 } from "@hook-sdk/sdk";
2404
+ import { useHook as useHook11 } from "@hook-sdk/sdk";
2333
2405
  var warned = false;
2334
2406
  function useAuthPrimitives() {
2335
- const { auth } = useHook10();
2407
+ const { auth } = useHook11();
2336
2408
  useEffect9(() => {
2337
2409
  if (!warned && process.env.NODE_ENV !== "production") {
2338
2410
  warned = true;
@@ -2355,9 +2427,9 @@ function useAuthPrimitives() {
2355
2427
  }
2356
2428
 
2357
2429
  // src/hooks/useSubscription.ts
2358
- import { useHook as useHook11 } from "@hook-sdk/sdk";
2430
+ import { useHook as useHook12 } from "@hook-sdk/sdk";
2359
2431
  function useSubscription() {
2360
- const { subscription } = useHook11();
2432
+ const { subscription } = useHook12();
2361
2433
  return {
2362
2434
  status: subscription.status()
2363
2435
  };
@@ -2365,9 +2437,9 @@ function useSubscription() {
2365
2437
 
2366
2438
  // src/hooks/useReminders.ts
2367
2439
  import { useCallback as useCallback9, useEffect as useEffect10, useState as useState13 } from "react";
2368
- import { useHook as useHook12 } from "@hook-sdk/sdk";
2440
+ import { useHook as useHook13 } from "@hook-sdk/sdk";
2369
2441
  function useReminders() {
2370
- const { push } = useHook12();
2442
+ const { push } = useHook13();
2371
2443
  const r = push.reminders;
2372
2444
  const [reminders, setReminders] = useState13([]);
2373
2445
  const [loading, setLoading] = useState13(true);
@@ -2429,11 +2501,16 @@ export {
2429
2501
  InstallSplash,
2430
2502
  LoadingState,
2431
2503
  PushPrompt2 as PushPrompt,
2504
+ computeAnchorCents,
2505
+ dailyFromYearly,
2432
2506
  detectAndroidBrowser,
2433
2507
  detectIOSBrowser,
2434
2508
  detectInAppApp,
2435
2509
  detectPlatform,
2436
2510
  detectStandalone,
2511
+ discountPercent,
2512
+ formatBRL,
2513
+ monthlyFromYearly,
2437
2514
  shouldBlockInstall,
2438
2515
  shouldShowPermanentOption,
2439
2516
  useAuth,
@@ -2442,6 +2519,7 @@ export {
2442
2519
  useInstallPrompt,
2443
2520
  useLoginForm,
2444
2521
  usePaywallState,
2522
+ usePlan,
2445
2523
  usePush,
2446
2524
  useReminders,
2447
2525
  useResetForm,