fix cookies
This commit is contained in:
parent
6d7bbc766a
commit
fb7c21a7f5
47
src/components/analytics/FacebookPixels.tsx
Normal file
47
src/components/analytics/FacebookPixels.tsx
Normal 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');
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
src/components/analytics/index.ts
Normal file
1
src/components/analytics/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { FacebookPixels } from "./FacebookPixels";
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
73
src/components/providers/PixelsProvider.tsx
Normal file
73
src/components/providers/PixelsProvider.tsx
Normal 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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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"]),
|
||||
|
||||
25
src/shared/utils/source.ts
Normal file
25
src/shared/utils/source.ts
Normal 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";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user