feat(ads): wire Meta Pixel for ad attribution
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Pixel ID 1904240520247944 (sase.tr) created on ad account act_1227112768351770. Lazy-injected like PostHog so it stays off when VITE_META_PIXEL_ID is unset. Tracks PageView on every route change, CompleteRegistration on email/Google signup, and InitiateCheckout on Stripe button click (value in TRY). Coolify env: set VITE_META_PIXEL_ID=1904240520247944 before next deploy so it gets baked into the Vite build. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import { startAction } from "@/lib/faro";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { track as trackMeta } from "@/lib/meta-pixel";
|
||||
import { capture } from "@/lib/posthog";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { formatTRY } from "@sase/shared";
|
||||
@@ -54,6 +55,12 @@ export function StripeCheckoutButton({
|
||||
period,
|
||||
amount: totalAmount,
|
||||
});
|
||||
trackMeta("InitiateCheckout", {
|
||||
content_name: planKey,
|
||||
content_category: period,
|
||||
value: totalAmount,
|
||||
currency: "TRY",
|
||||
});
|
||||
mutation.mutate();
|
||||
}
|
||||
|
||||
|
||||
69
apps/web/src/lib/meta-pixel.ts
Normal file
69
apps/web/src/lib/meta-pixel.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// Meta Pixel — lazily injected so it doesn't land in the initial bundle
|
||||
// and stays off entirely when VITE_META_PIXEL_ID is unset (dev/preview).
|
||||
// All exported functions are fire-and-forget; tracking loss on failure is acceptable.
|
||||
|
||||
type FbqArgs = unknown[];
|
||||
type Fbq = ((...args: FbqArgs) => void) & {
|
||||
callMethod?: (...args: FbqArgs) => void;
|
||||
queue?: FbqArgs[];
|
||||
loaded?: boolean;
|
||||
version?: string;
|
||||
push?: Fbq;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
fbq?: Fbq;
|
||||
_fbq?: Fbq;
|
||||
}
|
||||
}
|
||||
|
||||
let _initialized = false;
|
||||
|
||||
function ensureStub(): Fbq | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
if (window.fbq) return window.fbq;
|
||||
|
||||
const n: Fbq = function (...args: FbqArgs) {
|
||||
if (n.callMethod) {
|
||||
n.callMethod(...args);
|
||||
} else {
|
||||
n.queue?.push(args);
|
||||
}
|
||||
};
|
||||
n.push = n;
|
||||
n.loaded = true;
|
||||
n.version = "2.0";
|
||||
n.queue = [];
|
||||
window.fbq = n;
|
||||
if (!window._fbq) window._fbq = n;
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.async = true;
|
||||
script.src = "https://connect.facebook.net/en_US/fbevents.js";
|
||||
const first = document.getElementsByTagName("script")[0];
|
||||
first?.parentNode?.insertBefore(script, first);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
export function initMetaPixel(): void {
|
||||
const id = import.meta.env.VITE_META_PIXEL_ID;
|
||||
if (!id || _initialized) return;
|
||||
_initialized = true;
|
||||
const fbq = ensureStub();
|
||||
if (!fbq) return;
|
||||
fbq("init", id);
|
||||
// PageView is fired from the router root effect on every navigation
|
||||
// (including initial mount), so we don't fire it here to avoid duplicates.
|
||||
}
|
||||
|
||||
export function trackPageView(): void {
|
||||
if (!_initialized) return;
|
||||
window.fbq?.("track", "PageView");
|
||||
}
|
||||
|
||||
export function track(event: string, params?: Record<string, unknown>): void {
|
||||
if (!_initialized) return;
|
||||
window.fbq?.("track", event, params);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { initFaro } from "./lib/faro";
|
||||
import { initMetaPixel } from "./lib/meta-pixel";
|
||||
import { initPostHog } from "./lib/posthog";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import "./globals.css";
|
||||
@@ -13,6 +14,9 @@ initFaro();
|
||||
// Initialize product analytics
|
||||
initPostHog();
|
||||
|
||||
// Initialize Meta Pixel (ad attribution)
|
||||
initMetaPixel();
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { trackPageView as trackMetaPageView } from "@/lib/meta-pixel";
|
||||
import { capturePageView, identifyUser, resetUser } from "@/lib/posthog";
|
||||
import { Toaster } from "@/lib/toast";
|
||||
import { getUserSettings } from "@/lib/user-settings";
|
||||
@@ -93,6 +94,7 @@ function RootComponent() {
|
||||
// Pageview tracking
|
||||
useEffect(() => {
|
||||
capturePageView(location.pathname);
|
||||
trackMetaPageView();
|
||||
}, [location.pathname]);
|
||||
|
||||
// User identification
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import { signIn, signUp } from "@/lib/auth-client";
|
||||
import { startAction } from "@/lib/faro";
|
||||
import { track as trackMeta } from "@/lib/meta-pixel";
|
||||
import { capture } from "@/lib/posthog";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { Button } from "@sase/ui";
|
||||
@@ -39,6 +40,7 @@ function RegisterPage() {
|
||||
try {
|
||||
await signUp.email({ name, email, password, callbackURL: redirectUrl });
|
||||
capture("user_signed_up", { method: "email" });
|
||||
trackMeta("CompleteRegistration", { method: "email" });
|
||||
if (refCode.trim()) {
|
||||
try {
|
||||
await api.post("/referrals/apply", { code: refCode.trim().toUpperCase() });
|
||||
@@ -76,6 +78,7 @@ function RegisterPage() {
|
||||
onClick={() => {
|
||||
startAction("register", { method: "google" });
|
||||
capture("user_signed_up", { method: "google" });
|
||||
trackMeta("CompleteRegistration", { method: "google" });
|
||||
signIn.social({ provider: "google", callbackURL: redirectUrl });
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user