Ymetrika update (#245)

* update claude with message about components

* yandex metrika to ts

* rm env

* add .env example

* use account from env

* add tests
This commit is contained in:
tmshv authored and GitHub committed 2026-02-08 03:29:28 +07:00
1 parent 013578b518
commit 8c57976663
7 files changed
+60 -11

No files matched your search

+1 -1
View File
@@ -1,2 +1,2 @@
APP_CARD_DEFAULT_IMAGE=https://art.shlisselburg.org/static/img/main.jpg APP_CARD_DEFAULT_IMAGE=https://art.shlisselburg.org/static/img/main.jpg
NEXT_PUBLIC_YMETRIKA_ACCOUNT=XXX
+1
View File
@@ -21,3 +21,4 @@ dist
.vercel .vercel
.next .next
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
.env
+12
View File
@@ -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`. 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 <div>{value}</div>
}
```
## Architecture ## 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. **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.
+4 -2
View File
@@ -7,7 +7,7 @@ import "src/style/kazimir/video.css"
import App from "next/app" import App from "next/app"
import Head from "next/head" import Head from "next/head"
import Script from "next/script" import Script from "next/script"
import { YMetrika } from "src/components/YMetrika" import { YMetrika } from "@/components/YMetrika"
import { ThemeColor } from "@/ui/ThemeColor" import { ThemeColor } from "@/ui/ThemeColor"
export default class MyApp extends App { export default class MyApp extends App {
@@ -41,7 +41,9 @@ export default class MyApp extends App {
href="/feed.xml" href="/feed.xml"
/> />
<YMetrika account={24027460} /> {process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT && (
<YMetrika account={Number(process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT)} />
)}
<ThemeColor <ThemeColor
color="white" color="white"
+32
View File
@@ -0,0 +1,32 @@
import { describe, it, expect } from "vitest"
import { script } from "./YMetrika"
describe("YMetrika script", () => {
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")
})
})
@@ -1,6 +1,8 @@
import React from "react" export type YMetrikaProps = {
account: number
}
function script(account) { export function script(account: number) {
return ` return `
(function (d, w, c) { (function (d, w, c) {
(w[c] = w[c] || []).push(function() { (w[c] = w[c] || []).push(function() {
@@ -14,14 +16,14 @@ function script(account) {
}); });
} catch(e) { } } catch(e) { }
}); });
var n = d.getElementsByTagName("script")[0], var n = d.getElementsByTagName("script")[0],
s = d.createElement("script"), s = d.createElement("script"),
f = function () { n.parentNode.insertBefore(s, n); }; f = function () { n.parentNode.insertBefore(s, n); };
s.type = "text/javascript"; s.type = "text/javascript";
s.async = true; s.async = true;
s.src = "https://mc.yandex.ru/metrika/watch.js"; s.src = "https://mc.yandex.ru/metrika/watch.js";
if (w.opera == "[object Opera]") { if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false); d.addEventListener("DOMContentLoaded", f, false);
} else { f(); } } else { f(); }
@@ -29,6 +31,6 @@ function script(account) {
` `
} }
export const YMetrika = (props) => ( export function YMetrika({ account }: YMetrikaProps) {
<script dangerouslySetInnerHTML={{ __html: script(props.account) }} /> return <script dangerouslySetInnerHTML={{ __html: script(account) }} />
) }
+1 -1
View File
@@ -9,6 +9,6 @@ export default defineConfig({
}, },
}, },
test: { test: {
include: ["src/**/*.test.ts"], include: ["src/**/*.test.{ts,tsx}"],
}, },
}) })