feat(ads): wire Meta Pixel for ad attribution
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:
2026-05-22 23:28:40 +03:00
parent 39ad0d4c2a
commit b4f1b2bd10
7 changed files with 87 additions and 0 deletions

View File

@@ -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();
}

View 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);
}

View File

@@ -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: {

View File

@@ -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

View File

@@ -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 });
}}
>