@m13v/seo-components 0.32.0 → 0.32.2
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/package.json
CHANGED
|
@@ -120,13 +120,18 @@ export function InstallEmailGate({
|
|
|
120
120
|
|
|
121
121
|
const onOpen = () => {
|
|
122
122
|
const skip = remember && hasCapturedInstallEmail(storageKey);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
if (skip) {
|
|
124
|
+
// Gate already passed previously: fire the canonical funnel event for
|
|
125
|
+
// the gated-passed click. No event when the gate is fresh and we're
|
|
126
|
+
// about to ask for the email; that fires on submit instead.
|
|
127
|
+
trackGetStartedClick({
|
|
128
|
+
destination: "modal:command",
|
|
129
|
+
site,
|
|
130
|
+
section,
|
|
131
|
+
text: label,
|
|
132
|
+
component: "InstallEmailGate",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
130
135
|
setStage(skip ? "command" : "email");
|
|
131
136
|
setError("");
|
|
132
137
|
};
|
|
@@ -186,12 +191,16 @@ export function InstallEmailGate({
|
|
|
186
191
|
try {
|
|
187
192
|
await navigator.clipboard.writeText(text);
|
|
188
193
|
setCopied(which);
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
// Track the copy as its own event so it does not inflate the
|
|
195
|
+
// canonical `get_started_click` funnel. The gate event fires once
|
|
196
|
+
// when the email is submitted (or once on each subsequent gated-
|
|
197
|
+
// passed click), not for every clipboard copy.
|
|
198
|
+
capture("install_command_copied", {
|
|
199
|
+
component: "InstallEmailGate",
|
|
191
200
|
site,
|
|
192
201
|
section,
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
which,
|
|
203
|
+
page: typeof window !== "undefined" ? window.location.pathname : undefined,
|
|
195
204
|
});
|
|
196
205
|
setTimeout(() => setCopied(null), 1800);
|
|
197
206
|
} catch {
|
|
@@ -91,6 +91,14 @@ export interface BookCallConfig {
|
|
|
91
91
|
emailSubject?: string;
|
|
92
92
|
/** Override the email HTML. Receives the email-click URL and the submitted email. */
|
|
93
93
|
emailHtml?: (emailClickUrl: string, subscriberEmail: string) => string;
|
|
94
|
+
/**
|
|
95
|
+
* Optional: log the outbound send. Called after the Resend send call returns;
|
|
96
|
+
* receives the subscriber email and the Resend email id (or null on failure).
|
|
97
|
+
* Errors thrown here are logged and swallowed so a flaky DB doesn't break the
|
|
98
|
+
* booking flow. Required for sites that want delivery / open / click webhook
|
|
99
|
+
* events to update the right `<slug>_emails` row by `resend_id`.
|
|
100
|
+
*/
|
|
101
|
+
onSent?: (email: string, resendEmailId: string | null) => Promise<void>;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
/* ------------------------------------------------------------------ */
|
|
@@ -121,6 +129,7 @@ export function createBookCallHandler(config: BookCallConfig) {
|
|
|
121
129
|
apiKeyEnv = "RESEND_API_KEY",
|
|
122
130
|
emailSubject,
|
|
123
131
|
emailHtml,
|
|
132
|
+
onSent,
|
|
124
133
|
} = config;
|
|
125
134
|
|
|
126
135
|
return async function POST(req: NextRequest) {
|
|
@@ -175,14 +184,43 @@ export function createBookCallHandler(config: BookCallConfig) {
|
|
|
175
184
|
? emailHtml(emailClickUrl, email)
|
|
176
185
|
: defaultBookCallEmailHtml(brand, siteUrl, emailClickUrl);
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
if (onSent) {
|
|
188
|
+
// Sequential so the `onSent` callback receives the actual Resend id.
|
|
189
|
+
try {
|
|
190
|
+
const sendRes = await fetch("https://api.resend.com/emails", {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: `Bearer ${resendKey}`,
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({ from: fromEmail, to: email, subject, html }),
|
|
197
|
+
});
|
|
198
|
+
let resendEmailId: string | null = null;
|
|
199
|
+
if (sendRes.ok) {
|
|
200
|
+
const data = (await sendRes.json().catch(() => ({}))) as { id?: string };
|
|
201
|
+
resendEmailId = data.id || null;
|
|
202
|
+
} else {
|
|
203
|
+
const detail = await sendRes.text().catch(() => "");
|
|
204
|
+
console.error("[book-call] email send failed:", sendRes.status, detail);
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await onSent(email, resendEmailId);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error("[book-call] onSent callback error:", err);
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error("[book-call] email send threw:", err);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
fetch("https://api.resend.com/emails", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: {
|
|
218
|
+
Authorization: `Bearer ${resendKey}`,
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({ from: fromEmail, to: email, subject, html }),
|
|
222
|
+
}).catch((err) => console.error("[book-call] email send threw:", err));
|
|
223
|
+
}
|
|
186
224
|
|
|
187
225
|
return new Response(
|
|
188
226
|
JSON.stringify({ ok: true }),
|