@hook-sdk/template 0.4.2 → 0.6.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>;
@@ -278,6 +279,7 @@ type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past
278
279
  declare function usePaywallState(): {
279
280
  status: SubscriptionStatus;
280
281
  daysLeftInTrial: number | null;
282
+ initialLoadComplete: boolean;
281
283
  checkout: (args: {
282
284
  cpf: string;
283
285
  cycle?: "MONTHLY" | "YEARLY";
@@ -287,6 +289,66 @@ declare function usePaywallState(): {
287
289
  error: Error | null;
288
290
  };
289
291
 
292
+ /**
293
+ * Thin wrapper sobre `useHook().plan`. Existe pra apps importarem "tudo
294
+ * que precisam" via `@hook-sdk/template` sem mudar de package pra cada
295
+ * primitive, e pra manter espaço pra evoluir a API (ex: expor campos
296
+ * derivados como `monthlyEquivalent()`) sem mexer no SDK.
297
+ *
298
+ * Retorna o PlanState bruto. Use junto com helpers de `@hook-sdk/template`:
299
+ *
300
+ * const { data, initialLoadComplete } = usePlan();
301
+ * if (!initialLoadComplete) return <Skeleton />;
302
+ * if (!data) return null;
303
+ * return <span>{formatBRL(data.priceCents)}</span>;
304
+ */
305
+ declare function usePlan(): PlanState;
306
+
307
+ /**
308
+ * Helpers de formatação de preço pro Hook.
309
+ *
310
+ * Preço vive no banco em CENTAVOS (int), sempre. Apps que renderizam preço
311
+ * leem do hook `usePlan()` e passam por `formatBRL(cents)` — nunca formatam
312
+ * inline. Isso evita os dois patterns errados que já vimos em conversões
313
+ * Lovable→Hook: (a) strings hardcoded "R$ 19,90" no JSX, (b) divisões ad-hoc
314
+ * por 100 + `toFixed(2)` que ignoram locale.
315
+ *
316
+ * Ver G72 em .claude/skills/lovable-to-hook/catalog/known-gotchas.md.
317
+ */
318
+ /**
319
+ * Formata centavos como BRL no locale pt-BR. Aceita null pra conveniência
320
+ * (callers que lidam com `yearlyPriceCents: number | null`).
321
+ *
322
+ * Examples:
323
+ * formatBRL(1990) → "R$ 19,90"
324
+ * formatBRL(4999) → "R$ 49,99"
325
+ * formatBRL(23988) → "R$ 239,88"
326
+ * formatBRL(0) → "R$ 0,00"
327
+ * formatBRL(null) → ""
328
+ */
329
+ declare function formatBRL(cents: number | null | undefined): string;
330
+ /**
331
+ * Deriva "preço mensal" a partir do preço anual (em centavos). Round half-up
332
+ * pra evitar R$ 19,899... → R$ 19,89 quando o usuário esperava R$ 19,90.
333
+ *
334
+ * Não assume que o creator configurou yearlyPriceCents "redondo": se ele
335
+ * setou 23988 (= R$ 239,88), 23988/12 = 1999 (= R$ 19,99). Se setou 23880
336
+ * (= R$ 238,80), 23880/12 = 1990 (= R$ 19,90).
337
+ *
338
+ * Example (papomaterno):
339
+ * monthlyFromYearly(23988) = 1999 → formatBRL = "R$ 19,99"
340
+ * monthlyFromYearly(23880) = 1990 → formatBRL = "R$ 19,90"
341
+ */
342
+ declare function monthlyFromYearly(yearlyCents: number | null | undefined): number;
343
+ /**
344
+ * Deriva "preço por dia" (365 dias) a partir do anual, em centavos. Útil
345
+ * pra copy tipo "apenas R$ 0,66 por dia, menos que um café".
346
+ *
347
+ * Example:
348
+ * dailyFromYearly(23988) = 66 → formatBRL = "R$ 0,66"
349
+ */
350
+ declare function dailyFromYearly(yearlyCents: number | null | undefined): number;
351
+
290
352
  /**
291
353
  * Escape hatch pra screens FORA do fluxo de auth (ex: settings/trocar-senha).
292
354
  * Pra LoginScreen/SignupScreen/ForgotScreen custom, use useLoginForm/useSignupForm/useForgotForm.
@@ -492,4 +554,4 @@ declare function shouldBlockInstall(state: InstallState, now?: number): boolean;
492
554
  */
493
555
  declare function shouldShowPermanentOption(state: InstallState): boolean;
494
556
 
495
- 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 };
557
+ 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, dailyFromYearly, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, 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>;
@@ -278,6 +279,7 @@ type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past
278
279
  declare function usePaywallState(): {
279
280
  status: SubscriptionStatus;
280
281
  daysLeftInTrial: number | null;
282
+ initialLoadComplete: boolean;
281
283
  checkout: (args: {
282
284
  cpf: string;
283
285
  cycle?: "MONTHLY" | "YEARLY";
@@ -287,6 +289,66 @@ declare function usePaywallState(): {
287
289
  error: Error | null;
288
290
  };
289
291
 
292
+ /**
293
+ * Thin wrapper sobre `useHook().plan`. Existe pra apps importarem "tudo
294
+ * que precisam" via `@hook-sdk/template` sem mudar de package pra cada
295
+ * primitive, e pra manter espaço pra evoluir a API (ex: expor campos
296
+ * derivados como `monthlyEquivalent()`) sem mexer no SDK.
297
+ *
298
+ * Retorna o PlanState bruto. Use junto com helpers de `@hook-sdk/template`:
299
+ *
300
+ * const { data, initialLoadComplete } = usePlan();
301
+ * if (!initialLoadComplete) return <Skeleton />;
302
+ * if (!data) return null;
303
+ * return <span>{formatBRL(data.priceCents)}</span>;
304
+ */
305
+ declare function usePlan(): PlanState;
306
+
307
+ /**
308
+ * Helpers de formatação de preço pro Hook.
309
+ *
310
+ * Preço vive no banco em CENTAVOS (int), sempre. Apps que renderizam preço
311
+ * leem do hook `usePlan()` e passam por `formatBRL(cents)` — nunca formatam
312
+ * inline. Isso evita os dois patterns errados que já vimos em conversões
313
+ * Lovable→Hook: (a) strings hardcoded "R$ 19,90" no JSX, (b) divisões ad-hoc
314
+ * por 100 + `toFixed(2)` que ignoram locale.
315
+ *
316
+ * Ver G72 em .claude/skills/lovable-to-hook/catalog/known-gotchas.md.
317
+ */
318
+ /**
319
+ * Formata centavos como BRL no locale pt-BR. Aceita null pra conveniência
320
+ * (callers que lidam com `yearlyPriceCents: number | null`).
321
+ *
322
+ * Examples:
323
+ * formatBRL(1990) → "R$ 19,90"
324
+ * formatBRL(4999) → "R$ 49,99"
325
+ * formatBRL(23988) → "R$ 239,88"
326
+ * formatBRL(0) → "R$ 0,00"
327
+ * formatBRL(null) → ""
328
+ */
329
+ declare function formatBRL(cents: number | null | undefined): string;
330
+ /**
331
+ * Deriva "preço mensal" a partir do preço anual (em centavos). Round half-up
332
+ * pra evitar R$ 19,899... → R$ 19,89 quando o usuário esperava R$ 19,90.
333
+ *
334
+ * Não assume que o creator configurou yearlyPriceCents "redondo": se ele
335
+ * setou 23988 (= R$ 239,88), 23988/12 = 1999 (= R$ 19,99). Se setou 23880
336
+ * (= R$ 238,80), 23880/12 = 1990 (= R$ 19,90).
337
+ *
338
+ * Example (papomaterno):
339
+ * monthlyFromYearly(23988) = 1999 → formatBRL = "R$ 19,99"
340
+ * monthlyFromYearly(23880) = 1990 → formatBRL = "R$ 19,90"
341
+ */
342
+ declare function monthlyFromYearly(yearlyCents: number | null | undefined): number;
343
+ /**
344
+ * Deriva "preço por dia" (365 dias) a partir do anual, em centavos. Útil
345
+ * pra copy tipo "apenas R$ 0,66 por dia, menos que um café".
346
+ *
347
+ * Example:
348
+ * dailyFromYearly(23988) = 66 → formatBRL = "R$ 0,66"
349
+ */
350
+ declare function dailyFromYearly(yearlyCents: number | null | undefined): number;
351
+
290
352
  /**
291
353
  * Escape hatch pra screens FORA do fluxo de auth (ex: settings/trocar-senha).
292
354
  * Pra LoginScreen/SignupScreen/ForgotScreen custom, use useLoginForm/useSignupForm/useForgotForm.
@@ -492,4 +554,4 @@ declare function shouldBlockInstall(state: InstallState, now?: number): boolean;
492
554
  */
493
555
  declare function shouldShowPermanentOption(state: InstallState): boolean;
494
556
 
495
- 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 };
557
+ 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, dailyFromYearly, detectAndroidBrowser, detectIOSBrowser, detectInAppApp, detectPlatform, detectStandalone, formatBRL, monthlyFromYearly, shouldBlockInstall, shouldShowPermanentOption, useAuth, useAuthPrimitives, useForgotForm, useInstallPrompt, useLoginForm, usePaywallState, usePlan, usePush, useReminders, useResetForm, useSignupForm, useSubscription, useToast };
package/dist/index.js CHANGED
@@ -100,6 +100,7 @@ function usePaywallState() {
100
100
  const [error, setError] = useState2(null);
101
101
  const status = subscription.status();
102
102
  const daysLeftInTrial = subscription.daysLeftInTrial();
103
+ const initialLoadComplete = subscription.initialLoadComplete;
103
104
  const checkout = useCallback(
104
105
  async (args) => {
105
106
  setOpening(true);
@@ -125,7 +126,7 @@ function usePaywallState() {
125
126
  setError(err);
126
127
  }
127
128
  }, [subscription]);
128
- return { status, daysLeftInTrial, checkout, cancel, opening, error };
129
+ return { status, daysLeftInTrial, initialLoadComplete, checkout, cancel, opening, error };
129
130
  }
130
131
 
131
132
  // src/internal/SubscriptionGate.tsx
@@ -138,8 +139,9 @@ var BLOCKING = /* @__PURE__ */ new Set([
138
139
  ]);
139
140
  function SubscriptionGate({ Paywall, children }) {
140
141
  const { mode } = useTemplateConfig();
141
- const { status } = usePaywallState();
142
+ const { status, initialLoadComplete } = usePaywallState();
142
143
  if (mode === "free") return /* @__PURE__ */ jsx5(Fragment2, { children });
144
+ if (!initialLoadComplete && status === "none") return null;
143
145
  if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(Paywall, {});
144
146
  return /* @__PURE__ */ jsx5(Fragment2, { children });
145
147
  }
@@ -2325,12 +2327,37 @@ function EmptyState({ title, description, action }) {
2325
2327
  ] });
2326
2328
  }
