Create Loops profiles on user signup (#180)
This commit is contained in:
parent
ba9f30fde0
commit
740c899764
3
src/env.d.ts
vendored
3
src/env.d.ts
vendored
@ -13,6 +13,9 @@ declare namespace Cloudflare {
|
|||||||
POSTHOG_HOST?: string;
|
POSTHOG_HOST?: string;
|
||||||
BETTER_AUTH_SECRET?: string;
|
BETTER_AUTH_SECRET?: string;
|
||||||
BETTER_AUTH_URL?: string;
|
BETTER_AUTH_URL?: string;
|
||||||
|
LOOPS_API_KEY?: string;
|
||||||
|
LOOPS_TRANSACTIONAL_VERIFY_EMAIL_ID?: string;
|
||||||
|
LOOPS_TRANSACTIONAL_RESET_PASSWORD_ID?: string;
|
||||||
|
|
||||||
// DataForSEO API Basic auth value (base64 of login:password)
|
// DataForSEO API Basic auth value (base64 of login:password)
|
||||||
DATAFORSEO_API_KEY: string;
|
DATAFORSEO_API_KEY: string;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { getOrCreateDefaultHostedOrganization } from "@/server/auth/default-host
|
|||||||
import {
|
import {
|
||||||
sendHostedPasswordResetEmail,
|
sendHostedPasswordResetEmail,
|
||||||
sendHostedVerificationEmail,
|
sendHostedVerificationEmail,
|
||||||
|
upsertHostedSignupContact,
|
||||||
} from "@/server/email/loops";
|
} from "@/server/email/loops";
|
||||||
|
|
||||||
const hostedBaseUrlSchema = z
|
const hostedBaseUrlSchema = z
|
||||||
@ -61,6 +62,25 @@ function createAuth() {
|
|||||||
}),
|
}),
|
||||||
plugins: [...baseAuthConfig.plugins, tanstackStartCookies()],
|
plugins: [...baseAuthConfig.plugins, tanstackStartCookies()],
|
||||||
databaseHooks: {
|
databaseHooks: {
|
||||||
|
user: {
|
||||||
|
create: {
|
||||||
|
after: async (user) => {
|
||||||
|
try {
|
||||||
|
await upsertHostedSignupContact({
|
||||||
|
userId: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create Loops profile for signup:", {
|
||||||
|
userId: user.id,
|
||||||
|
email: user.email,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
session: {
|
session: {
|
||||||
create: {
|
create: {
|
||||||
before: async (session) => {
|
before: async (session) => {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { env } from "cloudflare:workers";
|
import { env } from "cloudflare:workers";
|
||||||
|
|
||||||
const LOOPS_TRANSACTIONAL_URL = "https://app.loops.so/api/v1/transactional";
|
const LOOPS_TRANSACTIONAL_URL = "https://app.loops.so/api/v1/transactional";
|
||||||
|
const LOOPS_CONTACT_UPDATE_URL = "https://app.loops.so/api/v1/contacts/update";
|
||||||
|
|
||||||
function getRequiredEnv(name: string) {
|
function getRequiredEnv(name: string) {
|
||||||
const value: unknown = Reflect.get(env, name);
|
const value: unknown = Reflect.get(env, name);
|
||||||
@ -67,6 +68,77 @@ async function sendLoopsTransactionalEmail({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptionalEnv(name: string) {
|
||||||
|
const value: unknown = Reflect.get(env, name);
|
||||||
|
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||||
|
|
||||||
|
return trimmed || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContactNameParts(name: string | null | undefined) {
|
||||||
|
const trimmedName = name?.trim();
|
||||||
|
|
||||||
|
if (!trimmedName) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [firstName, ...lastNameParts] = trimmedName.split(/\s+/);
|
||||||
|
const lastName = lastNameParts.join(" ");
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstName,
|
||||||
|
...(lastName ? { lastName } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertHostedSignupContact({
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
email: string;
|
||||||
|
name?: string | null;
|
||||||
|
}) {
|
||||||
|
const apiKey = getOptionalEnv("LOOPS_API_KEY");
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
console.warn(
|
||||||
|
"Skipping Loops signup contact sync: LOOPS_API_KEY is not set",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(LOOPS_CONTACT_UPDATE_URL, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
userId,
|
||||||
|
source: "openseo-signup",
|
||||||
|
userGroup: "app-user",
|
||||||
|
...getContactNameParts(name),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorPayload = await response.json().catch(() => null);
|
||||||
|
console.error("Loops signup contact sync error:", {
|
||||||
|
status: response.status,
|
||||||
|
email,
|
||||||
|
userId,
|
||||||
|
errorPayload,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(`Failed to sync Loops signup contact (${response.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
export async function sendHostedVerificationEmail({
|
export async function sendHostedVerificationEmail({
|
||||||
email,
|
email,
|
||||||
confirmationUrl,
|
confirmationUrl,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user