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 type { ReactNode } from "react";
|
||||||
|
|
||||||
import { FunnelProvider } from "@/lib/funnel/FunnelProvider";
|
import { FunnelProvider } from "@/lib/funnel/FunnelProvider";
|
||||||
|
import { PixelsProvider } from "@/components/providers/PixelsProvider";
|
||||||
|
|
||||||
interface AppProvidersProps {
|
interface AppProvidersProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppProviders({ children }: AppProvidersProps) {
|
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,
|
IUpdateSessionRequest,
|
||||||
IUpdateSessionResponse,
|
IUpdateSessionResponse,
|
||||||
UpdateSessionResponseSchema,
|
UpdateSessionResponseSchema,
|
||||||
|
IGetPixelsRequest,
|
||||||
|
IGetPixelsResponse,
|
||||||
|
GetPixelsResponseSchema,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { API_ROUTES } from "@/shared/constants/api-routes";
|
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(),
|
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 ICreateSessionRequest = z.infer<typeof CreateSessionRequestSchema>;
|
||||||
export type IUpdateSessionRequest = z.infer<typeof UpdateSessionRequestSchema>;
|
export type IUpdateSessionRequest = z.infer<typeof UpdateSessionRequestSchema>;
|
||||||
export type ICreateSessionResponse = z.infer<
|
export type ICreateSessionResponse = z.infer<
|
||||||
@ -43,3 +56,5 @@ export type ICreateSessionResponse = z.infer<
|
|||||||
export type IUpdateSessionResponse = z.infer<
|
export type IUpdateSessionResponse = z.infer<
|
||||||
typeof UpdateSessionResponseSchema
|
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 [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const getAllCookies = useCallback(() => {
|
const getAllCookies = useCallback(() => {
|
||||||
|
// Токены которые не должны передаваться на backend
|
||||||
|
const EXCLUDED_COOKIES = ["accessToken", "activeSessionId"];
|
||||||
|
|
||||||
const cookies: Record<string, string> = {};
|
const cookies: Record<string, string> = {};
|
||||||
document.cookie.split(";").forEach((cookie) => {
|
document.cookie.split(";").forEach((cookie) => {
|
||||||
const [name, value] = cookie.trim().split("=");
|
const [name, value] = cookie.trim().split("=");
|
||||||
if (name && value) {
|
if (name && value && !EXCLUDED_COOKIES.includes(name)) {
|
||||||
cookies[name] = decodeURIComponent(value);
|
cookies[name] = decodeURIComponent(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const createRoute = (
|
|||||||
|
|
||||||
export const API_ROUTES = {
|
export const API_ROUTES = {
|
||||||
session: (id?: string) => createRoute(["session", id], ROOT_ROUTE_V2),
|
session: (id?: string) => createRoute(["session", id], ROOT_ROUTE_V2),
|
||||||
|
sessionPixels: () => createRoute(["session", "pixels"], ROOT_ROUTE_V2),
|
||||||
authorization: () => createRoute(["users", "auth"]),
|
authorization: () => createRoute(["users", "auth"]),
|
||||||
paymentCheckout: () => createRoute(["payment", "checkout"], ROOT_ROUTE_V2),
|
paymentCheckout: () => createRoute(["payment", "checkout"], ROOT_ROUTE_V2),
|
||||||
paymentSingleCheckout: () => createRoute(["payment", "checkout"]),
|
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