fix cookies

This commit is contained in:
dev.daminik00 2025-10-08 00:39:30 +02:00
parent 6d7bbc766a
commit fb7c21a7f5
9 changed files with 186 additions and 2 deletions

View File

@ -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) => (
<Script
key={pixelId}
id={`fb-pixel-${pixelId}`}
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${pixelId}');
fbq('track', 'PageView');
`,
}}
/>
))}
</>
);
}

View File

@ -0,0 +1 @@
export { FacebookPixels } from "./FacebookPixels";

View File

@ -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 <FunnelProvider>{children}</FunnelProvider>;
return (
<PixelsProvider>
<FunnelProvider>{children}</FunnelProvider>
</PixelsProvider>
);
}

View File

@ -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<string[]>([]);
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 && <FacebookPixels pixels={pixels} />}
{children}
</>
);
}

View File

@ -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<IGetPixelsResponse> => {
return http.get<IGetPixelsResponse>(API_ROUTES.sessionPixels(), {
tags: ["session", "pixels"],
schema: GetPixelsResponseSchema,
revalidate: 3600, // Cache for 1 hour
query: payload,
});
};

View File

@ -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<typeof CreateSessionRequestSchema>;
export type IUpdateSessionRequest = z.infer<typeof UpdateSessionRequestSchema>;
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<typeof GetPixelsRequestSchema>;
export type IGetPixelsResponse = z.infer<typeof GetPixelsResponseSchema>;

View File

@ -28,10 +28,13 @@ export const useAuth = ({ funnelId, registrationData }: IUseAuthProps) => {
const [error, setError] = useState<string | null>(null);
const getAllCookies = useCallback(() => {
// Токены которые не должны передаваться на backend
const EXCLUDED_COOKIES = ["accessToken", "activeSessionId"];
const cookies: Record<string, string> = {};
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);
}
});

View File

@ -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"]),

View File

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