From f2043e406353e3ccb29c39768067e32c95ae8006 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Fri, 23 May 2025 14:41:38 +0000 Subject: [PATCH 01/14] =?UTF-8?q?=E2=9E=95=20chore(deps):=20analytics=20an?= =?UTF-8?q?d=20@analytics/google-analytics=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 122 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 124 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3ee2dee..4ef05c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,14 @@ "name": "m-tartari.github.io", "version": "1.0.0-alpha.0", "dependencies": { + "@analytics/google-analytics": "^1.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.2.5", "@mui/icons-material": "^7.1.0", "@mui/material": "^7.0.0", "@vitejs/plugin-react-swc": "^3.10.0", + "analytics": "^0.8.16", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.1", @@ -70,6 +72,84 @@ "node": ">=6.0.0" } }, + "node_modules/@analytics/cookie-utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@analytics/cookie-utils/-/cookie-utils-0.2.12.tgz", + "integrity": "sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==", + "license": "MIT", + "dependencies": { + "@analytics/global-storage-utils": "^0.1.7" + } + }, + "node_modules/@analytics/core": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/@analytics/core/-/core-0.12.17.tgz", + "integrity": "sha512-GMxRm5Dp3Wam/w5NNvqNKMO6zWecozbVv21Kn4WhftCx6OjJI7zMlVtiLpjGjxa0RRZfVG80YhupF0Qh9XL2gw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/davidwells" + } + ], + "license": "MIT", + "dependencies": { + "@analytics/global-storage-utils": "^0.1.7", + "@analytics/type-utils": "^0.6.2", + "analytics-utils": "^1.0.14" + } + }, + "node_modules/@analytics/global-storage-utils": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@analytics/global-storage-utils/-/global-storage-utils-0.1.7.tgz", + "integrity": "sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==", + "license": "MIT", + "dependencies": { + "@analytics/type-utils": "^0.6.2" + } + }, + "node_modules/@analytics/google-analytics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@analytics/google-analytics/-/google-analytics-1.1.0.tgz", + "integrity": "sha512-i8uGyELMtwEUAf3GNWNLNBzhRvReDn1RUxvMdMhjUA7+GNGxPOM4kkzFfv3giQXKNxTEjfsh75kqNcscbJsuaA==", + "license": "MIT" + }, + "node_modules/@analytics/localstorage-utils": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@analytics/localstorage-utils/-/localstorage-utils-0.1.10.tgz", + "integrity": "sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==", + "license": "MIT", + "dependencies": { + "@analytics/global-storage-utils": "^0.1.7" + } + }, + "node_modules/@analytics/session-storage-utils": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@analytics/session-storage-utils/-/session-storage-utils-0.0.7.tgz", + "integrity": "sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==", + "license": "MIT", + "dependencies": { + "@analytics/global-storage-utils": "^0.1.7" + } + }, + "node_modules/@analytics/storage-utils": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@analytics/storage-utils/-/storage-utils-0.4.2.tgz", + "integrity": "sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==", + "license": "MIT", + "dependencies": { + "@analytics/cookie-utils": "^0.2.12", + "@analytics/global-storage-utils": "^0.1.7", + "@analytics/localstorage-utils": "^0.1.10", + "@analytics/session-storage-utils": "^0.0.7", + "@analytics/type-utils": "^0.6.2" + } + }, + "node_modules/@analytics/type-utils": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@analytics/type-utils/-/type-utils-0.6.2.tgz", + "integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==", + "license": "MIT" + }, "node_modules/@asamuzakjp/css-color": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.2.tgz", @@ -2421,6 +2501,13 @@ "dev": true, "peer": true }, + "node_modules/@types/dlv": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/dlv/-/dlv-1.1.5.tgz", + "integrity": "sha512-JHOWNfiWepAhfwlSw17kiWrWrk6od2dEQgHltJw9AS0JPFoLZJBge5+Dnil2NfdjAvJ/+vGSX60/BRW20PpUXw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -2913,6 +3000,35 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/analytics": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/analytics/-/analytics-0.8.16.tgz", + "integrity": "sha512-LEFQ47G9V1zVp9WIh2xhnbmSFEJq+WEzSv6voJ5uba88lefiIIYeG2nq87gFu83ocz1qtb9u7XgeaKKVBbbgWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/davidwells" + } + ], + "license": "MIT", + "dependencies": { + "@analytics/core": "^0.12.17", + "@analytics/storage-utils": "^0.4.2" + } + }, + "node_modules/analytics-utils": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/analytics-utils/-/analytics-utils-1.0.14.tgz", + "integrity": "sha512-9v0kPd8v0GuBvfQcg5BO48AElaEAr9IXMAfJWXYMAhrD3QprgozEIUgMp/de0vS136PUOBB+10XQH9eBgBmfMw==", + "license": "MIT", + "dependencies": { + "@analytics/type-utils": "^0.6.2", + "dlv": "^1.1.3" + }, + "peerDependencies": { + "@types/dlv": "^1.0.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3292,6 +3408,12 @@ "node": ">=6" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", diff --git a/package.json b/package.json index efe6cab..241e2f3 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,14 @@ "test:ci-workflow": "npm ci --omit=dev && npm run build:prod && npm ci && npm run build && npm run lint && npm run test:coverage" }, "dependencies": { + "@analytics/google-analytics": "^1.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.2.5", "@mui/icons-material": "^7.1.0", "@mui/material": "^7.0.0", "@vitejs/plugin-react-swc": "^3.10.0", + "analytics": "^0.8.16", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.1", From 8ed278df190570aec2d2256d9bf02d59cbfcb697 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Fri, 23 May 2025 14:43:47 +0000 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=94=A7=20chore(env):=20add=20`VITE?= =?UTF-8?q?=5FGA=5FMEASUREMENT=5FID`=20to=20env=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + .gitignore | 7 +++++++ src/vite-env.d.ts | 8 ++++++++ 3 files changed, 16 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c0f1740 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX diff --git a/.gitignore b/.gitignore index 6d036fa..f3de697 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,13 @@ dist dist-ssr *.local +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + # Editor directories and files # .vscode/* !.vscode/extensions.json diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..c8142b1 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly VITE_GA_MEASUREMENT_ID: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} From 820d43ec3d8e44ddbc3caecc2794b85e9e4b8659 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Tue, 27 May 2025 09:57:08 +0000 Subject: [PATCH 03/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(Toolbar):?= =?UTF-8?q?=20convert=20title=20to=20home=20page=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/molecules/Toolbar/index.tsx | 33 +++++++++++++++------- src/components/templates/Page/Page.tsx | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/components/molecules/Toolbar/index.tsx b/src/components/molecules/Toolbar/index.tsx index d7954b1..13be7a6 100644 --- a/src/components/molecules/Toolbar/index.tsx +++ b/src/components/molecules/Toolbar/index.tsx @@ -17,6 +17,7 @@ import { Typography, styled, useScrollTrigger, + useTheme, } from '@mui/material' import MenuIcon from '@mui/icons-material/Menu' @@ -43,6 +44,7 @@ interface CustomToolbarProps extends AppBarProps { } const CustomToolbar: React.FC = ({ drawerWidth = 240, window, ...props }) => { const [mobileOpen, setMobileOpen] = React.useState(false) + const theme = useTheme() const trigger = useScrollTrigger({ disableHysteresis: true, @@ -66,11 +68,6 @@ const CustomToolbar: React.FC = ({ drawerWidth = 240, window - {/* - - Home - - */} @@ -96,10 +93,15 @@ const CustomToolbar: React.FC = ({ drawerWidth = 240, window elevation={0} {...props} sx={{ - backgroundColor: trigger ? theme => (theme.palette.mode === 'dark' ? 'background.paper' : undefined) : 'transparent', + backgroundColor: trigger ? (theme.palette.mode === 'dark' ? 'background.paper' : undefined) : 'transparent', ...props.sx, }}> - + = ({ drawerWidth = 240, window sx={{ mr: 2, display: { md: 'none' }, position: 'absolute', backgroundColor: 'primary' }}> - - Michele Tartari - + ({ + [theme.breakpoints.down('md')]: { + mx: 'auto', + }, + [theme.breakpoints.up('md')]: { + mr: 'auto', + }, + })}> + Michele Tartari + Home diff --git a/src/components/templates/Page/Page.tsx b/src/components/templates/Page/Page.tsx index 1793612..c2538ae 100644 --- a/src/components/templates/Page/Page.tsx +++ b/src/components/templates/Page/Page.tsx @@ -52,7 +52,7 @@ const Page: React.FC = ({ title, children, ...props }) => { minHeight: '80vh', mx: 'auto', maxWidth: { xs: '100%', md: 'calc(100% - 4em)' }, - width: '70em', + width: '80em', ...props.sx, }}> {children} From 18c81f951c7a7b4b49c554c84385cb32e2e06aba Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:11:20 +0000 Subject: [PATCH 04/14] =?UTF-8?q?=E2=9E=95=20chore(deps):=20add=20use-anal?= =?UTF-8?q?ytics=20and=20@types/use-analytics=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 35 +++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 37 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4ef05c3..2522cee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-dom": "^19.1.0", "react-router-dom": "^7.6.1", "react-scroll-parallax": "^3.4.5", + "use-analytics": "^1.1.0", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" }, @@ -30,6 +31,7 @@ "@testing-library/user-event": "^14.6.1", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", + "@types/use-analytics": "^0.0.3", "@typescript-eslint/eslint-plugin": "^8.33.0", "@typescript-eslint/parser": "^8.32.1", "@vitest/coverage-istanbul": "^3.1.4", @@ -2559,6 +2561,17 @@ "@types/react": "*" } }, + "node_modules/@types/use-analytics": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-analytics/-/use-analytics-0.0.3.tgz", + "integrity": "sha512-s/b/R8q/pKj1INYyRWw46aI4rZFr6p0yW80EdD47FBytTGXPNJFOaPlvw2Atsrf+k6S4idl1nN5kXiLBl1MrJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "analytics": "^0.8.1" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", @@ -5495,6 +5508,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5741,6 +5760,22 @@ "punycode": "^2.1.0" } }, + "node_modules/use-analytics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-analytics/-/use-analytics-1.1.0.tgz", + "integrity": "sha512-1PMT5doiCoTm6ng9PscCHuFgl8vYNSFgGizXuFlmEBqnaRJ+KnygKnETUbBJ1W7MJPWQeHwA5Mys9t7b4P8ijw==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.2", + "tiny-invariant": "^1.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/package.json b/package.json index 241e2f3..d7af18b 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-dom": "^19.1.0", "react-router-dom": "^7.6.1", "react-scroll-parallax": "^3.4.5", + "use-analytics": "^1.1.0", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" }, @@ -41,6 +42,7 @@ "@testing-library/user-event": "^14.6.1", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", + "@types/use-analytics": "^0.0.3", "@typescript-eslint/eslint-plugin": "^8.33.0", "@typescript-eslint/parser": "^8.32.1", "@vitest/coverage-istanbul": "^3.1.4", From 5d1e609b67365e96332a04ec3068698d930ae729 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:14:22 +0000 Subject: [PATCH 05/14] =?UTF-8?q?=F0=9F=91=B7=20chore(ci):=20add=20analyti?= =?UTF-8?q?cs=20patterns=20to=20dependabot=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3b10c79..e14bf47 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -45,3 +45,9 @@ updates: patterns: - 'lodash*' - '@types/lodash*' + analytics: + patterns: + - 'analytics' + - '@analytics/*' + - 'use-analytics' + - '@types/use-analytics' From 642dff328bb9b727f1d4a5dc4bf882008f7621ae Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:15:21 +0000 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=94=A7=20chore(devcontainer):=20fav?= =?UTF-8?q?our=20ESM=20import=20notation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 2 ++ .vscode/settings.json | 6 ++++++ tsconfig.base.json | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 53b5602..47405d7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -74,6 +74,8 @@ "diffEditor.ignoreTrimWhitespace": false, "diffEditor.wordWrap": "off", // Javascript + "javascript.preferences.importModuleSpecifierEnding": "js", + "typescript.preferences.importModuleSpecifierEnding": "js", "javascript.updateImportsOnFileMove.enabled": "always", "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.updateImportsOnFileMove.enabled": "always", diff --git a/.vscode/settings.json b/.vscode/settings.json index d4500b7..15c6958 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,4 +69,10 @@ "**/node_modules" ], "vitest.enable": true, + "javascript.preferences.importModuleSpecifierEnding": "js", + "typescript.preferences.importModuleSpecifierEnding": "js", + "cSpell.words": [ + "SRDF", + "URDF" + ], } diff --git a/tsconfig.base.json b/tsconfig.base.json index 8e7753a..c0e40be 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,7 +14,7 @@ "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", - "allowImportingTsExtensions": true, + "allowImportingTsExtensions": false, "resolveJsonModule": true, "isolatedModules": true, "moduleDetection": "force", From fc67e227724aa48fd2c454823d68e70d6597510b Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:24:53 +0000 Subject: [PATCH 07/14] =?UTF-8?q?=E2=9E=95=20chore(deps-dev):=20add=20@typ?= =?UTF-8?q?es/node=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2522cee..e426a63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.15.23", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "@types/use-analytics": "^0.0.3", @@ -2523,6 +2524,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.15.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.23.tgz", + "integrity": "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -5720,6 +5731,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", diff --git a/package.json b/package.json index d7af18b..224f072 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.15.23", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "@types/use-analytics": "^0.0.3", From bf6d945c61e4cf4e6f365cb36a0a5db8fbd21258 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:26:52 +0000 Subject: [PATCH 08/14] =?UTF-8?q?=E2=9C=A8=20feat(utils):=20add=20analytic?= =?UTF-8?q?s=20util?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/utils/analytics/index.ts | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/utils/analytics/index.ts diff --git a/src/components/utils/analytics/index.ts b/src/components/utils/analytics/index.ts new file mode 100644 index 0000000..1dee12c --- /dev/null +++ b/src/components/utils/analytics/index.ts @@ -0,0 +1,44 @@ +import Analytics from 'analytics' +import googleAnalytics from '@analytics/google-analytics' + +// Get measurement ID from environment variables +const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID || '' +export const COOKIE_CONSENT_KEY = 'm-tartari-cookie-consent' +export const ANALYTICS_PLUGINS = ['google-analytics'] + +// Log warning if measurement ID is not set +if (!GA_MEASUREMENT_ID) { + console.warn('Google Analytics Measurement ID is not set. Analytics will not track events.') +} + +const analyticsInstance = Analytics({ + app: 'm-tartari-portfolio', + plugins: [ + googleAnalytics({ + measurementIds: [GA_MEASUREMENT_ID], + // Start disabled for GDPR compliance + enabled: false, + }), + ], +}) + +// Initialize analytics +analyticsInstance.ready(() => { + if (process.env.NODE_ENV !== 'test') console.info('Analytics system initialized') +}) + +// Track page views +export const trackPageView = (path: string) => { + analyticsInstance.page({ + path, + title: document.title, + }) +} + +// Track custom events +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const trackEvent = (event: string, payload: Record = {}) => { + analyticsInstance.track(event, payload) +} + +export default analyticsInstance From e0f90d5d8767d0f9f3019d1e2363f1bfc8ca1354 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:29:48 +0000 Subject: [PATCH 09/14] =?UTF-8?q?=E2=9C=A8=20feat(App):=20add=20page=20tra?= =?UTF-8?q?cking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.test.tsx | 7 ++ src/App.tsx | 68 ++++++++++--------- src/components/molecules/Toolbar/index.tsx | 2 +- .../templates/RouteTracker/index.tsx | 19 ++++++ src/components/templates/index.ts | 3 +- src/main.tsx | 19 +++--- 6 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 src/components/templates/RouteTracker/index.tsx diff --git a/src/App.test.tsx b/src/App.test.tsx index a64c386..2710b73 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,7 +1,14 @@ import { render, screen } from '@testing-library/react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { useAnalytics } from 'use-analytics' import App from './App' +// Mock the analytics module +vi.mock('use-analytics', () => ({ + useAnalytics: () => ({ plugins: { enable: vi.fn(), disable: vi.fn() }, page: vi.fn() }), +})) + describe('App component', () => { test('renders MainPage without crashing', async () => { render() diff --git a/src/App.tsx b/src/App.tsx index c1aba46..047dccd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import React from 'react' import { createBrowserRouter, RouterProvider } from 'react-router-dom' -import { Backdrop, CircularProgress } from '@mui/material' -import { ErrorPage } from 'components/pages' +import ErrorPage from 'components/pages/ErrorPage/index.js' +import RootLayout from 'components/templates/RouteTracker/index.js' const MainPage = React.lazy(() => import('components/pages/MainPage')) const ProjectsPage = React.lazy(() => import('components/pages/ProjectsPage')) @@ -18,36 +18,42 @@ const Loading = () => ( const router = createBrowserRouter([ { path: '/', - element: ( - }> - - - ), + element: , errorElement: , - }, - { - path: '/projects', - element: ( - }> - - - ), - }, - { - path: '/curriculum', - element: ( - }> - - - ), - }, - { - path: '/contacts', - element: ( - }> - - - ), + children: [ + { + index: true, + element: ( + }> + + + ), + }, + { + path: 'projects', + element: ( + }> + + + ), + }, + { + path: 'curriculum', + element: ( + }> + + + ), + }, + { + path: 'contacts', + element: ( + }> + + + ), + }, + ], }, ]) diff --git a/src/components/molecules/Toolbar/index.tsx b/src/components/molecules/Toolbar/index.tsx index 13be7a6..261ec60 100644 --- a/src/components/molecules/Toolbar/index.tsx +++ b/src/components/molecules/Toolbar/index.tsx @@ -100,7 +100,7 @@ const CustomToolbar: React.FC = ({ drawerWidth = 240, window sx={{ display: 'flex', // maxWidth: '100%', - // [theme.breakpoints.up('md')]: { width: '80em', mx: 'auto' }, + // [theme.breakpoints.up('md')]: { width: theme => theme.breakpoints.values.lg, mx: 'auto' }, }}> { + const location = useLocation() + const analytics = useAnalytics() + + useEffect(() => { + analytics.page() + }, [analytics, location]) + + return +} + +export default RouteTracker diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index c6f64a4..eba39ba 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -1 +1,2 @@ -export { default as Page } from './Page' +export { default as Page } from './Page/index.js' +export { default as RouteTracker } from './RouteTracker/index.js' diff --git a/src/main.tsx b/src/main.tsx index e40bf2d..fcf4f83 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,22 @@ /* istanbul ignore file */ import React from 'react' import ReactDOM from 'react-dom/client' -import { ThemeProvider } from '@mui/material/styles' -import { CssBaseline } from '@mui/material' +import { AnalyticsProvider } from 'use-analytics' +import { ThemeProvider, CssBaseline } from '@mui/material' -import App from './App.tsx' -import theme from 'style/theme.ts' +import analytics from 'components/utils/analytics/index.js' +import theme from 'style/theme.js' +import App from './App.js' import './index.css' const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) root.render( - - - - + + + + + + ) From 2547572e1cb164f8aac3941cec8e44c3736f0548 Mon Sep 17 00:00:00 2001 From: m-tartari <37861893+m-tartari@users.noreply.github.com> Date: Wed, 28 May 2025 10:32:29 +0000 Subject: [PATCH 10/14] =?UTF-8?q?=E2=9C=A8=20feat(analytics):=20add=20cook?= =?UTF-8?q?ie=20consent=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CookieConsent/CookieConsent.test.tsx | 64 +++++++++++ .../molecules/CookieConsent/index.tsx | 100 ++++++++++++++++++ .../molecules/Footer/Footer.test.tsx | 30 ++++++ src/components/molecules/Footer/Footer.tsx | 21 ---- src/components/molecules/Footer/index.ts | 1 - src/components/molecules/Footer/index.tsx | 52 +++++++++ .../pages/ContactPage/ContactPage.test.tsx | 7 ++ .../CurriculumPage/CurriculumPage.test.tsx | 7 +- .../pages/ErrorPage/ErrorPage.test.tsx | 9 +- src/components/pages/ErrorPage/ErrorPage.tsx | 8 +- .../pages/MainPage/MainPage.test.tsx | 10 +- .../pages/ProjectsPage/ProjectsPage.test.tsx | 7 ++ src/components/templates/Page/Page.test.tsx | 10 +- src/components/templates/Page/Page.tsx | 10 +- 14 files changed, 302 insertions(+), 34 deletions(-) create mode 100644 src/components/molecules/CookieConsent/CookieConsent.test.tsx create mode 100644 src/components/molecules/CookieConsent/index.tsx create mode 100644 src/components/molecules/Footer/Footer.test.tsx delete mode 100644 src/components/molecules/Footer/Footer.tsx delete mode 100644 src/components/molecules/Footer/index.ts create mode 100644 src/components/molecules/Footer/index.tsx diff --git a/src/components/molecules/CookieConsent/CookieConsent.test.tsx b/src/components/molecules/CookieConsent/CookieConsent.test.tsx new file mode 100644 index 0000000..42e707c --- /dev/null +++ b/src/components/molecules/CookieConsent/CookieConsent.test.tsx @@ -0,0 +1,64 @@ +import { render, screen, fireEvent } from '@testing-library/react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { useAnalytics } from 'use-analytics' + +import CookieConsent from './index.js' + +const analytics = { plugins: { enable: vi.fn(), disable: vi.fn() } } + +// Mock the analytics module +vi.mock('use-analytics', () => ({ + useAnalytics: () => analytics, +})) + +// Mock localStorage +const localStorageMock = (() => { + let store: Record = {} + return { + getItem: vi.fn((key: string) => store[key] || null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value + }), + clear: vi.fn(() => { + store = {} + }), + } +})() + +Object.defineProperty(window, 'localStorage', { value: localStorageMock }) + +describe('CookieConsent', () => { + beforeEach(() => { + vi.clearAllMocks() + localStorageMock.clear() + }) + + it('renders the cookie consent dialog when no consent is stored', () => { + render() + + expect(screen.getByText('Cookie Preferences')).toBeInTheDocument() + expect(screen.getByText(/We use cookies to improve your browsing experience/)).toBeInTheDocument() + expect(screen.getByText('Accept All')).toBeInTheDocument() + expect(screen.getByText('Reject All')).toBeInTheDocument() + }) + + it('accepts all cookies when clicking Accept All', () => { + render() + + fireEvent.click(screen.getByText('Accept All')) + + expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ analytics: true, necessary: true })) + expect(analytics.plugins.disable).not.toHaveBeenCalled() + expect(analytics.plugins.enable).toHaveBeenCalled() + }) + + it('rejects analytics cookies when clicking Reject All', () => { + render() + + fireEvent.click(screen.getByText('Reject All')) + + expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ analytics: false, necessary: true })) + expect(analytics.plugins.enable).not.toHaveBeenCalled() + expect(analytics.plugins.disable).toHaveBeenCalled() + }) +}) diff --git a/src/components/molecules/CookieConsent/index.tsx b/src/components/molecules/CookieConsent/index.tsx new file mode 100644 index 0000000..1b598ab --- /dev/null +++ b/src/components/molecules/CookieConsent/index.tsx @@ -0,0 +1,100 @@ +import { useState, useEffect, useCallback } from 'react' +import { Box, Button, Link, Stack, Typography, Unstable_TrapFocus as TrapFocus, Paper, Fade } from '@mui/material' +import { useAnalytics } from 'use-analytics' + +import { ANALYTICS_PLUGINS, COOKIE_CONSENT_KEY } from 'components/utils/analytics/index.js' + +interface Consent { + necessary: boolean // Always true, but included for completeness + analytics: boolean +} + +const CookieConsent = () => { + const [open, setOpen] = useState(false) + const analytics = useAnalytics() + + useEffect(() => { + // Check if user has already given consent + const savedConsent = localStorage.getItem(COOKIE_CONSENT_KEY) + + if (!savedConsent) { + setOpen(true) + } else { + const parsedConsent = JSON.parse(savedConsent) as Consent + if (parsedConsent.analytics) { + analytics.plugins.enable(ANALYTICS_PLUGINS) + } + } + }, [analytics]) + + const handleAcceptAll = useCallback(() => { + localStorage.setItem(COOKIE_CONSENT_KEY, JSON.stringify({ analytics: true, necessary: true })) + analytics.plugins.enable(ANALYTICS_PLUGINS) + setOpen(false) + }, [analytics.plugins]) + + const handleRejectAll = useCallback(() => { + localStorage.setItem(COOKIE_CONSENT_KEY, JSON.stringify({ analytics: false, necessary: true })) + analytics.plugins.disable(ANALYTICS_PLUGINS) + setOpen(false) + }, [analytics.plugins]) + + return ( + <> + + + ({ + position: 'fixed', + bottom: 0, + left: '50%', + transform: 'translate(-50%, 0)', + width: theme.breakpoints.values.sm, + maxWidth: `calc(100% - ${theme.spacing(2)})`, + mb: theme.spacing(1), + p: theme.spacing(3), + zIndex: 9999, + })}> + + + Cookie Preferences + + + We use cookies to improve your browsing experience and analyze our traffic. + By clicking "Accept All", you consent to our use of cookies. + + For more details, please see our{' '} + + Privacy Policy + + . + + + + + + + Customize + + + + + + + + + ) +} + +export default CookieConsent diff --git a/src/components/molecules/Footer/Footer.test.tsx b/src/components/molecules/Footer/Footer.test.tsx new file mode 100644 index 0000000..ac1656a --- /dev/null +++ b/src/components/molecules/Footer/Footer.test.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { screen } from '@testing-library/react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { useAnalytics } from 'use-analytics' + +import { renderWithRouter as render } from 'components/utils/tests/index.js' + +import Footer from './index.js' + +// Mock the analytics module +const analytics = { plugins: { enable: vi.fn(), disable: vi.fn() } } +vi.mock('use-analytics', () => ({ + useAnalytics: () => analytics, +})) + +describe('Footer', () => { + it('renders the Footer', async () => { + render(