diff --git a/package.json b/package.json index ea410d2..89736de 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start -p 3001", - "lint": "next lint" + "lint": "next lint", + "lint:fix": "next lint --fix" }, "dependencies": { "@lottiefiles/dotlottie-react": "^0.14.1", @@ -34,4 +35,4 @@ "prettier": "^3.5.3", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/public/metrics-scripts/ym-script.js b/public/metrics-scripts/ym-script.js new file mode 100644 index 0000000..1751cbc --- /dev/null +++ b/public/metrics-scripts/ym-script.js @@ -0,0 +1,24 @@ +(function (m, e, t, r, i, k, a) { + m[i] = + m[i] || + function () { + (m[i].a = m[i].a || []).push(arguments); + }; + m[i].l = 1 * new Date(); + for (var j = 0; j < document.scripts.length; j++) { + if (document.scripts[j].src === r) { + return; + } + } + (k = e.createElement(t)), + (a = e.getElementsByTagName(t)[0]), + (k.async = 1), + (k.src = r), + a.parentNode.insertBefore(k, a); +})( + window, + document, + "script", + "https://cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js", + "ym" +); \ No newline at end of file diff --git a/src/app/[locale]/(core)/payment/success/Metrics.module.scss b/src/app/[locale]/(core)/payment/success/Metrics.module.scss new file mode 100644 index 0000000..979775c --- /dev/null +++ b/src/app/[locale]/(core)/payment/success/Metrics.module.scss @@ -0,0 +1,23 @@ +.button { + position: fixed; + bottom: calc(0dvh + 64px); + left: 50%; + transform: translate(-50%, 0); + opacity: 0; + pointer-events: none; + max-width: 400px; + width: calc(100dvw - 32px); + animation: fadeIn 0.5s ease-in-out 2s forwards; +} + +@keyframes fadeIn { + from { + opacity: 0; + pointer-events: none; + } + + to { + opacity: 1; + pointer-events: auto; + } +} \ No newline at end of file diff --git a/src/app/[locale]/(core)/payment/success/Metrics.tsx b/src/app/[locale]/(core)/payment/success/Metrics.tsx new file mode 100644 index 0000000..51984bd --- /dev/null +++ b/src/app/[locale]/(core)/payment/success/Metrics.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Script from "next/script"; +import { useTranslations } from "next-intl"; + +import { Button, Typography } from "@/components/ui"; +import { ROUTES } from "@/shared/constants/client-routes"; + +import styles from "./Metrics.module.scss"; + +interface MetricsProps { + fbPixels: string[]; + productPrice: string; + currency: string; +} + +export default function Metrics({ fbPixels, productPrice, currency }: MetricsProps) { + const t = useTranslations("Payment.Success"); + + const [isButtonVisible, setIsButtonVisible] = useState(false); + + const navigateToHome = () => { + window.location.href = ROUTES.home() + } + + // Yandex Metrica + useEffect(() => { + const interval = setInterval(() => { + if (typeof window.ym === 'function' && typeof window.klaviyo === 'object' && typeof window.gtag === 'function') { + try { + window.gtag('event', 'PaymentSuccess') + window.klaviyo.push(['track', "PaymentSuccess"]); + + window.ym(95799066, "init", { + clickmap: true, + trackLinks: true, + accurateTrackBounce: true, + webvisor: true, + }); + + window.ym(95799066, 'reachGoal', "PaymentSuccess", {}, () => { + console.log("Запрос отправлен"); + // deleteYm() + setIsButtonVisible(true); + }) + + } catch (e) { + console.error('YM error:', e) + } finally { + clearInterval(interval); + } + } + }, 200); + + return () => clearInterval(interval) + }, []); + + return <> + + {/* Klaviyo */} + {/* + + + {/* Yandex Metrica */} + + + {/* Google Analytics */} + + + {/* Facebook Pixel */} + {fbPixels.map((pixel) => ( + + ))} + + {isButtonVisible && + + } + ; +} \ No newline at end of file diff --git a/src/app/[locale]/(core)/payment/success/page.tsx b/src/app/[locale]/(core)/payment/success/page.tsx index ec03e9d..81dfb50 100644 --- a/src/app/[locale]/(core)/payment/success/page.tsx +++ b/src/app/[locale]/(core)/payment/success/page.tsx @@ -1,20 +1,34 @@ import { getTranslations } from "next-intl/server"; import { AnimatedInfoScreen, LottieAnimation } from "@/components/widgets"; -import { ROUTES } from "@/shared/constants/client-routes"; import { ELottieKeys } from "@/shared/constants/lottie"; -export default async function PaymentSuccess() { +import Metrics from "./Metrics"; + +export default async function PaymentSuccess({ searchParams }: { + searchParams: Promise<{ + [key: string]: string | undefined + }>; +}) { + const params = await searchParams; + + const fbPixels = params?.fb_pixels?.split(",") || []; + const productPrice = params?.price || "0"; + const currency = params?.currency || "USD"; + const t = await getTranslations("Payment.Success"); return ( - } - title={t("title")} - animationTime={0} - animationTexts={[]} - buttonText={t("button")} - nextRoute={ROUTES.home()} - /> + <> + } + title={t("title")} + /> + + ); } \ No newline at end of file diff --git a/src/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen.tsx b/src/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen.tsx index 7933e22..8b59ac1 100644 --- a/src/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen.tsx +++ b/src/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen.tsx @@ -8,10 +8,10 @@ import styles from "./AnimatedInfoScreen.module.scss"; interface AnimatedInfoScreenProps { lottieAnimation: React.ReactNode; title: string; - animationTime: number; + animationTime?: number; animationTexts?: string[]; - buttonText: string; - nextRoute: string; + buttonText?: string; + nextRoute?: string; } export default async function AnimatedInfoScreen({ @@ -29,15 +29,15 @@ export default async function AnimatedInfoScreen({ {title} - {!!animationTexts?.length && } - + {nextRoute && buttonText && {buttonText} - + } ) } \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 9e7a649..ec37068 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -20,4 +20,21 @@ export enum ERetainingFunnel { Green = "green", Purple = "purple", Stay50 = "stay50", +} + +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ym: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ymab: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + klaviyo: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fbq: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + CollectJS: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + gtag: any; + } } \ No newline at end of file