From 8c4a5f0ebe82b0e6133b36f4a6c8566f3b48b9c9 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Wed, 8 Oct 2025 01:55:37 +0200 Subject: [PATCH] add pixel and metrika --- public/funnels/soulmate.json | 3 +- public/funnels/soulmate_prod.json | 3 +- src/app/[funnelId]/layout.tsx | 77 +++++++ .../admin/builder/Sidebar/BuilderSidebar.tsx | 21 ++ .../builder/Sidebar/FunnelSettingsPanel.tsx | 97 --------- src/components/analytics/GoogleAnalytics.tsx | 47 ++++ src/components/analytics/PageViewTracker.tsx | 42 ++++ src/components/analytics/YandexMetrika.tsx | 65 ++++++ src/components/analytics/index.ts | 3 + src/components/providers/AppProviders.tsx | 7 +- src/components/providers/PixelsProvider.tsx | 34 ++- src/hooks/auth/useAuth.ts | 62 +++--- src/lib/funnel/bakedFunnels.ts | 6 +- src/lib/funnel/types.ts | 10 + src/lib/models/Funnel.ts | 2 + src/services/analytics/analyticsService.ts | 201 ++++++++++++++++++ src/services/analytics/index.ts | 1 + src/services/analytics/types.ts | 25 +++ 18 files changed, 554 insertions(+), 152 deletions(-) create mode 100644 src/app/[funnelId]/layout.tsx delete mode 100644 src/components/admin/builder/Sidebar/FunnelSettingsPanel.tsx create mode 100644 src/components/analytics/GoogleAnalytics.tsx create mode 100644 src/components/analytics/PageViewTracker.tsx create mode 100644 src/components/analytics/YandexMetrika.tsx create mode 100644 src/services/analytics/analyticsService.ts create mode 100644 src/services/analytics/index.ts create mode 100644 src/services/analytics/types.ts diff --git a/public/funnels/soulmate.json b/public/funnels/soulmate.json index 1719284..9413687 100644 --- a/public/funnels/soulmate.json +++ b/public/funnels/soulmate.json @@ -3,7 +3,8 @@ "id": "soulmate", "title": "Новая воронка", "description": "Описание новой воронки", - "firstScreenId": "onboarding" + "firstScreenId": "onboarding", + "yandexMetrikaId": "104471567" }, "defaultTexts": { "nextButton": "Continue" diff --git a/public/funnels/soulmate_prod.json b/public/funnels/soulmate_prod.json index 0df78bc..06b3d57 100644 --- a/public/funnels/soulmate_prod.json +++ b/public/funnels/soulmate_prod.json @@ -3,7 +3,8 @@ "id": "soulmate_prod", "title": "Soulmate V1", "description": "Soulmate", - "firstScreenId": "onboarding" + "firstScreenId": "onboarding", + "yandexMetrikaId": "104471567" }, "defaultTexts": { "nextButton": "Next", diff --git a/src/app/[funnelId]/layout.tsx b/src/app/[funnelId]/layout.tsx new file mode 100644 index 0000000..875bd94 --- /dev/null +++ b/src/app/[funnelId]/layout.tsx @@ -0,0 +1,77 @@ +import type { ReactNode } from "react"; +import { notFound } from "next/navigation"; +import { PixelsProvider } from "@/components/providers/PixelsProvider"; +import type { FunnelDefinition } from "@/lib/funnel/types"; +import { BAKED_FUNNELS } from "@/lib/funnel/bakedFunnels"; +import { IS_FULL_SYSTEM_BUILD } from "@/lib/runtime/buildVariant"; + +// Функция для загрузки воронки из базы данных +async function loadFunnelFromDatabase( + funnelId: string +): Promise { + if (!IS_FULL_SYSTEM_BUILD) { + return null; + } + + try { + const { default: connectMongoDB } = await import("@/lib/mongodb"); + const { default: FunnelModel } = await import("@/lib/models/Funnel"); + + await connectMongoDB(); + + const funnel = await FunnelModel.findOne({ + "funnelData.meta.id": funnelId, + status: { $in: ["published", "draft"] }, + }).lean(); + + if (funnel) { + return funnel.funnelData as FunnelDefinition; + } + + return null; + } catch (error) { + console.error( + `Failed to load funnel '${funnelId}' from database:`, + error + ); + return null; + } +} + +interface FunnelLayoutProps { + children: ReactNode; + params: Promise<{ + funnelId: string; + }>; +} + +export default async function FunnelLayout({ + children, + params, +}: FunnelLayoutProps) { + const { funnelId } = await params; + + let funnel: FunnelDefinition | null = null; + + // Сначала пытаемся загрузить из базы данных + funnel = await loadFunnelFromDatabase(funnelId); + + // Если не найдено в базе, пытаемся загрузить из JSON файлов + if (!funnel) { + funnel = BAKED_FUNNELS[funnelId] || null; + } + + // Если воронка не найдена ни в базе, ни в файлах + if (!funnel) { + notFound(); + } + + return ( + + {children} + + ); +} diff --git a/src/components/admin/builder/Sidebar/BuilderSidebar.tsx b/src/components/admin/builder/Sidebar/BuilderSidebar.tsx index f3d5e2a..3c08c27 100644 --- a/src/components/admin/builder/Sidebar/BuilderSidebar.tsx +++ b/src/components/admin/builder/Sidebar/BuilderSidebar.tsx @@ -409,6 +409,27 @@ export function BuilderSidebar() { +
+ + handleMetaChange("googleAnalyticsId", event.target.value) + } + className="placeholder:text-sm" + /> + + handleMetaChange("yandexMetrikaId", event.target.value) + } + className="placeholder:text-sm" + /> +
+
- state.screens.map((screen: BuilderScreen) => ({ - id: screen.id, - title: screen.title?.text, - })), - [state.screens] - ); - - const handleMetaChange = (field: keyof typeof state.meta, value: string) => { - dispatch({ type: "set-meta", payload: { [field]: value } }); - }; - - const handleFirstScreenChange = (value: string) => { - dispatch({ type: "set-meta", payload: { firstScreenId: value } }); - }; - - const handleDefaultTextsChange = ( - field: keyof NonNullable, - value: string - ) => { - dispatch({ type: "set-default-texts", payload: { [field]: value } }); - }; - - return ( - <> -
-
- handleMetaChange("id", e.target.value)} - /> - handleMetaChange("title", e.target.value)} - /> - handleMetaChange("description", e.target.value)} - /> - -
-
- -
-
- - handleDefaultTextsChange("nextButton", e.target.value) - } - /> - - handleDefaultTextsChange("continueButton", e.target.value) - } - /> -
-
- - ); -} diff --git a/src/components/analytics/GoogleAnalytics.tsx b/src/components/analytics/GoogleAnalytics.tsx new file mode 100644 index 0000000..96615ca --- /dev/null +++ b/src/components/analytics/GoogleAnalytics.tsx @@ -0,0 +1,47 @@ +"use client"; + +import Script from "next/script"; + +interface GoogleAnalyticsProps { + measurementId?: string; +} + +/** + * Google Analytics Integration Component + * + * Loads Google Analytics (GA4) tracking script dynamically based on measurement ID + * received from the funnel configuration. + * + * Page views are tracked by PageViewTracker component on route changes. + * + * @param measurementId - Google Analytics Measurement ID (e.g., "G-XXXXXXXXXX") + */ +export function GoogleAnalytics({ measurementId }: GoogleAnalyticsProps) { + if (!measurementId) { + return null; + } + + return ( + <> +