From bc7ce1fb2ed376bf5f4246bd675e49ccad262791 Mon Sep 17 00:00:00 2001 From: Bryan Wills Date: Sat, 2 Aug 2025 16:30:12 -0400 Subject: [PATCH 1/2] feat: Add welcome banner and enhanced analytics features - Implement welcome banner with 24-hour persistence - Add comprehensive analytics dashboard with IP tracking - Include marketing intelligence and bot detection - Add XLSX export functionality and brain favicon - Fix TypeScript errors and improve code quality --- CONFIGURATION.md | 0 package-lock.json | 185 +++++++++++++++++- public/brain-favicon.svg | 0 scripts/create-favicon.js | 0 src/app/analytics/ip/[ip]/page.tsx | 0 src/app/api/analytics/ip-analytics/route.ts | 0 src/app/api/analytics/ip-logs/route.ts | 0 .../analytics/marketing-intelligence/route.ts | 0 src/app/api/analytics/nginx-logs/route.ts | 0 .../analytics/EnhancedAnalyticsDashboard.tsx | 0 src/components/analytics/IPDetailsModal.tsx | 0 .../MarketingIntelligenceDashboard.tsx | 0 src/components/analytics/UniqueIPsList.tsx | 0 src/components/shared/UpdateBanner.tsx | 0 src/components/ui/dialog.tsx | 0 src/components/ui/progress.tsx | 0 src/components/ui/select.tsx | 0 src/components/ui/tabs.tsx | 0 src/lib/botDetection.ts | 0 src/lib/config.ts | 0 src/lib/exportUtils.ts | 0 src/lib/marketingIntelligence.ts | 0 src/lib/nginxLogParser.ts | 0 vercel-import-env.sh | 0 24 files changed, 182 insertions(+), 3 deletions(-) mode change 100644 => 100755 CONFIGURATION.md mode change 100644 => 100755 public/brain-favicon.svg mode change 100644 => 100755 scripts/create-favicon.js mode change 100644 => 100755 src/app/analytics/ip/[ip]/page.tsx mode change 100644 => 100755 src/app/api/analytics/ip-analytics/route.ts mode change 100644 => 100755 src/app/api/analytics/ip-logs/route.ts mode change 100644 => 100755 src/app/api/analytics/marketing-intelligence/route.ts mode change 100644 => 100755 src/app/api/analytics/nginx-logs/route.ts mode change 100644 => 100755 src/components/analytics/EnhancedAnalyticsDashboard.tsx mode change 100644 => 100755 src/components/analytics/IPDetailsModal.tsx mode change 100644 => 100755 src/components/analytics/MarketingIntelligenceDashboard.tsx mode change 100644 => 100755 src/components/analytics/UniqueIPsList.tsx mode change 100644 => 100755 src/components/shared/UpdateBanner.tsx mode change 100644 => 100755 src/components/ui/dialog.tsx mode change 100644 => 100755 src/components/ui/progress.tsx mode change 100644 => 100755 src/components/ui/select.tsx mode change 100644 => 100755 src/components/ui/tabs.tsx mode change 100644 => 100755 src/lib/botDetection.ts mode change 100644 => 100755 src/lib/config.ts mode change 100644 => 100755 src/lib/exportUtils.ts mode change 100644 => 100755 src/lib/marketingIntelligence.ts mode change 100644 => 100755 src/lib/nginxLogParser.ts mode change 100644 => 100755 vercel-import-env.sh diff --git a/CONFIGURATION.md b/CONFIGURATION.md old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index be9a5bc..7fabf9d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "nextjs-temp", + "name": "BigBrainCoding", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "nextjs-temp", + "name": "BigBrainCoding", "version": "0.1.0", "dependencies": { "@c15t/react": "^1.4.4", @@ -14,6 +14,8 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.7", @@ -27,7 +29,8 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -3198,6 +3201,12 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", @@ -3711,6 +3720,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", @@ -3742,6 +3775,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -5069,6 +5145,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5520,6 +5605,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5594,6 +5692,15 @@ "node": ">=6" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -5698,6 +5805,18 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6902,6 +7021,15 @@ "node": ">= 0.6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/framer-motion": { "version": "12.23.9", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.9.tgz", @@ -9661,6 +9789,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -10480,6 +10620,24 @@ "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==", "license": "ISC" }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -10513,6 +10671,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/public/brain-favicon.svg b/public/brain-favicon.svg old mode 100644 new mode 100755 diff --git a/scripts/create-favicon.js b/scripts/create-favicon.js old mode 100644 new mode 100755 diff --git a/src/app/analytics/ip/[ip]/page.tsx b/src/app/analytics/ip/[ip]/page.tsx old mode 100644 new mode 100755 diff --git a/src/app/api/analytics/ip-analytics/route.ts b/src/app/api/analytics/ip-analytics/route.ts old mode 100644 new mode 100755 diff --git a/src/app/api/analytics/ip-logs/route.ts b/src/app/api/analytics/ip-logs/route.ts old mode 100644 new mode 100755 diff --git a/src/app/api/analytics/marketing-intelligence/route.ts b/src/app/api/analytics/marketing-intelligence/route.ts old mode 100644 new mode 100755 diff --git a/src/app/api/analytics/nginx-logs/route.ts b/src/app/api/analytics/nginx-logs/route.ts old mode 100644 new mode 100755 diff --git a/src/components/analytics/EnhancedAnalyticsDashboard.tsx b/src/components/analytics/EnhancedAnalyticsDashboard.tsx old mode 100644 new mode 100755 diff --git a/src/components/analytics/IPDetailsModal.tsx b/src/components/analytics/IPDetailsModal.tsx old mode 100644 new mode 100755 diff --git a/src/components/analytics/MarketingIntelligenceDashboard.tsx b/src/components/analytics/MarketingIntelligenceDashboard.tsx old mode 100644 new mode 100755 diff --git a/src/components/analytics/UniqueIPsList.tsx b/src/components/analytics/UniqueIPsList.tsx old mode 100644 new mode 100755 diff --git a/src/components/shared/UpdateBanner.tsx b/src/components/shared/UpdateBanner.tsx old mode 100644 new mode 100755 diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx old mode 100644 new mode 100755 diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx old mode 100644 new mode 100755 diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx old mode 100644 new mode 100755 diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx old mode 100644 new mode 100755 diff --git a/src/lib/botDetection.ts b/src/lib/botDetection.ts old mode 100644 new mode 100755 diff --git a/src/lib/config.ts b/src/lib/config.ts old mode 100644 new mode 100755 diff --git a/src/lib/exportUtils.ts b/src/lib/exportUtils.ts old mode 100644 new mode 100755 diff --git a/src/lib/marketingIntelligence.ts b/src/lib/marketingIntelligence.ts old mode 100644 new mode 100755 diff --git a/src/lib/nginxLogParser.ts b/src/lib/nginxLogParser.ts old mode 100644 new mode 100755 diff --git a/vercel-import-env.sh b/vercel-import-env.sh old mode 100644 new mode 100755 From c8a01858da20bcf20bd1f4dc8e9351e26053d973 Mon Sep 17 00:00:00 2001 From: Bryan Wills Date: Mon, 4 Aug 2025 22:30:33 -0400 Subject: [PATCH 2/2] feat: Add advanced fingerprint tracking and enhanced analytics dashboard with IP details modal and marketing intelligence features --- package.json | 1 + pnpm-lock.yaml | 10 + scripts/deploy-server.sh | 173 +++++ scripts/deploy-simple.sh | 88 +++ src/app/analytics/fingerprint/page.tsx | 8 + src/app/analytics/ip/[ip]/page.tsx | 201 +++-- src/app/api/analytics/fingerprint/route.ts | 219 ++++++ src/app/api/analytics/ip-analytics/route.ts | 19 +- src/app/api/tracking/fingerprint/route.ts | 131 ++++ src/app/layout.tsx | 2 + .../analytics/EnhancedAnalyticsDashboard.tsx | 137 ++-- .../FingerprintAnalyticsDashboard.tsx | 714 ++++++++++++++++++ .../analytics/FingerprintTracker.tsx | 14 + src/components/analytics/IPDetailsModal.tsx | 149 ++-- .../MarketingIntelligenceDashboard.tsx | 145 ++-- src/lib/fingerprintTracker.ts | 313 ++++++++ src/lib/nginxLogParser.ts | 2 +- 17 files changed, 2093 insertions(+), 233 deletions(-) create mode 100755 scripts/deploy-server.sh create mode 100755 scripts/deploy-simple.sh create mode 100644 src/app/analytics/fingerprint/page.tsx create mode 100644 src/app/api/analytics/fingerprint/route.ts create mode 100644 src/app/api/tracking/fingerprint/route.ts create mode 100644 src/components/analytics/FingerprintAnalyticsDashboard.tsx create mode 100644 src/components/analytics/FingerprintTracker.tsx create mode 100644 src/lib/fingerprintTracker.ts diff --git a/package.json b/package.json index fc344d0..37584c0 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@c15t/react": "^1.4.4", + "@fingerprintjs/fingerprintjs": "^4.6.2", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 376ed6c..94a5a8d 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@c15t/react': specifier: ^1.4.4 version: 1.4.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@fingerprintjs/fingerprintjs': + specifier: ^4.6.2 + version: 4.6.2 '@radix-ui/react-accordion': specifier: ^1.2.11 version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -185,6 +188,9 @@ packages: resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fingerprintjs/fingerprintjs@4.6.2': + resolution: {integrity: sha512-g8mXuqcFKbgH2CZKwPfVtsUJDHyvcgIABQI7Y0tzWEFXpGxJaXuAuzlifT2oTakjDBLTK4Gaa9/5PERDhqUjtw==} + '@floating-ui/core@1.7.2': resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} @@ -3689,6 +3695,10 @@ snapshots: '@eslint/core': 0.15.1 levn: 0.4.1 + '@fingerprintjs/fingerprintjs@4.6.2': + dependencies: + tslib: 2.8.1 + '@floating-ui/core@1.7.2': dependencies: '@floating-ui/utils': 0.2.10 diff --git a/scripts/deploy-server.sh b/scripts/deploy-server.sh new file mode 100755 index 0000000..331e706 --- /dev/null +++ b/scripts/deploy-server.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Big Brain Coding - Server-Side Deployment Script +# This script builds and deploys the Next.js application with server-side functionality +# for analytics features that require API routes and dynamic data processing + +set -e # Exit on any error + +# Configuration +PROJECT_DIR="$(pwd)" +BUILD_DIR=".next" +DEPLOY_DIR="/var/www/bigbraincoding.com/html" +PORT=3000 + +echo "🚀 Big Brain Coding - Server-Side Deployment" +echo "===========================================" +echo "📁 Project Directory: $PROJECT_DIR" +echo "đŸ“Ļ Build Directory: $BUILD_DIR" +echo "🌐 Deploy Directory: $DEPLOY_DIR" +echo "🔌 Port: $PORT" +echo "" + +# Step 1: Install dependencies +echo "🔧 Step 1: Installing dependencies..." +npm install + +# Step 2: Build the Next.js application +echo "" +echo "🔨 Step 2: Building Next.js application..." +npm run build + +# Step 3: Create backup of current deployment +echo "" +echo "📋 Step 3: Creating backup of current deployment..." +BACKUP_DIR="/tmp/bigbraincoding-backup-$(date +%Y%m%d-%H%M%S)" +BACKUP_ARCHIVE="$BACKUP_DIR.tar.bz2" +if [ -d "$DEPLOY_DIR" ]; then + mkdir -p "$BACKUP_DIR" + cp -r "$DEPLOY_DIR"/* "$BACKUP_DIR/" 2>/dev/null || true + echo "đŸ“Ļ Compressing backup to save disk space..." + tar -cjf "$BACKUP_ARCHIVE" -C "$(dirname "$BACKUP_DIR")" "$(basename "$BACKUP_DIR")" + rm -rf "$BACKUP_DIR" + echo "✅ Compressed backup created at: $BACKUP_ARCHIVE" +else + echo "â„šī¸ No existing deployment to backup" +fi + +# Step 4: Deploy application files +echo "" +echo "📤 Step 4: Deploying application files..." + +# Create deploy directory if it doesn't exist +sudo mkdir -p "$DEPLOY_DIR" + +# Copy necessary files for server-side deployment +echo "📁 Copying application files..." +sudo cp -r "$BUILD_DIR" "$DEPLOY_DIR/" +sudo cp package.json "$DEPLOY_DIR/" +sudo cp package-lock.json "$DEPLOY_DIR/" +sudo cp next.config.js "$DEPLOY_DIR/" +sudo cp -r public "$DEPLOY_DIR/" +sudo cp -r src "$DEPLOY_DIR/" + +# Step 5: Set proper permissions +echo "" +echo "🔐 Step 5: Setting permissions..." +sudo chown -R $USER:$USER "$DEPLOY_DIR" +sudo chmod -R 755 "$DEPLOY_DIR" +echo "✅ Permissions set" + +# Step 6: Install production dependencies in deploy directory +echo "" +echo "đŸ“Ļ Step 6: Installing production dependencies..." +cd "$DEPLOY_DIR" +npm ci --only=production +cd "$PROJECT_DIR" + +# Step 7: Create PM2 ecosystem file for process management +echo "" +echo "âš™ī¸ Step 7: Setting up process management..." +cat > "$DEPLOY_DIR/ecosystem.config.js" << EOF +module.exports = { + apps: [{ + name: 'bigbraincoding', + script: 'node_modules/next/dist/bin/next', + args: 'start', + cwd: '$DEPLOY_DIR', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'production', + PORT: $PORT + } + }] +} +EOF + +# Step 8: Stop existing PM2 process if running +echo "" +echo "🛑 Step 8: Managing PM2 processes..." +if command -v pm2 &> /dev/null; then + pm2 stop bigbraincoding 2>/dev/null || true + pm2 delete bigbraincoding 2>/dev/null || true + echo "✅ Stopped existing PM2 process" +else + echo "â„šī¸ PM2 not found, installing..." + npm install -g pm2 +fi + +# Step 9: Start the application with PM2 +echo "" +echo "🚀 Step 9: Starting application with PM2..." +cd "$DEPLOY_DIR" +pm2 start ecosystem.config.js +pm2 save +cd "$PROJECT_DIR" + +# Step 10: Configure nginx to proxy to the Node.js application +echo "" +echo "🌐 Step 10: Configuring nginx proxy..." +NGINX_CONFIG="/etc/nginx/sites-available/bigbraincoding.com" +sudo tee "$NGINX_CONFIG" > /dev/null << EOF +server { + listen 80; + server_name bigbraincoding.com www.bigbraincoding.com; + + location / { + proxy_pass http://localhost:$PORT; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_cache_bypass \$http_upgrade; + } +} +EOF + +# Enable the site and restart nginx +sudo ln -sf "$NGINX_CONFIG" /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx + +# Step 11: Verify deployment +echo "" +echo "✅ Step 11: Verifying deployment..." +sleep 5 # Give the application time to start + +if curl -f http://localhost:$PORT > /dev/null 2>&1; then + echo "✅ Application is running on port $PORT" + echo "✅ Deployment successful!" + echo "" + echo "🌐 Your site should now be live at: https://bigbraincoding.com" + echo "📊 Analytics features are now active!" + echo "🔧 PM2 process: bigbraincoding" + echo "📈 Monitor with: pm2 monit" +else + echo "❌ Error: Application is not responding on port $PORT" + echo "🔍 Check logs with: pm2 logs bigbraincoding" + exit 1 +fi + +echo "" +echo "🎉 Server-side deployment complete!" +echo "" +echo "📋 Useful commands:" +echo " pm2 status - Check application status" +echo " pm2 logs bigbraincoding - View application logs" +echo " pm2 restart bigbraincoding - Restart application" +echo " pm2 monit - Monitor all processes" \ No newline at end of file diff --git a/scripts/deploy-simple.sh b/scripts/deploy-simple.sh new file mode 100755 index 0000000..305b18b --- /dev/null +++ b/scripts/deploy-simple.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# Big Brain Coding - Simple Server-Side Deployment +# This script starts the Next.js application with PM2 for analytics features + +set -e # Exit on any error + +# Configuration +PROJECT_DIR="$(pwd)" +PORT=3000 + +echo "🚀 Big Brain Coding - Simple Server Deployment" +echo "=============================================" +echo "📁 Project Directory: $PROJECT_DIR" +echo "🔌 Port: $PORT" +echo "" + +# Step 1: Build the application +echo "🔨 Step 1: Building Next.js application..." +npm run build + +# Step 2: Stop existing PM2 process if running +echo "" +echo "🛑 Step 2: Managing PM2 processes..." +pm2 stop bigbraincoding 2>/dev/null || true +pm2 delete bigbraincoding 2>/dev/null || true +echo "✅ Stopped existing PM2 process" + +# Step 3: Start the application with PM2 +echo "" +echo "🚀 Step 3: Starting application with PM2..." +pm2 start "npm start" --name bigbraincoding --cwd "$PROJECT_DIR" +pm2 save + +# Step 4: Configure nginx to proxy to the Node.js application +echo "" +echo "🌐 Step 4: Configuring nginx proxy..." +NGINX_CONFIG="/etc/nginx/sites-available/bigbraincoding.com" +sudo tee "$NGINX_CONFIG" > /dev/null << EOF +server { + listen 80; + server_name bigbraincoding.com www.bigbraincoding.com; + + location / { + proxy_pass http://localhost:$PORT; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_cache_bypass \$http_upgrade; + } +} +EOF + +# Enable the site and restart nginx +sudo ln -sf "$NGINX_CONFIG" /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx + +# Step 5: Verify deployment +echo "" +echo "✅ Step 5: Verifying deployment..." +sleep 5 # Give the application time to start + +if curl -f http://localhost:$PORT > /dev/null 2>&1; then + echo "✅ Application is running on port $PORT" + echo "✅ Deployment successful!" + echo "" + echo "🌐 Your site should now be live at: https://bigbraincoding.com" + echo "📊 Analytics features are now active!" + echo "🔧 PM2 process: bigbraincoding" + echo "📈 Monitor with: pm2 monit" +else + echo "❌ Error: Application is not responding on port $PORT" + echo "🔍 Check logs with: pm2 logs bigbraincoding" + exit 1 +fi + +echo "" +echo "🎉 Server-side deployment complete!" +echo "" +echo "📋 Useful commands:" +echo " pm2 status - Check application status" +echo " pm2 logs bigbraincoding - View application logs" +echo " pm2 restart bigbraincoding - Restart application" +echo " pm2 monit - Monitor all processes" \ No newline at end of file diff --git a/src/app/analytics/fingerprint/page.tsx b/src/app/analytics/fingerprint/page.tsx new file mode 100644 index 0000000..62181c3 --- /dev/null +++ b/src/app/analytics/fingerprint/page.tsx @@ -0,0 +1,8 @@ +import FingerprintAnalyticsDashboard from '@/components/analytics/FingerprintAnalyticsDashboard'; + +// Force dynamic rendering to avoid caching issues +export const dynamic = 'force-dynamic'; + +export default function FingerprintPage() { + return ; +} \ No newline at end of file diff --git a/src/app/analytics/ip/[ip]/page.tsx b/src/app/analytics/ip/[ip]/page.tsx index 47a4971..91a1ef6 100755 --- a/src/app/analytics/ip/[ip]/page.tsx +++ b/src/app/analytics/ip/[ip]/page.tsx @@ -35,8 +35,21 @@ export default function IPAnalyticsPage() { const response = await fetch(`/api/analytics/ip-analytics?ip=${ipAddress}`); if (!response.ok) throw new Error('Failed to fetch IP analytics'); const data = await response.json(); + console.log('IP Analytics API Response:', data); // Debug log + if (!data) { + setError('No analytics data found for this IP address'); + return; + } + // Ensure sessionDetails is always an array + if (data && typeof data === 'object') { + if (!Array.isArray(data.sessionDetails)) { + console.warn('sessionDetails is not an array:', data.sessionDetails); + data.sessionDetails = []; + } + } setAnalytics(data); } catch (err) { + console.error('Error fetching IP analytics:', err); setError(err instanceof Error ? err.message : 'An error occurred'); } finally { setLoading(false); @@ -49,8 +62,17 @@ export default function IPAnalyticsPage() { const response = await fetch(`/api/analytics/ip-logs?ip=${ipAddress}`); if (!response.ok) throw new Error('Failed to fetch IP logs'); const data = await response.json(); - setLogs(data); + console.log('IP Logs API Response:', data); // Debug log + + // The API returns an object with 'entries' property, not a direct array + if (data && data.entries && Array.isArray(data.entries)) { + setLogs(data.entries); + } else { + console.warn('IP Logs API returned invalid data:', data); + setLogs([]); + } } catch (err) { + console.error('Error fetching IP logs:', err); setError(err instanceof Error ? err.message : 'An error occurred'); } }, [ipAddress]); @@ -153,11 +175,23 @@ export default function IPAnalyticsPage() {

