diff --git a/.env b/.env.example similarity index 68% rename from .env rename to .env.example index 537be565..380aaaba 100644 --- a/.env +++ b/.env.example @@ -1,2 +1,2 @@ APP_CARD_DEFAULT_IMAGE=https://art.shlisselburg.org/static/img/main.jpg - +NEXT_PUBLIC_YMETRIKA_ACCOUNT=XXX diff --git a/.gitignore b/.gitignore index 817645cf..35ab2d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ dist .vercel .next tsconfig.tsbuildinfo +.env diff --git a/CLAUDE.md b/CLAUDE.md index a9fe8cea..7b9608d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,18 @@ Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue n ESLint 9 flat config (`eslint.config.js`) using `FlatCompat` from `@eslint/eslintrc` to bridge `eslint-config-next` (which doesn't support native flat config in Next.js 15.x). Extends `next/core-web-vitals`. Enforces: no semicolons, double quotes, 4-space indentation, always-multiline trailing commas, `eol-last`. +**React components:** Use regular function declarations (not arrow functions) and `type` for props. Do not use `React.FC`. + +```tsx +export type MyComponentProps = { + value: string +} + +export function MyComponent({ value }: MyComponentProps) { + return
{value}
+} +``` + ## Architecture **Pages Router with SSG/ISR:** All pages use `getStaticProps`/`getStaticPaths` with 30-second revalidation. The catch-all route `pages/[...slug].tsx` handles all dynamic content pages. diff --git a/pages/_app.tsx b/pages/_app.tsx index 0c71cc1d..4554ee69 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -7,7 +7,7 @@ import "src/style/kazimir/video.css" import App from "next/app" import Head from "next/head" import Script from "next/script" -import { YMetrika } from "src/components/YMetrika" +import { YMetrika } from "@/components/YMetrika" import { ThemeColor } from "@/ui/ThemeColor" export default class MyApp extends App { @@ -41,7 +41,9 @@ export default class MyApp extends App { href="/feed.xml" /> - + {process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT && ( + + )} { + it("should embed account id into metrika counter variable", () => { + const result = script(99999) + expect(result).toContain("w.yaCounter99999") + }) + + it("should embed account id into metrika config", () => { + const result = script(99999) + expect(result).toContain("id:99999") + }) + + it("should include metrika watch.js script url", () => { + const result = script(99999) + expect(result).toContain("https://mc.yandex.ru/metrika/watch.js") + }) + + it("should use yandex_metrika_callbacks", () => { + const result = script(99999) + expect(result).toContain("yandex_metrika_callbacks") + }) + + it("should produce different output for different accounts", () => { + const a = script(11111) + const b = script(22222) + expect(a).toContain("yaCounter11111") + expect(b).toContain("yaCounter22222") + expect(a).not.toContain("yaCounter22222") + }) +}) diff --git a/src/components/YMetrika.jsx b/src/components/YMetrika.tsx similarity index 80% rename from src/components/YMetrika.jsx rename to src/components/YMetrika.tsx index 33d61f26..14c3ee0c 100644 --- a/src/components/YMetrika.jsx +++ b/src/components/YMetrika.tsx @@ -1,6 +1,8 @@ -import React from "react" +export type YMetrikaProps = { + account: number +} -function script(account) { +export function script(account: number) { return ` (function (d, w, c) { (w[c] = w[c] || []).push(function() { @@ -14,14 +16,14 @@ function script(account) { }); } catch(e) { } }); - + var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () { n.parentNode.insertBefore(s, n); }; s.type = "text/javascript"; s.async = true; s.src = "https://mc.yandex.ru/metrika/watch.js"; - + if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } @@ -29,6 +31,6 @@ function script(account) { ` } -export const YMetrika = (props) => ( -