@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.cjs +43 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +39 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
2357
|
+
import { useHook as useHook11 } from "@hook-sdk/sdk";
|
|
2331
2358
|
var warned = false;
|
|
2332
2359
|
function useAuthPrimitives() {
|
|
2333
|
-
const { auth } =
|
|
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
|
|
2383
|
+
import { useHook as useHook12 } from "@hook-sdk/sdk";
|
|
2357
2384
|
function useSubscription() {
|
|
2358
|
-
const { subscription } =
|
|
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
|
|
2393
|
+
import { useHook as useHook13 } from "@hook-sdk/sdk";
|
|
2367
2394
|
function useReminders() {
|
|
2368
|
-
const { push } =
|
|
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,
|