mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-06-01 00:53:36 +00:00
2 nextjs scaffold with store mode shop and admin session experiment wiring event emission v1 (#17)
* chore: cleaning gitignore * formating and env documentation * feat: context switching of hotel/airline depndent on env var via middleware * fixed alignment and building * wrong file * prods * fixed applying style * better session cookie management * tentative session storage with maybe using airtable * migrated api of ingestion * events and products apge * fixing build * 13 create outline for research paper draft (#18) * updated outline for paper from issue * extra paper sections and some formalization of series data * algorithms and acknowledgements * updated outline for paper from issue * upadted text formating * event unification * refactor tracking to ues callbacks instead of refs * implement a pricing display api with session passing * moved middleware to proxy according to new changes in Nextjs * refactoed kafka ingestion to go via backend not web-db * Refactor docker-compose services to use individual Dockerfiles (#20) * Initial plan * Refactor services into individual Dockerfiles Co-authored-by: velocitatem <60182044+velocitatem@users.noreply.github.com> * Add EXPOSE directives to all Dockerfiles with port documentation Co-authored-by: velocitatem <60182044+velocitatem@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: velocitatem <60182044+velocitatem@users.noreply.github.com> * fixing small bugs and adding exepriments to tracking * added some doc
This commit is contained in:
committed by
GitHub
parent
7ece6e82cb
commit
37b2099ee0
@@ -1,117 +1,86 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import '@/lib/experiments' // ensure experiments lib is loaded
|
||||
import type { EventName } from '@/lib/events';
|
||||
|
||||
const genSessionId = () => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
let sid = sessionStorage.getItem('phantom_session_id');
|
||||
if (!sid) {
|
||||
sid = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
sessionStorage.setItem('phantom_session_id', sid);
|
||||
// TODO: when creating new id send to exepriemtn tracking db
|
||||
// match between sesion-id and experiment-id for this session
|
||||
// so that we can identify all interactions aligning with a specific experiment goal.
|
||||
}
|
||||
return sid;
|
||||
const fetchSessionId = async (): Promise<string> => {
|
||||
try {
|
||||
const res = await fetch('/api/session');
|
||||
const data = await res.json();
|
||||
return data.sessionId || '';
|
||||
} catch (err) {
|
||||
console.error('failed to fetch session:', err);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const track = async (ev: {
|
||||
sessionId: string;
|
||||
eventType: string;
|
||||
targetEl?: string;
|
||||
targetUrl?: string;
|
||||
metadata?: Record<string, any>;
|
||||
sessionId: string;
|
||||
eventName: EventName;
|
||||
page: string;
|
||||
productId?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}) => {
|
||||
try {
|
||||
await fetch('/api/track', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(ev),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('track failed:', err);
|
||||
}
|
||||
try {
|
||||
await fetch('/api/ingest', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(ev),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('track failed:', err);
|
||||
}
|
||||
};
|
||||
|
||||
export const useInteractionTracking = () => {
|
||||
const sidRef = useRef<string>('');
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
sidRef.current = genSessionId();
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const tgt = e.target as HTMLElement;
|
||||
track({
|
||||
sessionId: sidRef.current,
|
||||
eventType: 'click',
|
||||
targetEl: tgt.tagName,
|
||||
targetUrl: tgt instanceof HTMLAnchorElement ? tgt.href : undefined,
|
||||
metadata: {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
path: window.location.pathname,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
track({
|
||||
sessionId: sidRef.current,
|
||||
eventType: 'scroll',
|
||||
metadata: {
|
||||
scrollY: window.scrollY,
|
||||
path: window.location.pathname,
|
||||
},
|
||||
});
|
||||
};
|
||||
// fetch session id from httpOnly cookie via API
|
||||
fetchSessionId().then((sid) => {
|
||||
sidRef.current = sid;
|
||||
setReady(true);
|
||||
});
|
||||
|
||||
const handlePageView = () => {
|
||||
if (!sidRef.current) return;
|
||||
const page = window.location.pathname;
|
||||
track({
|
||||
sessionId: sidRef.current,
|
||||
eventType: 'pageview',
|
||||
eventName: 'page_view',
|
||||
page,
|
||||
metadata: {
|
||||
path: window.location.pathname,
|
||||
referrer: document.referrer,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
enum DefinedInteractions {
|
||||
ADD_TO_CART = 'add_to_cart',
|
||||
PURCHASE = 'purchase',
|
||||
}
|
||||
|
||||
// called when clicking on "Add to Cart" button or "Purchase" button
|
||||
const handleDefinedInteraction = (
|
||||
interactionType: DefinedInteractions,
|
||||
metadata?: Record<string, any>
|
||||
) => {
|
||||
// called for canonical events dispatched via custom events
|
||||
const handleDefinedInteraction = (e: Event) => {
|
||||
if (!sidRef.current) return;
|
||||
const customEvent = e as CustomEvent<{
|
||||
eventName: EventName;
|
||||
productId?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}>;
|
||||
const page = window.location.pathname;
|
||||
track({
|
||||
sessionId: sidRef.current,
|
||||
eventType: interactionType,
|
||||
metadata: {
|
||||
path: window.location.pathname,
|
||||
...metadata,
|
||||
},
|
||||
eventName: customEvent.detail.eventName,
|
||||
page,
|
||||
productId: customEvent.detail.productId,
|
||||
metadata: customEvent.detail.metadata,
|
||||
});
|
||||
};
|
||||
|
||||
// wait for session to be ready before tracking
|
||||
if (!ready) return;
|
||||
|
||||
handlePageView();
|
||||
document.addEventListener('click', handleClick);
|
||||
document.addEventListener('definedInteraction', (e: Event) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
handleDefinedInteraction(customEvent.detail.interactionType, customEvent.detail.metadata);
|
||||
});
|
||||
// TOO NOISY: enable if needed but tbh not worth it
|
||||
//window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
document.addEventListener('definedInteraction', handleDefinedInteraction);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick);
|
||||
document.removeEventListener('definedInteraction', (e: Event) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
handleDefinedInteraction(customEvent.detail.interactionType, customEvent.detail.metadata);
|
||||
});
|
||||
//window.removeEventListener('scroll', handleScroll);
|
||||
document.removeEventListener('definedInteraction', handleDefinedInteraction);
|
||||
};
|
||||
}, []);
|
||||
}, [ready]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user