add cookie component
This commit is contained in:
parent
d7048b97c7
commit
690c4b20a9
6 changed files with 258 additions and 0 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import { CookieConsent } from "@/widgets/cookie-consent";
|
||||||
import { Cursor } from "@/widgets/cursor";
|
import { Cursor } from "@/widgets/cursor";
|
||||||
import { Preloader } from "@/widgets/preloader";
|
import { Preloader } from "@/widgets/preloader";
|
||||||
import { ScrollCounter } from "@/widgets/scroll-counter";
|
import { ScrollCounter } from "@/widgets/scroll-counter";
|
||||||
|
import { YandexMetrika } from "@/widgets/yandex-metrika";
|
||||||
import "@/static/globals.css";
|
import "@/static/globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
|
|
@ -31,6 +33,8 @@ const RootLayout = ({ children }: LayoutProps<"/">) => {
|
||||||
<Cursor />
|
<Cursor />
|
||||||
<ScrollCounter />
|
<ScrollCounter />
|
||||||
{children}
|
{children}
|
||||||
|
<CookieConsent />
|
||||||
|
<YandexMetrika />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
115
src/static/cookie-consent/cookie-consent.module.css
Normal file
115
src/static/cookie-consent/cookie-consent.module.css
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
.banner {
|
||||||
|
position: fixed;
|
||||||
|
right: var(--container-padding);
|
||||||
|
bottom: var(--space-md);
|
||||||
|
left: var(--container-padding);
|
||||||
|
z-index: 9980;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-md);
|
||||||
|
|
||||||
|
width: min(720px, 100%);
|
||||||
|
padding: 18px;
|
||||||
|
|
||||||
|
border: 1px solid var(--color-line);
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
background: rgba(10, 10, 10, 0.92);
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.42);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
-webkit-backdrop-filter: blur(18px);
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
animation: bannerIn 0.35s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
max-width: 52ch;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
color: var(--color-muted);
|
||||||
|
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0 22px;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.86);
|
||||||
|
border-radius: 100px;
|
||||||
|
|
||||||
|
background: var(--color-text);
|
||||||
|
color: var(--color-bg);
|
||||||
|
|
||||||
|
font: inherit;
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition:
|
||||||
|
transform 0.25s ease,
|
||||||
|
opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
opacity: 0.86;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bannerIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.content {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/widgets/cookie-consent/index.ts
Normal file
5
src/widgets/cookie-consent/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export {
|
||||||
|
COOKIE_CONSENT_EVENT,
|
||||||
|
COOKIE_CONSENT_KEY,
|
||||||
|
default as CookieConsent,
|
||||||
|
} from "./ui/CookieConsent";
|
||||||
48
src/widgets/cookie-consent/ui/CookieConsent.tsx
Normal file
48
src/widgets/cookie-consent/ui/CookieConsent.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import styles from "@/static/cookie-consent/cookie-consent.module.css";
|
||||||
|
|
||||||
|
export const COOKIE_CONSENT_KEY = "autli-cookie-consent";
|
||||||
|
export const COOKIE_CONSENT_EVENT = "autli-cookie-consent-accepted";
|
||||||
|
|
||||||
|
const CookieConsent = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
setVisible(window.localStorage.getItem(COOKIE_CONSENT_KEY) !== "accepted");
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const acceptCookies = () => {
|
||||||
|
window.localStorage.setItem(COOKIE_CONSENT_KEY, "accepted");
|
||||||
|
window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.banner} aria-label="Согласие на использование cookie">
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.text}>
|
||||||
|
<p className={styles.title}>Cookie-файлы</p>
|
||||||
|
<p className={styles.description}>
|
||||||
|
Я использую cookie и Яндекс Метрику, чтобы понимать, как работает
|
||||||
|
сайт, и улучшать его для посетителей.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" className={styles.button} onClick={acceptCookies}>
|
||||||
|
Принять
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CookieConsent;
|
||||||
1
src/widgets/yandex-metrika/index.ts
Normal file
1
src/widgets/yandex-metrika/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as YandexMetrika } from "./ui/YandexMetrika";
|
||||||
85
src/widgets/yandex-metrika/ui/YandexMetrika.tsx
Normal file
85
src/widgets/yandex-metrika/ui/YandexMetrika.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
import {
|
||||||
|
COOKIE_CONSENT_EVENT,
|
||||||
|
COOKIE_CONSENT_KEY,
|
||||||
|
} from "@/widgets/cookie-consent";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
ym?: (
|
||||||
|
counterId: number,
|
||||||
|
action: "init" | "hit",
|
||||||
|
options?: Record<string, unknown> | string
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterId = Number(process.env.NEXT_PUBLIC_YANDEX_METRIKA_ID);
|
||||||
|
|
||||||
|
const YandexMetrika = () => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [accepted, setAccepted] = useState(false);
|
||||||
|
const previousUrl = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
setAccepted(window.localStorage.getItem(COOKIE_CONSENT_KEY) === "accepted");
|
||||||
|
}, 0);
|
||||||
|
const onAccepted = () => setAccepted(true);
|
||||||
|
|
||||||
|
window.addEventListener(COOKIE_CONSENT_EVENT, onAccepted);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
window.removeEventListener(COOKIE_CONSENT_EVENT, onAccepted);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!accepted || !counterId || !window.ym) return;
|
||||||
|
|
||||||
|
const url = window.location.href;
|
||||||
|
|
||||||
|
if (previousUrl.current === null) {
|
||||||
|
previousUrl.current = url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousUrl.current === url) return;
|
||||||
|
|
||||||
|
previousUrl.current = url;
|
||||||
|
window.ym(counterId, "hit", url);
|
||||||
|
}, [accepted, pathname]);
|
||||||
|
|
||||||
|
if (!accepted || !counterId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Script
|
||||||
|
id="yandex-metrika"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(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();
|
||||||
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
||||||
|
})(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||||
|
ym(${counterId}, "init", {
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
clickmap: true,
|
||||||
|
defer: true,
|
||||||
|
trackLinks: true,
|
||||||
|
webvisor: true
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default YandexMetrika;
|
||||||
Loading…
Add table
Reference in a new issue