diff --git a/src/components/analytics/FacebookPixels.tsx b/src/components/analytics/FacebookPixels.tsx
new file mode 100644
index 0000000..7dc4766
--- /dev/null
+++ b/src/components/analytics/FacebookPixels.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import Script from "next/script";
+
+interface FacebookPixelsProps {
+ pixels?: string[];
+}
+
+/**
+ * Facebook Pixel Integration Component
+ *
+ * Loads Facebook pixel tracking scripts dynamically based on pixel IDs
+ * received from the backend. Each pixel is initialized with PageView tracking.
+ *
+ * @param pixels - Array of Facebook pixel IDs to load
+ */
+export function FacebookPixels({ pixels }: FacebookPixelsProps) {
+ if (!pixels || pixels.length === 0) {
+ return null;
+ }
+
+ return (
+ <>
+ {pixels.map((pixelId) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/components/analytics/index.ts b/src/components/analytics/index.ts
new file mode 100644
index 0000000..58bcaae
--- /dev/null
+++ b/src/components/analytics/index.ts
@@ -0,0 +1 @@
+export { FacebookPixels } from "./FacebookPixels";
diff --git a/src/components/providers/AppProviders.tsx b/src/components/providers/AppProviders.tsx
index c78c50d..abe8186 100644
--- a/src/components/providers/AppProviders.tsx
+++ b/src/components/providers/AppProviders.tsx
@@ -3,11 +3,16 @@
import type { ReactNode } from "react";
import { FunnelProvider } from "@/lib/funnel/FunnelProvider";
+import { PixelsProvider } from "@/components/providers/PixelsProvider";
interface AppProvidersProps {
children: ReactNode;
}
export function AppProviders({ children }: AppProvidersProps) {
- return {children};
+ return (
+
+ {children}
+
+ );
}
diff --git a/src/components/providers/PixelsProvider.tsx b/src/components/providers/PixelsProvider.tsx
new file mode 100644
index 0000000..bfa57be
--- /dev/null
+++ b/src/components/providers/PixelsProvider.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { useEffect, useState, type ReactNode } from "react";
+import { FacebookPixels } from "@/components/analytics/FacebookPixels";
+import { getPixels } from "@/entities/session/actions";
+import { getSourceByPathname } from "@/shared/utils/source";
+
+interface PixelsProviderProps {
+ children: ReactNode;
+}
+
+/**
+ * Pixels Provider Component
+ *
+ * Loads Facebook pixels from backend and renders them.
+ * Pixels are loaded once on mount and cached in localStorage.
+ *
+ * Flow:
+ * 1. Check localStorage for cached pixels
+ * 2. If not cached, request from backend
+ * 3. Save to localStorage and render
+ */
+export function PixelsProvider({ children }: PixelsProviderProps) {
+ const [pixels, setPixels] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const loadPixels = async () => {
+ try {
+ // Check localStorage first
+ const cachedPixels = localStorage.getItem("fb_pixels");
+ if (cachedPixels) {
+ const parsed = JSON.parse(cachedPixels);
+ setPixels(parsed);
+ setIsLoading(false);
+ return;
+ }
+
+ // Load from backend
+ const locale = "en"; // TODO: Get from context or config
+ const source = getSourceByPathname();
+ const domain = window.location.hostname;
+
+ const response = await getPixels({
+ domain,
+ source,
+ locale,
+ });
+
+ const pixelIds = response?.data?.fb || [];
+
+ // Save to localStorage
+ localStorage.setItem("fb_pixels", JSON.stringify(pixelIds));
+
+ setPixels(pixelIds);
+ } catch (error) {
+ console.error("Failed to load Facebook pixels:", error);
+ setPixels([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadPixels();
+ }, []);
+
+ return (
+ <>
+ {!isLoading && }
+ {children}
+ >
+ );
+}
diff --git a/src/entities/session/actions.ts b/src/entities/session/actions.ts
index 118a29f..16f01ab 100644
--- a/src/entities/session/actions.ts
+++ b/src/entities/session/actions.ts
@@ -6,6 +6,9 @@ import {
IUpdateSessionRequest,
IUpdateSessionResponse,
UpdateSessionResponseSchema,
+ IGetPixelsRequest,
+ IGetPixelsResponse,
+ GetPixelsResponseSchema,
} from "./types";
import { API_ROUTES } from "@/shared/constants/api-routes";
@@ -33,3 +36,14 @@ export const updateSession = async (
}
);
};
+
+export const getPixels = async (
+ payload: IGetPixelsRequest
+): Promise => {
+ return http.get(API_ROUTES.sessionPixels(), {
+ tags: ["session", "pixels"],
+ schema: GetPixelsResponseSchema,
+ revalidate: 3600, // Cache for 1 hour
+ query: payload,
+ });
+};
diff --git a/src/entities/session/types.ts b/src/entities/session/types.ts
index 293147c..5ff76ff 100644
--- a/src/entities/session/types.ts
+++ b/src/entities/session/types.ts
@@ -35,6 +35,19 @@ export const UpdateSessionResponseSchema = z.object({
message: z.string(),
});
+export const GetPixelsRequestSchema = z.object({
+ domain: z.string(),
+ source: z.string(),
+ locale: z.string(),
+});
+
+export const GetPixelsResponseSchema = z.object({
+ status: z.string(),
+ data: z.object({
+ fb: z.array(z.string()).optional(),
+ }),
+});
+
export type ICreateSessionRequest = z.infer;
export type IUpdateSessionRequest = z.infer;
export type ICreateSessionResponse = z.infer<
@@ -43,3 +56,5 @@ export type ICreateSessionResponse = z.infer<
export type IUpdateSessionResponse = z.infer<
typeof UpdateSessionResponseSchema
>;
+export type IGetPixelsRequest = z.infer;
+export type IGetPixelsResponse = z.infer;
diff --git a/src/hooks/auth/useAuth.ts b/src/hooks/auth/useAuth.ts
index 86417ce..93adbc2 100644
--- a/src/hooks/auth/useAuth.ts
+++ b/src/hooks/auth/useAuth.ts
@@ -28,10 +28,13 @@ export const useAuth = ({ funnelId, registrationData }: IUseAuthProps) => {
const [error, setError] = useState(null);
const getAllCookies = useCallback(() => {
+ // Токены которые не должны передаваться на backend
+ const EXCLUDED_COOKIES = ["accessToken", "activeSessionId"];
+
const cookies: Record = {};
document.cookie.split(";").forEach((cookie) => {
const [name, value] = cookie.trim().split("=");
- if (name && value) {
+ if (name && value && !EXCLUDED_COOKIES.includes(name)) {
cookies[name] = decodeURIComponent(value);
}
});
diff --git a/src/shared/constants/api-routes.ts b/src/shared/constants/api-routes.ts
index 8feda64..e6e3111 100644
--- a/src/shared/constants/api-routes.ts
+++ b/src/shared/constants/api-routes.ts
@@ -10,6 +10,7 @@ const createRoute = (
export const API_ROUTES = {
session: (id?: string) => createRoute(["session", id], ROOT_ROUTE_V2),
+ sessionPixels: () => createRoute(["session", "pixels"], ROOT_ROUTE_V2),
authorization: () => createRoute(["users", "auth"]),
paymentCheckout: () => createRoute(["payment", "checkout"], ROOT_ROUTE_V2),
paymentSingleCheckout: () => createRoute(["payment", "checkout"]),
diff --git a/src/shared/utils/source.ts b/src/shared/utils/source.ts
new file mode 100644
index 0000000..fc6b8be
--- /dev/null
+++ b/src/shared/utils/source.ts
@@ -0,0 +1,25 @@
+/**
+ * Get source identifier based on current pathname
+ * Used for pixel loading and analytics tracking
+ *
+ * @param pathname - Current window pathname (defaults to window.location.pathname)
+ * @returns Source identifier string
+ */
+export function getSourceByPathname(pathname?: string): string {
+ if (typeof window === "undefined") {
+ return "unknown";
+ }
+
+ const currentPath = pathname ?? window.location.pathname;
+
+ // Extract funnel ID from pathname
+ // Expected format: /[funnelId] or /[funnelId]/[screenId]
+ const segments = currentPath.split("/").filter(Boolean);
+
+ if (segments.length > 0) {
+ // Return first segment as funnel ID
+ return segments[0];
+ }
+
+ return "home";
+}