2327
2329
 
2330
+ // src/hooks/usePlan.ts
2331
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
2332
+ function usePlan() {
2333
+ const { plan } = useHook10();
2334
+ return plan;
2335
+ }
2336
+
2337
+ // src/utils/price.ts
2338
+ function formatBRL(cents) {
2339
+ if (cents === null || cents === void 0) return "";
2340
+ const reais = cents / 100;
2341
+ return new Intl.NumberFormat("pt-BR", {
2342
+ style: "currency",
2343
+ currency: "BRL"
2344
+ }).format(reais);
2345
+ }
2346
+ function monthlyFromYearly(yearlyCents) {
2347
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2348
+ return Math.round(yearlyCents / 12);
2349
+ }
2350
+ function dailyFromYearly(yearlyCents) {
2351
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2352
+ return Math.round(yearlyCents / 365);
2353
+ }
2354
+
2328
2355
  // src/hooks/useAuthPrimitives.ts
2329
2356
  import { useEffect as useEffect9 } from "react";
2330
- import { useHook as useHook10 } from "@hook-sdk/sdk";
2357
+ import { useHook as useHook11 } from "@hook-sdk/sdk";
2331
2358
  var warned = false;
2332
2359
  function useAuthPrimitives() {
2333
- const { auth } = useHook10();
2360
+ const { auth } = useHook11();
2334
2361
  useEffect9(() => {
2335
2362
  if (!warned && process.env.NODE_ENV !== "production") {
2336
2363
  warned = true;
@@ -2353,9 +2380,9 @@ function useAuthPrimitives() {
2353
2380
  }
2354
2381
 
2355
2382
  // src/hooks/useSubscription.ts
2356
- import { useHook as useHook11 } from "@hook-sdk/sdk";
2383
+ import { useHook as useHook12 } from "@hook-sdk/sdk";
2357
2384
  function useSubscription() {
2358
- const { subscription } = useHook11();
2385
+ const { subscription } = useHook12();
2359
2386
  return {
2360
2387
  status: subscription.status()
2361
2388
  };
@@ -2363,9 +2390,9 @@ function useSubscription() {
2363
2390
 
2364
2391
  // src/hooks/useReminders.ts
2365
2392
  import { useCallback as useCallback9, useEffect as useEffect10, useState as useState13 } from "react";
2366
- import { useHook as useHook12 } from "@hook-sdk/sdk";
2393
+ import { useHook as useHook13 } from "@hook-sdk/sdk";
2367
2394
  function useReminders() {
2368
- const { push } = useHook12();
2395
+ const { push } = useHook13();
2369
2396
  const r = push.reminders;
2370
2397
  const [reminders, setReminders] = useState13([]);
2371
2398
  const [loading, setLoading] = useState13(true);
@@ -2427,11 +2454,14 @@ export {
2427
2454
  InstallSplash,
2428
2455
  LoadingState,
2429
2456
  PushPrompt2 as PushPrompt,
2457
+ dailyFromYearly,
2430
2458
  detectAndroidBrowser,
2431
2459
  detectIOSBrowser,
2432
2460
  detectInAppApp,
2433
2461
  detectPlatform,
2434
2462
  detectStandalone,
2463
+ formatBRL,
2464
+ monthlyFromYearly,
2435
2465
  shouldBlockInstall,
2436
2466
  shouldShowPermanentOption,
2437
2467
  useAuth,
@@ -2440,6 +2470,7 @@ export {
2440
2470
  useInstallPrompt,
2441
2471
  useLoginForm,
2442
2472
  usePaywallState,
2473
+ usePlan,
2443
2474
  usePush,
2444
2475
  useReminders,
2445
2476
  useResetForm,