IP Analytics: {ipAddress}

- - {analytics.engagementScore}% Engagement + + {analytics.engagementScore || 0}% Engagement
+ + Back to Analytics + + + Fingerprint Data +
@@ -296,18 +330,30 @@ export default function IPAnalyticsPage() {
- {Object.entries(analytics.devices).map(([device, count]) => ( -
- {device} - {count} -
- ))} - {Object.entries(analytics.browsers).map(([browser, count]) => ( -
- {browser} - {count} -
- ))} + {(() => { + const devices = analytics.devices; + const browsers = analytics.browsers; + + console.log('Devices:', devices); // Debug log + console.log('Browsers:', browsers); // Debug log + + return ( + <> + {devices && typeof devices === 'object' && Object.entries(devices).map(([device, count]) => ( +
+ {device} + {count} +
+ ))} + {browsers && typeof browsers === 'object' && Object.entries(browsers).map(([browser, count]) => ( +
+ {browser} + {count} +
+ ))} + + ); + })()}
@@ -321,17 +367,27 @@ export default function IPAnalyticsPage() {
- {Object.entries(analytics.pages) - .sort(([, a], [, b]) => b - a) - .map(([page, count]) => ( -
- {page} -
- - {count} + {(() => { + const pages = analytics.pages; + console.log('Pages:', pages); // Debug log + + if (!pages || typeof pages !== 'object') { + console.warn('pages is not an object:', pages); + return
No page data available
; + } + + return Object.entries(pages) + .sort(([, a], [, b]) => b - a) + .map(([page, count]) => ( +
+ {page} +
+ + {count} +
-
- ))} + )); + })()}
@@ -344,29 +400,39 @@ export default function IPAnalyticsPage() {
- {analytics.sessionDetails.map((session, index) => ( -
-
-
-

Session {index + 1}

-

- {formatDate(session.startTime)} - {formatDate(session.endTime)} -

+ {(() => { + const sessionDetails = analytics.sessionDetails; + console.log('Session Details:', sessionDetails); // Debug log + + if (!sessionDetails || !Array.isArray(sessionDetails)) { + console.warn('sessionDetails is not an array:', sessionDetails); + return
No session details available
; + } + + return sessionDetails.map((session, index) => ( +
+
+
+

Session {index + 1}

+

+ {formatDate(session.startTime)} - {formatDate(session.endTime)} +

+
+ {formatDuration(session.duration)} +
+
+ {session.totalRequests} requests +
+
+ {(session.pages || []).map((page, pageIndex) => ( + + {page} + + ))}
- {formatDuration(session.duration)} -
-
- {session.totalRequests} requests -
-
- {session.pages.map((page, pageIndex) => ( - - {page} - - ))}
-
- ))} + )); + })()}
@@ -399,18 +465,29 @@ export default function IPAnalyticsPage() {
- {logs.map((log, index) => ( -
-
- {formatDate(log.timestamp)} - = 400 ? 'destructive' : 'default'}> - {log.statusCode} - + {(() => { + console.log('Logs data:', logs); // Debug log + + if (!logs || !Array.isArray(logs)) { + console.warn('logs is not an array:', logs); + return
No logs available
; + } + + return logs + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + .map((log, index) => ( +
+
+ {formatDate(log.timestamp)} + = 400 ? 'destructive' : 'default'}> + {log.statusCode} + +
+
{log.method} {log.path}
+
{log.userAgent}
-
{log.method} {log.path}
-
{log.userAgent}
-
- ))} + )); + })()}
diff --git a/src/app/api/analytics/fingerprint/route.ts b/src/app/api/analytics/fingerprint/route.ts new file mode 100644 index 0000000..c787b68 --- /dev/null +++ b/src/app/api/analytics/fingerprint/route.ts @@ -0,0 +1,219 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { join } from 'path'; +import { existsSync } from 'fs'; + +interface FingerprintEntry { + timestamp: string; + visitorId: string; + ipAddress: string; + userAgent: string; + browser: string; + deviceType: string; + screenResolution: string; + timezone: string; + language: string; + platform: string; + cpuCores: number; + memorySize: number; + canvas: string; + webgl: string; + audio: string; + fonts: string; // Changed from string[] to string + plugins: string; // Changed from string[] to string + sessionId: string; + pageUrl: string; + referrer: string; + timeOnPage: number; + scrollDepth: number; + clicks: number; + scrolls: number; + formInteractions: number; +} + +interface FingerprintAnalytics { + totalVisitors: number; + uniqueVisitors: number; + totalSessions: number; + averageTimeOnSite: number; + averageScrollDepth: number; + browsers: Record; + devices: Record; + timezones: Record; + languages: Record; + platforms: Record; + screenResolutions: Record; + topPages: Record; + topReferrers: Record; + interactionStats: { + averageClicks: number; + averageScrolls: number; + averageFormInteractions: number; + }; + recentVisitors: FingerprintEntry[]; +} + +export async function GET(request: NextRequest) { + try { + // Read fingerprint log file + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs'); + const fingerprintLogFile = join(logDir, 'fingerprint_tracking.log'); + + if (!existsSync(fingerprintLogFile)) { + return NextResponse.json({ + totalVisitors: 0, + uniqueVisitors: 0, + totalSessions: 0, + averageTimeOnSite: 0, + averageScrollDepth: 0, + browsers: {}, + devices: {}, + timezones: {}, + languages: {}, + platforms: {}, + screenResolutions: {}, + topPages: {}, + topReferrers: {}, + interactionStats: { + averageClicks: 0, + averageScrolls: 0, + averageFormInteractions: 0 + }, + recentVisitors: [] + }); + } + + const { readFile } = await import('fs/promises'); + const content = await readFile(fingerprintLogFile, 'utf-8'); + const lines = content.split('\n').filter(line => line.trim()); + + const entries: FingerprintEntry[] = lines + .map(line => { + try { + return JSON.parse(line); + } catch (error) { + console.error('Error parsing fingerprint log line:', error); + return null; + } + }) + .filter(entry => entry !== null) as FingerprintEntry[]; + + if (entries.length === 0) { + return NextResponse.json({ + totalVisitors: 0, + uniqueVisitors: 0, + totalSessions: 0, + averageTimeOnSite: 0, + averageScrollDepth: 0, + browsers: {}, + devices: {}, + timezones: {}, + languages: {}, + platforms: {}, + screenResolutions: {}, + topPages: {}, + topReferrers: {}, + interactionStats: { + averageClicks: 0, + averageScrolls: 0, + averageFormInteractions: 0 + }, + recentVisitors: [] + }); + } + + // Calculate analytics + const uniqueVisitors = new Set(entries.map(entry => entry.visitorId)); + const uniqueSessions = new Set(entries.map(entry => entry.sessionId)); + + const browsers: Record = {}; + const devices: Record = {}; + const timezones: Record = {}; + const languages: Record = {}; + const platforms: Record = {}; + const screenResolutions: Record = {}; + const topPages: Record = {}; + const topReferrers: Record = {}; + + let totalTimeOnSite = 0; + let totalScrollDepth = 0; + let totalClicks = 0; + let totalScrolls = 0; + let totalFormInteractions = 0; + + entries.forEach(entry => { + // Count browsers + browsers[entry.browser] = (browsers[entry.browser] || 0) + 1; + + // Count devices + devices[entry.deviceType] = (devices[entry.deviceType] || 0) + 1; + + // Count timezones + timezones[entry.timezone] = (timezones[entry.timezone] || 0) + 1; + + // Count languages + languages[entry.language] = (languages[entry.language] || 0) + 1; + + // Count platforms + platforms[entry.platform] = (platforms[entry.platform] || 0) + 1; + + // Count screen resolutions + screenResolutions[entry.screenResolution] = (screenResolutions[entry.screenResolution] || 0) + 1; + + // Count pages + topPages[entry.pageUrl] = (topPages[entry.pageUrl] || 0) + 1; + + // Count referrers + if (entry.referrer && entry.referrer !== 'unknown') { + topReferrers[entry.referrer] = (topReferrers[entry.referrer] || 0) + 1; + } + + // Sum interaction metrics + totalTimeOnSite += entry.timeOnPage; + totalScrollDepth += entry.scrollDepth; + totalClicks += entry.clicks; + totalScrolls += entry.scrolls; + totalFormInteractions += entry.formInteractions; + }); + + const averageTimeOnSite = entries.length > 0 ? totalTimeOnSite / entries.length : 0; + const averageScrollDepth = entries.length > 0 ? totalScrollDepth / entries.length : 0; + const averageClicks = entries.length > 0 ? totalClicks / entries.length : 0; + const averageScrolls = entries.length > 0 ? totalScrolls / entries.length : 0; + const averageFormInteractions = entries.length > 0 ? totalFormInteractions / entries.length : 0; + + // Get recent visitors (last 50 entries) + const recentVisitors = entries + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + .slice(0, 50); + + const analytics: FingerprintAnalytics = { + totalVisitors: uniqueVisitors.size, // Changed from entries.length to uniqueVisitors.size + uniqueVisitors: uniqueVisitors.size, + totalSessions: uniqueSessions.size, + averageTimeOnSite, + averageScrollDepth, + browsers, + devices, + timezones, + languages, + platforms, + screenResolutions, + topPages, + topReferrers, + interactionStats: { + averageClicks, + averageScrolls, + averageFormInteractions + }, + recentVisitors + }; + + return NextResponse.json(analytics); + } catch (error) { + console.error('Error processing fingerprint analytics:', error); + return NextResponse.json( + { error: 'Failed to process fingerprint analytics' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/analytics/ip-analytics/route.ts b/src/app/api/analytics/ip-analytics/route.ts index a4a9da7..bcf762d 100755 --- a/src/app/api/analytics/ip-analytics/route.ts +++ b/src/app/api/analytics/ip-analytics/route.ts @@ -40,36 +40,29 @@ export async function POST(request: NextRequest) { } if (selectedLogFiles.length === 0) { - return NextResponse.json({ - analytics: null, - error: 'No log files found' - }); + return NextResponse.json(null); } // Parse log files const entries = await nginxLogParser.parseLogFiles(selectedLogFiles); + console.log(`IP Analytics API: Parsed ${entries.length} total entries from ${selectedLogFiles.length} log files`); // Filter by date range if provided let filteredEntries = entries; if (dateRange && dateRange.start && dateRange.end) { filteredEntries = nginxLogParser.filterByDateRange(entries, dateRange.start, dateRange.end); + console.log(`IP Analytics API: After date filtering: ${filteredEntries.length} entries`); } // Get detailed analytics for the specific IP const analytics = nginxLogParser.getIPAnalytics(filteredEntries, ipAddress); + console.log(`IP Analytics API: Analytics for IP ${ipAddress}: ${analytics?.totalVisits || 0} visits`); if (!analytics) { - return NextResponse.json({ - analytics: null, - error: 'IP address not found in logs' - }); + return NextResponse.json(null); } - return NextResponse.json({ - analytics, - logType, - filesProcessed: selectedLogFiles - }); + return NextResponse.json(analytics); } catch (error) { console.error('IP Analytics API error:', error); return NextResponse.json( diff --git a/src/app/api/tracking/fingerprint/route.ts b/src/app/api/tracking/fingerprint/route.ts new file mode 100644 index 0000000..d33c94a --- /dev/null +++ b/src/app/api/tracking/fingerprint/route.ts @@ -0,0 +1,131 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { writeFile, appendFile, mkdir } from 'fs/promises'; +import { existsSync } from 'fs'; +import { join } from 'path'; +import { FingerprintData } from '@/lib/fingerprintTracker'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const fingerprintData: FingerprintData = body; + + // Get client IP address + const forwarded = request.headers.get('x-forwarded-for'); + const ip = forwarded ? forwarded.split(',')[0] : request.headers.get('x-real-ip') || 'unknown'; + + // Update the fingerprint data with the actual IP + fingerprintData.ipAddress = ip; + + // Create log directory if it doesn't exist + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs'); + const fingerprintLogFile = join(logDir, 'fingerprint_tracking.log'); + + // Ensure directory exists + try { + await mkdir(logDir, { recursive: true }); + } catch (error) { + // Directory might already exist + } + + // Format the data as a log entry similar to NGINX format + const logEntry = formatFingerprintLogEntry(fingerprintData); + + // Append to fingerprint log file + await appendFile(fingerprintLogFile, logEntry + '\n'); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Fingerprint tracking error:', error); + return NextResponse.json( + { error: 'Failed to track fingerprint data' }, + { status: 500 } + ); + } +} + +function formatFingerprintLogEntry(data: FingerprintData): string { + const timestamp = new Date(data.timestamp).toISOString(); + const logData = { + timestamp, + visitorId: data.visitorId || 'unknown', + ipAddress: data.ipAddress || 'unknown', + userAgent: data.userAgent || 'unknown', + browser: data.browser || 'unknown', + deviceType: data.deviceType || 'unknown', + screenResolution: data.screenResolution || 'unknown', + timezone: data.timezone || 'unknown', + language: data.language || 'unknown', + platform: data.platform || 'unknown', + cpuCores: data.cpuCores || 0, + memorySize: data.memorySize || 0, + canvas: data.canvas || '', + webgl: data.webgl || '', + audio: data.audio || '', + fonts: (data.fonts || []).join(','), + plugins: (data.plugins || []).join(','), + sessionId: data.sessionId || 'unknown', + pageUrl: data.pageUrl || 'unknown', + referrer: data.referrer || 'unknown', + timeOnPage: data.timeOnPage || 0, + scrollDepth: data.scrollDepth || 0, + clicks: data.interactions?.clicks || 0, + scrolls: data.interactions?.scrolls || 0, + formInteractions: data.interactions?.formInteractions || 0 + }; + + // Format as JSON for easy parsing + return JSON.stringify(logData); +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const visitorId = searchParams.get('visitorId'); + const ipAddress = searchParams.get('ip'); + + if (!visitorId && !ipAddress) { + return NextResponse.json( + { error: 'visitorId or ip parameter is required' }, + { status: 400 } + ); + } + + // Read fingerprint log file + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs'); + const fingerprintLogFile = join(logDir, 'fingerprint_tracking.log'); + + if (!existsSync(fingerprintLogFile)) { + return NextResponse.json({ entries: [] }); + } + + const { readFile } = await import('fs/promises'); + const content = await readFile(fingerprintLogFile, 'utf-8'); + const lines = content.split('\n').filter(line => line.trim()); + + const entries = lines + .map(line => { + try { + return JSON.parse(line); + } catch (error) { + return null; + } + }) + .filter(entry => entry !== null) + .filter(entry => { + if (visitorId && entry.visitorId !== visitorId) return false; + if (ipAddress && entry.ipAddress !== ipAddress) return false; + return true; + }); + + return NextResponse.json({ + entries, + totalEntries: entries.length + }); + } catch (error) { + console.error('Error fetching fingerprint data:', error); + return NextResponse.json( + { error: 'Failed to fetch fingerprint data' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e03d877..49d71fd 100755 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -12,6 +12,7 @@ import { ConsentManagerProvider, CookieBanner, } from '@c15t/react'; +import FingerprintTracker from "@/components/analytics/FingerprintTracker"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -93,6 +94,7 @@ export default function RootLayout({
{children}
+
diff --git a/src/components/analytics/EnhancedAnalyticsDashboard.tsx b/src/components/analytics/EnhancedAnalyticsDashboard.tsx index d73de53..60e6c21 100755 --- a/src/components/analytics/EnhancedAnalyticsDashboard.tsx +++ b/src/components/analytics/EnhancedAnalyticsDashboard.tsx @@ -176,6 +176,13 @@ export default function EnhancedAnalyticsDashboard() { + + Fingerprint Analytics + + {selectedPlatform === 'nginx' && (