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/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/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/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
old mode 100644
new mode 100755
index 47a4971..91a1ef6
--- 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]) => (
+
-
- ))}
+ ));
+ })()}
@@ -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
old mode 100644
new mode 100755
index a4a9da7..bcf762d
--- 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/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/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
old mode 100644
new mode 100755
index d73de53..60e6c21
--- a/src/components/analytics/EnhancedAnalyticsDashboard.tsx
+++ b/src/components/analytics/EnhancedAnalyticsDashboard.tsx
@@ -176,6 +176,13 @@ export default function EnhancedAnalyticsDashboard() {
+
+ Fingerprint Analytics
+
+
{selectedPlatform === 'nginx' && (