@hook-sdk/template 0.9.1 → 0.11.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 +1263 -1692
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +328 -129
- package/dist/index.d.ts +328 -129
- package/dist/index.js +1211 -1648
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,147 @@
|
|
|
1
1
|
// src/AppRoot.tsx
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useMemo as useMemo3 } from "react";
|
|
3
|
+
import { BrowserRouter, MemoryRouter, Navigate, Route, Routes } from "react-router-dom";
|
|
4
|
+
import { useHook as useHook4 } from "@hook-sdk/sdk";
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
-
import { createContext, useContext
|
|
6
|
+
// src/config/AppConfigContext.tsx
|
|
7
|
+
import { createContext, useContext } from "react";
|
|
7
8
|
import { jsx } from "react/jsx-runtime";
|
|
8
|
-
var
|
|
9
|
+
var AppConfigContext = createContext(null);
|
|
10
|
+
function AppConfigProvider({
|
|
11
|
+
config,
|
|
12
|
+
children
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ jsx(AppConfigContext.Provider, { value: config, children });
|
|
15
|
+
}
|
|
16
|
+
function useAppConfig() {
|
|
17
|
+
const v = useContext(AppConfigContext);
|
|
18
|
+
if (!v) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"[hook-template] useAppConfig: AppConfigProvider missing \u2014 wrap your app in <AppRoot>"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/config/schema.ts
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
var SnakeKeyRE = /^[a-z0-9][a-z0-9_.-]{0,127}$/;
|
|
29
|
+
var AuthFlowSchema = z.object({
|
|
30
|
+
minPassword: z.number().int().min(6).max(64),
|
|
31
|
+
requiresEmailVerify: z.boolean(),
|
|
32
|
+
googleOAuth: z.boolean(),
|
|
33
|
+
postAuthLanding: z.string().startsWith("/"),
|
|
34
|
+
preAuthRoutes: z.array(z.string().startsWith("/"))
|
|
35
|
+
});
|
|
36
|
+
var PaywallNonFreeSchema = z.object({
|
|
37
|
+
mode: z.enum(["trial", "pay_first"]),
|
|
38
|
+
trialDays: z.number().int().nonnegative().optional(),
|
|
39
|
+
cycles: z.array(z.enum(["MONTHLY", "YEARLY"])).min(1),
|
|
40
|
+
prices: z.object({
|
|
41
|
+
monthlyCents: z.number().int().nonnegative(),
|
|
42
|
+
yearlyCents: z.number().int().nonnegative()
|
|
43
|
+
}),
|
|
44
|
+
anchorPrices: z.object({
|
|
45
|
+
monthlyCents: z.number().int().nonnegative(),
|
|
46
|
+
yearlyCents: z.number().int().nonnegative()
|
|
47
|
+
}).optional(),
|
|
48
|
+
checkoutMethods: z.array(z.enum(["card", "pix-auto", "pix-once"])).min(1),
|
|
49
|
+
requiresCpf: z.boolean(),
|
|
50
|
+
cancelWindowDays: z.number().int().nonnegative().optional(),
|
|
51
|
+
errorMessages: z.enum(["default", "custom"])
|
|
52
|
+
});
|
|
53
|
+
var PaywallFreeSchema = z.object({ mode: z.literal("free") });
|
|
54
|
+
var PaywallSchema = z.discriminatedUnion("mode", [PaywallNonFreeSchema, PaywallFreeSchema]);
|
|
55
|
+
var PersistedKeySchema = z.object({
|
|
56
|
+
key: z.string().regex(SnakeKeyRE, "key must be snake_case (matches /^[a-z0-9][a-z0-9_.-]{0,127}$/)"),
|
|
57
|
+
default: z.unknown(),
|
|
58
|
+
guardRegen: z.boolean().optional(),
|
|
59
|
+
debounceMs: z.number().int().positive().optional()
|
|
60
|
+
});
|
|
61
|
+
var OnboardingSchema = z.object({
|
|
62
|
+
trigger: z.enum(["pre_signup", "post_signup", "pre_signup_custom", "optional"]),
|
|
63
|
+
steps: z.array(
|
|
64
|
+
z.object({
|
|
65
|
+
id: z.string().regex(SnakeKeyRE),
|
|
66
|
+
screen: z.string(),
|
|
67
|
+
validates: z.array(z.string()).optional()
|
|
68
|
+
})
|
|
69
|
+
).min(1),
|
|
70
|
+
persistTo: z.literal("appData"),
|
|
71
|
+
persistKey: z.string().regex(SnakeKeyRE)
|
|
72
|
+
});
|
|
73
|
+
var DeepLinksSchema = z.object({
|
|
74
|
+
passwordReset: z.string().startsWith("/").optional(),
|
|
75
|
+
emailVerify: z.string().startsWith("/").optional()
|
|
76
|
+
}).strict();
|
|
77
|
+
var AppConfigSchema = z.object({
|
|
78
|
+
slug: z.string().regex(/^[a-z0-9-]+$/),
|
|
79
|
+
name: z.string().min(1),
|
|
80
|
+
branding: z.object({ primaryColor: z.string(), logoUrl: z.string().url() }),
|
|
81
|
+
authFlow: AuthFlowSchema,
|
|
82
|
+
paywall: PaywallSchema,
|
|
83
|
+
persistedKeys: z.array(PersistedKeySchema),
|
|
84
|
+
onboarding: OnboardingSchema.optional(),
|
|
85
|
+
deepLinks: DeepLinksSchema.optional(),
|
|
86
|
+
features_enabled: z.array(z.string()).optional(),
|
|
87
|
+
// Build-time injected theme metadata (e.g. icon_url for InstallSplash).
|
|
88
|
+
// Apps don't author this directly; deploy workflows fill it from
|
|
89
|
+
// env-resolved bundle host. Permissive shape so apps/workflows can
|
|
90
|
+
// extend without re-bumping the template schema.
|
|
91
|
+
theme: z.object({}).passthrough().optional()
|
|
92
|
+
}).strict();
|
|
93
|
+
function parseAppConfig(input) {
|
|
94
|
+
const r = AppConfigSchema.safeParse(input);
|
|
95
|
+
if (!r.success) {
|
|
96
|
+
const messages = r.error.issues.map((i) => `[${i.path.join(".")}] ${i.message}`).join("\n");
|
|
97
|
+
throw new Error(`Invalid app.config.json:
|
|
98
|
+
${messages}`);
|
|
99
|
+
}
|
|
100
|
+
return r.data;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/PersistenceRegistry.tsx
|
|
104
|
+
import { useEffect } from "react";
|
|
105
|
+
import { useHook } from "@hook-sdk/sdk";
|
|
106
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
107
|
+
function PersistenceRegistry({ config, children }) {
|
|
108
|
+
const { appData } = useHook();
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (config.length === 0) return;
|
|
111
|
+
const keys = config.map((c) => c.key);
|
|
112
|
+
const bulk = appData.bulkRead;
|
|
113
|
+
bulk?.(keys).catch(() => {
|
|
114
|
+
});
|
|
115
|
+
}, [config, appData]);
|
|
116
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/DeepLinkHandler.tsx
|
|
120
|
+
import { useEffect as useEffect2 } from "react";
|
|
121
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
122
|
+
function DeepLinkHandler({ deepLinks }) {
|
|
123
|
+
const nav = useNavigate();
|
|
124
|
+
const loc = useLocation();
|
|
125
|
+
useEffect2(() => {
|
|
126
|
+
if (!deepLinks) return;
|
|
127
|
+
const params = new URLSearchParams(loc.search);
|
|
128
|
+
const token = params.get("token");
|
|
129
|
+
if (!token) return;
|
|
130
|
+
if (deepLinks.passwordReset && deepLinks.passwordReset.includes(":token") && loc.pathname === "/") {
|
|
131
|
+
nav(deepLinks.passwordReset.replace(":token", token));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (deepLinks.emailVerify && deepLinks.emailVerify.includes(":token") && loc.pathname === "/") {
|
|
135
|
+
nav(deepLinks.emailVerify.replace(":token", token));
|
|
136
|
+
}
|
|
137
|
+
}, [deepLinks, loc, nav]);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/internal/TemplateConfigContext.tsx
|
|
142
|
+
import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
|
|
143
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
144
|
+
var TemplateConfigContext = createContext2(null);
|
|
9
145
|
function TemplateConfigProvider({
|
|
10
146
|
config,
|
|
11
147
|
children
|
|
@@ -14,10 +150,10 @@ function TemplateConfigProvider({
|
|
|
14
150
|
...config,
|
|
15
151
|
mode: config.subscription?.mode ?? "trial"
|
|
16
152
|
}), [config]);
|
|
17
|
-
return /* @__PURE__ */
|
|
153
|
+
return /* @__PURE__ */ jsx3(TemplateConfigContext.Provider, { value, children });
|
|
18
154
|
}
|
|
19
155
|
function useTemplateConfig() {
|
|
20
|
-
const ctx =
|
|
156
|
+
const ctx = useContext2(TemplateConfigContext);
|
|
21
157
|
if (ctx === null) {
|
|
22
158
|
throw new Error("useTemplateConfig must be used inside <TemplateConfigProvider>");
|
|
23
159
|
}
|
|
@@ -25,7 +161,7 @@ function useTemplateConfig() {
|
|
|
25
161
|
}
|
|
26
162
|
|
|
27
163
|
// src/internal/ThemeProvider.tsx
|
|
28
|
-
import { jsx as
|
|
164
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
29
165
|
function ThemeProvider({ children }) {
|
|
30
166
|
const config = useTemplateConfig();
|
|
31
167
|
const style = {
|
|
@@ -34,182 +170,308 @@ function ThemeProvider({ children }) {
|
|
|
34
170
|
"--hook-color-background": config.theme.background_color
|
|
35
171
|
}
|
|
36
172
|
};
|
|
37
|
-
return /* @__PURE__ */
|
|
173
|
+
return /* @__PURE__ */ jsx4("div", { style, children });
|
|
38
174
|
}
|
|
39
175
|
|
|
40
|
-
// src/
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
// src/hooks/useAuth.ts
|
|
44
|
-
import { useHook } from "@hook-sdk/sdk";
|
|
45
|
-
function useAuth() {
|
|
46
|
-
const { user, authStatus, auth } = useHook();
|
|
47
|
-
return {
|
|
48
|
-
user,
|
|
49
|
-
authStatus,
|
|
50
|
-
refresh: auth.refresh
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/defaults/LoadingState.tsx
|
|
55
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
56
|
-
function LoadingState({ message }) {
|
|
57
|
-
return /* @__PURE__ */ jsx3("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx3("span", { children: message ?? "Carregando..." }) });
|
|
58
|
-
}
|
|
176
|
+
// src/hooks/usePaywallState.ts
|
|
177
|
+
import { useCallback, useContext as useContext3, useMemo as useMemo2, useState } from "react";
|
|
178
|
+
import { useHook as useHook2 } from "@hook-sdk/sdk";
|
|
59
179
|
|
|
60
|
-
// src/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
const onPop = () => {
|
|
79
|
-
if (detectResetFromUrl()) setScreen("reset");
|
|
80
|
-
};
|
|
81
|
-
window.addEventListener("popstate", onPop);
|
|
82
|
-
return () => window.removeEventListener("popstate", onPop);
|
|
83
|
-
}, []);
|
|
84
|
-
if (authStatus === "loading") return /* @__PURE__ */ jsx4(LoadingState, {});
|
|
85
|
-
if (!user) {
|
|
86
|
-
if (screen === "reset") return /* @__PURE__ */ jsx4(Reset, { onNavigate: setScreen });
|
|
87
|
-
if (screen === "signup") return /* @__PURE__ */ jsx4(Signup, { onNavigate: setScreen });
|
|
88
|
-
if (screen === "forgot") return /* @__PURE__ */ jsx4(Forgot, { onNavigate: setScreen });
|
|
89
|
-
return /* @__PURE__ */ jsx4(Login, { onNavigate: setScreen });
|
|
90
|
-
}
|
|
91
|
-
return /* @__PURE__ */ jsx4(Fragment, { children });
|
|
180
|
+
// src/errors/asaas-pt-br.ts
|
|
181
|
+
var MAP = {
|
|
182
|
+
invalid_cpf: "CPF inv\xE1lido. Confira os n\xFAmeros e tente novamente.",
|
|
183
|
+
cpf_required: "Por favor, informe seu CPF para continuar.",
|
|
184
|
+
card_declined: "Cart\xE3o recusado pela operadora. Tente outro cart\xE3o ou m\xE9todo.",
|
|
185
|
+
insufficient_funds: "Saldo insuficiente no cart\xE3o. Tente outro m\xE9todo.",
|
|
186
|
+
card_expired: "Cart\xE3o expirado. Use um cart\xE3o v\xE1lido.",
|
|
187
|
+
invalid_card_number: "N\xFAmero de cart\xE3o inv\xE1lido.",
|
|
188
|
+
invalid_cvv: "C\xF3digo de seguran\xE7a (CVV) inv\xE1lido.",
|
|
189
|
+
invalid_expiration: "Data de validade inv\xE1lida.",
|
|
190
|
+
generic_decline: "Pagamento recusado. Tente novamente em instantes ou use outro m\xE9todo.",
|
|
191
|
+
webhook_unverified: "N\xE3o conseguimos confirmar seu pagamento. Atualize a p\xE1gina em alguns segundos.",
|
|
192
|
+
pix_expired: "QR Code do PIX expirou. Gere um novo.",
|
|
193
|
+
pix_not_paid_yet: "PIX ainda n\xE3o foi pago. Aguardando confirma\xE7\xE3o."
|
|
194
|
+
};
|
|
195
|
+
function asaasErrorMessage(code) {
|
|
196
|
+
return MAP[code] ?? "Ocorreu um erro inesperado. Tente novamente em instantes.";
|
|
92
197
|
}
|
|
93
198
|
|
|
94
199
|
// src/hooks/usePaywallState.ts
|
|
95
|
-
|
|
96
|
-
|
|
200
|
+
function isCheckoutFailure(r) {
|
|
201
|
+
return "ok" in r && r.ok === false;
|
|
202
|
+
}
|
|
203
|
+
var FALLBACK_PAYWALL = {
|
|
204
|
+
mode: "pay_first",
|
|
205
|
+
cycles: ["MONTHLY"],
|
|
206
|
+
prices: { monthlyCents: 0, yearlyCents: 0 },
|
|
207
|
+
checkoutMethods: ["card", "pix-auto"],
|
|
208
|
+
requiresCpf: true,
|
|
209
|
+
errorMessages: "default"
|
|
210
|
+
};
|
|
211
|
+
var isMethodAvailable = (availability, method) => availability[method] !== false;
|
|
97
212
|
function usePaywallState() {
|
|
98
213
|
const { subscription, plan } = useHook2();
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
214
|
+
const configFromCtx = useContext3(AppConfigContext);
|
|
215
|
+
const paywall = configFromCtx?.paywall ?? FALLBACK_PAYWALL;
|
|
216
|
+
const isFree = paywall.mode === "free";
|
|
217
|
+
const declaredMethods = useMemo2(
|
|
218
|
+
() => isFree ? [] : paywall.checkoutMethods,
|
|
219
|
+
[isFree, paywall]
|
|
220
|
+
);
|
|
221
|
+
const availability = subscription.methodAvailability ?? {
|
|
222
|
+
card: null,
|
|
223
|
+
"pix-auto": null,
|
|
224
|
+
"pix-once": null
|
|
225
|
+
};
|
|
226
|
+
const methods = useMemo2(
|
|
227
|
+
() => declaredMethods.filter((m) => isMethodAvailable(availability, m)),
|
|
228
|
+
[declaredMethods, availability]
|
|
229
|
+
);
|
|
230
|
+
const defaultMethod = methods[0] ?? declaredMethods[0] ?? "card";
|
|
231
|
+
const [selectedMethodRaw, setSelectedMethod] = useState(defaultMethod);
|
|
232
|
+
const selectedMethod = methods.includes(selectedMethodRaw) ? selectedMethodRaw : methods[0] ?? selectedMethodRaw;
|
|
233
|
+
const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
|
|
234
|
+
const [cycle, setCycle] = useState(initialCycle);
|
|
235
|
+
const cpfRequired = !isFree && paywall.requiresCpf;
|
|
236
|
+
const [cpf, setCpf] = useState("");
|
|
237
|
+
const cpfValid = useMemo2(() => /^[0-9]{11}$/.test(cpf), [cpf]);
|
|
238
|
+
const [card, setCardState] = useState({
|
|
239
|
+
number: "",
|
|
240
|
+
cvv: "",
|
|
241
|
+
expiry: "",
|
|
242
|
+
holder: ""
|
|
243
|
+
});
|
|
244
|
+
const setCard = useCallback((patch) => {
|
|
245
|
+
setCardState((prev) => ({ ...prev, ...patch }));
|
|
246
|
+
}, []);
|
|
247
|
+
const [error, setError] = useState(null);
|
|
248
|
+
const [submitting, setSubmitting] = useState(false);
|
|
102
249
|
const status = subscription.status();
|
|
103
250
|
const daysLeftInTrial = subscription.daysLeftInTrial();
|
|
104
251
|
const initialLoadComplete = subscription.initialLoadComplete;
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
252
|
+
const pixPending = useMemo2(() => {
|
|
253
|
+
const sdkPix = subscription.pixPending;
|
|
254
|
+
if (!sdkPix) return null;
|
|
255
|
+
const liveStatus = subscription.current?.status;
|
|
256
|
+
const paid = liveStatus === "ACTIVE" || liveStatus === "TRIAL";
|
|
257
|
+
return {
|
|
258
|
+
method: sdkPix.method,
|
|
259
|
+
qrCodePayload: sdkPix.qrCodePayload,
|
|
260
|
+
qrCodeBase64: sdkPix.qrCodeBase64,
|
|
261
|
+
expiresAt: null,
|
|
262
|
+
paid
|
|
263
|
+
};
|
|
264
|
+
}, [subscription.pixPending, subscription.current]);
|
|
111
265
|
const monthlyEquivalent = useCallback(
|
|
112
|
-
(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return
|
|
266
|
+
(c) => {
|
|
267
|
+
const monthlyCents = plan.data?.priceCents ?? (isFree ? 0 : paywall.prices.monthlyCents);
|
|
268
|
+
const yearlyCents = plan.data?.yearlyPriceCents ?? (isFree ? null : paywall.prices.yearlyCents);
|
|
269
|
+
if (c === "YEARLY" && yearlyCents) return Math.round(yearlyCents / 12);
|
|
270
|
+
return monthlyCents;
|
|
117
271
|
},
|
|
118
|
-
[
|
|
272
|
+
[plan, paywall, isFree]
|
|
119
273
|
);
|
|
274
|
+
const planDerived = useMemo2(() => {
|
|
275
|
+
if (isFree) return null;
|
|
276
|
+
const monthlyCents = paywall.prices.monthlyCents;
|
|
277
|
+
const yearlyCents = paywall.prices.yearlyCents;
|
|
278
|
+
const anchor = paywall.anchorPrices;
|
|
279
|
+
const discount = anchor && anchor.yearlyCents > 0 ? Math.round((1 - paywall.prices.yearlyCents / anchor.yearlyCents) * 100) : 0;
|
|
280
|
+
return {
|
|
281
|
+
monthlyCents,
|
|
282
|
+
yearlyCents,
|
|
283
|
+
anchorMonthlyCents: anchor?.monthlyCents,
|
|
284
|
+
anchorYearlyCents: anchor?.yearlyCents,
|
|
285
|
+
monthlyEquivalent: cycle === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents,
|
|
286
|
+
discountPercent: discount
|
|
287
|
+
};
|
|
288
|
+
}, [paywall, cycle, isFree]);
|
|
289
|
+
const useDefaultMessages = paywall.mode !== "free" && paywall.errorMessages === "default";
|
|
290
|
+
const buildError = useCallback(
|
|
291
|
+
(code, fallbackMessage) => ({
|
|
292
|
+
code,
|
|
293
|
+
message: fallbackMessage,
|
|
294
|
+
userMessage: useDefaultMessages ? asaasErrorMessage(code) : fallbackMessage
|
|
295
|
+
}),
|
|
296
|
+
[useDefaultMessages]
|
|
297
|
+
);
|
|
298
|
+
const submit = useCallback(async () => {
|
|
299
|
+
setSubmitting(true);
|
|
300
|
+
setError(null);
|
|
301
|
+
const methodToUse = selectedMethod;
|
|
302
|
+
if (!isMethodAvailable(availability, methodToUse)) {
|
|
303
|
+
const code = "method_unavailable";
|
|
304
|
+
setError(buildError(code, `method ${methodToUse} unavailable`));
|
|
305
|
+
setSubmitting(false);
|
|
306
|
+
return {
|
|
307
|
+
ok: false,
|
|
308
|
+
code: "method_unavailable",
|
|
309
|
+
method: methodToUse,
|
|
310
|
+
reason: "pre_flight_unavailable"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
let result;
|
|
315
|
+
if (methodToUse === "card") {
|
|
316
|
+
const [expMonthRaw = "", expYearRaw = ""] = card.expiry.split("/");
|
|
317
|
+
const expYearTrimmed = expYearRaw.trim();
|
|
318
|
+
const cardData = {
|
|
319
|
+
number: card.number,
|
|
320
|
+
holderName: card.holder,
|
|
321
|
+
expiryMonth: expMonthRaw.trim(),
|
|
322
|
+
expiryYear: expYearTrimmed.length === 2 ? `20${expYearTrimmed}` : expYearTrimmed,
|
|
323
|
+
ccv: card.cvv
|
|
324
|
+
};
|
|
325
|
+
const holderInfo = {
|
|
326
|
+
name: card.holder,
|
|
327
|
+
email: "",
|
|
328
|
+
cpfCnpj: cpf,
|
|
329
|
+
postalCode: "",
|
|
330
|
+
addressNumber: ""
|
|
331
|
+
};
|
|
332
|
+
result = await subscription.checkout({
|
|
333
|
+
method: "card",
|
|
334
|
+
cycle,
|
|
335
|
+
cpf,
|
|
336
|
+
card: cardData,
|
|
337
|
+
holderInfo
|
|
338
|
+
});
|
|
339
|
+
} else if (methodToUse === "pix-auto") {
|
|
340
|
+
result = await subscription.checkout({ method: "pix-auto", cycle, cpf });
|
|
341
|
+
} else {
|
|
342
|
+
result = await subscription.checkout({ method: "pix-once", cycle, cpf });
|
|
343
|
+
}
|
|
344
|
+
if (isCheckoutFailure(result)) {
|
|
345
|
+
setError(buildError(result.code, `${result.code}: ${result.reason}`));
|
|
346
|
+
setSubmitting(false);
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
await subscription.refresh();
|
|
350
|
+
setSubmitting(false);
|
|
351
|
+
return result;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const code = err?.code ?? "generic_decline";
|
|
354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
355
|
+
setError(buildError(code, message));
|
|
356
|
+
setSubmitting(false);
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
}, [selectedMethod, availability, subscription, cycle, cpf, card, buildError]);
|
|
120
360
|
const checkout = useCallback(
|
|
121
361
|
async (args) => {
|
|
122
|
-
|
|
362
|
+
setSubmitting(true);
|
|
123
363
|
setError(null);
|
|
124
|
-
setPixPending(null);
|
|
125
364
|
try {
|
|
126
365
|
if (args.method === "card") {
|
|
127
366
|
if (!args.card || !args.holderInfo) {
|
|
128
|
-
throw
|
|
367
|
+
throw Object.assign(
|
|
368
|
+
new Error('card and holderInfo are required when method is "card"'),
|
|
369
|
+
{ code: "validation" }
|
|
370
|
+
);
|
|
129
371
|
}
|
|
130
|
-
|
|
372
|
+
const sdkArgs = {
|
|
131
373
|
method: "card",
|
|
132
374
|
cycle: args.cycle,
|
|
133
375
|
cpf: args.cpf,
|
|
134
376
|
card: args.card,
|
|
135
377
|
holderInfo: args.holderInfo,
|
|
136
378
|
...args.remoteIp ? { remoteIp: args.remoteIp } : {}
|
|
137
|
-
}
|
|
379
|
+
};
|
|
380
|
+
const result2 = await subscription.checkout(sdkArgs);
|
|
381
|
+
if (isCheckoutFailure(result2)) {
|
|
382
|
+
setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
|
|
383
|
+
setSubmitting(false);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
138
386
|
await subscription.refresh();
|
|
139
|
-
|
|
387
|
+
setSubmitting(false);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (args.method === "pix-auto") {
|
|
391
|
+
const result2 = await subscription.checkout({
|
|
392
|
+
method: "pix-auto",
|
|
393
|
+
cycle: args.cycle,
|
|
394
|
+
cpf: args.cpf
|
|
395
|
+
});
|
|
396
|
+
if (isCheckoutFailure(result2)) {
|
|
397
|
+
setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
|
|
398
|
+
setSubmitting(false);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
setSubmitting(false);
|
|
140
402
|
return;
|
|
141
403
|
}
|
|
142
404
|
const result = await subscription.checkout({
|
|
143
|
-
method: "pix-
|
|
405
|
+
method: "pix-once",
|
|
144
406
|
cycle: args.cycle,
|
|
145
407
|
cpf: args.cpf
|
|
146
408
|
});
|
|
147
|
-
if (result
|
|
148
|
-
|
|
409
|
+
if (isCheckoutFailure(result)) {
|
|
410
|
+
setError(buildError(result.code, `${result.code}: ${result.reason}`));
|
|
411
|
+
setSubmitting(false);
|
|
412
|
+
return;
|
|
149
413
|
}
|
|
150
|
-
|
|
151
|
-
method: "pix-auto",
|
|
152
|
-
qrCodePayload: result.qrCodePayload,
|
|
153
|
-
qrCodeBase64: result.qrCodeBase64,
|
|
154
|
-
expiresAt: null,
|
|
155
|
-
paid: false
|
|
156
|
-
});
|
|
157
|
-
setOpening(false);
|
|
414
|
+
setSubmitting(false);
|
|
158
415
|
} catch (err) {
|
|
159
|
-
|
|
160
|
-
|
|
416
|
+
const code = err?.code ?? "generic_decline";
|
|
417
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
418
|
+
setError(buildError(code, message));
|
|
419
|
+
setSubmitting(false);
|
|
161
420
|
}
|
|
162
421
|
},
|
|
163
|
-
[subscription]
|
|
422
|
+
[subscription, buildError]
|
|
164
423
|
);
|
|
165
424
|
const cancel = useCallback(async () => {
|
|
166
425
|
try {
|
|
167
426
|
await subscription.cancel();
|
|
168
427
|
await subscription.refresh();
|
|
169
428
|
} catch (err) {
|
|
170
|
-
|
|
429
|
+
const code = err?.code ?? "generic_decline";
|
|
430
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
431
|
+
setError(buildError(code, message));
|
|
171
432
|
}
|
|
172
|
-
}, [subscription]);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const tick = async () => {
|
|
182
|
-
if (cancelled || attempts >= MAX_ATTEMPTS) return;
|
|
183
|
-
attempts++;
|
|
184
|
-
try {
|
|
185
|
-
await subRef.current.refresh();
|
|
186
|
-
if (cancelled) return;
|
|
187
|
-
const s = subRef.current.status();
|
|
188
|
-
if (s === "active" || s === "trialing") {
|
|
189
|
-
setPixPending((prev) => prev ? { ...prev, paid: true } : prev);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
} catch {
|
|
193
|
-
}
|
|
194
|
-
if (!cancelled) setTimeout(tick, 3e3);
|
|
195
|
-
};
|
|
196
|
-
setTimeout(tick, 3e3);
|
|
197
|
-
return () => {
|
|
198
|
-
cancelled = true;
|
|
199
|
-
};
|
|
200
|
-
}, [pixPending]);
|
|
433
|
+
}, [subscription, buildError]);
|
|
434
|
+
const cardState = useMemo2(
|
|
435
|
+
() => ({ ...card, set: setCard }),
|
|
436
|
+
[card, setCard]
|
|
437
|
+
);
|
|
438
|
+
const cpfState = useMemo2(
|
|
439
|
+
() => ({ required: cpfRequired, value: cpf, set: setCpf, valid: cpfValid }),
|
|
440
|
+
[cpfRequired, cpf, cpfValid]
|
|
441
|
+
);
|
|
201
442
|
return {
|
|
443
|
+
// Subscription status (reactive, proxied from SDK)
|
|
202
444
|
status,
|
|
203
445
|
daysLeftInTrial,
|
|
204
446
|
initialLoadComplete,
|
|
447
|
+
// Plan derivation from config (sync, no fetch)
|
|
448
|
+
plan: planDerived,
|
|
449
|
+
// Cycle + method selection
|
|
450
|
+
cycle,
|
|
451
|
+
setCycle,
|
|
452
|
+
methods,
|
|
453
|
+
selectedMethod,
|
|
454
|
+
setSelectedMethod,
|
|
455
|
+
// Form state
|
|
456
|
+
cpfState,
|
|
457
|
+
cardState,
|
|
458
|
+
// High-level + legacy actions
|
|
459
|
+
submit,
|
|
205
460
|
checkout,
|
|
206
461
|
cancel,
|
|
207
|
-
|
|
208
|
-
error,
|
|
462
|
+
// PIX state (proxied from SDK; legacy `paid`/`expiresAt` derived)
|
|
209
463
|
pixPending,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
464
|
+
// Error + submit progress
|
|
465
|
+
error,
|
|
466
|
+
submitting,
|
|
467
|
+
// Backward-compat aliases (deprecated; remove in template 0.11)
|
|
468
|
+
opening: submitting,
|
|
469
|
+
availableMethods: methods,
|
|
470
|
+
monthlyEquivalent,
|
|
471
|
+
dismissPix: () => {
|
|
472
|
+
},
|
|
473
|
+
refreshPlan: () => {
|
|
474
|
+
}
|
|
213
475
|
};
|
|
214
476
|
}
|
|
215
477
|
|
|
@@ -230,51 +492,11 @@ function SubscriptionGate({ Paywall, children }) {
|
|
|
230
492
|
return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
231
493
|
}
|
|
232
494
|
|
|
233
|
-
// src/internal/PersistedKeysPrefetch.tsx
|
|
234
|
-
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
235
|
-
import { useHook as useHook3 } from "@hook-sdk/sdk";
|
|
236
|
-
import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
|
|
237
|
-
var SAFETY_TIMEOUT_MS = 3e3;
|
|
238
|
-
function PersistedKeysPrefetch({ children }) {
|
|
239
|
-
const { appData } = useHook3();
|
|
240
|
-
const config = useTemplateConfig();
|
|
241
|
-
const hasKeys = !!config.persistedKeys && config.persistedKeys.length > 0;
|
|
242
|
-
const [ready, setReady] = useState3(!hasKeys);
|
|
243
|
-
useEffect3(() => {
|
|
244
|
-
const keys = config.persistedKeys;
|
|
245
|
-
if (!keys || keys.length === 0) {
|
|
246
|
-
setReady(true);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
let cancelled = false;
|
|
250
|
-
appData.cache.startPrefetch(keys, (ks) => appData.bulkRead(ks));
|
|
251
|
-
void appData.cache.waitForPrimed(SAFETY_TIMEOUT_MS).then((result) => {
|
|
252
|
-
if (cancelled) return;
|
|
253
|
-
if (result === "timeout") {
|
|
254
|
-
console.warn(
|
|
255
|
-
`[@hook-sdk/template] PersistedKeysPrefetch: bulk-read n\xE3o completou em ${SAFETY_TIMEOUT_MS}ms. Liberando render mesmo assim \u2014 usePersistedState pode expor defaultValue at\xE9 o fetch terminar (G77).`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
setReady(true);
|
|
259
|
-
});
|
|
260
|
-
return () => {
|
|
261
|
-
cancelled = true;
|
|
262
|
-
};
|
|
263
|
-
}, [appData, config.persistedKeys]);
|
|
264
|
-
if (!ready) return null;
|
|
265
|
-
return /* @__PURE__ */ jsx6(Fragment3, { children });
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/internal/PushPrompt.tsx
|
|
269
|
-
function PushPrompt() {
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
495
|
// src/components/InstallGate/InstallGate.tsx
|
|
274
|
-
import { useEffect as
|
|
496
|
+
import { useEffect as useEffect4, useRef } from "react";
|
|
275
497
|
|
|
276
498
|
// src/hooks/useInstallPrompt.ts
|
|
277
|
-
import { useCallback as useCallback2, useEffect as
|
|
499
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
278
500
|
var IOS_RE = /iPad|iPhone|iPod/;
|
|
279
501
|
var IOS_NON_SAFARI_RE = /CriOS|FxiOS|EdgiOS/;
|
|
280
502
|
var ANDROID_RE = /Android/;
|
|
@@ -407,18 +629,18 @@ function useInstallPrompt(slug) {
|
|
|
407
629
|
const iosBrowser = detectIOSBrowser(ua);
|
|
408
630
|
const androidBrowser = detectAndroidBrowser(ua);
|
|
409
631
|
const inAppApp = detectInAppApp(ua);
|
|
410
|
-
const [isInstallable, setIsInstallable] =
|
|
632
|
+
const [isInstallable, setIsInstallable] = useState2(() => {
|
|
411
633
|
if (typeof window === "undefined") return false;
|
|
412
634
|
return window.__pwaInstallPrompt != null;
|
|
413
635
|
});
|
|
414
|
-
const [isInstalled, setIsInstalled] =
|
|
636
|
+
const [isInstalled, setIsInstalled] = useState2(() => {
|
|
415
637
|
const { installed } = detectStandalone();
|
|
416
638
|
return installed || readInstalledMarker(slug);
|
|
417
639
|
});
|
|
418
|
-
const [isDismissedSession, setIsDismissedSession] =
|
|
419
|
-
const [isDismissedPermanent, setIsDismissedPermanent] =
|
|
420
|
-
const [skipCount, setSkipCount] =
|
|
421
|
-
|
|
640
|
+
const [isDismissedSession, setIsDismissedSession] = useState2(() => readSessionSkip(slug));
|
|
641
|
+
const [isDismissedPermanent, setIsDismissedPermanent] = useState2(() => readPermanentDismiss(slug).dismissed);
|
|
642
|
+
const [skipCount, setSkipCount] = useState2(() => readSkipCount(slug));
|
|
643
|
+
useEffect3(() => {
|
|
422
644
|
if (typeof window === "undefined") return;
|
|
423
645
|
if (window.__pwaInstallPrompt) {
|
|
424
646
|
setIsInstallable(true);
|
|
@@ -442,7 +664,7 @@ function useInstallPrompt(slug) {
|
|
|
442
664
|
window.removeEventListener("appinstalled", onInstalled);
|
|
443
665
|
};
|
|
444
666
|
}, [slug]);
|
|
445
|
-
|
|
667
|
+
useEffect3(() => {
|
|
446
668
|
if (typeof window === "undefined") return;
|
|
447
669
|
const mq = window.matchMedia?.("(display-mode: standalone)");
|
|
448
670
|
if (!mq) return;
|
|
@@ -697,11 +919,11 @@ var INSTALL_COPY = {
|
|
|
697
919
|
};
|
|
698
920
|
|
|
699
921
|
// src/components/InstallGate/InstallSplash.tsx
|
|
700
|
-
import { jsx as
|
|
922
|
+
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
701
923
|
function InstallSplash({ children, title, subtitle }) {
|
|
702
924
|
const { name, theme } = useTemplateConfig();
|
|
703
925
|
const iconUrl = theme.icon_url || theme.logo_url || null;
|
|
704
|
-
return /* @__PURE__ */
|
|
926
|
+
return /* @__PURE__ */ jsx6(
|
|
705
927
|
"div",
|
|
706
928
|
{
|
|
707
929
|
role: "dialog",
|
|
@@ -710,14 +932,14 @@ function InstallSplash({ children, title, subtitle }) {
|
|
|
710
932
|
"aria-describedby": subtitle ? "install-splash-subtitle" : void 0,
|
|
711
933
|
style: overlayStyle,
|
|
712
934
|
children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
|
|
713
|
-
/* @__PURE__ */
|
|
935
|
+
/* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ jsx6(
|
|
714
936
|
"img",
|
|
715
937
|
{
|
|
716
938
|
src: iconUrl,
|
|
717
939
|
alt: `\xCDcone de ${name}`,
|
|
718
940
|
style: { width: 80, height: 80, borderRadius: 20, objectFit: "cover" }
|
|
719
941
|
}
|
|
720
|
-
) : /* @__PURE__ */
|
|
942
|
+
) : /* @__PURE__ */ jsx6(
|
|
721
943
|
"div",
|
|
722
944
|
{
|
|
723
945
|
style: {
|
|
@@ -735,10 +957,10 @@ function InstallSplash({ children, title, subtitle }) {
|
|
|
735
957
|
children: name.charAt(0).toUpperCase()
|
|
736
958
|
}
|
|
737
959
|
) }),
|
|
738
|
-
/* @__PURE__ */
|
|
739
|
-
subtitle && /* @__PURE__ */
|
|
740
|
-
/* @__PURE__ */
|
|
741
|
-
/* @__PURE__ */
|
|
960
|
+
/* @__PURE__ */ jsx6("h1", { id: "install-splash-title", style: titleStyle, children: title }),
|
|
961
|
+
subtitle && /* @__PURE__ */ jsx6("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
|
|
962
|
+
/* @__PURE__ */ jsx6("div", { style: { marginTop: 24 }, children }),
|
|
963
|
+
/* @__PURE__ */ jsx6("p", { style: footerStyle, children: "por Hook" })
|
|
742
964
|
] })
|
|
743
965
|
}
|
|
744
966
|
);
|
|
@@ -812,7 +1034,7 @@ var footerStyle = {
|
|
|
812
1034
|
};
|
|
813
1035
|
|
|
814
1036
|
// src/components/InstallGate/icons.tsx
|
|
815
|
-
import { jsx as
|
|
1037
|
+
import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
816
1038
|
var defaultSvgProps = (size) => ({
|
|
817
1039
|
width: size,
|
|
818
1040
|
height: size,
|
|
@@ -825,55 +1047,55 @@ var defaultSvgProps = (size) => ({
|
|
|
825
1047
|
});
|
|
826
1048
|
function ShareIconIOS({ size = 24, style, className }) {
|
|
827
1049
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
828
|
-
/* @__PURE__ */
|
|
829
|
-
/* @__PURE__ */
|
|
830
|
-
/* @__PURE__ */
|
|
1050
|
+
/* @__PURE__ */ jsx7("path", { d: "M12 2L12 15" }),
|
|
1051
|
+
/* @__PURE__ */ jsx7("path", { d: "M8 6L12 2L16 6" }),
|
|
1052
|
+
/* @__PURE__ */ jsx7("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
|
|
831
1053
|
] });
|
|
832
1054
|
}
|
|
833
1055
|
function MenuDotsVerticalIcon({ size = 24, style, className }) {
|
|
834
1056
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
835
|
-
/* @__PURE__ */
|
|
836
|
-
/* @__PURE__ */
|
|
837
|
-
/* @__PURE__ */
|
|
1057
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "5", r: "1.5" }),
|
|
1058
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
|
|
1059
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "19", r: "1.5" })
|
|
838
1060
|
] });
|
|
839
1061
|
}
|
|
840
1062
|
function MenuDotsHorizontalIcon({ size = 24, style, className }) {
|
|
841
1063
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
842
|
-
/* @__PURE__ */
|
|
843
|
-
/* @__PURE__ */
|
|
844
|
-
/* @__PURE__ */
|
|
1064
|
+
/* @__PURE__ */ jsx7("circle", { cx: "5", cy: "12", r: "1.5" }),
|
|
1065
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
|
|
1066
|
+
/* @__PURE__ */ jsx7("circle", { cx: "19", cy: "12", r: "1.5" })
|
|
845
1067
|
] });
|
|
846
1068
|
}
|
|
847
1069
|
function SquarePlusIcon({ size = 24, style, className }) {
|
|
848
1070
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
849
|
-
/* @__PURE__ */
|
|
850
|
-
/* @__PURE__ */
|
|
851
|
-
/* @__PURE__ */
|
|
1071
|
+
/* @__PURE__ */ jsx7("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
1072
|
+
/* @__PURE__ */ jsx7("path", { d: "M12 8v8" }),
|
|
1073
|
+
/* @__PURE__ */ jsx7("path", { d: "M8 12h8" })
|
|
852
1074
|
] });
|
|
853
1075
|
}
|
|
854
1076
|
function DownloadIcon({ size = 24, style, className }) {
|
|
855
1077
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
856
|
-
/* @__PURE__ */
|
|
857
|
-
/* @__PURE__ */
|
|
858
|
-
/* @__PURE__ */
|
|
1078
|
+
/* @__PURE__ */ jsx7("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
|
|
1079
|
+
/* @__PURE__ */ jsx7("polyline", { points: "7 10 12 15 17 10" }),
|
|
1080
|
+
/* @__PURE__ */ jsx7("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
859
1081
|
] });
|
|
860
1082
|
}
|
|
861
1083
|
function ExternalLinkIcon({ size = 24, style, className }) {
|
|
862
1084
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
863
|
-
/* @__PURE__ */
|
|
864
|
-
/* @__PURE__ */
|
|
865
|
-
/* @__PURE__ */
|
|
1085
|
+
/* @__PURE__ */ jsx7("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
|
|
1086
|
+
/* @__PURE__ */ jsx7("polyline", { points: "15 3 21 3 21 9" }),
|
|
1087
|
+
/* @__PURE__ */ jsx7("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
866
1088
|
] });
|
|
867
1089
|
}
|
|
868
1090
|
function XIcon({ size = 20, style, className }) {
|
|
869
1091
|
return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
|
|
870
|
-
/* @__PURE__ */
|
|
871
|
-
/* @__PURE__ */
|
|
1092
|
+
/* @__PURE__ */ jsx7("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1093
|
+
/* @__PURE__ */ jsx7("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
872
1094
|
] });
|
|
873
1095
|
}
|
|
874
1096
|
|
|
875
1097
|
// src/components/InstallGate/variants/AndroidNativeVariant.tsx
|
|
876
|
-
import { jsx as
|
|
1098
|
+
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
877
1099
|
function AndroidNativeVariant({
|
|
878
1100
|
state,
|
|
879
1101
|
actions
|
|
@@ -889,12 +1111,12 @@ function AndroidNativeVariant({
|
|
|
889
1111
|
onClick: () => void actions.promptInstall(),
|
|
890
1112
|
style: { ...primaryButtonStyle, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8 },
|
|
891
1113
|
children: [
|
|
892
|
-
/* @__PURE__ */
|
|
1114
|
+
/* @__PURE__ */ jsx8(DownloadIcon, { size: 18 }),
|
|
893
1115
|
copy.cta
|
|
894
1116
|
]
|
|
895
1117
|
}
|
|
896
1118
|
),
|
|
897
|
-
/* @__PURE__ */
|
|
1119
|
+
/* @__PURE__ */ jsx8(
|
|
898
1120
|
"button",
|
|
899
1121
|
{
|
|
900
1122
|
"data-testid": "install-prompt-skip-session",
|
|
@@ -904,7 +1126,7 @@ function AndroidNativeVariant({
|
|
|
904
1126
|
children: copy.skip
|
|
905
1127
|
}
|
|
906
1128
|
),
|
|
907
|
-
showPermanent && /* @__PURE__ */
|
|
1129
|
+
showPermanent && /* @__PURE__ */ jsx8(
|
|
908
1130
|
"button",
|
|
909
1131
|
{
|
|
910
1132
|
"data-testid": "install-prompt-skip-permanent",
|
|
@@ -918,7 +1140,7 @@ function AndroidNativeVariant({
|
|
|
918
1140
|
}
|
|
919
1141
|
|
|
920
1142
|
// src/components/InstallGate/variants/AndroidManualVariant.tsx
|
|
921
|
-
import { jsx as
|
|
1143
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
922
1144
|
function AndroidManualVariant({
|
|
923
1145
|
state,
|
|
924
1146
|
actions
|
|
@@ -926,9 +1148,9 @@ function AndroidManualVariant({
|
|
|
926
1148
|
const copy = INSTALL_COPY.android.manual;
|
|
927
1149
|
const showPermanent = shouldShowPermanentOption(state);
|
|
928
1150
|
return /* @__PURE__ */ jsxs4(InstallSplash, { title: copy.title, children: [
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
/* @__PURE__ */
|
|
1151
|
+
/* @__PURE__ */ jsx9(Step, { n: 1, icon: /* @__PURE__ */ jsx9(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
|
|
1152
|
+
/* @__PURE__ */ jsx9(Step, { n: 2, icon: /* @__PURE__ */ jsx9(DownloadIcon, { size: 18 }), children: copy.step2 }),
|
|
1153
|
+
/* @__PURE__ */ jsx9(
|
|
932
1154
|
"button",
|
|
933
1155
|
{
|
|
934
1156
|
"data-testid": "install-prompt-cta-android-manual",
|
|
@@ -938,7 +1160,7 @@ function AndroidManualVariant({
|
|
|
938
1160
|
children: copy.cta
|
|
939
1161
|
}
|
|
940
1162
|
),
|
|
941
|
-
/* @__PURE__ */
|
|
1163
|
+
/* @__PURE__ */ jsx9(
|
|
942
1164
|
"button",
|
|
943
1165
|
{
|
|
944
1166
|
"data-testid": "install-prompt-skip-session",
|
|
@@ -948,7 +1170,7 @@ function AndroidManualVariant({
|
|
|
948
1170
|
children: copy.skip
|
|
949
1171
|
}
|
|
950
1172
|
),
|
|
951
|
-
showPermanent && /* @__PURE__ */
|
|
1173
|
+
showPermanent && /* @__PURE__ */ jsx9(
|
|
952
1174
|
"button",
|
|
953
1175
|
{
|
|
954
1176
|
"data-testid": "install-prompt-skip-permanent",
|
|
@@ -975,7 +1197,7 @@ function Step({ n, icon, children }) {
|
|
|
975
1197
|
textAlign: "left"
|
|
976
1198
|
},
|
|
977
1199
|
children: [
|
|
978
|
-
/* @__PURE__ */
|
|
1200
|
+
/* @__PURE__ */ jsx9(
|
|
979
1201
|
"div",
|
|
980
1202
|
{
|
|
981
1203
|
style: {
|
|
@@ -994,15 +1216,15 @@ function Step({ n, icon, children }) {
|
|
|
994
1216
|
children: n
|
|
995
1217
|
}
|
|
996
1218
|
),
|
|
997
|
-
/* @__PURE__ */
|
|
998
|
-
/* @__PURE__ */
|
|
1219
|
+
/* @__PURE__ */ jsx9("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
|
|
1220
|
+
/* @__PURE__ */ jsx9("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
|
|
999
1221
|
]
|
|
1000
1222
|
}
|
|
1001
1223
|
);
|
|
1002
1224
|
}
|
|
1003
1225
|
|
|
1004
1226
|
// src/components/InstallGate/Step.tsx
|
|
1005
|
-
import { jsx as
|
|
1227
|
+
import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1006
1228
|
function Step2({
|
|
1007
1229
|
n,
|
|
1008
1230
|
title,
|
|
@@ -1020,7 +1242,7 @@ function Step2({
|
|
|
1020
1242
|
textAlign: "left"
|
|
1021
1243
|
},
|
|
1022
1244
|
children: [
|
|
1023
|
-
/* @__PURE__ */
|
|
1245
|
+
/* @__PURE__ */ jsx10(
|
|
1024
1246
|
"div",
|
|
1025
1247
|
{
|
|
1026
1248
|
style: {
|
|
@@ -1040,8 +1262,8 @@ function Step2({
|
|
|
1040
1262
|
}
|
|
1041
1263
|
),
|
|
1042
1264
|
/* @__PURE__ */ jsxs5("div", { style: { flex: 1 }, children: [
|
|
1043
|
-
/* @__PURE__ */
|
|
1044
|
-
subtitle && /* @__PURE__ */
|
|
1265
|
+
/* @__PURE__ */ jsx10("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
|
|
1266
|
+
subtitle && /* @__PURE__ */ jsx10("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
|
|
1045
1267
|
visual
|
|
1046
1268
|
] })
|
|
1047
1269
|
]
|
|
@@ -1050,7 +1272,7 @@ function Step2({
|
|
|
1050
1272
|
}
|
|
1051
1273
|
|
|
1052
1274
|
// src/components/InstallGate/variants/IOSafariVariant.tsx
|
|
1053
|
-
import { jsx as
|
|
1275
|
+
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1054
1276
|
function IOSafariVariant({
|
|
1055
1277
|
state,
|
|
1056
1278
|
actions
|
|
@@ -1058,13 +1280,13 @@ function IOSafariVariant({
|
|
|
1058
1280
|
const copy = INSTALL_COPY.iosSafari;
|
|
1059
1281
|
const showPermanent = shouldShowPermanentOption(state);
|
|
1060
1282
|
return /* @__PURE__ */ jsxs6(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
|
|
1061
|
-
/* @__PURE__ */
|
|
1283
|
+
/* @__PURE__ */ jsx11(
|
|
1062
1284
|
Step2,
|
|
1063
1285
|
{
|
|
1064
1286
|
n: 1,
|
|
1065
1287
|
title: copy.step1.title,
|
|
1066
1288
|
subtitle: copy.step1.subtitle,
|
|
1067
|
-
visual: /* @__PURE__ */
|
|
1289
|
+
visual: /* @__PURE__ */ jsx11(
|
|
1068
1290
|
"div",
|
|
1069
1291
|
{
|
|
1070
1292
|
style: {
|
|
@@ -1076,12 +1298,12 @@ function IOSafariVariant({
|
|
|
1076
1298
|
padding: "12px 0",
|
|
1077
1299
|
marginTop: 8
|
|
1078
1300
|
},
|
|
1079
|
-
children: /* @__PURE__ */
|
|
1301
|
+
children: /* @__PURE__ */ jsx11(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
|
|
1080
1302
|
}
|
|
1081
1303
|
)
|
|
1082
1304
|
}
|
|
1083
1305
|
),
|
|
1084
|
-
/* @__PURE__ */
|
|
1306
|
+
/* @__PURE__ */ jsx11(
|
|
1085
1307
|
Step2,
|
|
1086
1308
|
{
|
|
1087
1309
|
n: 2,
|
|
@@ -1099,19 +1321,19 @@ function IOSafariVariant({
|
|
|
1099
1321
|
marginTop: 8
|
|
1100
1322
|
},
|
|
1101
1323
|
children: [
|
|
1102
|
-
/* @__PURE__ */
|
|
1103
|
-
/* @__PURE__ */
|
|
1324
|
+
/* @__PURE__ */ jsx11(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
|
|
1325
|
+
/* @__PURE__ */ jsx11("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
|
|
1104
1326
|
]
|
|
1105
1327
|
}
|
|
1106
1328
|
)
|
|
1107
1329
|
}
|
|
1108
1330
|
),
|
|
1109
|
-
/* @__PURE__ */
|
|
1331
|
+
/* @__PURE__ */ jsx11(
|
|
1110
1332
|
Step2,
|
|
1111
1333
|
{
|
|
1112
1334
|
n: 3,
|
|
1113
1335
|
title: copy.step3.title,
|
|
1114
|
-
visual: /* @__PURE__ */
|
|
1336
|
+
visual: /* @__PURE__ */ jsx11(
|
|
1115
1337
|
"div",
|
|
1116
1338
|
{
|
|
1117
1339
|
style: {
|
|
@@ -1122,7 +1344,7 @@ function IOSafariVariant({
|
|
|
1122
1344
|
padding: "10px 14px",
|
|
1123
1345
|
marginTop: 8
|
|
1124
1346
|
},
|
|
1125
|
-
children: /* @__PURE__ */
|
|
1347
|
+
children: /* @__PURE__ */ jsx11(
|
|
1126
1348
|
"span",
|
|
1127
1349
|
{
|
|
1128
1350
|
style: {
|
|
@@ -1137,7 +1359,7 @@ function IOSafariVariant({
|
|
|
1137
1359
|
)
|
|
1138
1360
|
}
|
|
1139
1361
|
),
|
|
1140
|
-
/* @__PURE__ */
|
|
1362
|
+
/* @__PURE__ */ jsx11(
|
|
1141
1363
|
"button",
|
|
1142
1364
|
{
|
|
1143
1365
|
"data-testid": "install-prompt-skip-session",
|
|
@@ -1147,7 +1369,7 @@ function IOSafariVariant({
|
|
|
1147
1369
|
children: copy.skip
|
|
1148
1370
|
}
|
|
1149
1371
|
),
|
|
1150
|
-
showPermanent && /* @__PURE__ */
|
|
1372
|
+
showPermanent && /* @__PURE__ */ jsx11(
|
|
1151
1373
|
"button",
|
|
1152
1374
|
{
|
|
1153
1375
|
"data-testid": "install-prompt-skip-permanent",
|
|
@@ -1161,7 +1383,7 @@ function IOSafariVariant({
|
|
|
1161
1383
|
}
|
|
1162
1384
|
|
|
1163
1385
|
// src/components/InstallGate/variants/IOSOtherVariant.tsx
|
|
1164
|
-
import { jsx as
|
|
1386
|
+
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1165
1387
|
function IOSOtherVariant({
|
|
1166
1388
|
state,
|
|
1167
1389
|
actions
|
|
@@ -1169,13 +1391,13 @@ function IOSOtherVariant({
|
|
|
1169
1391
|
const copy = INSTALL_COPY.iosOther;
|
|
1170
1392
|
const showPermanent = shouldShowPermanentOption(state);
|
|
1171
1393
|
return /* @__PURE__ */ jsxs7(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
|
|
1172
|
-
/* @__PURE__ */
|
|
1394
|
+
/* @__PURE__ */ jsx12(
|
|
1173
1395
|
Step2,
|
|
1174
1396
|
{
|
|
1175
1397
|
n: 1,
|
|
1176
1398
|
title: copy.step1.title,
|
|
1177
1399
|
subtitle: copy.step1.subtitle,
|
|
1178
|
-
visual: /* @__PURE__ */
|
|
1400
|
+
visual: /* @__PURE__ */ jsx12(
|
|
1179
1401
|
"div",
|
|
1180
1402
|
{
|
|
1181
1403
|
style: {
|
|
@@ -1187,12 +1409,12 @@ function IOSOtherVariant({
|
|
|
1187
1409
|
padding: "12px 0",
|
|
1188
1410
|
marginTop: 8
|
|
1189
1411
|
},
|
|
1190
|
-
children: /* @__PURE__ */
|
|
1412
|
+
children: /* @__PURE__ */ jsx12(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
|
|
1191
1413
|
}
|
|
1192
1414
|
)
|
|
1193
1415
|
}
|
|
1194
1416
|
),
|
|
1195
|
-
/* @__PURE__ */
|
|
1417
|
+
/* @__PURE__ */ jsx12(
|
|
1196
1418
|
Step2,
|
|
1197
1419
|
{
|
|
1198
1420
|
n: 2,
|
|
@@ -1210,19 +1432,19 @@ function IOSOtherVariant({
|
|
|
1210
1432
|
marginTop: 8
|
|
1211
1433
|
},
|
|
1212
1434
|
children: [
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
/* @__PURE__ */
|
|
1435
|
+
/* @__PURE__ */ jsx12(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
|
|
1436
|
+
/* @__PURE__ */ jsx12("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
|
|
1215
1437
|
]
|
|
1216
1438
|
}
|
|
1217
1439
|
)
|
|
1218
1440
|
}
|
|
1219
1441
|
),
|
|
1220
|
-
/* @__PURE__ */
|
|
1442
|
+
/* @__PURE__ */ jsx12(
|
|
1221
1443
|
Step2,
|
|
1222
1444
|
{
|
|
1223
1445
|
n: 3,
|
|
1224
1446
|
title: copy.step3.title,
|
|
1225
|
-
visual: /* @__PURE__ */
|
|
1447
|
+
visual: /* @__PURE__ */ jsx12(
|
|
1226
1448
|
"div",
|
|
1227
1449
|
{
|
|
1228
1450
|
style: {
|
|
@@ -1233,7 +1455,7 @@ function IOSOtherVariant({
|
|
|
1233
1455
|
padding: "10px 14px",
|
|
1234
1456
|
marginTop: 8
|
|
1235
1457
|
},
|
|
1236
|
-
children: /* @__PURE__ */
|
|
1458
|
+
children: /* @__PURE__ */ jsx12(
|
|
1237
1459
|
"span",
|
|
1238
1460
|
{
|
|
1239
1461
|
style: {
|
|
@@ -1248,7 +1470,7 @@ function IOSOtherVariant({
|
|
|
1248
1470
|
)
|
|
1249
1471
|
}
|
|
1250
1472
|
),
|
|
1251
|
-
/* @__PURE__ */
|
|
1473
|
+
/* @__PURE__ */ jsx12(
|
|
1252
1474
|
"button",
|
|
1253
1475
|
{
|
|
1254
1476
|
"data-testid": "install-prompt-skip-session",
|
|
@@ -1258,7 +1480,7 @@ function IOSOtherVariant({
|
|
|
1258
1480
|
children: copy.skip
|
|
1259
1481
|
}
|
|
1260
1482
|
),
|
|
1261
|
-
showPermanent && /* @__PURE__ */
|
|
1483
|
+
showPermanent && /* @__PURE__ */ jsx12(
|
|
1262
1484
|
"button",
|
|
1263
1485
|
{
|
|
1264
1486
|
"data-testid": "install-prompt-skip-permanent",
|
|
@@ -1272,8 +1494,8 @@ function IOSOtherVariant({
|
|
|
1272
1494
|
}
|
|
1273
1495
|
|
|
1274
1496
|
// src/components/InstallGate/variants/InAppBrowserVariant.tsx
|
|
1275
|
-
import { useState as
|
|
1276
|
-
import { jsx as
|
|
1497
|
+
import { useState as useState3 } from "react";
|
|
1498
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1277
1499
|
function InAppBrowserVariant({
|
|
1278
1500
|
state,
|
|
1279
1501
|
actions
|
|
@@ -1282,7 +1504,7 @@ function InAppBrowserVariant({
|
|
|
1282
1504
|
const appCopy = INSTALL_COPY.inApp[app] ?? INSTALL_COPY.inApp.other;
|
|
1283
1505
|
const copy = INSTALL_COPY.inApp;
|
|
1284
1506
|
const showPermanent = shouldShowPermanentOption(state);
|
|
1285
|
-
const [copied, setCopied] =
|
|
1507
|
+
const [copied, setCopied] = useState3(false);
|
|
1286
1508
|
const handleCopy = async () => {
|
|
1287
1509
|
await actions.copyLink();
|
|
1288
1510
|
setCopied(true);
|
|
@@ -1290,9 +1512,9 @@ function InAppBrowserVariant({
|
|
|
1290
1512
|
};
|
|
1291
1513
|
const DotsIcon = app === "facebook" || app === "telegram" ? MenuDotsVerticalIcon : MenuDotsHorizontalIcon;
|
|
1292
1514
|
return /* @__PURE__ */ jsxs8(InstallSplash, { title: appCopy.title, children: [
|
|
1293
|
-
/* @__PURE__ */
|
|
1294
|
-
/* @__PURE__ */
|
|
1295
|
-
/* @__PURE__ */
|
|
1515
|
+
/* @__PURE__ */ jsx13(Step3, { n: 1, icon: /* @__PURE__ */ jsx13(DotsIcon, { size: 20 }), children: appCopy.step1 }),
|
|
1516
|
+
/* @__PURE__ */ jsx13(Step3, { n: 2, icon: /* @__PURE__ */ jsx13(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
|
|
1517
|
+
/* @__PURE__ */ jsx13(
|
|
1296
1518
|
"button",
|
|
1297
1519
|
{
|
|
1298
1520
|
"data-testid": "install-prompt-cta-inapp-copy",
|
|
@@ -1302,7 +1524,7 @@ function InAppBrowserVariant({
|
|
|
1302
1524
|
children: copied ? copy.copiedToast : copy.copy
|
|
1303
1525
|
}
|
|
1304
1526
|
),
|
|
1305
|
-
/* @__PURE__ */
|
|
1527
|
+
/* @__PURE__ */ jsx13(
|
|
1306
1528
|
"button",
|
|
1307
1529
|
{
|
|
1308
1530
|
"data-testid": "install-prompt-skip-session",
|
|
@@ -1312,7 +1534,7 @@ function InAppBrowserVariant({
|
|
|
1312
1534
|
children: copy.skip
|
|
1313
1535
|
}
|
|
1314
1536
|
),
|
|
1315
|
-
showPermanent && /* @__PURE__ */
|
|
1537
|
+
showPermanent && /* @__PURE__ */ jsx13(
|
|
1316
1538
|
"button",
|
|
1317
1539
|
{
|
|
1318
1540
|
"data-testid": "install-prompt-skip-permanent",
|
|
@@ -1343,7 +1565,7 @@ function Step3({
|
|
|
1343
1565
|
textAlign: "left"
|
|
1344
1566
|
},
|
|
1345
1567
|
children: [
|
|
1346
|
-
/* @__PURE__ */
|
|
1568
|
+
/* @__PURE__ */ jsx13(
|
|
1347
1569
|
"div",
|
|
1348
1570
|
{
|
|
1349
1571
|
style: {
|
|
@@ -1362,15 +1584,15 @@ function Step3({
|
|
|
1362
1584
|
children: n
|
|
1363
1585
|
}
|
|
1364
1586
|
),
|
|
1365
|
-
/* @__PURE__ */
|
|
1366
|
-
/* @__PURE__ */
|
|
1587
|
+
/* @__PURE__ */ jsx13("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
|
|
1588
|
+
/* @__PURE__ */ jsx13("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
|
|
1367
1589
|
]
|
|
1368
1590
|
}
|
|
1369
1591
|
);
|
|
1370
1592
|
}
|
|
1371
1593
|
|
|
1372
1594
|
// src/components/InstallGate/variants/DesktopVariant.tsx
|
|
1373
|
-
import { jsx as
|
|
1595
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1374
1596
|
function DesktopVariant({
|
|
1375
1597
|
state,
|
|
1376
1598
|
actions
|
|
@@ -1386,14 +1608,14 @@ function DesktopVariant({
|
|
|
1386
1608
|
"aria-label": copy.title,
|
|
1387
1609
|
style: bannerStyle,
|
|
1388
1610
|
children: [
|
|
1389
|
-
iconUrl ? /* @__PURE__ */
|
|
1611
|
+
iconUrl ? /* @__PURE__ */ jsx14(
|
|
1390
1612
|
"img",
|
|
1391
1613
|
{
|
|
1392
1614
|
src: iconUrl,
|
|
1393
1615
|
alt: "",
|
|
1394
1616
|
style: { width: 40, height: 40, borderRadius: 10, objectFit: "cover", flexShrink: 0 }
|
|
1395
1617
|
}
|
|
1396
|
-
) : /* @__PURE__ */
|
|
1618
|
+
) : /* @__PURE__ */ jsx14(
|
|
1397
1619
|
"div",
|
|
1398
1620
|
{
|
|
1399
1621
|
style: {
|
|
@@ -1413,8 +1635,8 @@ function DesktopVariant({
|
|
|
1413
1635
|
}
|
|
1414
1636
|
),
|
|
1415
1637
|
/* @__PURE__ */ jsxs9("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1638
|
+
/* @__PURE__ */ jsx14("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
|
|
1639
|
+
/* @__PURE__ */ jsx14("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
|
|
1418
1640
|
] }),
|
|
1419
1641
|
/* @__PURE__ */ jsxs9(
|
|
1420
1642
|
"button",
|
|
@@ -1437,12 +1659,12 @@ function DesktopVariant({
|
|
|
1437
1659
|
flexShrink: 0
|
|
1438
1660
|
},
|
|
1439
1661
|
children: [
|
|
1440
|
-
/* @__PURE__ */
|
|
1662
|
+
/* @__PURE__ */ jsx14(DownloadIcon, { size: 14 }),
|
|
1441
1663
|
copy.cta
|
|
1442
1664
|
]
|
|
1443
1665
|
}
|
|
1444
1666
|
),
|
|
1445
|
-
/* @__PURE__ */
|
|
1667
|
+
/* @__PURE__ */ jsx14(
|
|
1446
1668
|
"button",
|
|
1447
1669
|
{
|
|
1448
1670
|
"data-testid": "install-prompt-desktop-close",
|
|
@@ -1457,7 +1679,7 @@ function DesktopVariant({
|
|
|
1457
1679
|
padding: 4,
|
|
1458
1680
|
flexShrink: 0
|
|
1459
1681
|
},
|
|
1460
|
-
children: /* @__PURE__ */
|
|
1682
|
+
children: /* @__PURE__ */ jsx14(XIcon, { size: 16 })
|
|
1461
1683
|
}
|
|
1462
1684
|
)
|
|
1463
1685
|
]
|
|
@@ -1481,14 +1703,14 @@ var bannerStyle = {
|
|
|
1481
1703
|
};
|
|
1482
1704
|
|
|
1483
1705
|
// src/components/InstallGate/InstallGate.tsx
|
|
1484
|
-
import { Fragment as
|
|
1706
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1485
1707
|
function InstallGate({ children }) {
|
|
1486
1708
|
const { slug, features_enabled } = useTemplateConfig();
|
|
1487
1709
|
const enabled = features_enabled.includes("install_prompt");
|
|
1488
1710
|
const installState = useInstallPrompt(slug);
|
|
1489
1711
|
const shouldBlock = enabled && shouldBlockInstall(installState);
|
|
1490
|
-
const trackedRef =
|
|
1491
|
-
|
|
1712
|
+
const trackedRef = useRef(null);
|
|
1713
|
+
useEffect4(() => {
|
|
1492
1714
|
if (!shouldBlock) return;
|
|
1493
1715
|
if (typeof window === "undefined") return;
|
|
1494
1716
|
const variantKey = `${slug}:${installState.variant}`;
|
|
@@ -1502,109 +1724,449 @@ function InstallGate({ children }) {
|
|
|
1502
1724
|
variant: installState.variant
|
|
1503
1725
|
});
|
|
1504
1726
|
}, [shouldBlock, slug, installState.variant, installState.platform, installState.iosBrowser, installState.androidBrowser, installState.inAppApp]);
|
|
1505
|
-
if (!enabled) return /* @__PURE__ */
|
|
1506
|
-
if (installState.isInstalled) return /* @__PURE__ */
|
|
1727
|
+
if (!enabled) return /* @__PURE__ */ jsx15(Fragment3, { children });
|
|
1728
|
+
if (installState.isInstalled) return /* @__PURE__ */ jsx15(Fragment3, { children });
|
|
1507
1729
|
if (installState.variant === "desktop") {
|
|
1508
1730
|
const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
|
|
1509
|
-
return /* @__PURE__ */ jsxs10(
|
|
1731
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1510
1732
|
children,
|
|
1511
|
-
showBanner && /* @__PURE__ */
|
|
1733
|
+
showBanner && /* @__PURE__ */ jsx15(DesktopVariant, { state: installState, actions: installState })
|
|
1512
1734
|
] });
|
|
1513
1735
|
}
|
|
1514
|
-
if (!shouldBlock) return /* @__PURE__ */
|
|
1736
|
+
if (!shouldBlock) return /* @__PURE__ */ jsx15(Fragment3, { children });
|
|
1515
1737
|
switch (installState.variant) {
|
|
1516
1738
|
case "android-native":
|
|
1517
|
-
return /* @__PURE__ */
|
|
1739
|
+
return /* @__PURE__ */ jsx15(AndroidNativeVariant, { state: installState, actions: installState });
|
|
1518
1740
|
case "android-manual":
|
|
1519
|
-
return /* @__PURE__ */
|
|
1741
|
+
return /* @__PURE__ */ jsx15(AndroidManualVariant, { state: installState, actions: installState });
|
|
1520
1742
|
case "ios-safari":
|
|
1521
|
-
return /* @__PURE__ */
|
|
1743
|
+
return /* @__PURE__ */ jsx15(IOSafariVariant, { state: installState, actions: installState });
|
|
1522
1744
|
case "ios-other":
|
|
1523
|
-
return /* @__PURE__ */
|
|
1745
|
+
return /* @__PURE__ */ jsx15(IOSOtherVariant, { state: installState, actions: installState });
|
|
1524
1746
|
case "in-app":
|
|
1525
|
-
return /* @__PURE__ */
|
|
1747
|
+
return /* @__PURE__ */ jsx15(InAppBrowserVariant, { state: installState, actions: installState });
|
|
1526
1748
|
case "none":
|
|
1527
1749
|
default:
|
|
1528
|
-
return /* @__PURE__ */
|
|
1750
|
+
return /* @__PURE__ */ jsx15(Fragment3, { children });
|
|
1529
1751
|
}
|
|
1530
1752
|
}
|
|
1531
1753
|
|
|
1754
|
+
// src/internal/PushPrompt.tsx
|
|
1755
|
+
function PushPrompt() {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1532
1759
|
// src/defaults/ErrorBoundary.tsx
|
|
1533
1760
|
import { Component } from "react";
|
|
1534
|
-
import { Fragment as
|
|
1761
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1535
1762
|
var ErrorBoundary = class extends Component {
|
|
1536
1763
|
state = { error: null };
|
|
1537
1764
|
static getDerivedStateFromError(error) {
|
|
1538
1765
|
return { error };
|
|
1539
1766
|
}
|
|
1540
|
-
componentDidCatch(error) {
|
|
1541
|
-
console.error(
|
|
1767
|
+
componentDidCatch(error, info) {
|
|
1768
|
+
console.error(
|
|
1769
|
+
"[ErrorBoundary] caught:",
|
|
1770
|
+
error?.message || "(no message)",
|
|
1771
|
+
"\nstack:",
|
|
1772
|
+
error?.stack || "(no stack)",
|
|
1773
|
+
"\ncomponentStack:",
|
|
1774
|
+
info?.componentStack || "(no componentStack)"
|
|
1775
|
+
);
|
|
1542
1776
|
}
|
|
1543
1777
|
render() {
|
|
1544
1778
|
if (this.state.error) {
|
|
1545
1779
|
return this.props.fallback ?? /* @__PURE__ */ jsxs11("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
|
|
1546
|
-
/* @__PURE__ */
|
|
1547
|
-
/* @__PURE__ */
|
|
1780
|
+
/* @__PURE__ */ jsx16("h2", { children: "Algo deu errado" }),
|
|
1781
|
+
/* @__PURE__ */ jsx16("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
|
|
1548
1782
|
] });
|
|
1549
1783
|
}
|
|
1550
|
-
return /* @__PURE__ */
|
|
1784
|
+
return /* @__PURE__ */ jsx16(Fragment4, { children: this.props.children });
|
|
1551
1785
|
}
|
|
1552
1786
|
};
|
|
1553
1787
|
|
|
1554
|
-
// src/
|
|
1555
|
-
import { useCallback as useCallback3,
|
|
1556
|
-
import { useHook as
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1788
|
+
// src/internal/PaymentReturnHandler.tsx
|
|
1789
|
+
import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef2, useState as useState4 } from "react";
|
|
1790
|
+
import { useHook as useHook3 } from "@hook-sdk/sdk";
|
|
1791
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1792
|
+
var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
|
|
1793
|
+
function PaymentReturnHandler({ children }) {
|
|
1794
|
+
const { subscription } = useHook3();
|
|
1795
|
+
const subRef = useRef2(subscription);
|
|
1796
|
+
subRef.current = subscription;
|
|
1797
|
+
const runIdRef = useRef2(0);
|
|
1798
|
+
const [state, setState] = useState4("idle");
|
|
1799
|
+
const runPoll = useCallback3(() => {
|
|
1800
|
+
const runId = ++runIdRef.current;
|
|
1801
|
+
setState("confirming");
|
|
1802
|
+
let attempts = 0;
|
|
1803
|
+
const tick = async () => {
|
|
1804
|
+
if (runIdRef.current !== runId) return;
|
|
1805
|
+
attempts++;
|
|
1806
|
+
try {
|
|
1807
|
+
await subRef.current.refresh();
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
if (runIdRef.current !== runId) return;
|
|
1811
|
+
const status = subRef.current.status();
|
|
1812
|
+
if (status === "active" || status === "trialing") {
|
|
1813
|
+
const cleanUrl = new URL(window.location.href);
|
|
1814
|
+
cleanUrl.searchParams.delete("paymentReturn");
|
|
1815
|
+
window.history.replaceState({}, "", cleanUrl.toString());
|
|
1816
|
+
setState("idle");
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
const delay = BACKOFF_MS[attempts - 1];
|
|
1820
|
+
if (delay === void 0) {
|
|
1821
|
+
setState("waiting");
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
setTimeout(tick, delay);
|
|
1566
1825
|
};
|
|
1826
|
+
void tick();
|
|
1827
|
+
}, []);
|
|
1828
|
+
useEffect5(() => {
|
|
1829
|
+
if (typeof window === "undefined") return;
|
|
1830
|
+
const url = new URL(window.location.href);
|
|
1831
|
+
if (url.searchParams.get("paymentReturn") !== "1") return;
|
|
1832
|
+
runPoll();
|
|
1833
|
+
return () => {
|
|
1834
|
+
runIdRef.current++;
|
|
1835
|
+
};
|
|
1836
|
+
}, [runPoll]);
|
|
1837
|
+
if (state === "confirming") {
|
|
1838
|
+
return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
|
|
1567
1839
|
}
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
}
|
|
1573
|
-
if (detail === "account_locked") {
|
|
1574
|
-
return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
|
|
1575
|
-
}
|
|
1576
|
-
return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
|
|
1577
|
-
}
|
|
1578
|
-
if (err instanceof SdkError && err.httpStatus === 0) {
|
|
1579
|
-
return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
|
|
1580
|
-
}
|
|
1581
|
-
if (err instanceof TypeError) {
|
|
1582
|
-
return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
|
|
1840
|
+
if (state === "waiting") {
|
|
1841
|
+
return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs12("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
|
|
1842
|
+
/* @__PURE__ */ jsx17("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
|
|
1843
|
+
/* @__PURE__ */ jsx17("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
|
|
1844
|
+
] }) });
|
|
1583
1845
|
}
|
|
1584
|
-
return
|
|
1846
|
+
return /* @__PURE__ */ jsx17(Fragment5, { children });
|
|
1585
1847
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1848
|
+
var overlayStyle2 = {
|
|
1849
|
+
position: "fixed",
|
|
1850
|
+
inset: 0,
|
|
1851
|
+
display: "flex",
|
|
1852
|
+
alignItems: "center",
|
|
1853
|
+
justifyContent: "center",
|
|
1854
|
+
background: "rgba(0,0,0,0.4)",
|
|
1855
|
+
zIndex: 9999,
|
|
1856
|
+
color: "white",
|
|
1857
|
+
fontSize: "1rem",
|
|
1858
|
+
padding: 24
|
|
1859
|
+
};
|
|
1860
|
+
var buttonStyle = {
|
|
1861
|
+
background: "white",
|
|
1862
|
+
color: "black",
|
|
1863
|
+
border: "none",
|
|
1864
|
+
borderRadius: 8,
|
|
1865
|
+
padding: "10px 24px",
|
|
1866
|
+
fontSize: "1rem",
|
|
1867
|
+
fontWeight: 600,
|
|
1868
|
+
cursor: "pointer"
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
// src/AppRoot.tsx
|
|
1872
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1873
|
+
function buildLegacyConfigShim(config) {
|
|
1874
|
+
const paywall = config.paywall;
|
|
1875
|
+
const isFree = paywall.mode === "free";
|
|
1876
|
+
const monthlyCents = isFree ? 0 : paywall.prices.monthlyCents;
|
|
1877
|
+
const trialDays = isFree ? 0 : paywall.trialDays ?? 0;
|
|
1878
|
+
return {
|
|
1879
|
+
slug: config.slug,
|
|
1880
|
+
name: config.name,
|
|
1881
|
+
email_alias: config.slug,
|
|
1882
|
+
theme: { primary_color: config.branding.primaryColor },
|
|
1883
|
+
features_enabled: config.features_enabled ?? [],
|
|
1884
|
+
dependencies_allowlist: ["react", "react-dom"],
|
|
1885
|
+
subscription: {
|
|
1886
|
+
mode: paywall.mode,
|
|
1887
|
+
price_cents: monthlyCents,
|
|
1888
|
+
currency: "brl",
|
|
1889
|
+
trial_days: trialDays,
|
|
1890
|
+
paywall_config: {
|
|
1891
|
+
title: config.name,
|
|
1892
|
+
benefits: ["Acesso completo"],
|
|
1893
|
+
cta: "Assinar"
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
sdk_version_required: ">=0.16.0",
|
|
1897
|
+
max_bundle_size_kb: 500
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
function AppRoot(props) {
|
|
1901
|
+
const {
|
|
1902
|
+
config: rawConfig,
|
|
1903
|
+
children,
|
|
1904
|
+
testRouter,
|
|
1905
|
+
testInitialEntries,
|
|
1906
|
+
Login,
|
|
1907
|
+
Signup,
|
|
1908
|
+
Forgot,
|
|
1909
|
+
Reset,
|
|
1910
|
+
EmailVerify,
|
|
1911
|
+
Paywall,
|
|
1912
|
+
Onboarding,
|
|
1913
|
+
PreAuthFlow
|
|
1914
|
+
} = props;
|
|
1915
|
+
if (!Login || !Signup || !Forgot || !Reset) {
|
|
1916
|
+
throw new Error(
|
|
1917
|
+
"[hook-template] <AppRoot>: Login, Signup, Forgot, Reset slot props are required."
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
const config = parseAppConfig(rawConfig);
|
|
1921
|
+
if (config.paywall.mode !== "free" && !Paywall) {
|
|
1922
|
+
throw new Error(
|
|
1923
|
+
"[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
if (config.authFlow.requiresEmailVerify && !EmailVerify) {
|
|
1927
|
+
throw new Error(
|
|
1928
|
+
"[hook-template] <AppRoot>: EmailVerify slot prop is required when config.authFlow.requiresEmailVerify === true."
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
if (config.onboarding?.trigger === "pre_signup_custom" && !PreAuthFlow) {
|
|
1932
|
+
throw new Error(
|
|
1933
|
+
"[hook-template] <AppRoot>: PreAuthFlow slot prop is required when config.onboarding.trigger === 'pre_signup_custom'."
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
const legacyShim = useMemo3(() => buildLegacyConfigShim(config), [config]);
|
|
1937
|
+
const Router = testRouter === "memory" ? MemoryRouter : BrowserRouter;
|
|
1938
|
+
const basename = `/app/${config.slug}`;
|
|
1939
|
+
const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
|
|
1940
|
+
return /* @__PURE__ */ jsx18(ErrorBoundary, { children: /* @__PURE__ */ jsx18(AppConfigProvider, { config, children: /* @__PURE__ */ jsx18(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ jsx18(ThemeProvider, { children: /* @__PURE__ */ jsx18(PersistenceRegistry, { config: config.persistedKeys, children: /* @__PURE__ */ jsxs13(Router, { ...routerProps, children: [
|
|
1941
|
+
/* @__PURE__ */ jsx18(DeepLinkHandler, { deepLinks: config.deepLinks }),
|
|
1942
|
+
/* @__PURE__ */ jsx18(InstallGate, { children: /* @__PURE__ */ jsx18(
|
|
1943
|
+
AuthGated,
|
|
1944
|
+
{
|
|
1945
|
+
config,
|
|
1946
|
+
Login,
|
|
1947
|
+
Signup,
|
|
1948
|
+
Forgot,
|
|
1949
|
+
Reset,
|
|
1950
|
+
EmailVerify,
|
|
1951
|
+
Paywall,
|
|
1952
|
+
Onboarding,
|
|
1953
|
+
PreAuthFlow,
|
|
1954
|
+
children: /* @__PURE__ */ jsxs13(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
|
|
1955
|
+
children,
|
|
1956
|
+
/* @__PURE__ */ jsx18(PushPrompt, {})
|
|
1957
|
+
] })
|
|
1958
|
+
}
|
|
1959
|
+
) })
|
|
1960
|
+
] }) }) }) }) }) });
|
|
1961
|
+
}
|
|
1962
|
+
function AuthGated({
|
|
1963
|
+
children,
|
|
1964
|
+
config,
|
|
1965
|
+
Login,
|
|
1966
|
+
Signup,
|
|
1967
|
+
Forgot,
|
|
1968
|
+
Reset,
|
|
1969
|
+
EmailVerify,
|
|
1970
|
+
PreAuthFlow
|
|
1971
|
+
}) {
|
|
1972
|
+
const { authStatus } = useHook4();
|
|
1973
|
+
if (authStatus === "loading") return null;
|
|
1974
|
+
if (authStatus !== "authenticated") {
|
|
1975
|
+
if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
|
|
1976
|
+
return /* @__PURE__ */ jsxs13(Routes, { children: [
|
|
1977
|
+
/* @__PURE__ */ jsx18(Route, { path: "/signin", element: /* @__PURE__ */ jsx18(Login, {}) }),
|
|
1978
|
+
/* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
|
|
1979
|
+
/* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
|
|
1980
|
+
/* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
|
|
1981
|
+
EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
|
|
1982
|
+
/* @__PURE__ */ jsx18(Route, { path: "/*", element: /* @__PURE__ */ jsx18(PreAuthFlow, {}) })
|
|
1983
|
+
] });
|
|
1984
|
+
}
|
|
1985
|
+
return /* @__PURE__ */ jsxs13(Routes, { children: [
|
|
1986
|
+
/* @__PURE__ */ jsx18(Route, { path: "/", element: /* @__PURE__ */ jsx18(Login, {}) }),
|
|
1987
|
+
/* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
|
|
1988
|
+
/* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
|
|
1989
|
+
/* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
|
|
1990
|
+
EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
|
|
1991
|
+
/* @__PURE__ */ jsx18(Route, { path: "*", element: /* @__PURE__ */ jsx18(Navigate, { to: "/", replace: true }) })
|
|
1992
|
+
] });
|
|
1993
|
+
}
|
|
1994
|
+
return /* @__PURE__ */ jsx18(Fragment6, { children });
|
|
1995
|
+
}
|
|
1996
|
+
function FallbackPaywall() {
|
|
1997
|
+
return null;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/hooks/usePush.ts
|
|
2001
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useState as useState5 } from "react";
|
|
2002
|
+
import { useHook as useHook5 } from "@hook-sdk/sdk";
|
|
2003
|
+
function detectIosNeedsInstall() {
|
|
2004
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") return false;
|
|
2005
|
+
const ua = navigator.userAgent || "";
|
|
2006
|
+
const isIos = /iPhone|iPad|iPod/.test(ua);
|
|
2007
|
+
if (!isIos) return false;
|
|
2008
|
+
const mm = window.matchMedia?.("(display-mode: standalone)");
|
|
2009
|
+
const standalone = mm?.matches === true;
|
|
2010
|
+
const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
|
|
2011
|
+
return !(standalone || legacyStandalone);
|
|
2012
|
+
}
|
|
2013
|
+
function deriveState(push) {
|
|
2014
|
+
if (!push.isAvailable()) {
|
|
2015
|
+
if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
|
|
2016
|
+
return { kind: "unsupported" };
|
|
2017
|
+
}
|
|
2018
|
+
const status = push.status();
|
|
2019
|
+
if (status === "granted") return { kind: "subscribed" };
|
|
2020
|
+
if (status === "denied") return { kind: "denied" };
|
|
2021
|
+
if (status === "unsupported") {
|
|
2022
|
+
if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
|
|
2023
|
+
return { kind: "unsupported" };
|
|
2024
|
+
}
|
|
2025
|
+
return { kind: "prompt" };
|
|
2026
|
+
}
|
|
2027
|
+
function usePush() {
|
|
2028
|
+
const { push } = useHook5();
|
|
2029
|
+
const [state, setState] = useState5(() => deriveState(push));
|
|
2030
|
+
useEffect6(() => {
|
|
2031
|
+
setState(deriveState(push));
|
|
2032
|
+
}, [push]);
|
|
2033
|
+
const subscribe = useCallback4(async () => {
|
|
2034
|
+
try {
|
|
2035
|
+
await push.subscribe();
|
|
2036
|
+
setState({ kind: "subscribed" });
|
|
2037
|
+
} catch (e) {
|
|
2038
|
+
const code = e?.code ?? "push.unknown";
|
|
2039
|
+
const message = e?.message ?? "Push subscription failed";
|
|
2040
|
+
if (code === "push.permission_denied") setState({ kind: "denied" });
|
|
2041
|
+
else setState({ kind: "error", code, message });
|
|
2042
|
+
throw e;
|
|
2043
|
+
}
|
|
2044
|
+
}, [push]);
|
|
2045
|
+
const unsubscribe = useCallback4(async () => {
|
|
2046
|
+
try {
|
|
2047
|
+
await push.unsubscribe();
|
|
2048
|
+
setState({ kind: "prompt" });
|
|
2049
|
+
} catch (e) {
|
|
2050
|
+
setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
|
|
2051
|
+
throw e;
|
|
2052
|
+
}
|
|
2053
|
+
}, [push]);
|
|
2054
|
+
return { state, subscribe, unsubscribe };
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// src/components/PushPrompt.tsx
|
|
2058
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2059
|
+
function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
|
|
2060
|
+
const { state, subscribe } = usePush();
|
|
2061
|
+
if (state.kind === "subscribed") return null;
|
|
2062
|
+
if (state.kind === "ios_needs_install") {
|
|
2063
|
+
return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
|
|
2064
|
+
/* @__PURE__ */ jsx19("h3", { children: texts.iosInstallTitle }),
|
|
2065
|
+
/* @__PURE__ */ jsx19("p", { children: texts.iosInstallBody }),
|
|
2066
|
+
onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx19("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
|
|
2067
|
+
] });
|
|
2068
|
+
}
|
|
2069
|
+
if (state.kind === "denied") {
|
|
2070
|
+
return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
|
|
2071
|
+
/* @__PURE__ */ jsx19("h3", { children: texts.deniedTitle }),
|
|
2072
|
+
/* @__PURE__ */ jsx19("p", { children: texts.deniedBody })
|
|
2073
|
+
] });
|
|
2074
|
+
}
|
|
2075
|
+
if (state.kind === "unsupported") {
|
|
2076
|
+
return /* @__PURE__ */ jsx19("div", { className, role: "region", children: /* @__PURE__ */ jsx19("p", { children: texts.unsupportedBody }) });
|
|
2077
|
+
}
|
|
2078
|
+
if (state.kind === "error") {
|
|
2079
|
+
return /* @__PURE__ */ jsx19("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx19("p", { children: state.message }) });
|
|
2080
|
+
}
|
|
2081
|
+
return /* @__PURE__ */ jsxs14("div", { className, role: "region", children: [
|
|
2082
|
+
/* @__PURE__ */ jsx19(
|
|
2083
|
+
"button",
|
|
2084
|
+
{
|
|
2085
|
+
type: "button",
|
|
2086
|
+
onClick: async () => {
|
|
2087
|
+
try {
|
|
2088
|
+
await subscribe();
|
|
2089
|
+
onSubscribed?.();
|
|
2090
|
+
} catch {
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
children: texts.cta
|
|
2094
|
+
}
|
|
2095
|
+
),
|
|
2096
|
+
onDeclined && /* @__PURE__ */ jsx19("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
|
|
2097
|
+
] });
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/defaults/LoadingState.tsx
|
|
2101
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2102
|
+
function LoadingState({ message }) {
|
|
2103
|
+
return /* @__PURE__ */ jsx20("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx20("span", { children: message ?? "Carregando..." }) });
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
// src/defaults/EmptyState.tsx
|
|
2107
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2108
|
+
function EmptyState({ title, description, action }) {
|
|
2109
|
+
return /* @__PURE__ */ jsxs15("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
|
|
2110
|
+
/* @__PURE__ */ jsx21("h2", { style: { marginBottom: 8 }, children: title }),
|
|
2111
|
+
description && /* @__PURE__ */ jsx21("p", { style: { opacity: 0.7 }, children: description }),
|
|
2112
|
+
action && /* @__PURE__ */ jsx21("div", { style: { marginTop: 16 }, children: action })
|
|
2113
|
+
] });
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// src/hooks/useLoginForm.ts
|
|
2117
|
+
import { useCallback as useCallback5, useMemo as useMemo4, useState as useState6 } from "react";
|
|
2118
|
+
import { useHook as useHook6 } from "@hook-sdk/sdk";
|
|
2119
|
+
|
|
2120
|
+
// src/errors.ts
|
|
2121
|
+
import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
|
|
2122
|
+
function mapSdkError(err) {
|
|
2123
|
+
if (err instanceof SdkRateLimitError) {
|
|
2124
|
+
return {
|
|
2125
|
+
code: "rate_limited",
|
|
2126
|
+
message: `Aguarde ${err.retryAfter}s e tente novamente.`,
|
|
2127
|
+
retryAfter: err.retryAfter
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
if (err instanceof SdkAuthError) {
|
|
2131
|
+
const detail = err.detail;
|
|
2132
|
+
if (detail === "email_unverified") {
|
|
2133
|
+
return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
|
|
2134
|
+
}
|
|
2135
|
+
if (detail === "account_locked") {
|
|
2136
|
+
return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
|
|
2137
|
+
}
|
|
2138
|
+
return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
|
|
2139
|
+
}
|
|
2140
|
+
if (err instanceof SdkError && err.httpStatus === 0) {
|
|
2141
|
+
return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
|
|
2142
|
+
}
|
|
2143
|
+
if (err instanceof TypeError) {
|
|
2144
|
+
return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
|
|
2145
|
+
}
|
|
2146
|
+
return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/hooks/useLoginForm.ts
|
|
2150
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1589
2151
|
var MIN_PASSWORD = 8;
|
|
1590
2152
|
function useLoginForm() {
|
|
1591
|
-
const { auth } =
|
|
2153
|
+
const { auth } = useHook6();
|
|
1592
2154
|
const [email, setEmail] = useState6("");
|
|
1593
2155
|
const [password, setPassword] = useState6("");
|
|
1594
2156
|
const [submitting, setSubmitting] = useState6(false);
|
|
1595
2157
|
const [error, setError] = useState6(null);
|
|
1596
|
-
const emailError =
|
|
2158
|
+
const emailError = useMemo4(() => {
|
|
1597
2159
|
if (email.length === 0) return null;
|
|
1598
2160
|
if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
1599
2161
|
return null;
|
|
1600
2162
|
}, [email]);
|
|
1601
|
-
const passwordError =
|
|
2163
|
+
const passwordError = useMemo4(() => {
|
|
1602
2164
|
if (password.length === 0) return null;
|
|
1603
2165
|
if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
|
|
1604
2166
|
return null;
|
|
1605
2167
|
}, [password]);
|
|
1606
2168
|
const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
|
|
1607
|
-
const submit =
|
|
2169
|
+
const submit = useCallback5(async () => {
|
|
1608
2170
|
if (!canSubmit) return false;
|
|
1609
2171
|
setSubmitting(true);
|
|
1610
2172
|
setError(null);
|
|
@@ -1633,259 +2195,35 @@ function useLoginForm() {
|
|
|
1633
2195
|
};
|
|
1634
2196
|
}
|
|
1635
2197
|
|
|
1636
|
-
// src/internal/GoogleSignInButton.tsx
|
|
1637
|
-
import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1638
|
-
function GoogleSignInButton({
|
|
1639
|
-
onClick,
|
|
1640
|
-
testId = "oauth-google",
|
|
1641
|
-
label = "Continuar com Google"
|
|
1642
|
-
}) {
|
|
1643
|
-
return /* @__PURE__ */ jsxs12(
|
|
1644
|
-
"button",
|
|
1645
|
-
{
|
|
1646
|
-
"data-testid": testId,
|
|
1647
|
-
type: "button",
|
|
1648
|
-
onClick,
|
|
1649
|
-
style: {
|
|
1650
|
-
width: "100%",
|
|
1651
|
-
padding: "10px 12px",
|
|
1652
|
-
display: "flex",
|
|
1653
|
-
alignItems: "center",
|
|
1654
|
-
justifyContent: "center",
|
|
1655
|
-
gap: 10,
|
|
1656
|
-
background: "#fff",
|
|
1657
|
-
color: "#1f1f1f",
|
|
1658
|
-
border: "1px solid #dadce0",
|
|
1659
|
-
borderRadius: 8,
|
|
1660
|
-
cursor: "pointer",
|
|
1661
|
-
fontSize: 14,
|
|
1662
|
-
fontWeight: 500,
|
|
1663
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
1664
|
-
},
|
|
1665
|
-
children: [
|
|
1666
|
-
/* @__PURE__ */ jsx18(GoogleGlyph, {}),
|
|
1667
|
-
label
|
|
1668
|
-
]
|
|
1669
|
-
}
|
|
1670
|
-
);
|
|
1671
|
-
}
|
|
1672
|
-
function GoogleGlyph() {
|
|
1673
|
-
return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
|
|
1674
|
-
/* @__PURE__ */ jsx18(
|
|
1675
|
-
"path",
|
|
1676
|
-
{
|
|
1677
|
-
d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",
|
|
1678
|
-
fill: "#4285F4"
|
|
1679
|
-
}
|
|
1680
|
-
),
|
|
1681
|
-
/* @__PURE__ */ jsx18(
|
|
1682
|
-
"path",
|
|
1683
|
-
{
|
|
1684
|
-
d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z",
|
|
1685
|
-
fill: "#34A853"
|
|
1686
|
-
}
|
|
1687
|
-
),
|
|
1688
|
-
/* @__PURE__ */ jsx18(
|
|
1689
|
-
"path",
|
|
1690
|
-
{
|
|
1691
|
-
d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z",
|
|
1692
|
-
fill: "#FBBC05"
|
|
1693
|
-
}
|
|
1694
|
-
),
|
|
1695
|
-
/* @__PURE__ */ jsx18(
|
|
1696
|
-
"path",
|
|
1697
|
-
{
|
|
1698
|
-
d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z",
|
|
1699
|
-
fill: "#EA4335"
|
|
1700
|
-
}
|
|
1701
|
-
)
|
|
1702
|
-
] });
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
// src/internal/OAuthErrorBanner.tsx
|
|
1706
|
-
import { useEffect as useEffect6, useState as useState7 } from "react";
|
|
1707
|
-
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1708
|
-
var ERROR_MESSAGES = {
|
|
1709
|
-
invalid_state: "Sess\xE3o expirou, tente de novo.",
|
|
1710
|
-
access_denied: "Voc\xEA cancelou o login com Google.",
|
|
1711
|
-
provider_error: "O Google recusou a autentica\xE7\xE3o. Tente de novo em alguns segundos.",
|
|
1712
|
-
invalid_return_to: "Link inv\xE1lido. Tente entrar novamente."
|
|
1713
|
-
};
|
|
1714
|
-
function readErrorCode() {
|
|
1715
|
-
if (typeof window === "undefined") return null;
|
|
1716
|
-
const code = new URLSearchParams(window.location.search).get("oauth_error");
|
|
1717
|
-
if (!code) return null;
|
|
1718
|
-
return code;
|
|
1719
|
-
}
|
|
1720
|
-
function stripErrorFromUrl() {
|
|
1721
|
-
if (typeof window === "undefined") return;
|
|
1722
|
-
const url = new URL(window.location.href);
|
|
1723
|
-
url.searchParams.delete("oauth_error");
|
|
1724
|
-
window.history.replaceState({}, "", url.toString());
|
|
1725
|
-
}
|
|
1726
|
-
function OAuthErrorBanner() {
|
|
1727
|
-
const [code, setCode] = useState7(() => readErrorCode());
|
|
1728
|
-
useEffect6(() => {
|
|
1729
|
-
if (code !== null) stripErrorFromUrl();
|
|
1730
|
-
}, [code]);
|
|
1731
|
-
if (!code) return null;
|
|
1732
|
-
const message = ERROR_MESSAGES[code] ?? "N\xE3o conseguimos conectar ao Google. Tente de novo.";
|
|
1733
|
-
return /* @__PURE__ */ jsxs13(
|
|
1734
|
-
"div",
|
|
1735
|
-
{
|
|
1736
|
-
role: "alert",
|
|
1737
|
-
"data-testid": "oauth-error-banner",
|
|
1738
|
-
style: {
|
|
1739
|
-
padding: "10px 12px",
|
|
1740
|
-
marginBottom: 16,
|
|
1741
|
-
background: "#fce8e6",
|
|
1742
|
-
color: "#a50e0e",
|
|
1743
|
-
borderRadius: 8,
|
|
1744
|
-
fontSize: 14
|
|
1745
|
-
},
|
|
1746
|
-
children: [
|
|
1747
|
-
message,
|
|
1748
|
-
/* @__PURE__ */ jsx19(
|
|
1749
|
-
"button",
|
|
1750
|
-
{
|
|
1751
|
-
type: "button",
|
|
1752
|
-
onClick: () => setCode(null),
|
|
1753
|
-
"aria-label": "Fechar",
|
|
1754
|
-
style: {
|
|
1755
|
-
float: "right",
|
|
1756
|
-
background: "none",
|
|
1757
|
-
border: "none",
|
|
1758
|
-
color: "#a50e0e",
|
|
1759
|
-
cursor: "pointer",
|
|
1760
|
-
fontSize: 16,
|
|
1761
|
-
lineHeight: 1,
|
|
1762
|
-
padding: 0
|
|
1763
|
-
},
|
|
1764
|
-
children: "\xD7"
|
|
1765
|
-
}
|
|
1766
|
-
)
|
|
1767
|
-
]
|
|
1768
|
-
}
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
// src/defaults/DefaultLoginScreen.tsx
|
|
1773
|
-
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1774
|
-
function DefaultLoginScreen({ onNavigate }) {
|
|
1775
|
-
const { name } = useTemplateConfig();
|
|
1776
|
-
const f = useLoginForm();
|
|
1777
|
-
return /* @__PURE__ */ jsxs14("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
|
|
1778
|
-
/* @__PURE__ */ jsx20("h1", { style: { marginBottom: 8 }, children: name }),
|
|
1779
|
-
/* @__PURE__ */ jsx20("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Entre na sua conta" }),
|
|
1780
|
-
/* @__PURE__ */ jsx20(OAuthErrorBanner, {}),
|
|
1781
|
-
/* @__PURE__ */ jsx20(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "login-oauth-google" }),
|
|
1782
|
-
/* @__PURE__ */ jsxs14(
|
|
1783
|
-
"div",
|
|
1784
|
-
{
|
|
1785
|
-
"aria-hidden": "true",
|
|
1786
|
-
style: {
|
|
1787
|
-
display: "flex",
|
|
1788
|
-
alignItems: "center",
|
|
1789
|
-
gap: 8,
|
|
1790
|
-
margin: "16px 0",
|
|
1791
|
-
color: "rgba(0,0,0,0.45)",
|
|
1792
|
-
fontSize: 12
|
|
1793
|
-
},
|
|
1794
|
-
children: [
|
|
1795
|
-
/* @__PURE__ */ jsx20("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
|
|
1796
|
-
"ou",
|
|
1797
|
-
/* @__PURE__ */ jsx20("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
|
|
1798
|
-
]
|
|
1799
|
-
}
|
|
1800
|
-
),
|
|
1801
|
-
/* @__PURE__ */ jsxs14("form", { onSubmit: (e) => {
|
|
1802
|
-
e.preventDefault();
|
|
1803
|
-
void f.submit();
|
|
1804
|
-
}, children: [
|
|
1805
|
-
/* @__PURE__ */ jsxs14("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
1806
|
-
"E-mail",
|
|
1807
|
-
/* @__PURE__ */ jsx20(
|
|
1808
|
-
"input",
|
|
1809
|
-
{
|
|
1810
|
-
"data-testid": "login-email",
|
|
1811
|
-
type: "email",
|
|
1812
|
-
value: f.email,
|
|
1813
|
-
onChange: (e) => f.setEmail(e.target.value),
|
|
1814
|
-
style: { display: "block", width: "100%" }
|
|
1815
|
-
}
|
|
1816
|
-
),
|
|
1817
|
-
f.emailError && /* @__PURE__ */ jsx20("small", { style: { color: "#c00" }, children: f.emailError })
|
|
1818
|
-
] }),
|
|
1819
|
-
/* @__PURE__ */ jsxs14("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
1820
|
-
"Senha",
|
|
1821
|
-
/* @__PURE__ */ jsx20(
|
|
1822
|
-
"input",
|
|
1823
|
-
{
|
|
1824
|
-
"data-testid": "login-password",
|
|
1825
|
-
type: "password",
|
|
1826
|
-
value: f.password,
|
|
1827
|
-
onChange: (e) => f.setPassword(e.target.value),
|
|
1828
|
-
style: { display: "block", width: "100%" }
|
|
1829
|
-
}
|
|
1830
|
-
),
|
|
1831
|
-
f.passwordError && /* @__PURE__ */ jsx20("small", { style: { color: "#c00" }, children: f.passwordError })
|
|
1832
|
-
] }),
|
|
1833
|
-
f.error && /* @__PURE__ */ jsx20("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
|
|
1834
|
-
/* @__PURE__ */ jsx20(
|
|
1835
|
-
"button",
|
|
1836
|
-
{
|
|
1837
|
-
"data-testid": "login-submit",
|
|
1838
|
-
type: "submit",
|
|
1839
|
-
disabled: !f.canSubmit,
|
|
1840
|
-
style: {
|
|
1841
|
-
width: "100%",
|
|
1842
|
-
padding: 12,
|
|
1843
|
-
background: "var(--hook-color-primary)",
|
|
1844
|
-
color: "#fff",
|
|
1845
|
-
border: "none",
|
|
1846
|
-
borderRadius: 8,
|
|
1847
|
-
opacity: f.canSubmit ? 1 : 0.5
|
|
1848
|
-
},
|
|
1849
|
-
children: f.submitting ? "Entrando..." : "Entrar"
|
|
1850
|
-
}
|
|
1851
|
-
)
|
|
1852
|
-
] }),
|
|
1853
|
-
/* @__PURE__ */ jsxs14("div", { style: { marginTop: 16, display: "flex", justifyContent: "space-between" }, children: [
|
|
1854
|
-
/* @__PURE__ */ jsx20("button", { "data-testid": "login-goto-signup", type: "button", onClick: () => onNavigate("signup"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Criar conta" }),
|
|
1855
|
-
/* @__PURE__ */ jsx20("button", { "data-testid": "login-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Esqueci senha" })
|
|
1856
|
-
] })
|
|
1857
|
-
] });
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
2198
|
// src/hooks/useSignupForm.ts
|
|
1861
|
-
import { useCallback as
|
|
1862
|
-
import { useHook as
|
|
2199
|
+
import { useCallback as useCallback6, useMemo as useMemo5, useState as useState7 } from "react";
|
|
2200
|
+
import { useHook as useHook7 } from "@hook-sdk/sdk";
|
|
1863
2201
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1864
2202
|
var MIN_PASSWORD2 = 8;
|
|
1865
2203
|
function useSignupForm() {
|
|
1866
|
-
const { auth } =
|
|
1867
|
-
const [name, setName] =
|
|
1868
|
-
const [email, setEmail] =
|
|
1869
|
-
const [password, setPassword] =
|
|
1870
|
-
const [submitting, setSubmitting] =
|
|
1871
|
-
const [error, setError] =
|
|
1872
|
-
const nameError =
|
|
2204
|
+
const { auth } = useHook7();
|
|
2205
|
+
const [name, setName] = useState7("");
|
|
2206
|
+
const [email, setEmail] = useState7("");
|
|
2207
|
+
const [password, setPassword] = useState7("");
|
|
2208
|
+
const [submitting, setSubmitting] = useState7(false);
|
|
2209
|
+
const [error, setError] = useState7(null);
|
|
2210
|
+
const nameError = useMemo5(() => {
|
|
1873
2211
|
if (name.length === 0) return null;
|
|
1874
2212
|
if (name.trim().length < 2) return "Nome muito curto.";
|
|
1875
2213
|
return null;
|
|
1876
2214
|
}, [name]);
|
|
1877
|
-
const emailError =
|
|
2215
|
+
const emailError = useMemo5(() => {
|
|
1878
2216
|
if (email.length === 0) return null;
|
|
1879
2217
|
if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
1880
2218
|
return null;
|
|
1881
2219
|
}, [email]);
|
|
1882
|
-
const passwordError =
|
|
2220
|
+
const passwordError = useMemo5(() => {
|
|
1883
2221
|
if (password.length === 0) return null;
|
|
1884
2222
|
if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
|
|
1885
2223
|
return null;
|
|
1886
2224
|
}, [password]);
|
|
1887
2225
|
const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
|
|
1888
|
-
const submit =
|
|
2226
|
+
const submit = useCallback6(async () => {
|
|
1889
2227
|
if (!canSubmit) return false;
|
|
1890
2228
|
setSubmitting(true);
|
|
1891
2229
|
setError(null);
|
|
@@ -1904,1095 +2242,168 @@ function useSignupForm() {
|
|
|
1904
2242
|
setName,
|
|
1905
2243
|
nameError,
|
|
1906
2244
|
email,
|
|
1907
|
-
setEmail,
|
|
1908
|
-
emailError,
|
|
1909
|
-
password,
|
|
1910
|
-
setPassword,
|
|
1911
|
-
passwordError,
|
|
1912
|
-
submit,
|
|
1913
|
-
submitting,
|
|
1914
|
-
canSubmit,
|
|
1915
|
-
error,
|
|
1916
|
-
loginWithGoogle: () => auth.loginWithGoogle()
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
// src/defaults/DefaultSignupScreen.tsx
|
|
1921
|
-
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1922
|
-
function DefaultSignupScreen({ onNavigate }) {
|
|
1923
|
-
const { name } = useTemplateConfig();
|
|
1924
|
-
const f = useSignupForm();
|
|
1925
|
-
return /* @__PURE__ */ jsxs15("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
|
|
1926
|
-
/* @__PURE__ */ jsx21("h1", { style: { marginBottom: 8 }, children: name }),
|
|
1927
|
-
/* @__PURE__ */ jsx21("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Criar sua conta" }),
|
|
1928
|
-
/* @__PURE__ */ jsx21(OAuthErrorBanner, {}),
|
|
1929
|
-
/* @__PURE__ */ jsx21(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "signup-oauth-google" }),
|
|
1930
|
-
/* @__PURE__ */ jsxs15(
|
|
1931
|
-
"div",
|
|
1932
|
-
{
|
|
1933
|
-
"aria-hidden": "true",
|
|
1934
|
-
style: {
|
|
1935
|
-
display: "flex",
|
|
1936
|
-
alignItems: "center",
|
|
1937
|
-
gap: 8,
|
|
1938
|
-
margin: "16px 0",
|
|
1939
|
-
color: "rgba(0,0,0,0.45)",
|
|
1940
|
-
fontSize: 12
|
|
1941
|
-
},
|
|
1942
|
-
children: [
|
|
1943
|
-
/* @__PURE__ */ jsx21("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
|
|
1944
|
-
"ou",
|
|
1945
|
-
/* @__PURE__ */ jsx21("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
|
|
1946
|
-
]
|
|
1947
|
-
}
|
|
1948
|
-
),
|
|
1949
|
-
/* @__PURE__ */ jsxs15("form", { onSubmit: (e) => {
|
|
1950
|
-
e.preventDefault();
|
|
1951
|
-
void f.submit();
|
|
1952
|
-
}, children: [
|
|
1953
|
-
/* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
1954
|
-
"Nome",
|
|
1955
|
-
/* @__PURE__ */ jsx21("input", { "data-testid": "signup-name", value: f.name, onChange: (e) => f.setName(e.target.value), style: { display: "block", width: "100%" } }),
|
|
1956
|
-
f.nameError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.nameError })
|
|
1957
|
-
] }),
|
|
1958
|
-
/* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
1959
|
-
"E-mail",
|
|
1960
|
-
/* @__PURE__ */ jsx21("input", { "data-testid": "signup-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
|
|
1961
|
-
f.emailError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.emailError })
|
|
1962
|
-
] }),
|
|
1963
|
-
/* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
1964
|
-
"Senha",
|
|
1965
|
-
/* @__PURE__ */ jsx21("input", { "data-testid": "signup-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" } }),
|
|
1966
|
-
f.passwordError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.passwordError })
|
|
1967
|
-
] }),
|
|
1968
|
-
f.error && /* @__PURE__ */ jsx21("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
|
|
1969
|
-
/* @__PURE__ */ jsx21("button", { "data-testid": "signup-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Criando..." : "Criar conta" })
|
|
1970
|
-
] }),
|
|
1971
|
-
/* @__PURE__ */ jsx21("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ jsx21("button", { "data-testid": "signup-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "J\xE1 tem conta? Entre" }) })
|
|
1972
|
-
] });
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
// src/hooks/useForgotForm.ts
|
|
1976
|
-
import { useCallback as useCallback5, useMemo as useMemo5, useState as useState9 } from "react";
|
|
1977
|
-
import { useHook as useHook6 } from "@hook-sdk/sdk";
|
|
1978
|
-
var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1979
|
-
function useForgotForm() {
|
|
1980
|
-
const { auth } = useHook6();
|
|
1981
|
-
const [email, setEmail] = useState9("");
|
|
1982
|
-
const [submitting, setSubmitting] = useState9(false);
|
|
1983
|
-
const [sent, setSent] = useState9(false);
|
|
1984
|
-
const [error, setError] = useState9(null);
|
|
1985
|
-
const emailError = useMemo5(() => {
|
|
1986
|
-
if (email.length === 0) return null;
|
|
1987
|
-
if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
1988
|
-
return null;
|
|
1989
|
-
}, [email]);
|
|
1990
|
-
const canSubmit = email.length > 0 && emailError === null && !submitting;
|
|
1991
|
-
const submit = useCallback5(async () => {
|
|
1992
|
-
if (!canSubmit) return false;
|
|
1993
|
-
setSubmitting(true);
|
|
1994
|
-
setError(null);
|
|
1995
|
-
try {
|
|
1996
|
-
await auth.forgot({ email });
|
|
1997
|
-
setSent(true);
|
|
1998
|
-
return true;
|
|
1999
|
-
} catch (err) {
|
|
2000
|
-
setError(mapSdkError(err));
|
|
2001
|
-
return false;
|
|
2002
|
-
} finally {
|
|
2003
|
-
setSubmitting(false);
|
|
2004
|
-
}
|
|
2005
|
-
}, [auth, email, canSubmit]);
|
|
2006
|
-
return {
|
|
2007
|
-
email,
|
|
2008
|
-
setEmail,
|
|
2009
|
-
emailError,
|
|
2010
|
-
submit,
|
|
2011
|
-
submitting,
|
|
2012
|
-
canSubmit,
|
|
2013
|
-
sent,
|
|
2014
|
-
error
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
// src/defaults/DefaultForgotScreen.tsx
|
|
2019
|
-
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2020
|
-
function DefaultForgotScreen({ onNavigate }) {
|
|
2021
|
-
const { name } = useTemplateConfig();
|
|
2022
|
-
const f = useForgotForm();
|
|
2023
|
-
if (f.sent) {
|
|
2024
|
-
return /* @__PURE__ */ jsxs16("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
|
|
2025
|
-
/* @__PURE__ */ jsx22("h1", { children: "Verifique seu e-mail" }),
|
|
2026
|
-
/* @__PURE__ */ jsx22("p", { style: { opacity: 0.7 }, children: "Enviamos um link pra redefinir sua senha." }),
|
|
2027
|
-
/* @__PURE__ */ jsx22("button", { "data-testid": "forgot-back-login", type: "button", onClick: () => onNavigate("login"), children: "Voltar pro login" })
|
|
2028
|
-
] });
|
|
2029
|
-
}
|
|
2030
|
-
return /* @__PURE__ */ jsxs16("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
|
|
2031
|
-
/* @__PURE__ */ jsx22("h1", { style: { marginBottom: 8 }, children: name }),
|
|
2032
|
-
/* @__PURE__ */ jsx22("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Redefinir senha" }),
|
|
2033
|
-
/* @__PURE__ */ jsxs16("form", { onSubmit: (e) => {
|
|
2034
|
-
e.preventDefault();
|
|
2035
|
-
void f.submit();
|
|
2036
|
-
}, children: [
|
|
2037
|
-
/* @__PURE__ */ jsxs16("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
2038
|
-
"E-mail",
|
|
2039
|
-
/* @__PURE__ */ jsx22("input", { "data-testid": "forgot-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
|
|
2040
|
-
f.emailError && /* @__PURE__ */ jsx22("small", { style: { color: "#c00" }, children: f.emailError })
|
|
2041
|
-
] }),
|
|
2042
|
-
f.error && /* @__PURE__ */ jsx22("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
|
|
2043
|
-
/* @__PURE__ */ jsx22("button", { "data-testid": "forgot-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Enviando..." : "Enviar link" })
|
|
2044
|
-
] }),
|
|
2045
|
-
/* @__PURE__ */ jsx22("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ jsx22("button", { "data-testid": "forgot-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Voltar pro login" }) })
|
|
2046
|
-
] });
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
// src/hooks/useResetForm.ts
|
|
2050
|
-
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useState as useState10 } from "react";
|
|
2051
|
-
import { useHook as useHook7 } from "@hook-sdk/sdk";
|
|
2052
|
-
var MIN_PASSWORD3 = 12;
|
|
2053
|
-
function useResetForm() {
|
|
2054
|
-
const { auth } = useHook7();
|
|
2055
|
-
const [token, setToken] = useState10(null);
|
|
2056
|
-
const [password, setPassword] = useState10("");
|
|
2057
|
-
const [confirm, setConfirm] = useState10("");
|
|
2058
|
-
const [submitting, setSubmitting] = useState10(false);
|
|
2059
|
-
const [done, setDone] = useState10(false);
|
|
2060
|
-
const [error, setError] = useState10(null);
|
|
2061
|
-
useEffect7(() => {
|
|
2062
|
-
if (typeof window === "undefined") return;
|
|
2063
|
-
const params = new URLSearchParams(window.location.search);
|
|
2064
|
-
const t = params.get("token");
|
|
2065
|
-
setToken(t && t.length > 0 ? t : null);
|
|
2066
|
-
}, []);
|
|
2067
|
-
const passwordError = useMemo6(() => {
|
|
2068
|
-
if (password.length === 0) return null;
|
|
2069
|
-
if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
|
|
2070
|
-
return null;
|
|
2071
|
-
}, [password]);
|
|
2072
|
-
const confirmError = useMemo6(() => {
|
|
2073
|
-
if (confirm.length === 0) return null;
|
|
2074
|
-
if (confirm !== password) return "Senhas n\xE3o coincidem.";
|
|
2075
|
-
return null;
|
|
2076
|
-
}, [confirm, password]);
|
|
2077
|
-
const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
|
|
2078
|
-
const submit = useCallback6(async () => {
|
|
2079
|
-
if (!canSubmit || token === null) return;
|
|
2080
|
-
setSubmitting(true);
|
|
2081
|
-
setError(null);
|
|
2082
|
-
try {
|
|
2083
|
-
await auth.reset({ token, newPassword: password });
|
|
2084
|
-
setDone(true);
|
|
2085
|
-
if (typeof window !== "undefined") {
|
|
2086
|
-
const url = new URL(window.location.href);
|
|
2087
|
-
url.searchParams.delete("token");
|
|
2088
|
-
url.searchParams.delete("screen");
|
|
2089
|
-
window.history.replaceState({}, "", url.toString());
|
|
2090
|
-
}
|
|
2091
|
-
} catch (err) {
|
|
2092
|
-
setError(mapSdkError(err));
|
|
2093
|
-
} finally {
|
|
2094
|
-
setSubmitting(false);
|
|
2095
|
-
}
|
|
2096
|
-
}, [auth, token, password, canSubmit]);
|
|
2097
|
-
return {
|
|
2098
|
-
token,
|
|
2099
|
-
password,
|
|
2100
|
-
setPassword,
|
|
2101
|
-
passwordError,
|
|
2102
|
-
confirm,
|
|
2103
|
-
setConfirm,
|
|
2104
|
-
confirmError,
|
|
2105
|
-
submit,
|
|
2106
|
-
submitting,
|
|
2107
|
-
canSubmit,
|
|
2108
|
-
done,
|
|
2109
|
-
error
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
// src/defaults/DefaultResetScreen.tsx
|
|
2114
|
-
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2115
|
-
function DefaultResetScreen({ onNavigate }) {
|
|
2116
|
-
const { name } = useTemplateConfig();
|
|
2117
|
-
const f = useResetForm();
|
|
2118
|
-
if (f.done) {
|
|
2119
|
-
return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
|
|
2120
|
-
/* @__PURE__ */ jsx23("h1", { children: "Senha alterada" }),
|
|
2121
|
-
/* @__PURE__ */ jsx23("p", { style: { opacity: 0.7 }, children: "Agora \xE9 s\xF3 fazer login com a nova senha." }),
|
|
2122
|
-
/* @__PURE__ */ jsx23("button", { "data-testid": "reset-back-login", type: "button", onClick: () => onNavigate("login"), children: "Ir pro login" })
|
|
2123
|
-
] });
|
|
2124
|
-
}
|
|
2125
|
-
if (f.token === null) {
|
|
2126
|
-
return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
|
|
2127
|
-
/* @__PURE__ */ jsx23("h1", { children: "Link inv\xE1lido" }),
|
|
2128
|
-
/* @__PURE__ */ jsx23("p", { style: { opacity: 0.7 }, children: "Pe\xE7a um novo link de reset." }),
|
|
2129
|
-
/* @__PURE__ */ jsx23("button", { "data-testid": "reset-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), children: "Pedir novo link" })
|
|
2130
|
-
] });
|
|
2131
|
-
}
|
|
2132
|
-
return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
|
|
2133
|
-
/* @__PURE__ */ jsx23("h1", { style: { marginBottom: 8 }, children: name }),
|
|
2134
|
-
/* @__PURE__ */ jsx23("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Escolha uma nova senha" }),
|
|
2135
|
-
/* @__PURE__ */ jsxs17("form", { onSubmit: (e) => {
|
|
2136
|
-
e.preventDefault();
|
|
2137
|
-
void f.submit();
|
|
2138
|
-
}, children: [
|
|
2139
|
-
/* @__PURE__ */ jsxs17("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
2140
|
-
"Nova senha",
|
|
2141
|
-
/* @__PURE__ */ jsx23("input", { "data-testid": "reset-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" }, autoComplete: "new-password" }),
|
|
2142
|
-
f.passwordError && /* @__PURE__ */ jsx23("small", { style: { color: "#c00" }, children: f.passwordError })
|
|
2143
|
-
] }),
|
|
2144
|
-
/* @__PURE__ */ jsxs17("label", { style: { display: "block", marginBottom: 12 }, children: [
|
|
2145
|
-
"Confirmar senha",
|
|
2146
|
-
/* @__PURE__ */ jsx23("input", { "data-testid": "reset-confirm", type: "password", value: f.confirm, onChange: (e) => f.setConfirm(e.target.value), style: { display: "block", width: "100%" }, autoComplete: "new-password" }),
|
|
2147
|
-
f.confirmError && /* @__PURE__ */ jsx23("small", { style: { color: "#c00" }, children: f.confirmError })
|
|
2148
|
-
] }),
|
|
2149
|
-
f.error && /* @__PURE__ */ jsx23("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
|
|
2150
|
-
/* @__PURE__ */ jsx23("button", { "data-testid": "reset-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Alterando..." : "Alterar senha" })
|
|
2151
|
-
] })
|
|
2152
|
-
] });
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
// src/defaults/DefaultPaywall.tsx
|
|
2156
|
-
import { useMemo as useMemo7, useState as useState11 } from "react";
|
|
2157
|
-
|
|
2158
|
-
// src/hooks/usePlan.ts
|
|
2159
|
-
import { useHook as useHook8 } from "@hook-sdk/sdk";
|
|
2160
|
-
function usePlan() {
|
|
2161
|
-
const { plan } = useHook8();
|
|
2162
|
-
return plan;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
// src/utils/price.ts
|
|
2166
|
-
function formatBRL(cents) {
|
|
2167
|
-
if (cents === null || cents === void 0) return "";
|
|
2168
|
-
const reais = cents / 100;
|
|
2169
|
-
return new Intl.NumberFormat("pt-BR", {
|
|
2170
|
-
style: "currency",
|
|
2171
|
-
currency: "BRL"
|
|
2172
|
-
}).format(reais);
|
|
2173
|
-
}
|
|
2174
|
-
function monthlyFromYearly(yearlyCents) {
|
|
2175
|
-
if (yearlyCents === null || yearlyCents === void 0) return 0;
|
|
2176
|
-
return Math.round(yearlyCents / 12);
|
|
2177
|
-
}
|
|
2178
|
-
function dailyFromYearly(yearlyCents) {
|
|
2179
|
-
if (yearlyCents === null || yearlyCents === void 0) return 0;
|
|
2180
|
-
return Math.round(yearlyCents / 365);
|
|
2181
|
-
}
|
|
2182
|
-
function computeAnchorCents(baseCents, multiplier) {
|
|
2183
|
-
if (multiplier === null || multiplier === void 0) return null;
|
|
2184
|
-
if (!Number.isFinite(multiplier)) return null;
|
|
2185
|
-
if (multiplier <= 1) return null;
|
|
2186
|
-
return Math.round(baseCents * multiplier);
|
|
2187
|
-
}
|
|
2188
|
-
function discountPercent(anchorCents, realCents) {
|
|
2189
|
-
if (anchorCents <= realCents) return 0;
|
|
2190
|
-
return Math.floor((anchorCents - realCents) / anchorCents * 100);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
// src/defaults/DefaultPaywall.tsx
|
|
2194
|
-
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2195
|
-
function DefaultPaywall() {
|
|
2196
|
-
const config = useTemplateConfig();
|
|
2197
|
-
const plan = usePlan();
|
|
2198
|
-
const {
|
|
2199
|
-
checkout,
|
|
2200
|
-
opening,
|
|
2201
|
-
error,
|
|
2202
|
-
availableMethods,
|
|
2203
|
-
monthlyEquivalent,
|
|
2204
|
-
pixPending,
|
|
2205
|
-
dismissPix
|
|
2206
|
-
} = usePaywallState();
|
|
2207
|
-
const p = config.subscription.paywall_config;
|
|
2208
|
-
const paywallCfg = plan.data?.paywallConfig ?? {};
|
|
2209
|
-
const anchorMultiplier = paywallCfg.anchorMultiplier ?? p.anchorMultiplier;
|
|
2210
|
-
const anchorPriceCents = paywallCfg.anchorPriceCents ?? p.anchorPriceCents;
|
|
2211
|
-
const [cycle, setCycle] = useState11("MONTHLY");
|
|
2212
|
-
const [method, setMethod] = useState11("card");
|
|
2213
|
-
const [cpf, setCpf] = useState11("");
|
|
2214
|
-
const [cardNumber, setCardNumber] = useState11("");
|
|
2215
|
-
const [cardHolderName, setCardHolderName] = useState11("");
|
|
2216
|
-
const [cardExpiryMonth, setCardExpiryMonth] = useState11("");
|
|
2217
|
-
const [cardExpiryYear, setCardExpiryYear] = useState11("");
|
|
2218
|
-
const [cardCcv, setCardCcv] = useState11("");
|
|
2219
|
-
const [holderName, setHolderName] = useState11("");
|
|
2220
|
-
const [holderEmail, setHolderEmail] = useState11("");
|
|
2221
|
-
const [holderPostalCode, setHolderPostalCode] = useState11("");
|
|
2222
|
-
const [holderAddressNumber, setHolderAddressNumber] = useState11("");
|
|
2223
|
-
const [holderPhone, setHolderPhone] = useState11("");
|
|
2224
|
-
const trialDays = plan.data?.trialDays ?? 0;
|
|
2225
|
-
const monthlyCents = plan.data?.priceCents ?? 0;
|
|
2226
|
-
const yearlyCents = plan.data?.yearlyPriceCents ?? null;
|
|
2227
|
-
const activeCents = cycle === "YEARLY" && yearlyCents ? monthlyEquivalent("YEARLY") : monthlyCents;
|
|
2228
|
-
const cycleValueCents = cycle === "YEARLY" && yearlyCents ? yearlyCents : monthlyCents;
|
|
2229
|
-
const cycleLabel = cycle === "YEARLY" ? "por ano" : "por m\xEAs";
|
|
2230
|
-
const pct = useMemo7(() => {
|
|
2231
|
-
if (!yearlyCents || !monthlyCents) return 0;
|
|
2232
|
-
const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
|
|
2233
|
-
return Math.max(0, derived);
|
|
2234
|
-
}, [monthlyCents, yearlyCents]);
|
|
2235
|
-
const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
|
|
2236
|
-
const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
|
|
2237
|
-
const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
|
|
2238
|
-
const cpfDigits = cpf.replace(/\D/g, "");
|
|
2239
|
-
const cardFieldsFilled = cardNumber.replace(/\s/g, "").length >= 13 && cardHolderName.trim().length > 0 && cardExpiryMonth.length === 2 && cardExpiryYear.length >= 2 && cardCcv.length >= 3 && holderName.trim().length > 0 && /.+@.+\..+/.test(holderEmail) && holderPostalCode.replace(/\D/g, "").length === 8 && holderAddressNumber.trim().length > 0;
|
|
2240
|
-
const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
|
|
2241
|
-
const ctaLabel = useMemo7(() => {
|
|
2242
|
-
if (opening) return "Abrindo\u2026";
|
|
2243
|
-
if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
|
|
2244
|
-
return p.cta ?? "Assinar agora";
|
|
2245
|
-
}, [opening, trialDays, p.cta]);
|
|
2246
|
-
const footer = useMemo7(() => {
|
|
2247
|
-
if (trialDays > 0) {
|
|
2248
|
-
return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
|
|
2249
|
-
}
|
|
2250
|
-
return "Cobran\xE7a imediata. Cancele quando quiser.";
|
|
2251
|
-
}, [trialDays]);
|
|
2252
|
-
const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
|
|
2253
|
-
const phoneDigits = holderPhone.replace(/\D/g, "");
|
|
2254
|
-
const postalDigits = holderPostalCode.replace(/\D/g, "");
|
|
2255
|
-
const submit = () => {
|
|
2256
|
-
if (method === "card") {
|
|
2257
|
-
void checkout({
|
|
2258
|
-
cpf: cpfDigits,
|
|
2259
|
-
cycle,
|
|
2260
|
-
method: "card",
|
|
2261
|
-
card: {
|
|
2262
|
-
number: cardNumber.replace(/\s/g, ""),
|
|
2263
|
-
holderName: cardHolderName.trim(),
|
|
2264
|
-
expiryMonth: cardExpiryMonth,
|
|
2265
|
-
expiryYear: yearSuffix,
|
|
2266
|
-
ccv: cardCcv
|
|
2267
|
-
},
|
|
2268
|
-
holderInfo: {
|
|
2269
|
-
name: holderName.trim(),
|
|
2270
|
-
email: holderEmail.trim(),
|
|
2271
|
-
cpfCnpj: cpfDigits,
|
|
2272
|
-
postalCode: postalDigits,
|
|
2273
|
-
addressNumber: holderAddressNumber.trim(),
|
|
2274
|
-
...phoneDigits ? { phone: phoneDigits } : {}
|
|
2275
|
-
}
|
|
2276
|
-
});
|
|
2277
|
-
return;
|
|
2278
|
-
}
|
|
2279
|
-
void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
|
|
2280
|
-
};
|
|
2281
|
-
const inputStyle = {
|
|
2282
|
-
width: "100%",
|
|
2283
|
-
padding: 10,
|
|
2284
|
-
fontSize: 14,
|
|
2285
|
-
borderRadius: 8,
|
|
2286
|
-
border: "1px solid #ccc",
|
|
2287
|
-
boxSizing: "border-box"
|
|
2288
|
-
};
|
|
2289
|
-
const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
|
|
2290
|
-
const fieldGroup = { marginBottom: 10, textAlign: "left" };
|
|
2291
|
-
return /* @__PURE__ */ jsxs18("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
|
|
2292
|
-
/* @__PURE__ */ jsx24("h1", { style: { marginBottom: 8 }, children: p.title }),
|
|
2293
|
-
p.subtitle && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
|
|
2294
|
-
/* @__PURE__ */ jsxs18(
|
|
2295
|
-
"div",
|
|
2296
|
-
{
|
|
2297
|
-
role: "group",
|
|
2298
|
-
"aria-label": "Per\xEDodo de cobran\xE7a",
|
|
2299
|
-
style: { display: "flex", gap: 8, marginBottom: 16 },
|
|
2300
|
-
children: [
|
|
2301
|
-
/* @__PURE__ */ jsx24(
|
|
2302
|
-
"button",
|
|
2303
|
-
{
|
|
2304
|
-
type: "button",
|
|
2305
|
-
"aria-pressed": cycle === "MONTHLY",
|
|
2306
|
-
onClick: () => setCycle("MONTHLY"),
|
|
2307
|
-
style: {
|
|
2308
|
-
flex: 1,
|
|
2309
|
-
padding: 12,
|
|
2310
|
-
borderRadius: 8,
|
|
2311
|
-
border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
|
|
2312
|
-
background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
|
|
2313
|
-
cursor: "pointer"
|
|
2314
|
-
},
|
|
2315
|
-
children: "Mensal"
|
|
2316
|
-
}
|
|
2317
|
-
),
|
|
2318
|
-
/* @__PURE__ */ jsxs18(
|
|
2319
|
-
"button",
|
|
2320
|
-
{
|
|
2321
|
-
type: "button",
|
|
2322
|
-
"aria-pressed": cycle === "YEARLY",
|
|
2323
|
-
onClick: () => setCycle("YEARLY"),
|
|
2324
|
-
disabled: !yearlyCents,
|
|
2325
|
-
style: {
|
|
2326
|
-
flex: 1,
|
|
2327
|
-
padding: 12,
|
|
2328
|
-
borderRadius: 8,
|
|
2329
|
-
border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
|
|
2330
|
-
background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
|
|
2331
|
-
cursor: yearlyCents ? "pointer" : "not-allowed",
|
|
2332
|
-
opacity: yearlyCents ? 1 : 0.5
|
|
2333
|
-
},
|
|
2334
|
-
children: [
|
|
2335
|
-
"Anual",
|
|
2336
|
-
pct > 0 ? ` \u2212${pct}%` : ""
|
|
2337
|
-
]
|
|
2338
|
-
}
|
|
2339
|
-
)
|
|
2340
|
-
]
|
|
2341
|
-
}
|
|
2342
|
-
),
|
|
2343
|
-
/* @__PURE__ */ jsxs18("div", { style: { marginBottom: 8 }, children: [
|
|
2344
|
-
/* @__PURE__ */ jsxs18("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
|
|
2345
|
-
formatBRL(activeCents),
|
|
2346
|
-
/* @__PURE__ */ jsx24("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
|
|
2347
|
-
] }),
|
|
2348
|
-
anchorCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
|
|
2349
|
-
/* @__PURE__ */ jsx24("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
|
|
2350
|
-
anchorDiscount > 0 && /* @__PURE__ */ jsxs18("span", { style: { marginLeft: 6 }, children: [
|
|
2351
|
-
"\u2212",
|
|
2352
|
-
anchorDiscount,
|
|
2353
|
-
"%"
|
|
2354
|
-
] })
|
|
2355
|
-
] }),
|
|
2356
|
-
cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
|
|
2357
|
-
"Cobrado ",
|
|
2358
|
-
formatBRL(yearlyCents),
|
|
2359
|
-
" por ano"
|
|
2360
|
-
] })
|
|
2361
|
-
] }),
|
|
2362
|
-
/* @__PURE__ */ jsx24("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", margin: "24px 0" }, children: p.benefits.map((b) => /* @__PURE__ */ jsxs18("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
|
|
2363
|
-
/* @__PURE__ */ jsx24("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
|
|
2364
|
-
/* @__PURE__ */ jsx24("span", { children: b })
|
|
2365
|
-
] }, b)) }),
|
|
2366
|
-
/* @__PURE__ */ jsx24("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ jsx24(
|
|
2367
|
-
"button",
|
|
2368
|
-
{
|
|
2369
|
-
type: "button",
|
|
2370
|
-
role: "tab",
|
|
2371
|
-
"aria-selected": method === m,
|
|
2372
|
-
onClick: () => setMethod(m),
|
|
2373
|
-
style: {
|
|
2374
|
-
flex: 1,
|
|
2375
|
-
padding: 10,
|
|
2376
|
-
borderRadius: 8,
|
|
2377
|
-
border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
|
|
2378
|
-
background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
|
|
2379
|
-
cursor: "pointer"
|
|
2380
|
-
},
|
|
2381
|
-
children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
|
|
2382
|
-
},
|
|
2383
|
-
m
|
|
2384
|
-
)) }),
|
|
2385
|
-
method === "card" && /* @__PURE__ */ jsxs18(
|
|
2386
|
-
"fieldset",
|
|
2387
|
-
{
|
|
2388
|
-
"data-testid": "paywall-card-form",
|
|
2389
|
-
style: { border: "none", padding: 0, marginBottom: 16 },
|
|
2390
|
-
children: [
|
|
2391
|
-
/* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
|
|
2392
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2393
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
|
|
2394
|
-
/* @__PURE__ */ jsx24(
|
|
2395
|
-
"input",
|
|
2396
|
-
{
|
|
2397
|
-
id: "pw-card-number",
|
|
2398
|
-
"data-testid": "pw-card-number",
|
|
2399
|
-
type: "text",
|
|
2400
|
-
inputMode: "numeric",
|
|
2401
|
-
autoComplete: "cc-number",
|
|
2402
|
-
placeholder: "0000 0000 0000 0000",
|
|
2403
|
-
value: cardNumber,
|
|
2404
|
-
onChange: (e) => setCardNumber(e.target.value),
|
|
2405
|
-
style: inputStyle
|
|
2406
|
-
}
|
|
2407
|
-
)
|
|
2408
|
-
] }),
|
|
2409
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2410
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
|
|
2411
|
-
/* @__PURE__ */ jsx24(
|
|
2412
|
-
"input",
|
|
2413
|
-
{
|
|
2414
|
-
id: "pw-card-holder",
|
|
2415
|
-
"data-testid": "pw-card-holder",
|
|
2416
|
-
type: "text",
|
|
2417
|
-
autoComplete: "cc-name",
|
|
2418
|
-
placeholder: "NOME SOBRENOME",
|
|
2419
|
-
value: cardHolderName,
|
|
2420
|
-
onChange: (e) => setCardHolderName(e.target.value),
|
|
2421
|
-
style: inputStyle
|
|
2422
|
-
}
|
|
2423
|
-
)
|
|
2424
|
-
] }),
|
|
2425
|
-
/* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
|
|
2426
|
-
/* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
|
|
2427
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
|
|
2428
|
-
/* @__PURE__ */ jsx24(
|
|
2429
|
-
"input",
|
|
2430
|
-
{
|
|
2431
|
-
id: "pw-card-exp-m",
|
|
2432
|
-
"data-testid": "pw-card-exp-m",
|
|
2433
|
-
type: "text",
|
|
2434
|
-
inputMode: "numeric",
|
|
2435
|
-
autoComplete: "cc-exp-month",
|
|
2436
|
-
placeholder: "MM",
|
|
2437
|
-
maxLength: 2,
|
|
2438
|
-
value: cardExpiryMonth,
|
|
2439
|
-
onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
|
|
2440
|
-
style: inputStyle
|
|
2441
|
-
}
|
|
2442
|
-
)
|
|
2443
|
-
] }),
|
|
2444
|
-
/* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
|
|
2445
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
|
|
2446
|
-
/* @__PURE__ */ jsx24(
|
|
2447
|
-
"input",
|
|
2448
|
-
{
|
|
2449
|
-
id: "pw-card-exp-y",
|
|
2450
|
-
"data-testid": "pw-card-exp-y",
|
|
2451
|
-
type: "text",
|
|
2452
|
-
inputMode: "numeric",
|
|
2453
|
-
autoComplete: "cc-exp-year",
|
|
2454
|
-
placeholder: "AA",
|
|
2455
|
-
maxLength: 4,
|
|
2456
|
-
value: cardExpiryYear,
|
|
2457
|
-
onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
|
|
2458
|
-
style: inputStyle
|
|
2459
|
-
}
|
|
2460
|
-
)
|
|
2461
|
-
] }),
|
|
2462
|
-
/* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
|
|
2463
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
|
|
2464
|
-
/* @__PURE__ */ jsx24(
|
|
2465
|
-
"input",
|
|
2466
|
-
{
|
|
2467
|
-
id: "pw-card-cvv",
|
|
2468
|
-
"data-testid": "pw-card-cvv",
|
|
2469
|
-
type: "text",
|
|
2470
|
-
inputMode: "numeric",
|
|
2471
|
-
autoComplete: "cc-csc",
|
|
2472
|
-
placeholder: "123",
|
|
2473
|
-
maxLength: 4,
|
|
2474
|
-
value: cardCcv,
|
|
2475
|
-
onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
|
|
2476
|
-
style: inputStyle
|
|
2477
|
-
}
|
|
2478
|
-
)
|
|
2479
|
-
] })
|
|
2480
|
-
] }),
|
|
2481
|
-
/* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
|
|
2482
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2483
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
|
|
2484
|
-
/* @__PURE__ */ jsx24(
|
|
2485
|
-
"input",
|
|
2486
|
-
{
|
|
2487
|
-
id: "pw-holder-name",
|
|
2488
|
-
"data-testid": "pw-holder-name",
|
|
2489
|
-
type: "text",
|
|
2490
|
-
autoComplete: "name",
|
|
2491
|
-
placeholder: "Nome Sobrenome",
|
|
2492
|
-
value: holderName,
|
|
2493
|
-
onChange: (e) => setHolderName(e.target.value),
|
|
2494
|
-
style: inputStyle
|
|
2495
|
-
}
|
|
2496
|
-
)
|
|
2497
|
-
] }),
|
|
2498
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2499
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
|
|
2500
|
-
/* @__PURE__ */ jsx24(
|
|
2501
|
-
"input",
|
|
2502
|
-
{
|
|
2503
|
-
id: "pw-holder-email",
|
|
2504
|
-
"data-testid": "pw-holder-email",
|
|
2505
|
-
type: "email",
|
|
2506
|
-
autoComplete: "email",
|
|
2507
|
-
placeholder: "voce@email.com",
|
|
2508
|
-
value: holderEmail,
|
|
2509
|
-
onChange: (e) => setHolderEmail(e.target.value),
|
|
2510
|
-
style: inputStyle
|
|
2511
|
-
}
|
|
2512
|
-
)
|
|
2513
|
-
] }),
|
|
2514
|
-
/* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
|
|
2515
|
-
/* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
|
|
2516
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
|
|
2517
|
-
/* @__PURE__ */ jsx24(
|
|
2518
|
-
"input",
|
|
2519
|
-
{
|
|
2520
|
-
id: "pw-holder-cep",
|
|
2521
|
-
"data-testid": "pw-holder-cep",
|
|
2522
|
-
type: "text",
|
|
2523
|
-
inputMode: "numeric",
|
|
2524
|
-
autoComplete: "postal-code",
|
|
2525
|
-
placeholder: "00000-000",
|
|
2526
|
-
value: holderPostalCode,
|
|
2527
|
-
onChange: (e) => setHolderPostalCode(e.target.value),
|
|
2528
|
-
style: inputStyle
|
|
2529
|
-
}
|
|
2530
|
-
)
|
|
2531
|
-
] }),
|
|
2532
|
-
/* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
|
|
2533
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
|
|
2534
|
-
/* @__PURE__ */ jsx24(
|
|
2535
|
-
"input",
|
|
2536
|
-
{
|
|
2537
|
-
id: "pw-holder-addr-n",
|
|
2538
|
-
"data-testid": "pw-holder-addr-n",
|
|
2539
|
-
type: "text",
|
|
2540
|
-
inputMode: "numeric",
|
|
2541
|
-
placeholder: "123",
|
|
2542
|
-
value: holderAddressNumber,
|
|
2543
|
-
onChange: (e) => setHolderAddressNumber(e.target.value),
|
|
2544
|
-
style: inputStyle
|
|
2545
|
-
}
|
|
2546
|
-
)
|
|
2547
|
-
] })
|
|
2548
|
-
] }),
|
|
2549
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2550
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
|
|
2551
|
-
/* @__PURE__ */ jsx24(
|
|
2552
|
-
"input",
|
|
2553
|
-
{
|
|
2554
|
-
id: "pw-holder-phone",
|
|
2555
|
-
"data-testid": "pw-holder-phone",
|
|
2556
|
-
type: "tel",
|
|
2557
|
-
inputMode: "tel",
|
|
2558
|
-
autoComplete: "tel",
|
|
2559
|
-
placeholder: "(11) 99999-9999",
|
|
2560
|
-
value: holderPhone,
|
|
2561
|
-
onChange: (e) => setHolderPhone(e.target.value),
|
|
2562
|
-
style: inputStyle
|
|
2563
|
-
}
|
|
2564
|
-
)
|
|
2565
|
-
] })
|
|
2566
|
-
]
|
|
2567
|
-
}
|
|
2568
|
-
),
|
|
2569
|
-
/* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
|
|
2570
|
-
/* @__PURE__ */ jsx24("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
|
|
2571
|
-
/* @__PURE__ */ jsx24(
|
|
2572
|
-
"input",
|
|
2573
|
-
{
|
|
2574
|
-
id: "pw-cpf",
|
|
2575
|
-
"data-testid": "paywall-cpf",
|
|
2576
|
-
type: "text",
|
|
2577
|
-
inputMode: "numeric",
|
|
2578
|
-
placeholder: "000.000.000-00",
|
|
2579
|
-
value: cpf,
|
|
2580
|
-
onChange: (e) => setCpf(e.target.value),
|
|
2581
|
-
style: inputStyle
|
|
2582
|
-
}
|
|
2583
|
-
)
|
|
2584
|
-
] }),
|
|
2585
|
-
error && /* @__PURE__ */ jsx24("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: error.message }),
|
|
2586
|
-
method === "pix-auto" && /* @__PURE__ */ jsxs18(
|
|
2587
|
-
"div",
|
|
2588
|
-
{
|
|
2589
|
-
"data-testid": "paywall-pix-callout",
|
|
2590
|
-
style: {
|
|
2591
|
-
background: "rgba(0,0,0,0.04)",
|
|
2592
|
-
borderLeft: "3px solid var(--hook-color-primary)",
|
|
2593
|
-
padding: "10px 12px",
|
|
2594
|
-
marginBottom: 12,
|
|
2595
|
-
borderRadius: 4,
|
|
2596
|
-
fontSize: 12,
|
|
2597
|
-
lineHeight: 1.45,
|
|
2598
|
-
textAlign: "left"
|
|
2599
|
-
},
|
|
2600
|
-
children: [
|
|
2601
|
-
/* @__PURE__ */ jsx24("strong", { children: "Como funciona:" }),
|
|
2602
|
-
" no app do seu banco vai aparecer uma cobran\xE7a de",
|
|
2603
|
-
" ",
|
|
2604
|
-
/* @__PURE__ */ jsx24("strong", { children: "R$ 0,01" }),
|
|
2605
|
-
" \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
|
|
2606
|
-
trialDays > 0 ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2607
|
-
" ",
|
|
2608
|
-
"Depois, ",
|
|
2609
|
-
/* @__PURE__ */ jsxs18("strong", { children: [
|
|
2610
|
-
trialDays,
|
|
2611
|
-
" dias gr\xE1tis"
|
|
2612
|
-
] }),
|
|
2613
|
-
"; a cobran\xE7a de",
|
|
2614
|
-
" ",
|
|
2615
|
-
/* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
|
|
2616
|
-
" ",
|
|
2617
|
-
cycleLabel,
|
|
2618
|
-
" s\xF3 come\xE7a depois."
|
|
2619
|
-
] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2620
|
-
" ",
|
|
2621
|
-
"A cobran\xE7a de ",
|
|
2622
|
-
/* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
|
|
2623
|
-
" ",
|
|
2624
|
-
cycleLabel,
|
|
2625
|
-
" vem em seguida."
|
|
2626
|
-
] })
|
|
2627
|
-
]
|
|
2628
|
-
}
|
|
2629
|
-
),
|
|
2630
|
-
/* @__PURE__ */ jsx24(
|
|
2631
|
-
"button",
|
|
2632
|
-
{
|
|
2633
|
-
"data-testid": "paywall-cta",
|
|
2634
|
-
type: "button",
|
|
2635
|
-
onClick: submit,
|
|
2636
|
-
disabled: !canCheckout,
|
|
2637
|
-
style: {
|
|
2638
|
-
width: "100%",
|
|
2639
|
-
padding: 14,
|
|
2640
|
-
background: "var(--hook-color-primary)",
|
|
2641
|
-
color: "#fff",
|
|
2642
|
-
border: "none",
|
|
2643
|
-
borderRadius: 8,
|
|
2644
|
-
opacity: canCheckout ? 1 : 0.5,
|
|
2645
|
-
fontSize: 16,
|
|
2646
|
-
fontWeight: 600,
|
|
2647
|
-
cursor: canCheckout ? "pointer" : "not-allowed"
|
|
2648
|
-
},
|
|
2649
|
-
children: ctaLabel
|
|
2650
|
-
}
|
|
2651
|
-
),
|
|
2652
|
-
/* @__PURE__ */ jsx24("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
|
|
2653
|
-
p.priceHint && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
|
|
2654
|
-
p.footerNote && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
|
|
2655
|
-
pixPending && /* @__PURE__ */ jsx24(
|
|
2656
|
-
"div",
|
|
2657
|
-
{
|
|
2658
|
-
"data-testid": "paywall-pix-modal",
|
|
2659
|
-
role: "dialog",
|
|
2660
|
-
"aria-label": "Pagamento Pix pendente",
|
|
2661
|
-
style: {
|
|
2662
|
-
position: "fixed",
|
|
2663
|
-
inset: 0,
|
|
2664
|
-
background: "rgba(0,0,0,0.6)",
|
|
2665
|
-
display: "flex",
|
|
2666
|
-
alignItems: "center",
|
|
2667
|
-
justifyContent: "center",
|
|
2668
|
-
padding: 24,
|
|
2669
|
-
zIndex: 1e3
|
|
2670
|
-
},
|
|
2671
|
-
children: /* @__PURE__ */ jsxs18("div", { style: {
|
|
2672
|
-
background: "#fff",
|
|
2673
|
-
borderRadius: 12,
|
|
2674
|
-
padding: 24,
|
|
2675
|
-
maxWidth: 360,
|
|
2676
|
-
width: "100%",
|
|
2677
|
-
textAlign: "center"
|
|
2678
|
-
}, children: [
|
|
2679
|
-
pixPending.paid ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2680
|
-
/* @__PURE__ */ jsx24("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
|
|
2681
|
-
/* @__PURE__ */ jsx24("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
|
|
2682
|
-
] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2683
|
-
/* @__PURE__ */ jsx24("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
|
|
2684
|
-
/* @__PURE__ */ jsx24("p", { style: { fontSize: 13, opacity: 0.75 }, children: "Escaneie o QR Code no app do seu banco pra autorizar o d\xE9bito recorrente." }),
|
|
2685
|
-
/* @__PURE__ */ jsxs18(
|
|
2686
|
-
"p",
|
|
2687
|
-
{
|
|
2688
|
-
"data-testid": "pix-modal-explain",
|
|
2689
|
-
style: { fontSize: 12, opacity: 0.65, marginTop: 8, lineHeight: 1.4 },
|
|
2690
|
-
children: [
|
|
2691
|
-
"Seu banco vai mostrar uma cobran\xE7a de ",
|
|
2692
|
-
/* @__PURE__ */ jsx24("strong", { children: "R$ 0,01" }),
|
|
2693
|
-
" \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
|
|
2694
|
-
trialDays > 0 ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2695
|
-
" ",
|
|
2696
|
-
"A cobran\xE7a de ",
|
|
2697
|
-
/* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
|
|
2698
|
-
" ",
|
|
2699
|
-
cycleLabel,
|
|
2700
|
-
" come\xE7a depois dos ",
|
|
2701
|
-
/* @__PURE__ */ jsxs18("strong", { children: [
|
|
2702
|
-
trialDays,
|
|
2703
|
-
" dias gr\xE1tis"
|
|
2704
|
-
] }),
|
|
2705
|
-
", automaticamente."
|
|
2706
|
-
] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
2707
|
-
" ",
|
|
2708
|
-
"A cobran\xE7a de ",
|
|
2709
|
-
/* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
|
|
2710
|
-
" ",
|
|
2711
|
-
cycleLabel,
|
|
2712
|
-
" vem logo em seguida."
|
|
2713
|
-
] })
|
|
2714
|
-
]
|
|
2715
|
-
}
|
|
2716
|
-
),
|
|
2717
|
-
pixPending.qrCodeBase64 && /* @__PURE__ */ jsx24(
|
|
2718
|
-
"img",
|
|
2719
|
-
{
|
|
2720
|
-
"data-testid": "pix-qr-image",
|
|
2721
|
-
alt: "QR Code Pix",
|
|
2722
|
-
src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
|
|
2723
|
-
style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
|
|
2724
|
-
}
|
|
2725
|
-
),
|
|
2726
|
-
pixPending.qrCodePayload && /* @__PURE__ */ jsx24(
|
|
2727
|
-
"textarea",
|
|
2728
|
-
{
|
|
2729
|
-
"data-testid": "pix-qr-payload",
|
|
2730
|
-
readOnly: true,
|
|
2731
|
-
value: pixPending.qrCodePayload,
|
|
2732
|
-
style: {
|
|
2733
|
-
width: "100%",
|
|
2734
|
-
minHeight: 72,
|
|
2735
|
-
padding: 8,
|
|
2736
|
-
fontSize: 11,
|
|
2737
|
-
fontFamily: "monospace",
|
|
2738
|
-
borderRadius: 6,
|
|
2739
|
-
border: "1px solid #ccc",
|
|
2740
|
-
resize: "none"
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
),
|
|
2744
|
-
/* @__PURE__ */ jsx24("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
|
|
2745
|
-
] }),
|
|
2746
|
-
/* @__PURE__ */ jsx24(
|
|
2747
|
-
"button",
|
|
2748
|
-
{
|
|
2749
|
-
type: "button",
|
|
2750
|
-
onClick: dismissPix,
|
|
2751
|
-
style: {
|
|
2752
|
-
marginTop: 16,
|
|
2753
|
-
padding: "8px 16px",
|
|
2754
|
-
border: "1px solid #ccc",
|
|
2755
|
-
borderRadius: 6,
|
|
2756
|
-
background: "white",
|
|
2757
|
-
cursor: "pointer"
|
|
2758
|
-
},
|
|
2759
|
-
children: "Fechar"
|
|
2760
|
-
}
|
|
2761
|
-
)
|
|
2762
|
-
] })
|
|
2763
|
-
}
|
|
2764
|
-
)
|
|
2765
|
-
] });
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
// src/AppRoot.tsx
|
|
2769
|
-
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2770
|
-
var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
|
|
2771
|
-
function PaymentReturnHandler({ children }) {
|
|
2772
|
-
const { subscription } = useHook9();
|
|
2773
|
-
const subRef = useRef3(subscription);
|
|
2774
|
-
subRef.current = subscription;
|
|
2775
|
-
const runIdRef = useRef3(0);
|
|
2776
|
-
const [state, setState] = useState12("idle");
|
|
2777
|
-
const runPoll = useCallback7(() => {
|
|
2778
|
-
const runId = ++runIdRef.current;
|
|
2779
|
-
setState("confirming");
|
|
2780
|
-
let attempts = 0;
|
|
2781
|
-
const tick = async () => {
|
|
2782
|
-
if (runIdRef.current !== runId) return;
|
|
2783
|
-
attempts++;
|
|
2784
|
-
try {
|
|
2785
|
-
await subRef.current.refresh();
|
|
2786
|
-
} catch {
|
|
2787
|
-
}
|
|
2788
|
-
if (runIdRef.current !== runId) return;
|
|
2789
|
-
const status = subRef.current.status();
|
|
2790
|
-
if (status === "active" || status === "trialing") {
|
|
2791
|
-
const cleanUrl = new URL(window.location.href);
|
|
2792
|
-
cleanUrl.searchParams.delete("paymentReturn");
|
|
2793
|
-
window.history.replaceState({}, "", cleanUrl.toString());
|
|
2794
|
-
setState("idle");
|
|
2795
|
-
return;
|
|
2796
|
-
}
|
|
2797
|
-
const delay = BACKOFF_MS[attempts - 1];
|
|
2798
|
-
if (delay === void 0) {
|
|
2799
|
-
setState("waiting");
|
|
2800
|
-
return;
|
|
2801
|
-
}
|
|
2802
|
-
setTimeout(tick, delay);
|
|
2803
|
-
};
|
|
2804
|
-
void tick();
|
|
2805
|
-
}, []);
|
|
2806
|
-
useEffect8(() => {
|
|
2807
|
-
if (typeof window === "undefined") return;
|
|
2808
|
-
const url = new URL(window.location.href);
|
|
2809
|
-
if (url.searchParams.get("paymentReturn") !== "1") return;
|
|
2810
|
-
runPoll();
|
|
2811
|
-
return () => {
|
|
2812
|
-
runIdRef.current++;
|
|
2813
|
-
};
|
|
2814
|
-
}, [runPoll]);
|
|
2815
|
-
if (state === "confirming") {
|
|
2816
|
-
return /* @__PURE__ */ jsx25(
|
|
2817
|
-
"div",
|
|
2818
|
-
{
|
|
2819
|
-
role: "status",
|
|
2820
|
-
"aria-live": "polite",
|
|
2821
|
-
style: overlayStyle2,
|
|
2822
|
-
children: "Confirmando pagamento\u2026"
|
|
2823
|
-
}
|
|
2824
|
-
);
|
|
2825
|
-
}
|
|
2826
|
-
if (state === "waiting") {
|
|
2827
|
-
return /* @__PURE__ */ jsx25("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs19("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
|
|
2828
|
-
/* @__PURE__ */ jsx25("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
|
|
2829
|
-
/* @__PURE__ */ jsx25(
|
|
2830
|
-
"button",
|
|
2831
|
-
{
|
|
2832
|
-
type: "button",
|
|
2833
|
-
onClick: runPoll,
|
|
2834
|
-
style: buttonStyle,
|
|
2835
|
-
children: "Atualizar"
|
|
2836
|
-
}
|
|
2837
|
-
)
|
|
2838
|
-
] }) });
|
|
2839
|
-
}
|
|
2840
|
-
return /* @__PURE__ */ jsx25(Fragment7, { children });
|
|
2841
|
-
}
|
|
2842
|
-
var overlayStyle2 = {
|
|
2843
|
-
position: "fixed",
|
|
2844
|
-
inset: 0,
|
|
2845
|
-
display: "flex",
|
|
2846
|
-
alignItems: "center",
|
|
2847
|
-
justifyContent: "center",
|
|
2848
|
-
background: "rgba(0,0,0,0.4)",
|
|
2849
|
-
zIndex: 9999,
|
|
2850
|
-
color: "white",
|
|
2851
|
-
fontSize: "1rem",
|
|
2852
|
-
padding: 24
|
|
2853
|
-
};
|
|
2854
|
-
var buttonStyle = {
|
|
2855
|
-
background: "white",
|
|
2856
|
-
color: "black",
|
|
2857
|
-
border: "none",
|
|
2858
|
-
borderRadius: 8,
|
|
2859
|
-
padding: "10px 24px",
|
|
2860
|
-
fontSize: "1rem",
|
|
2861
|
-
fontWeight: 600,
|
|
2862
|
-
cursor: "pointer"
|
|
2863
|
-
};
|
|
2864
|
-
function AppRoot({
|
|
2865
|
-
config,
|
|
2866
|
-
children,
|
|
2867
|
-
Login = DefaultLoginScreen,
|
|
2868
|
-
Signup = DefaultSignupScreen,
|
|
2869
|
-
Forgot = DefaultForgotScreen,
|
|
2870
|
-
Reset = DefaultResetScreen,
|
|
2871
|
-
Paywall = DefaultPaywall
|
|
2872
|
-
}) {
|
|
2873
|
-
return /* @__PURE__ */ jsx25(PaymentReturnHandler, { children: /* @__PURE__ */ jsx25(TemplateConfigProvider, { config, children: /* @__PURE__ */ jsx25(ErrorBoundary, { children: /* @__PURE__ */ jsx25(ThemeProvider, { children: /* @__PURE__ */ jsx25(InstallGate, { children: /* @__PURE__ */ jsx25(AuthGate, { Login, Signup, Forgot, Reset, children: /* @__PURE__ */ jsx25(PersistedKeysPrefetch, { children: /* @__PURE__ */ jsxs19(SubscriptionGate, { Paywall, children: [
|
|
2874
|
-
children,
|
|
2875
|
-
/* @__PURE__ */ jsx25(PushPrompt, {})
|
|
2876
|
-
] }) }) }) }) }) }) }) });
|
|
2245
|
+
setEmail,
|
|
2246
|
+
emailError,
|
|
2247
|
+
password,
|
|
2248
|
+
setPassword,
|
|
2249
|
+
passwordError,
|
|
2250
|
+
submit,
|
|
2251
|
+
submitting,
|
|
2252
|
+
canSubmit,
|
|
2253
|
+
error,
|
|
2254
|
+
loginWithGoogle: () => auth.loginWithGoogle()
|
|
2255
|
+
};
|
|
2877
2256
|
}
|
|
2878
2257
|
|
|
2879
|
-
// src/hooks/
|
|
2880
|
-
import { useCallback as
|
|
2881
|
-
import { useHook as
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
const
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2887
|
-
const
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
if (status === "denied") return { kind: "denied" };
|
|
2900
|
-
if (status === "unsupported") {
|
|
2901
|
-
if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
|
|
2902
|
-
return { kind: "unsupported" };
|
|
2903
|
-
}
|
|
2904
|
-
return { kind: "prompt" };
|
|
2905
|
-
}
|
|
2906
|
-
function usePush() {
|
|
2907
|
-
const { push } = useHook10();
|
|
2908
|
-
const [state, setState] = useState13(() => deriveState(push));
|
|
2909
|
-
useEffect9(() => {
|
|
2910
|
-
setState(deriveState(push));
|
|
2911
|
-
}, [push]);
|
|
2912
|
-
const subscribe = useCallback8(async () => {
|
|
2258
|
+
// src/hooks/useForgotForm.ts
|
|
2259
|
+
import { useCallback as useCallback7, useMemo as useMemo6, useState as useState8 } from "react";
|
|
2260
|
+
import { useHook as useHook8 } from "@hook-sdk/sdk";
|
|
2261
|
+
var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2262
|
+
function useForgotForm() {
|
|
2263
|
+
const { auth } = useHook8();
|
|
2264
|
+
const [email, setEmail] = useState8("");
|
|
2265
|
+
const [submitting, setSubmitting] = useState8(false);
|
|
2266
|
+
const [sent, setSent] = useState8(false);
|
|
2267
|
+
const [error, setError] = useState8(null);
|
|
2268
|
+
const emailError = useMemo6(() => {
|
|
2269
|
+
if (email.length === 0) return null;
|
|
2270
|
+
if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
2271
|
+
return null;
|
|
2272
|
+
}, [email]);
|
|
2273
|
+
const canSubmit = email.length > 0 && emailError === null && !submitting;
|
|
2274
|
+
const submit = useCallback7(async () => {
|
|
2275
|
+
if (!canSubmit) return false;
|
|
2276
|
+
setSubmitting(true);
|
|
2277
|
+
setError(null);
|
|
2913
2278
|
try {
|
|
2914
|
-
await
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2279
|
+
await auth.forgot({ email });
|
|
2280
|
+
setSent(true);
|
|
2281
|
+
return true;
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
setError(mapSdkError(err));
|
|
2284
|
+
return false;
|
|
2285
|
+
} finally {
|
|
2286
|
+
setSubmitting(false);
|
|
2922
2287
|
}
|
|
2923
|
-
}, [
|
|
2924
|
-
|
|
2288
|
+
}, [auth, email, canSubmit]);
|
|
2289
|
+
return {
|
|
2290
|
+
email,
|
|
2291
|
+
setEmail,
|
|
2292
|
+
emailError,
|
|
2293
|
+
submit,
|
|
2294
|
+
submitting,
|
|
2295
|
+
canSubmit,
|
|
2296
|
+
sent,
|
|
2297
|
+
error
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// src/hooks/useResetForm.ts
|
|
2302
|
+
import { useCallback as useCallback8, useEffect as useEffect7, useMemo as useMemo7, useState as useState9 } from "react";
|
|
2303
|
+
import { useHook as useHook9 } from "@hook-sdk/sdk";
|
|
2304
|
+
var MIN_PASSWORD3 = 12;
|
|
2305
|
+
function useResetForm() {
|
|
2306
|
+
const { auth } = useHook9();
|
|
2307
|
+
const [token, setToken] = useState9(null);
|
|
2308
|
+
const [password, setPassword] = useState9("");
|
|
2309
|
+
const [confirm, setConfirm] = useState9("");
|
|
2310
|
+
const [submitting, setSubmitting] = useState9(false);
|
|
2311
|
+
const [done, setDone] = useState9(false);
|
|
2312
|
+
const [error, setError] = useState9(null);
|
|
2313
|
+
useEffect7(() => {
|
|
2314
|
+
if (typeof window === "undefined") return;
|
|
2315
|
+
const params = new URLSearchParams(window.location.search);
|
|
2316
|
+
const t = params.get("token");
|
|
2317
|
+
setToken(t && t.length > 0 ? t : null);
|
|
2318
|
+
}, []);
|
|
2319
|
+
const passwordError = useMemo7(() => {
|
|
2320
|
+
if (password.length === 0) return null;
|
|
2321
|
+
if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
|
|
2322
|
+
return null;
|
|
2323
|
+
}, [password]);
|
|
2324
|
+
const confirmError = useMemo7(() => {
|
|
2325
|
+
if (confirm.length === 0) return null;
|
|
2326
|
+
if (confirm !== password) return "Senhas n\xE3o coincidem.";
|
|
2327
|
+
return null;
|
|
2328
|
+
}, [confirm, password]);
|
|
2329
|
+
const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
|
|
2330
|
+
const submit = useCallback8(async () => {
|
|
2331
|
+
if (!canSubmit || token === null) return;
|
|
2332
|
+
setSubmitting(true);
|
|
2333
|
+
setError(null);
|
|
2925
2334
|
try {
|
|
2926
|
-
await
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2335
|
+
await auth.reset({ token, newPassword: password });
|
|
2336
|
+
setDone(true);
|
|
2337
|
+
if (typeof window !== "undefined") {
|
|
2338
|
+
const url = new URL(window.location.href);
|
|
2339
|
+
url.searchParams.delete("token");
|
|
2340
|
+
url.searchParams.delete("screen");
|
|
2341
|
+
window.history.replaceState({}, "", url.toString());
|
|
2342
|
+
}
|
|
2343
|
+
} catch (err) {
|
|
2344
|
+
setError(mapSdkError(err));
|
|
2345
|
+
} finally {
|
|
2346
|
+
setSubmitting(false);
|
|
2931
2347
|
}
|
|
2932
|
-
}, [
|
|
2933
|
-
return {
|
|
2348
|
+
}, [auth, token, password, canSubmit]);
|
|
2349
|
+
return {
|
|
2350
|
+
token,
|
|
2351
|
+
password,
|
|
2352
|
+
setPassword,
|
|
2353
|
+
passwordError,
|
|
2354
|
+
confirm,
|
|
2355
|
+
setConfirm,
|
|
2356
|
+
confirmError,
|
|
2357
|
+
submit,
|
|
2358
|
+
submitting,
|
|
2359
|
+
canSubmit,
|
|
2360
|
+
done,
|
|
2361
|
+
error
|
|
2362
|
+
};
|
|
2934
2363
|
}
|
|
2935
2364
|
|
|
2936
|
-
// src/
|
|
2937
|
-
import {
|
|
2938
|
-
function
|
|
2939
|
-
const {
|
|
2940
|
-
|
|
2941
|
-
if (state.kind === "ios_needs_install") {
|
|
2942
|
-
return /* @__PURE__ */ jsxs20("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
|
|
2943
|
-
/* @__PURE__ */ jsx26("h3", { children: texts.iosInstallTitle }),
|
|
2944
|
-
/* @__PURE__ */ jsx26("p", { children: texts.iosInstallBody }),
|
|
2945
|
-
onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx26("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
|
|
2946
|
-
] });
|
|
2947
|
-
}
|
|
2948
|
-
if (state.kind === "denied") {
|
|
2949
|
-
return /* @__PURE__ */ jsxs20("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
|
|
2950
|
-
/* @__PURE__ */ jsx26("h3", { children: texts.deniedTitle }),
|
|
2951
|
-
/* @__PURE__ */ jsx26("p", { children: texts.deniedBody })
|
|
2952
|
-
] });
|
|
2953
|
-
}
|
|
2954
|
-
if (state.kind === "unsupported") {
|
|
2955
|
-
return /* @__PURE__ */ jsx26("div", { className, role: "region", children: /* @__PURE__ */ jsx26("p", { children: texts.unsupportedBody }) });
|
|
2956
|
-
}
|
|
2957
|
-
if (state.kind === "error") {
|
|
2958
|
-
return /* @__PURE__ */ jsx26("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx26("p", { children: state.message }) });
|
|
2959
|
-
}
|
|
2960
|
-
return /* @__PURE__ */ jsxs20("div", { className, role: "region", children: [
|
|
2961
|
-
/* @__PURE__ */ jsx26(
|
|
2962
|
-
"button",
|
|
2963
|
-
{
|
|
2964
|
-
type: "button",
|
|
2965
|
-
onClick: async () => {
|
|
2966
|
-
try {
|
|
2967
|
-
await subscribe();
|
|
2968
|
-
onSubscribed?.();
|
|
2969
|
-
} catch {
|
|
2970
|
-
}
|
|
2971
|
-
},
|
|
2972
|
-
children: texts.cta
|
|
2973
|
-
}
|
|
2974
|
-
),
|
|
2975
|
-
onDeclined && /* @__PURE__ */ jsx26("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
|
|
2976
|
-
] });
|
|
2365
|
+
// src/hooks/usePlan.ts
|
|
2366
|
+
import { useHook as useHook10 } from "@hook-sdk/sdk";
|
|
2367
|
+
function usePlan() {
|
|
2368
|
+
const { plan } = useHook10();
|
|
2369
|
+
return plan;
|
|
2977
2370
|
}
|
|
2978
2371
|
|
|
2979
|
-
// src/
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2372
|
+
// src/utils/price.ts
|
|
2373
|
+
function formatBRL(cents) {
|
|
2374
|
+
if (cents === null || cents === void 0) return "";
|
|
2375
|
+
const reais = cents / 100;
|
|
2376
|
+
return new Intl.NumberFormat("pt-BR", {
|
|
2377
|
+
style: "currency",
|
|
2378
|
+
currency: "BRL"
|
|
2379
|
+
}).format(reais);
|
|
2380
|
+
}
|
|
2381
|
+
function monthlyFromYearly(yearlyCents) {
|
|
2382
|
+
if (yearlyCents === null || yearlyCents === void 0) return 0;
|
|
2383
|
+
return Math.round(yearlyCents / 12);
|
|
2384
|
+
}
|
|
2385
|
+
function dailyFromYearly(yearlyCents) {
|
|
2386
|
+
if (yearlyCents === null || yearlyCents === void 0) return 0;
|
|
2387
|
+
return Math.round(yearlyCents / 365);
|
|
2388
|
+
}
|
|
2389
|
+
function computeAnchorCents(baseCents, multiplier) {
|
|
2390
|
+
if (multiplier === null || multiplier === void 0) return null;
|
|
2391
|
+
if (!Number.isFinite(multiplier)) return null;
|
|
2392
|
+
if (multiplier <= 1) return null;
|
|
2393
|
+
return Math.round(baseCents * multiplier);
|
|
2394
|
+
}
|
|
2395
|
+
function discountPercent(anchorCents, realCents) {
|
|
2396
|
+
if (anchorCents <= realCents) return 0;
|
|
2397
|
+
return Math.floor((anchorCents - realCents) / anchorCents * 100);
|
|
2987
2398
|
}
|
|
2988
2399
|
|
|
2989
2400
|
// src/hooks/useAuthPrimitives.ts
|
|
2990
|
-
import { useEffect as
|
|
2401
|
+
import { useEffect as useEffect8 } from "react";
|
|
2991
2402
|
import { useHook as useHook11 } from "@hook-sdk/sdk";
|
|
2992
2403
|
var warned = false;
|
|
2993
2404
|
function useAuthPrimitives() {
|
|
2994
2405
|
const { auth } = useHook11();
|
|
2995
|
-
|
|
2406
|
+
useEffect8(() => {
|
|
2996
2407
|
if (!warned && process.env.NODE_ENV !== "production") {
|
|
2997
2408
|
warned = true;
|
|
2998
2409
|
console.warn(
|
|
@@ -3013,23 +2424,34 @@ function useAuthPrimitives() {
|
|
|
3013
2424
|
};
|
|
3014
2425
|
}
|
|
3015
2426
|
|
|
3016
|
-
// src/hooks/
|
|
2427
|
+
// src/hooks/useAuth.ts
|
|
3017
2428
|
import { useHook as useHook12 } from "@hook-sdk/sdk";
|
|
2429
|
+
function useAuth() {
|
|
2430
|
+
const { user, authStatus, auth } = useHook12();
|
|
2431
|
+
return {
|
|
2432
|
+
user,
|
|
2433
|
+
authStatus,
|
|
2434
|
+
refresh: auth.refresh
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// src/hooks/useSubscription.ts
|
|
2439
|
+
import { useHook as useHook13 } from "@hook-sdk/sdk";
|
|
3018
2440
|
function useSubscription() {
|
|
3019
|
-
const { subscription } =
|
|
2441
|
+
const { subscription } = useHook13();
|
|
3020
2442
|
return {
|
|
3021
2443
|
status: subscription.status()
|
|
3022
2444
|
};
|
|
3023
2445
|
}
|
|
3024
2446
|
|
|
3025
2447
|
// src/hooks/useReminders.ts
|
|
3026
|
-
import { useCallback as useCallback9, useEffect as
|
|
3027
|
-
import { useHook as
|
|
2448
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useState as useState10 } from "react";
|
|
2449
|
+
import { useHook as useHook14 } from "@hook-sdk/sdk";
|
|
3028
2450
|
function useReminders() {
|
|
3029
|
-
const { push } =
|
|
2451
|
+
const { push } = useHook14();
|
|
3030
2452
|
const r = push.reminders;
|
|
3031
|
-
const [reminders, setReminders] =
|
|
3032
|
-
const [loading, setLoading] =
|
|
2453
|
+
const [reminders, setReminders] = useState10([]);
|
|
2454
|
+
const [loading, setLoading] = useState10(true);
|
|
3033
2455
|
const reload = useCallback9(async () => {
|
|
3034
2456
|
setLoading(true);
|
|
3035
2457
|
try {
|
|
@@ -3039,7 +2461,7 @@ function useReminders() {
|
|
|
3039
2461
|
setLoading(false);
|
|
3040
2462
|
}
|
|
3041
2463
|
}, [r]);
|
|
3042
|
-
|
|
2464
|
+
useEffect9(() => {
|
|
3043
2465
|
void reload();
|
|
3044
2466
|
}, [reload]);
|
|
3045
2467
|
const setReminder = useCallback9(async (input) => {
|
|
@@ -3060,9 +2482,9 @@ function useReminders() {
|
|
|
3060
2482
|
}
|
|
3061
2483
|
|
|
3062
2484
|
// src/hooks/useToast.ts
|
|
3063
|
-
import { useCallback as useCallback10, useState as
|
|
2485
|
+
import { useCallback as useCallback10, useState as useState11 } from "react";
|
|
3064
2486
|
function useToast() {
|
|
3065
|
-
const [items, setItems] =
|
|
2487
|
+
const [items, setItems] = useState11([]);
|
|
3066
2488
|
const show = useCallback10((message, kind = "info") => {
|
|
3067
2489
|
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3068
2490
|
setItems((prev) => [...prev, { id, message, kind }]);
|
|
@@ -3075,19 +2497,156 @@ function useToast() {
|
|
|
3075
2497
|
}, []);
|
|
3076
2498
|
return { items, show, dismiss };
|
|
3077
2499
|
}
|
|
2500
|
+
|
|
2501
|
+
// src/RouteBoundary.tsx
|
|
2502
|
+
import { Routes as Routes2, Route as Route2 } from "react-router-dom";
|
|
2503
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2504
|
+
function RouteBoundary({ children }) {
|
|
2505
|
+
return /* @__PURE__ */ jsxs16(Routes2, { children: [
|
|
2506
|
+
children,
|
|
2507
|
+
/* @__PURE__ */ jsx22(Route2, { path: "*", element: /* @__PURE__ */ jsx22(DefaultNotFound, {}) })
|
|
2508
|
+
] });
|
|
2509
|
+
}
|
|
2510
|
+
function DefaultNotFound() {
|
|
2511
|
+
return /* @__PURE__ */ jsx22("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/PreAuthShell.tsx
|
|
2515
|
+
import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
|
|
2516
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
2517
|
+
function PreAuthShell({
|
|
2518
|
+
basename,
|
|
2519
|
+
testRouter,
|
|
2520
|
+
testInitialEntries,
|
|
2521
|
+
children
|
|
2522
|
+
}) {
|
|
2523
|
+
if (testRouter === "memory") {
|
|
2524
|
+
return /* @__PURE__ */ jsx23(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
|
|
2525
|
+
}
|
|
2526
|
+
return /* @__PURE__ */ jsx23(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// src/OnboardingFlow.tsx
|
|
2530
|
+
import { useCallback as useCallback11, useMemo as useMemo8, useRef as useRef3 } from "react";
|
|
2531
|
+
import { usePersistedState } from "@hook-sdk/sdk";
|
|
2532
|
+
|
|
2533
|
+
// src/hooks/useOnboardingStep.ts
|
|
2534
|
+
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
2535
|
+
var OnboardingStepContext = createContext3(null);
|
|
2536
|
+
function useOnboardingStep() {
|
|
2537
|
+
const ctx = useContext4(OnboardingStepContext);
|
|
2538
|
+
if (!ctx) {
|
|
2539
|
+
throw new Error(
|
|
2540
|
+
"[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
return ctx;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/OnboardingFlow.tsx
|
|
2547
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
2548
|
+
var isFilled = (v) => v != null && v !== "";
|
|
2549
|
+
var CURRENT_STEP_FIELD = "currentStep";
|
|
2550
|
+
function readPersistedStepIdx(draft) {
|
|
2551
|
+
const raw = draft[CURRENT_STEP_FIELD];
|
|
2552
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
|
|
2553
|
+
}
|
|
2554
|
+
function OnboardingFlow({
|
|
2555
|
+
steps,
|
|
2556
|
+
screens,
|
|
2557
|
+
onComplete,
|
|
2558
|
+
persistKey
|
|
2559
|
+
}) {
|
|
2560
|
+
const [draft, setDraft, status] = usePersistedState(persistKey, {});
|
|
2561
|
+
const draftRef = useRef3(draft);
|
|
2562
|
+
draftRef.current = draft;
|
|
2563
|
+
const idx = readPersistedStepIdx(draft);
|
|
2564
|
+
const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
|
|
2565
|
+
const setIdx = useCallback11(
|
|
2566
|
+
(n) => {
|
|
2567
|
+
setDraft((prev) => {
|
|
2568
|
+
const prevIdx = readPersistedStepIdx(prev);
|
|
2569
|
+
const nextIdx = typeof n === "function" ? n(prevIdx) : n;
|
|
2570
|
+
return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
|
|
2571
|
+
});
|
|
2572
|
+
},
|
|
2573
|
+
[setDraft]
|
|
2574
|
+
);
|
|
2575
|
+
const setValue = useCallback11(
|
|
2576
|
+
(patch) => {
|
|
2577
|
+
draftRef.current = { ...draftRef.current, ...patch };
|
|
2578
|
+
setDraft((prev) => ({ ...prev, ...patch }));
|
|
2579
|
+
},
|
|
2580
|
+
[setDraft]
|
|
2581
|
+
);
|
|
2582
|
+
const step = steps[clampedIdx];
|
|
2583
|
+
const valid = useMemo8(
|
|
2584
|
+
() => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
|
|
2585
|
+
[draft, step]
|
|
2586
|
+
);
|
|
2587
|
+
const next = useCallback11(() => {
|
|
2588
|
+
if (!step) return;
|
|
2589
|
+
const current = draftRef.current;
|
|
2590
|
+
const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
|
|
2591
|
+
if (!validNow) return;
|
|
2592
|
+
if (clampedIdx + 1 >= steps.length) {
|
|
2593
|
+
onComplete(current);
|
|
2594
|
+
} else {
|
|
2595
|
+
setIdx(clampedIdx + 1);
|
|
2596
|
+
}
|
|
2597
|
+
}, [clampedIdx, onComplete, step, steps.length, setIdx]);
|
|
2598
|
+
const prevStep = useCallback11(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
|
|
2599
|
+
const ctx = useMemo8(
|
|
2600
|
+
() => ({
|
|
2601
|
+
stepIndex: clampedIdx,
|
|
2602
|
+
totalSteps: steps.length,
|
|
2603
|
+
value: draft,
|
|
2604
|
+
setValue,
|
|
2605
|
+
valid,
|
|
2606
|
+
next,
|
|
2607
|
+
prev: prevStep
|
|
2608
|
+
}),
|
|
2609
|
+
[clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
|
|
2610
|
+
);
|
|
2611
|
+
if (status.loading) {
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
if (!step) {
|
|
2615
|
+
throw new Error(
|
|
2616
|
+
`[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
const Screen = screens[step.screen];
|
|
2620
|
+
if (!Screen) {
|
|
2621
|
+
throw new Error(
|
|
2622
|
+
`[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
return /* @__PURE__ */ jsx24(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx24(Screen, {}) });
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/hooks/useFeature.ts
|
|
2629
|
+
function useFeature(name) {
|
|
2630
|
+
const config = useAppConfig();
|
|
2631
|
+
return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
|
|
2632
|
+
}
|
|
3078
2633
|
export {
|
|
2634
|
+
AppConfigProvider,
|
|
2635
|
+
AppConfigSchema,
|
|
3079
2636
|
AppRoot,
|
|
3080
|
-
|
|
3081
|
-
DefaultLoginScreen,
|
|
3082
|
-
DefaultPaywall,
|
|
3083
|
-
DefaultResetScreen,
|
|
3084
|
-
DefaultSignupScreen,
|
|
2637
|
+
DeepLinkHandler,
|
|
3085
2638
|
EmptyState,
|
|
3086
2639
|
ErrorBoundary,
|
|
3087
2640
|
InstallGate,
|
|
3088
2641
|
InstallSplash,
|
|
3089
2642
|
LoadingState,
|
|
2643
|
+
OnboardingFlow,
|
|
2644
|
+
PaymentReturnHandler,
|
|
2645
|
+
PersistenceRegistry,
|
|
2646
|
+
PreAuthShell,
|
|
3090
2647
|
PushPrompt2 as PushPrompt,
|
|
2648
|
+
RouteBoundary,
|
|
2649
|
+
asaasErrorMessage,
|
|
3091
2650
|
computeAnchorCents,
|
|
3092
2651
|
dailyFromYearly,
|
|
3093
2652
|
detectAndroidBrowser,
|
|
@@ -3098,13 +2657,17 @@ export {
|
|
|
3098
2657
|
discountPercent,
|
|
3099
2658
|
formatBRL,
|
|
3100
2659
|
monthlyFromYearly,
|
|
2660
|
+
parseAppConfig,
|
|
3101
2661
|
shouldBlockInstall,
|
|
3102
2662
|
shouldShowPermanentOption,
|
|
2663
|
+
useAppConfig,
|
|
3103
2664
|
useAuth,
|
|
3104
2665
|
useAuthPrimitives,
|
|
2666
|
+
useFeature,
|
|
3105
2667
|
useForgotForm,
|
|
3106
2668
|
useInstallPrompt,
|
|
3107
2669
|
useLoginForm,
|
|
2670
|
+
useOnboardingStep,
|
|
3108
2671
|
usePaywallState,
|
|
3109
2672
|
usePlan,
|
|
3110
2673
|
usePush,
|