feat(popup): add email capture popup with Mautic integration
Some checks failed
Build and Deploy / build (push) Has been cancelled
Some checks failed
Build and Deploy / build (push) Has been cancelled
- 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
This commit is contained in:
100
src/hooks/useVisitorStore.ts
Normal file
100
src/hooks/useVisitorStore.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
|
||||
const STORAGE_KEY = "manoonoils-visitor";
|
||||
const SESSION_DURATION_HOURS = 24;
|
||||
|
||||
interface VisitorState {
|
||||
visitorId: string;
|
||||
popupShown: boolean;
|
||||
popupShownAt: number | null;
|
||||
popupTrigger: "scroll" | "exit" | null;
|
||||
subscribed: boolean;
|
||||
}
|
||||
|
||||
export function useVisitorStore() {
|
||||
const [state, setState] = useState<VisitorState>({
|
||||
visitorId: "",
|
||||
popupShown: false,
|
||||
popupShownAt: null,
|
||||
popupTrigger: null,
|
||||
subscribed: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Check for reset flag in URL
|
||||
if (typeof window !== 'undefined' && window.location.search.includes('reset-popup=true')) {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
console.log("[VisitorStore] Reset popup tracking");
|
||||
}
|
||||
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
setState(parsed);
|
||||
console.log("[VisitorStore] Loaded state:", parsed);
|
||||
} else {
|
||||
const newState: VisitorState = {
|
||||
visitorId: generateVisitorId(),
|
||||
popupShown: false,
|
||||
popupShownAt: null,
|
||||
popupTrigger: null,
|
||||
subscribed: false,
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newState));
|
||||
setState(newState);
|
||||
console.log("[VisitorStore] Created new state:", newState);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const canShowPopup = useCallback((): boolean => {
|
||||
if (state.subscribed) {
|
||||
console.log("[VisitorStore] canShowPopup: false (already subscribed)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!state.popupShown || !state.popupShownAt) {
|
||||
console.log("[VisitorStore] canShowPopup: true (never shown)");
|
||||
return true;
|
||||
}
|
||||
|
||||
const hoursPassed = (Date.now() - state.popupShownAt) / (1000 * 60 * 60);
|
||||
const canShow = hoursPassed >= SESSION_DURATION_HOURS;
|
||||
console.log("[VisitorStore] canShowPopup:", canShow, "hours passed:", hoursPassed);
|
||||
return canShow;
|
||||
}, [state.popupShown, state.popupShownAt, state.subscribed]);
|
||||
|
||||
const markPopupShown = useCallback((trigger: "scroll" | "exit") => {
|
||||
const newState: VisitorState = {
|
||||
...state,
|
||||
popupShown: true,
|
||||
popupShownAt: Date.now(),
|
||||
popupTrigger: trigger,
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newState));
|
||||
setState(newState);
|
||||
}, [state]);
|
||||
|
||||
const markSubscribed = useCallback(() => {
|
||||
const newState: VisitorState = {
|
||||
...state,
|
||||
subscribed: true,
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newState));
|
||||
setState(newState);
|
||||
console.log("[VisitorStore] Marked as subscribed");
|
||||
}, [state]);
|
||||
|
||||
return {
|
||||
visitorId: state.visitorId,
|
||||
canShowPopup,
|
||||
markPopupShown,
|
||||
markSubscribed,
|
||||
popupTrigger: state.popupTrigger,
|
||||
};
|
||||
}
|
||||
|
||||
function generateVisitorId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user