diff --git a/.github/workflows/cicd-dev.yml b/.github/workflows/cicd-dev.yml index 5bd0f4b..ccc31bd 100644 --- a/.github/workflows/cicd-dev.yml +++ b/.github/workflows/cicd-dev.yml @@ -22,8 +22,8 @@ jobs: - name: Create environment file run: | - echo "NODE_ENV=dev" > .env - echo "VITE_API_URL=${{ secrets.VITE_API_URL }}" >> .env + echo "NODE_ENV=production" > dist/.env + echo "VITE_API_URL=${{ secrets.VITE_API_URL }}" >> dist/.env - name: Build the project run: npm run build diff --git a/package.json b/package.json index a4a0ef2..54575d1 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.1", "@tailwindcss/typography": "^0.5.15", - "@tanstack/react-query": "^5.52.1", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.427.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-ga4": "^2.1.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.26.0", "tailwind-merge": "^2.4.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8c2e1a..f12c244 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,13 +14,10 @@ importers: dependencies: '@radix-ui/react-dialog': specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.15(tailwindcss@3.4.9) - '@tanstack/react-query': - specifier: ^5.52.1 - version: 5.52.1(react@18.3.1) axios: specifier: ^1.7.7 version: 1.7.7 @@ -39,12 +36,15 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-ga4: + specifier: ^2.1.0 + version: 2.1.0 react-markdown: specifier: ^9.0.1 version: 9.0.1(@types/react@18.3.3)(react@18.3.1) react-router-dom: specifier: ^6.26.0 - version: 6.26.0(react-dom@18.3.1)(react@18.3.1) + version: 6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.4.0 version: 2.4.0 @@ -57,7 +57,7 @@ importers: version: 6.5.0 '@testing-library/react': specifier: ^16.0.0 - version: 16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + version: 16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/jest': specifier: ^29.5.12 version: 29.5.12 @@ -72,7 +72,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.4.0) + version: 4.3.1(vite@5.4.0(@types/node@22.2.0)) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.41) @@ -105,7 +105,7 @@ importers: version: 1.0.7(tailwindcss@3.4.9) ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.25.2)(jest@29.7.0)(typescript@5.5.3) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0))(typescript@5.5.3) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -862,14 +862,6 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' - '@tanstack/query-core@5.52.0': - resolution: {integrity: sha512-U1DOEgltjUwalN6uWYTewSnA14b+tE7lSylOiASKCAO61ENJeCq9VVD/TXHA6O5u9+6v5+UgGYBSccTKDoyMqw==} - - '@tanstack/react-query@5.52.1': - resolution: {integrity: sha512-soyn4dNIUZ8US8NaPVXv06gkZFHaZnPfKWPDjRJjFRW3Y7WZ0jx72eT6zhw3VQlkMPysmXye8l35ewPHspKgbQ==} - peerDependencies: - react: ^18 || ^19 - '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -2212,8 +2204,8 @@ packages: micromark@4.0.0: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.52.0: @@ -2468,6 +2460,9 @@ packages: peerDependencies: react: ^18.3.1 + react-ga4@2.1.0: + resolution: {integrity: sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==} + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -3416,7 +3411,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -3527,7 +3522,7 @@ snapshots: jest-haste-map: 29.7.0 jest-regex-util: 29.6.3 jest-util: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pirates: 4.0.6 slash: 3.0.0 write-file-atomic: 4.0.2 @@ -3579,121 +3574,136 @@ snapshots: '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 - '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dialog@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 - '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 '@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 - '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@types/react': 18.3.3 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 '@remix-run/router@1.19.0': {} @@ -3763,13 +3773,6 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.9 - '@tanstack/query-core@5.52.0': {} - - '@tanstack/react-query@5.52.1(react@18.3.1)': - dependencies: - '@tanstack/query-core': 5.52.0 - react: 18.3.1 - '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.24.7 @@ -3791,14 +3794,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)': + '@testing-library/react@16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.4 '@testing-library/dom': 10.4.0 - '@types/react': 18.3.3 - '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 '@tootallnate/once@2.0.0': {} @@ -3899,7 +3903,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.8.0)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.3))(eslint@9.8.0)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.11.0 '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.3) @@ -3912,6 +3916,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3924,6 +3929,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.6 eslint: 9.8.0 + optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3939,6 +3945,7 @@ snapshots: '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.3) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: - eslint @@ -3956,6 +3963,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3978,7 +3986,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.4.0)': + '@vitejs/plugin-react@4.3.1(vite@5.4.0(@types/node@22.2.0))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) @@ -4532,7 +4540,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} @@ -4895,7 +4903,6 @@ snapshots: '@babel/core': 7.25.2 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.2.0 babel-jest: 29.7.0(@babel/core@7.25.2) chalk: 4.1.2 ci-info: 3.9.0 @@ -4910,11 +4917,13 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.2.0 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -4975,7 +4984,7 @@ snapshots: jest-regex-util: 29.6.3 jest-util: 29.7.0 jest-worker: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 @@ -4999,7 +5008,7 @@ snapshots: '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -5011,7 +5020,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -5509,7 +5518,7 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -5673,8 +5682,9 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.41): dependencies: lilconfig: 3.1.2 - postcss: 8.4.41 yaml: 2.5.0 + optionalDependencies: + postcss: 8.4.41 postcss-nested@6.2.0(postcss@8.4.41): dependencies: @@ -5738,6 +5748,8 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-ga4@2.1.0: {} + react-is@17.0.2: {} react-is@18.3.1: {} @@ -5763,22 +5775,24 @@ snapshots: react-remove-scroll-bar@2.3.6(@types/react@18.3.3)(react@18.3.1): dependencies: - '@types/react': 18.3.3 react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1) tslib: 2.6.3 + optionalDependencies: + '@types/react': 18.3.3 react-remove-scroll@2.5.7(@types/react@18.3.3)(react@18.3.1): dependencies: - '@types/react': 18.3.3 react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1) tslib: 2.6.3 use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 - react-router-dom@6.26.0(react-dom@18.3.1)(react@18.3.1): + react-router-dom@6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@remix-run/router': 1.19.0 react: 18.3.1 @@ -5792,11 +5806,12 @@ snapshots: react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1): dependencies: - '@types/react': 18.3.3 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 tslib: 2.6.3 + optionalDependencies: + '@types/react': 18.3.3 react@18.3.1: dependencies: @@ -6015,7 +6030,7 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 @@ -6075,9 +6090,8 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.2)(jest@29.7.0)(typescript@5.5.3): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.2.0))(typescript@5.5.3): dependencies: - '@babel/core': 7.25.2 bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 @@ -6089,6 +6103,11 @@ snapshots: semver: 7.6.3 typescript: 5.5.3 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.25.2 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.25.2) tslib@2.6.3: {} @@ -6102,9 +6121,10 @@ snapshots: typescript-eslint@8.0.0(eslint@9.8.0)(typescript@5.5.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.8.0)(typescript@5.5.3) + '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.3))(eslint@9.8.0)(typescript@5.5.3) '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.3) '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.3) + optionalDependencies: typescript: 5.5.3 transitivePeerDependencies: - eslint @@ -6166,16 +6186,18 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.3)(react@18.3.1): dependencies: - '@types/react': 18.3.3 react: 18.3.1 tslib: 2.6.3 + optionalDependencies: + '@types/react': 18.3.3 use-sidecar@1.1.2(@types/react@18.3.3)(react@18.3.1): dependencies: - '@types/react': 18.3.3 detect-node-es: 1.1.0 react: 18.3.1 tslib: 2.6.3 + optionalDependencies: + '@types/react': 18.3.3 util-deprecate@1.0.2: {} @@ -6197,11 +6219,11 @@ snapshots: vite@5.4.0(@types/node@22.2.0): dependencies: - '@types/node': 22.2.0 esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.20.0 optionalDependencies: + '@types/node': 22.2.0 fsevents: 2.3.3 w3c-xmlserializer@4.0.0: diff --git a/src/App.tsx b/src/App.tsx index 4fe2061..456ff3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Routes, Route, useLocation } from 'react-router-dom'; import { Toaster } from '@/components/Toaster'; import LandingPage from './pages/LandingPage'; @@ -10,7 +9,6 @@ import AuthenticatedAdminPage from './pages/admin/AdminPage'; import AuthenticatedAdminSuggestionsPage from './pages/admin/AdminSuggestionsPage'; import './App.css'; -import Background from './components/Background'; import { useAuth } from './context/useAuth'; import UsernameBubble from './components/UsernameBubble'; import AuthenticatedAdminUsersPage from './pages/admin/AdminUsersPage'; @@ -19,49 +17,55 @@ import LeaderboardPage from './pages/Leaderboard'; import AuthenticatedModeratorPage from './pages/moderator/ModeratorPage'; import AuthenticatedModeratorSuggestionsPage from './pages/moderator/ModeratorSuggestionsPage'; import AuthenticatedModeratorQuestsPage from './pages/moderator/ModeratorQuestsPage'; - -const queryClient = new QueryClient() +import { useEffect } from 'react'; +import initializeGA, { trackPageView } from './analytics'; function App() { const { user } = useAuth(); + const location = useLocation(); + + useEffect(() => { + initializeGA(); + }, []); + + useEffect(() => { + trackPageView(location.pathname); + }, [location]); return ( - + <> - - - {/* public routes */} - } /> - } /> - } /> - } /> + + {/* public routes */} + } /> + } /> + } /> + } /> - {/* authorized routes */} - {/* } /> */} + {/* authorized routes */} + {/* } /> */} - {/* moderator routes */} - } /> - } /> - } /> + {/* moderator routes */} + } /> + } /> + } /> - {/* admin routes */} - } /> - } /> - } /> - } /> + {/* admin routes */} + } /> + } /> + } /> + } /> - {/* user routes */} - } /> - - + {/* user routes */} + } /> + { user && } - - + ) } diff --git a/src/analytics.ts b/src/analytics.ts new file mode 100644 index 0000000..5867a3d --- /dev/null +++ b/src/analytics.ts @@ -0,0 +1,49 @@ +import ReactGA from 'react-ga4'; +import crypto from 'crypto'; + +// Initialize Google Analytics +const initializeGA = () => { + const Tag = process.env.NODE_ENV === 'production' ? 'G-HVLY6L1CT8' : 'G-DEVY6L1CT8'; + ReactGA.initialize(Tag); +}; + +export const hashUserId = (userId: string) => { + const hash = crypto.createHash('sha256'); + hash.update(userId); + return hash.digest('hex'); +}; + +// Function to track page views +export const trackPageView = (path: string) => { + ReactGA.send({ hitType: 'pageview', page: path }); +}; + +// Function to track events +export const trackEvent = (category: string, action: string, label?: string, value?: number) => { + ReactGA.event({ + category: category, + action: action, + label: label, + value: value, + }); +}; + +// Custom events for login and registration +export const trackLoginAttempt = () => trackEvent('User', 'Login Attempt'); +export const trackLoginSuccess = () => trackEvent('User', 'Login Success'); +export const trackLoginFailure = (error: string) => trackEvent('User', 'Login Failure', error); + +export const trackRegistrationAttempt = () => trackEvent('User', 'Registration Attempt'); +export const trackRegistrationSuccess = () => trackEvent('User', 'Registration Success'); +export const trackRegistrationFailure = (error: string) => trackEvent('User', 'Registration Failure', error); + +export const trackGuestLogin = () => trackEvent('User', 'Guest Login'); + +// Custom events for quests +export const trackQuestCompletion = (questId: number) => trackEvent('Quest', 'Complete Quest', questId.toString()); +export const trackQuestSkip = (questId: number) => trackEvent('Quest', 'Skip Quest', questId.toString()); + +export const trackSuggestionCreation = () => trackEvent('Suggestion', 'Create Suggestion'); + +// Export the initialization function +export default initializeGA; diff --git a/src/components/LoginPanel/LoginPanel.tsx b/src/components/LoginPanel/LoginPanel.tsx index 7f84464..e8f05b0 100644 --- a/src/components/LoginPanel/LoginPanel.tsx +++ b/src/components/LoginPanel/LoginPanel.tsx @@ -5,7 +5,15 @@ import Button from "../Button"; import { toast } from "../Toaster"; import authService from "@/service/authService"; import { useNavigate } from "react-router-dom"; - +import { + trackLoginAttempt, + trackLoginSuccess, + trackLoginFailure, + trackRegistrationAttempt, + trackRegistrationSuccess, + trackRegistrationFailure, + trackGuestLogin, +} from '@/analytics'; interface LoginPanelProps { onLoginSuccess: () => void; onRegistrationSuccess: () => void; @@ -45,17 +53,28 @@ const LoginPanel: React.FC = ({ onLoginSuccess, onRegistrationS setDisableButtons(true); try { let authResponse; + if (isRegistering) { + trackRegistrationAttempt(); authResponse = await authService.register(username, password); + trackRegistrationSuccess(); onRegistrationSuccess(); } else { + trackLoginAttempt(); authResponse = await authService.login(username, password); + trackLoginSuccess(); onLoginSuccess(); } saveToken(authResponse.token); } catch (error: unknown) { if (error instanceof AxiosError) { + if (isRegistering) { + trackRegistrationFailure(error.response?.data?.error || 'Unknown Error'); + } else { + trackLoginFailure(error.response?.data?.error || 'Unknown Error'); + } + if (error.response?.status === 429) { setError("Too many requests. Please try again later."); } else { @@ -72,6 +91,7 @@ const LoginPanel: React.FC = ({ onLoginSuccess, onRegistrationS const loginAsGuest = async () => { setDisableButtons(true); try { + trackGuestLogin(); const loginGuestResponse = await authService.loginGuest(); saveToken(loginGuestResponse.token); onRegistrationSuccess(); diff --git a/src/components/SuggestionsPanel/SuggestionsPanel.tsx b/src/components/SuggestionsPanel/SuggestionsPanel.tsx index d254f19..0753edc 100644 --- a/src/components/SuggestionsPanel/SuggestionsPanel.tsx +++ b/src/components/SuggestionsPanel/SuggestionsPanel.tsx @@ -6,6 +6,7 @@ import { AxiosError } from "axios"; import { useAuth } from "@/context/useAuth"; import Button from "../Button"; import { toast } from "../Toaster"; +import { trackSuggestionCreation } from "@/analytics"; type SuggestionsPanelProps = { onClose: () => void; @@ -37,6 +38,7 @@ const SuggestionsPanel: React.FC = (props) => { newSuggestion.title = newSuggestion.title.trim(); newSuggestion.description = newSuggestion.description.trim(); + trackSuggestionCreation(); await suggestionService.createSuggestion(newSuggestion, accessToken!); toast.success("Suggestion submitted successfully!"); onClose(); diff --git a/src/main.tsx b/src/main.tsx index 31a0603..c2c58f3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,11 +2,16 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import { AuthProvider } from './context/AuthenticationProvider.tsx' +import Background from './components/Background/Background.tsx' +import { BrowserRouter as Router } from 'react-router-dom'; createRoot(document.getElementById('root')!).render( - + + + + , ) diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 3a08c92..d7b3d55 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -19,6 +19,7 @@ import RegisterAccount from "@/modals/RegisterAccount"; import { ModalTypes } from "@/models/modals"; import ConfirmDialog from "@/components/ConfirmDialog"; import SpinningWheel from "@/components/SpinningWheel"; +import { trackQuestCompletion, trackQuestSkip } from "@/analytics"; const LandingPage = () => { @@ -75,6 +76,9 @@ const LandingPage = () => { const handleQuestSkip = () => { // setCurrentQuest(null); localStorage.removeItem('currentQuest'); + if (currentQuest) { + trackQuestSkip(currentQuest.id); + }; handleSpinWheel(); }; @@ -82,8 +86,10 @@ const LandingPage = () => { if (!accessToken) return navigate("/login"); try { + if (!currentQuest) return; setIsModalLoading(true); - const completeQuestResponse = await userService.completeQuest(accessToken, currentQuest!.id); + trackQuestCompletion(currentQuest.id); + const completeQuestResponse = await userService.completeQuest(accessToken, currentQuest.id); // setCurrentQuest(null); localStorage.removeItem('currentQuest');