Files
manoon-headless/src/lib/mautic.ts
Unchained eb711fbf1a
Some checks failed
Build and Deploy / build (push) Has been cancelled
feat(popup): add email capture popup with Mautic integration
- Email capture popup with scroll (10%) and exit intent triggers
- First name field and full tracking (UTM, device, time on page)
- Mautic API integration for contact creation
- GeoIP detection for country/region
- 4 locale support (sr, en, de, fr)
- Mautic tracking script in layout
2026-04-03 20:44:15 +02:00

121 lines
3.7 KiB
TypeScript

interface MauticToken {
access_token: string;
expires_in: number;
token_type: string;
}
let cachedToken: MauticToken | null = null;
let tokenExpiresAt: number = 0;
async function getMauticToken(): Promise<string> {
if (cachedToken && Date.now() < tokenExpiresAt - 60000) {
return cachedToken.access_token;
}
const clientId = process.env.MAUTIC_CLIENT_ID;
const clientSecret = process.env.MAUTIC_CLIENT_SECRET;
const apiUrl = process.env.MAUTIC_API_URL || "https://mautic.nodecrew.me";
if (!clientId || !clientSecret) {
throw new Error("Mautic credentials not configured");
}
const response = await fetch(`${apiUrl}/oauth/v2/token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error("Mautic token error:", response.status, errorText);
throw new Error(`Failed to get Mautic token: ${response.status} - ${errorText}`);
}
const token: MauticToken = await response.json();
cachedToken = token;
tokenExpiresAt = Date.now() + token.expires_in * 1000;
return token.access_token;
}
export async function createMauticContact(
email: string,
tags: string[],
additionalData?: {
firstName?: string;
lastName?: string;
country?: string;
city?: string;
phone?: string;
website?: string;
preferredLocale?: string;
ipAddress?: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
utmContent?: string;
pageUrl?: string;
}
): Promise<{ success: boolean; alreadyExists?: boolean; contactId?: number }> {
try {
const token = await getMauticToken();
const apiUrl = process.env.MAUTIC_API_URL || "https://mautic.nodecrew.me";
const payload: any = {
email,
tags: tags.join(","),
};
if (additionalData) {
if (additionalData.firstName) payload.firstname = additionalData.firstName;
if (additionalData.lastName) payload.lastname = additionalData.lastName;
if (additionalData.country) payload.country = additionalData.country;
if (additionalData.city) payload.city = additionalData.city;
if (additionalData.phone) payload.phone = additionalData.phone;
if (additionalData.preferredLocale) payload.preferred_locale = additionalData.preferredLocale;
if (additionalData.utmSource) payload.utm_source = additionalData.utmSource;
if (additionalData.utmMedium) payload.utm_medium = additionalData.utmMedium;
if (additionalData.utmCampaign) payload.utm_campaign = additionalData.utmCampaign;
if (additionalData.utmContent) payload.utm_content = additionalData.utmContent;
if (additionalData.pageUrl) payload.page_url = additionalData.pageUrl;
}
const response = await fetch(`${apiUrl}/api/contacts/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (response.status === 409) {
return { success: true, alreadyExists: true };
}
if (!response.ok) {
const errorText = await response.text();
console.error("Mautic API error:", response.status, errorText);
throw new Error(`Mautic API error: ${response.status} - ${errorText}`);
}
const responseData = await response.json();
console.log("Mautic API success:", responseData);
return {
success: true,
contactId: responseData.contact?.id
};
} catch (error) {
console.error("Mautic contact creation failed:", error);
throw error;
}
}