diff --git a/.cursor/docs.json b/.cursor/docs.json index 240f43e..039267f 100755 --- a/.cursor/docs.json +++ b/.cursor/docs.json @@ -27,6 +27,10 @@ { "name": "Clerk Docs", "url": "https://clerk.com/docs" + }, + { + "name": "Vercel Docs", + "url": "https://vercel.com/docs" } ] } diff --git a/404.html b/404.html index f15758d..bc4cf5c 100755 --- a/404.html +++ b/404.html @@ -1,4 +1,4 @@ - + -404: This page could not be found.Big Brain Coding - Modern Software Development

404

This page could not be found.

\ No newline at end of file +404: This page could not be found.Big Brain Coding - Modern Software Development

404

This page could not be found.

\ No newline at end of file diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..ff5f89d --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,93 @@ +# Analytics Configuration Guide + +This document explains how to configure the analytics system for different environments and users. + +## Environment Variables + +The analytics system can be configured using environment variables. Here are the available options: + +### Log File Configuration + +- `LOGS_BASE_DIR`: Base directory for logs (defaults to user's home directory) +- `NGINX_LOGS_DIR`: NGINX log directory relative to base directory (default: `docker/nginx/logs`) +- `ACCESS_LOG_NAME`: Access log filename (default: `bigbraincoding.com_access.log`) +- `TRACKING_LOG_NAME`: Tracking log filename (default: `bigbraincoding.com_tracking.log`) +- `IP_TRACKING_LOG_NAME`: IP tracking log filename (default: `bigbraincoding.com_ip_tracking.log`) + +### Analytics Configuration + +- `ANALYTICS_TIMEZONE`: Timezone for analytics (default: `America/Kentucky/Louisville`) +- `SESSION_TIMEOUT`: Session timeout in minutes (default: `30`) +- `MIN_BOT_INDICATORS`: Minimum indicators to classify as bot (default: `3`) +- `MAX_HUMAN_REQUESTS`: Maximum requests per IP to consider as human (default: `100`) + +## Example Configuration + +### For a different user: +```bash +export LOGS_BASE_DIR="/home/johndoe" +export NGINX_LOGS_DIR="docker/nginx/logs" +export ACCESS_LOG_NAME="mywebsite.com_access.log" +export TRACKING_LOG_NAME="mywebsite.com_tracking.log" +export IP_TRACKING_LOG_NAME="mywebsite.com_ip_tracking.log" +export ANALYTICS_TIMEZONE="America/New_York" +``` + +### For a different timezone: +```bash +export ANALYTICS_TIMEZONE="Europe/London" +export SESSION_TIMEOUT="45" +``` + +### For different bot detection settings: +```bash +export MIN_BOT_INDICATORS="2" +export MAX_HUMAN_REQUESTS="200" +``` + +## Default Configuration + +If no environment variables are set, the system uses these defaults: + +- **Base Directory**: User's home directory (`$HOME` or `$USERPROFILE`) +- **NGINX Logs**: `~/docker/nginx/logs/` +- **Log Files**: `bigbraincoding.com_*.log` +- **Timezone**: `America/Kentucky/Louisville` +- **Session Timeout**: 30 minutes +- **Bot Detection**: 3 minimum indicators, 100 max human requests + +## File Structure + +The expected log file structure is: +``` +$LOGS_BASE_DIR/ +└── $NGINX_LOGS_DIR/ + ├── $ACCESS_LOG_NAME + ├── $TRACKING_LOG_NAME + └── $IP_TRACKING_LOG_NAME +``` + +## Deployment Notes + +1. **Environment Variables**: Set these in your deployment environment (e.g., `.env` file, Docker environment, or server environment) +2. **File Permissions**: Ensure the web server has read access to the log files +3. **Timezone**: Use IANA timezone identifiers (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`) +4. **Log Rotation**: The system will work with rotated log files as long as they follow the naming convention + +## Troubleshooting + +### Common Issues: + +1. **"No data to export"**: Check that log files exist and are readable +2. **"Invalid data structure"**: Verify log file format matches NGINX combined log format +3. **Timezone issues**: Ensure the timezone identifier is valid +4. **Permission denied**: Check file permissions on log directories + +### Debug Mode: + +Enable debug logging by setting: +```bash +export NODE_ENV="development" +``` + +This will provide detailed console output for troubleshooting. \ No newline at end of file diff --git a/README.md b/README.md index e215bc4..e9d6d5b 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Big Brain Coding - Modern Software Development + +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app) for Big Brain Coding's company website. + +## 🚀 Features + +### Current Features: +- **Modern Web Design** - Responsive, accessible, and performant +- **AI Integration Services** - Showcase of AI-powered solutions +- **Project Portfolio** - Interactive project showcases +- **Contact Management** - Professional contact forms and communication +- **Analytics Integration** - Comprehensive tracking and insights +- **Welcome Banner** - Informative banner with 24-hour persistence + +### Coming Soon: +- **Automated Project Documentation** - Screenshot generation for GitHub repos and live websites +- **Blog System** - MDX-based content with authentication and comments +- **E-commerce Integration** - Stripe-powered payment processing +- **Advanced Analytics** - Enhanced tracking and reporting features ## Getting Started diff --git a/TODO_BLOG_FEATURES.md b/TODO_BLOG_FEATURES.md index f1e837a..c08c5a2 100755 --- a/TODO_BLOG_FEATURES.md +++ b/TODO_BLOG_FEATURES.md @@ -17,6 +17,55 @@ --- +## 🖼️ Project Screenshot Automation (Coming Soon) + +### Automated Project Documentation: +- [ ] **Screenshot Generation Script** - Puppeteer-based automation +- [ ] **GitHub Repository Integration** - Capture repo information and stats +- [ ] **Live Website Screenshots** - Capture deployed project screenshots +- [ ] **Multi-Resolution Support** - Desktop (1920x1080), tablet, and mobile views +- [ ] **Modal Gallery System** - Interactive project showcase with image carousel +- [ ] **Technology Stack Display** - Automated detection and display of tech stack +- [ ] **Project Statistics** - Stars, forks, issues, and deployment status + +### Screenshot Capture Features: +- [ ] **GitHub Repository Pages** - Main branch, README, and key files +- [ ] **Live Website Pages** - Homepage, features, about, and key functionality +- [ ] **Responsive Design Testing** - Multiple viewport sizes for comprehensive coverage +- [ ] **Custom Viewport Support** - Specific resolutions for optimal presentation +- [ ] **Error Handling** - Graceful fallbacks for unavailable sites or repos + +### Technical Implementation: +- [ ] **Puppeteer Integration** - Reliable screenshot generation with headless Chrome +- [ ] **File Storage System** - Organized screenshot storage with versioning +- [ ] **Database Integration** - Project metadata and screenshot management +- [ ] **Manual Trigger System** - On-demand screenshot generation for updates +- [ ] **Image Optimization** - Compressed screenshots for fast loading +- [ ] **Caching Strategy** - Efficient storage and retrieval of project assets + +### User Experience Features: +- [ ] **Interactive Project Modals** - Rich project showcase with multiple views +- [ ] **Image Carousel Navigation** - Smooth browsing through project screenshots +- [ ] **Technology Badges** - Visual representation of project tech stack +- [ ] **GitHub Statistics Display** - Real-time repository metrics +- [ ] **Responsive Gallery** - Mobile-friendly project presentation +- [ ] **Loading States** - Smooth user experience during image generation + +### Workflow Process: +1. **Setup Phase** - Configure screenshot scripts for each project +2. **Capture Phase** - Generate screenshots of GitHub repos and live websites +3. **Integration Phase** - Update project pages with new screenshots and data +4. **Maintenance Phase** - Re-run scripts when projects undergo major updates + +### Benefits: +- **Consistent Project Documentation** - Standardized presentation across all projects +- **Time-Saving Automation** - Eliminates manual screenshot capture and editing +- **Professional Presentation** - High-quality, consistent project showcases +- **Easy Updates** - Simple script execution for project refresh +- **Comprehensive Coverage** - Both code and live website documentation + +--- + ## 📝 Blog Implementation (Future Priority) ### Core Blog Features: diff --git a/_next/static/KfXY7tz1ArGuwIJMgic5E/_buildManifest.js b/_next/static/KfXY7tz1ArGuwIJMgic5E/_buildManifest.js new file mode 100755 index 0000000..ec04654 --- /dev/null +++ b/_next/static/KfXY7tz1ArGuwIJMgic5E/_buildManifest.js @@ -0,0 +1 @@ +self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:8,errorRate:1e-4,numBits:154,numHashes:14,bitArray:[0,0,r,r,r,r,1,1,r,r,e,r,r,e,e,r,r,e,e,e,e,e,e,r,e,r,e,r,r,e,r,e,r,r,e,e,r,e,e,e,e,r,e,r,r,e,e,e,e,r,e,r,e,r,r,e,r,e,r,e,e,e,r,r,r,e,r,e,r,e,e,r,e,e,e,r,e,e,r,r,e,r,e,e,r,e,e,r,e,e,r,e,e,e,r,r,r,e,e,r,e,r,e,r,e,r,r,r,r,e,e,e,e,e,e,r,r,e,r,e,e,e,r,r,r,e,e,r,r,r,e,e,r,e,r,e,r,r,e,e,e,r,r,e,e,e,e,e,r,e,e,r,r,e]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-03529f2c21436739.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); \ No newline at end of file diff --git a/_next/static/KfXY7tz1ArGuwIJMgic5E/_ssgManifest.js b/_next/static/KfXY7tz1ArGuwIJMgic5E/_ssgManifest.js new file mode 100755 index 0000000..5b3ff59 --- /dev/null +++ b/_next/static/KfXY7tz1ArGuwIJMgic5E/_ssgManifest.js @@ -0,0 +1 @@ +self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB() \ No newline at end of file diff --git a/about.html b/about.html index 9f0e2e4..c7d3239 100755 --- a/about.html +++ b/about.html @@ -1,4 +1,4 @@ - + -About - Big Brain Coding

About Big Brain Coding

We're a passionate team of developers dedicated to creating innovative software solutions that solve real-world problems and drive business growth.

Our Mission

At Big Brain Coding, our mission is to empower businesses and individuals with innovative software solutions that leverage cutting-edge technology. We believe that great software should be accessible, intuitive, and impactful.

We specialize in creating modern web applications, AI-powered solutions, and custom software that helps our clients achieve their goals and stay ahead of the competition.

Our Values

The principles that guide everything we do

Passion for Innovation

We're passionate about creating innovative solutions that solve real-world problems.

Excellence in Delivery

We strive for excellence in every project, delivering high-quality solutions on time.

Client-Centric Approach

Your success is our success. We work closely with you to understand your needs.

Quality & Security

We prioritize quality and security in every line of code we write.

Fast & Efficient

We deliver results quickly without compromising on quality or attention to detail.

Modern Technology

We use cutting-edge technologies to build scalable, future-proof solutions.

Meet Our Team

The passionate individuals behind our innovative solutions

Bryan Wills
Founder & Lead Developer

Passionate about creating innovative software solutions that make a difference. Specializes in AI integration and modern web technologies.

Next.jsReactTypeScriptAI/MLNode.js

Our Journey

Key milestones in our company's growth

Company Founded
2024

Big Brain Coding was established with a vision to create innovative software solutions.

First Projects
2024

Successfully launched our first projects including NutriSync, MindMate, and AccessiView.

Technology Stack
2024

Established our modern technology stack with Next.js, TypeScript, and AI integration.

Future Growth
2025

Expanding our services and team to serve more clients with innovative solutions.

Ready to Work Together?

Let's discuss how we can help bring your ideas to life with our innovative approach and modern technology.

\ No newline at end of file +About - Big Brain Coding

About Big Brain Coding

We're a passionate team of developers dedicated to creating innovative software solutions that solve real-world problems and drive business growth.

Our Mission

At Big Brain Coding, our mission is to empower businesses and individuals with innovative software solutions that leverage cutting-edge technology. We believe that great software should be accessible, intuitive, and impactful.

We specialize in creating modern web applications, AI-powered solutions, and custom software that helps our clients achieve their goals and stay ahead of the competition.

Our Values

The principles that guide everything we do

Passion for Innovation

We're passionate about creating innovative solutions that solve real-world problems.

Excellence in Delivery

We strive for excellence in every project, delivering high-quality solutions on time.

Client-Centric Approach

Your success is our success. We work closely with you to understand your needs.

Quality & Security

We prioritize quality and security in every line of code we write.

Fast & Efficient

We deliver results quickly without compromising on quality or attention to detail.

Modern Technology

We use cutting-edge technologies to build scalable, future-proof solutions.

Meet Our Team

The passionate individuals behind our innovative solutions

Bryan Wills
Founder & Lead Developer

Passionate about creating innovative software solutions that make a difference. Specializes in AI integration and modern web technologies.

Next.jsReactTypeScriptAI/MLNode.js

Our Journey

Key milestones in our company's growth

Company Founded
2024

Big Brain Coding was established with a vision to create innovative software solutions.

First Projects
2024

Successfully launched our first projects including NutriSync, MindMate, and AccessiView.

Technology Stack
2024

Established our modern technology stack with Next.js, TypeScript, and AI integration.

Future Growth
2025

Expanding our services and team to serve more clients with innovative solutions.

Ready to Work Together?

Let's discuss how we can help bring your ideas to life with our innovative approach and modern technology.

\ No newline at end of file diff --git a/about.txt b/about.txt index 02c4b40..e1e8824 100755 --- a/about.txt +++ b/about.txt @@ -13,7 +13,7 @@ a:I[6874,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/_next/static/css/d0f4d3bb0b85b20d.css","style"] :HL["/_next/static/css/588bd2b89a9a790d.css","style"] -0:{"P":null,"b":"Jcpog2WwpZGCyxiGjUdpQ","p":"","c":["","about"],"i":false,"f":[[["",{"children":["about",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["about","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} +0:{"P":null,"b":"KfXY7tz1ArGuwIJMgic5E","p":"","c":["","about"],"i":false,"f":[[["",{"children":["about",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["about","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} 1a:I[2872,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d58e1160f88e35e1.js","177","static/chunks/app/layout-ff6b5496fe5edda3.js"],"ThemeToggle"] 1b:I[894,[],"ClientPageRoot"] 1c:I[3788,["36","static/chunks/36-0acc6fbd079e7e76.js","220","static/chunks/app/about/page-0a69ebfb9ee876c8.js"],"default"] diff --git a/contact.html b/contact.html index ea3912b..2d0607e 100755 --- a/contact.html +++ b/contact.html @@ -1,4 +1,4 @@ - + -Contact - Big Brain Coding

Get in Touch

Ready to start your next project? We'd love to hear from you. Let's discuss how we can help bring your ideas to life.

Email
Send us an email anytime

hello@bigbraincoding.com

Contact via Email
Phone
Call us during business hours

+1 (555) 123-4567

Contact via Phone
Location
Based in the United States

United States

Business Hours
Available for consultations

Mon - Fri, 9AM - 6PM EST

Send us a Message
Fill out the form below and we'll get back to you within 24 hours.

Let's Start Your Project

We're excited to hear about your project and help you bring your ideas to life. Our team is ready to discuss your requirements and provide a customized solution.

Free consultation and quote
24-hour response time
Transparent pricing
Ongoing support

Follow Us

What to Expect

1. Initial Consultation: We'll discuss your project requirements and goals.

2. Proposal: We'll provide a detailed proposal with timeline and pricing.

3. Development: Our team will build your solution with regular updates.

4. Launch: We'll deploy your project and provide ongoing support.

Frequently Asked Questions

Common questions about our services and process

How long does a typical project take?

Project timelines vary based on complexity. Simple websites take 2-4 weeks, while complex applications can take 2-3 months. We'll provide a detailed timeline during consultation.

What's included in your pricing?

Our pricing includes design, development, testing, deployment, and initial support. We provide transparent pricing with no hidden fees.

Do you provide ongoing support?

Yes, we offer ongoing support and maintenance packages. We can also provide hosting, updates, and technical support for your projects.

Can you work with existing systems?

Absolutely! We can integrate with your existing systems, APIs, and databases. We'll work with your current technology stack or recommend improvements.

Ready to Get Started?

Don't wait to bring your ideas to life. Contact us today and let's discuss your project.

\ No newline at end of file +Contact - Big Brain Coding

Get in Touch

Ready to start your next project? We'd love to hear from you. Let's discuss how we can help bring your ideas to life.

Email
Send us an email anytime

hello@bigbraincoding.com

Contact via Email
Phone
Call us during business hours

+1 (555) 123-4567

Contact via Phone
Location
Based in the United States

United States

Business Hours
Available for consultations

Mon - Fri, 9AM - 6PM EST

Send us a Message
Fill out the form below and we'll get back to you within 24 hours.

Let's Start Your Project

We're excited to hear about your project and help you bring your ideas to life. Our team is ready to discuss your requirements and provide a customized solution.

Free consultation and quote
24-hour response time
Transparent pricing
Ongoing support

Follow Us

What to Expect

1. Initial Consultation: We'll discuss your project requirements and goals.

2. Proposal: We'll provide a detailed proposal with timeline and pricing.

3. Development: Our team will build your solution with regular updates.

4. Launch: We'll deploy your project and provide ongoing support.

Frequently Asked Questions

Common questions about our services and process

How long does a typical project take?

Project timelines vary based on complexity. Simple websites take 2-4 weeks, while complex applications can take 2-3 months. We'll provide a detailed timeline during consultation.

What's included in your pricing?

Our pricing includes design, development, testing, deployment, and initial support. We provide transparent pricing with no hidden fees.

Do you provide ongoing support?

Yes, we offer ongoing support and maintenance packages. We can also provide hosting, updates, and technical support for your projects.

Can you work with existing systems?

Absolutely! We can integrate with your existing systems, APIs, and databases. We'll work with your current technology stack or recommend improvements.

Ready to Get Started?

Don't wait to bring your ideas to life. Contact us today and let's discuss your project.

\ No newline at end of file diff --git a/contact.txt b/contact.txt index b872972..b882cb8 100755 --- a/contact.txt +++ b/contact.txt @@ -13,7 +13,7 @@ a:I[6874,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/_next/static/css/d0f4d3bb0b85b20d.css","style"] :HL["/_next/static/css/588bd2b89a9a790d.css","style"] -0:{"P":null,"b":"Jcpog2WwpZGCyxiGjUdpQ","p":"","c":["","contact"],"i":false,"f":[[["",{"children":["contact",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["contact","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} +0:{"P":null,"b":"KfXY7tz1ArGuwIJMgic5E","p":"","c":["","contact"],"i":false,"f":[[["",{"children":["contact",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["contact","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} 1a:I[2872,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d58e1160f88e35e1.js","177","static/chunks/app/layout-ff6b5496fe5edda3.js"],"ThemeToggle"] 1b:I[894,[],"ClientPageRoot"] 1c:I[2331,["711","static/chunks/8e1d74a4-8abae22f90157330.js","36","static/chunks/36-0acc6fbd079e7e76.js","977","static/chunks/app/contact/page-b14243d6bbebbab7.js"],"default"] diff --git a/index.html b/index.html index 05f8ef1..2be194d 100755 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + -Big Brain Coding - Modern Software Development

Modern Solutions for Modern Problems

We build intelligent applications that solve real-world problems using cutting-edge technology.

Revolutionize Your Meal Planning

Discover NutriSync - the AI-powered meal planning app that makes healthy eating effortless.

3
Innovative Projects
5+
Core Services
100%
Modern Tech Stack

Our Services

We offer comprehensive software development solutions tailored to your needs

Website Design & Development
Modern, responsive websites built with the latest technologies
  • Custom design and development
  • Responsive layouts
  • SEO optimization
  • Performance optimization
  • +2 more features
Website Hosting Services
Reliable hosting solutions with 99.9% uptime guarantee
  • Cloud hosting solutions
  • SSL certificates
  • Domain management
  • Backup services
  • +2 more features
AI Feature Integration
Intelligent features that enhance user experience and business efficiency
  • Chatbot integration
  • Recommendation systems
  • Data analysis
  • Automation workflows
  • +2 more features
Custom Application Development
Tailored applications to solve your specific business needs
  • Web applications
  • Mobile apps
  • Desktop applications
  • API development
  • +2 more features
Chrome Extension Development
Browser extensions to enhance productivity and user experience
  • Custom browser extensions
  • Productivity tools
  • Accessibility features
  • Data collection
  • +2 more features

Featured Projects

Discover our innovative solutions that are changing the way people interact with technology

N

AI-powered meal planning with grocery integration

NutriSync
An intelligent meal planning application that uses AI to suggest recipes, manage dietary preferences, and integrate with grocery stores for seamless shopping.
development

Technologies

ReactTypeScriptAI/ML+2 more

Key Features

  • AI-powered recipe suggestions
  • Dietary preference management
  • Ingredient substitution
  • +4 more features
M

ADHD/Neurodiverse AI Assistant for habit tracking

MindMate
A compassionate AI assistant designed specifically for neurodiverse individuals to help with daily routines, medication reminders, and habit formation.
development

Technologies

React NativeTypeScriptAI/ML+2 more

Key Features

  • Gentle habit reminders
  • Medication tracking
  • Meal time suggestions
  • +4 more features
A

Chrome extension for neurodiverse-friendly browsing

AccessiView
A browser extension that makes the web more accessible for neurodiverse individuals by offering dyslexia-friendly fonts, reducing animations, and filtering out distracting content.
development

Technologies

Chrome ExtensionJavaScriptCSS+1 more

Key Features

  • Dyslexia-friendly font options
  • Animation reduction
  • Ad and noise filtering
  • +4 more features

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with modern technology

Get in Touch
We'd love to hear about your project
Business Hours

Monday - Friday, 9:00 AM - 6:00 PM EST

We typically respond within 24 hours

Quick Message
Tell us about your project

Follow Us

\ No newline at end of file +Big Brain Coding - Modern Software Development

Modern Solutions for Modern Problems

We build intelligent applications that solve real-world problems using cutting-edge technology.

Revolutionize Your Meal Planning

Discover NutriSync - the AI-powered meal planning app that makes healthy eating effortless.

3
Innovative Projects
5+
Core Services
100%
Modern Tech Stack

Our Services

We offer comprehensive software development solutions tailored to your needs

Website Design & Development
Modern, responsive websites built with the latest technologies
  • Custom design and development
  • Responsive layouts
  • SEO optimization
  • Performance optimization
  • +2 more features
Website Hosting Services
Reliable hosting solutions with 99.9% uptime guarantee
  • Cloud hosting solutions
  • SSL certificates
  • Domain management
  • Backup services
  • +2 more features
AI Feature Integration
Intelligent features that enhance user experience and business efficiency
  • Chatbot integration
  • Recommendation systems
  • Data analysis
  • Automation workflows
  • +2 more features
Custom Application Development
Tailored applications to solve your specific business needs
  • Web applications
  • Mobile apps
  • Desktop applications
  • API development
  • +2 more features
Chrome Extension Development
Browser extensions to enhance productivity and user experience
  • Custom browser extensions
  • Productivity tools
  • Accessibility features
  • Data collection
  • +2 more features

Featured Projects

Discover our innovative solutions that are changing the way people interact with technology

N

AI-powered meal planning with grocery integration

NutriSync
An intelligent meal planning application that uses AI to suggest recipes, manage dietary preferences, and integrate with grocery stores for seamless shopping.
development

Technologies

ReactTypeScriptAI/ML+2 more

Key Features

  • AI-powered recipe suggestions
  • Dietary preference management
  • Ingredient substitution
  • +4 more features
M

ADHD/Neurodiverse AI Assistant for habit tracking

MindMate
A compassionate AI assistant designed specifically for neurodiverse individuals to help with daily routines, medication reminders, and habit formation.
development

Technologies

React NativeTypeScriptAI/ML+2 more

Key Features

  • Gentle habit reminders
  • Medication tracking
  • Meal time suggestions
  • +4 more features
A

Chrome extension for neurodiverse-friendly browsing

AccessiView
A browser extension that makes the web more accessible for neurodiverse individuals by offering dyslexia-friendly fonts, reducing animations, and filtering out distracting content.
development

Technologies

Chrome ExtensionJavaScriptCSS+1 more

Key Features

  • Dyslexia-friendly font options
  • Animation reduction
  • Ad and noise filtering
  • +4 more features

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with modern technology

Get in Touch
We'd love to hear about your project
Business Hours

Monday - Friday, 9:00 AM - 6:00 PM EST

We typically respond within 24 hours

Quick Message
Tell us about your project

Follow Us

\ No newline at end of file diff --git a/index.txt b/index.txt index eae4c0d..6749fb7 100755 --- a/index.txt +++ b/index.txt @@ -13,7 +13,7 @@ a:I[6874,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/_next/static/css/d0f4d3bb0b85b20d.css","style"] :HL["/_next/static/css/588bd2b89a9a790d.css","style"] -b:T518,M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z0:{"P":null,"b":"Jcpog2WwpZGCyxiGjUdpQ","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",[["$","path","0",{"d":"$b","children":[]}]]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["__PAGE__","$L15",{},null,false]},null,false],"$L16",false]],"m":"$undefined","G":["$17",[]],"s":false,"S":true} +b:T518,M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z0:{"P":null,"b":"KfXY7tz1ArGuwIJMgic5E","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",[["$","path","0",{"d":"$b","children":[]}]]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["__PAGE__","$L15",{},null,false]},null,false],"$L16",false]],"m":"$undefined","G":["$17",[]],"s":false,"S":true} 18:I[2872,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d58e1160f88e35e1.js","177","static/chunks/app/layout-ff6b5496fe5edda3.js"],"ThemeToggle"] 19:I[844,["711","static/chunks/8e1d74a4-8abae22f90157330.js","36","static/chunks/36-0acc6fbd079e7e76.js","761","static/chunks/761-4794545736642b57.js","974","static/chunks/app/page-8779a809285a6b32.js"],"default"] 1a:I[6416,["711","static/chunks/8e1d74a4-8abae22f90157330.js","36","static/chunks/36-0acc6fbd079e7e76.js","761","static/chunks/761-4794545736642b57.js","974","static/chunks/app/page-8779a809285a6b32.js"],"default"] diff --git a/next.config.js b/next.config.js index 0442f49..9f513da 100755 --- a/next.config.js +++ b/next.config.js @@ -1,7 +1,5 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - // Enable static export - output: 'export', // Allow cross-origin requests from the development server allowedDevOrigins: [ '38.45.65.66', diff --git a/package-lock.json b/package-lock.json index 96dcc32..be9a5bc 100755 --- a/package-lock.json +++ b/package-lock.json @@ -10349,6 +10349,17 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index c429859..fc344d0 100755 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "nextjs-temp", + "name": "BigBrainCoding", "version": "0.1.0", "private": true, "scripts": { @@ -15,6 +15,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", @@ -28,7 +30,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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 862dc25..376ed6c 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@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) + 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)) '@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) @@ -26,6 +26,12 @@ importers: '@radix-ui/react-navigation-menu': specifier: ^1.2.13 version: 1.2.13(@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) + '@radix-ui/react-progress': + specifier: ^1.1.7 + version: 1.1.7(@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) + '@radix-ui/react-select': + specifier: ^2.2.5 + version: 2.2.5(@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) '@radix-ui/react-slot': specifier: ^1.2.3 version: 1.2.3(@types/react@19.1.8)(react@19.1.0) @@ -68,6 +74,9 @@ importers: tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + xlsx: + specifier: ^0.18.5 + version: 0.18.5 devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -842,6 +851,9 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@radix-ui/primitive@1.1.2': resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} @@ -1150,6 +1162,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + 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 + '@radix-ui/react-roving-focus@1.1.10': resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} peerDependencies: @@ -1163,6 +1188,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-select@2.2.5': + resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==} + 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 + '@radix-ui/react-slot@1.2.0': resolution: {integrity: sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==} peerDependencies: @@ -1624,6 +1662,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adler-32@1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1747,6 +1789,10 @@ packages: caniuse-lite@1.0.30001727: resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + cfb@1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1772,6 +1818,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + codepage@1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1809,6 +1859,11 @@ packages: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2217,6 +2272,10 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + frac@1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + framer-motion@12.23.9: resolution: {integrity: sha512-TqEHXj8LWfQSKqfdr5Y4mYltYLw96deu6/K9kGDd+ysqRJPNwF9nb5mZcrLmybHbU7gcJ+HQar41U3UTGanbbQ==} peerDependencies: @@ -3086,6 +3145,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + ssf@0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -3286,6 +3349,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3314,10 +3382,18 @@ packages: wildcard-match@5.1.4: resolution: {integrity: sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==} + wmf@1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + word@0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3325,6 +3401,11 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xlsx@0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -3479,17 +3560,17 @@ snapshots: - supports-color - ws - '@c15t/react@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)': + '@c15t/react@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))': dependencies: '@c15t/node-sdk': 1.4.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-accordion': 1.2.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) '@radix-ui/react-slot': 1.2.0(@types/react@19.1.8)(react@19.1.0) '@radix-ui/react-switch': 1.1.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) - c15t: 1.4.4(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + c15t: 1.4.4(@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)) clsx: 2.1.1 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 5.0.6(@types/react@19.1.8)(react@19.1.0) + zustand: 5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@babel/core' @@ -4236,6 +4317,8 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@radix-ui/number@1.1.1': {} + '@radix-ui/primitive@1.1.2': {} '@radix-ui/react-accordion@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)': @@ -4552,6 +4635,16 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-progress@1.1.7(@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)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@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) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-roving-focus@1.1.10(@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)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -4569,6 +4662,35 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-select@2.2.5(@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)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@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) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@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) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@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) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@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) + '@radix-ui/react-portal': 1.1.9(@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) + '@radix-ui/react-primitive': 2.1.3(@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) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@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) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-slot@1.2.0(@types/react@19.1.8)(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) @@ -4985,6 +5107,8 @@ snapshots: acorn@8.15.0: {} + adler-32@1.3.1: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -5122,13 +5246,13 @@ snapshots: bytes@3.1.2: {} - c15t@1.4.4(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + c15t@1.4.4(@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)): dependencies: '@c15t/backend': 1.4.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@c15t/translations': 1.4.2 '@orpc/client': 1.2.0 '@orpc/server': 1.2.0 - zustand: 5.0.6(@types/react@19.1.8)(react@19.1.0) + zustand: 5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@babel/core' @@ -5192,6 +5316,11 @@ snapshots: caniuse-lite@1.0.30001727: {} + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -5215,6 +5344,8 @@ snapshots: clsx@2.1.1: {} + codepage@1.15.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5249,6 +5380,8 @@ snapshots: dependencies: is-what: 4.1.16 + crc-32@1.2.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5742,6 +5875,8 @@ snapshots: forwarded@0.2.0: {} + frac@1.1.2: {} + framer-motion@12.23.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: motion-dom: 12.23.9 @@ -6677,6 +6812,10 @@ snapshots: source-map-js@1.2.1: {} + ssf@0.11.2: + dependencies: + frac: 1.1.2 + stable-hash@0.0.5: {} statuses@2.0.1: {} @@ -6917,6 +7056,11 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + optional: true + vary@1.1.2: {} which-boxed-primitive@1.1.1: @@ -6966,8 +7110,12 @@ snapshots: wildcard-match@5.1.4: {} + wmf@1.0.2: {} + word-wrap@1.2.5: {} + word@0.3.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6976,6 +7124,16 @@ snapshots: wrappy@1.0.2: {} + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + y18n@5.0.8: {} yallist@5.0.0: {} @@ -6996,7 +7154,8 @@ snapshots: zod@3.25.76: {} - zustand@5.0.6(@types/react@19.1.8)(react@19.1.0): + zustand@5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): optionalDependencies: '@types/react': 19.1.8 react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5f11bfc..a390684 100755 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ onlyBuiltDependencies: - '@tailwindcss/oxide' + - '@vercel/speed-insights' - protobufjs - sharp - unrs-resolver diff --git a/projects.html b/projects.html index abe40e0..209c357 100755 --- a/projects.html +++ b/projects.html @@ -1,4 +1,4 @@ - + -Projects - Big Brain Coding

Our Projects

Discover our innovative solutions that are changing the way people interact with technology. Each project represents our commitment to excellence and innovation.

3

Active Projects

Innovative solutions in development

100%

Client Satisfaction

Delivering exceptional results

< 2 weeks

Average Delivery

Fast and efficient development

24/7

Support Available

Ongoing maintenance and support

N

AI-powered meal planning with grocery integration

NutriSync
An intelligent meal planning application that uses AI to suggest recipes, manage dietary preferences, and integrate with grocery stores for seamless shopping.
development

Technologies Used

ReactTypeScriptAI/MLNode.jsPostgreSQL

Key Features

  • AI-powered recipe suggestions
  • Dietary preference management
  • Ingredient substitution
  • Recipe scaling (2x, 4x, 1/2)
  • Grocery list generation
  • Kroger & Walmart API integration
  • Direct grocery ordering

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

M

ADHD/Neurodiverse AI Assistant for habit tracking

MindMate
A compassionate AI assistant designed specifically for neurodiverse individuals to help with daily routines, medication reminders, and habit formation.
development

Technologies Used

React NativeTypeScriptAI/MLFirebasePush Notifications

Key Features

  • Gentle habit reminders
  • Medication tracking
  • Meal time suggestions
  • Bathroom break reminders
  • Customizable notification system
  • Progress tracking
  • Mood and energy monitoring

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

A

Chrome extension for neurodiverse-friendly browsing

AccessiView
A browser extension that makes the web more accessible for neurodiverse individuals by offering dyslexia-friendly fonts, reducing animations, and filtering out distracting content.
development

Technologies Used

Chrome ExtensionJavaScriptCSSWeb APIs

Key Features

  • Dyslexia-friendly font options
  • Animation reduction
  • Ad and noise filtering
  • Sensory overload prevention
  • Customizable reading modes
  • Focus enhancement tools
  • Website accessibility improvements

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

Our Development Process

We follow a systematic approach to deliver exceptional results

Research & Planning

We thoroughly research your requirements and create a detailed project plan with clear milestones.

Design & Development

Our team builds your solution using modern technologies and best practices.

Testing & Deployment

We rigorously test your solution and deploy it with ongoing support and maintenance.

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with the same level of innovation and quality.

\ No newline at end of file +Projects - Big Brain Coding

Our Projects

Discover our innovative solutions that are changing the way people interact with technology. Each project represents our commitment to excellence and innovation.

3

Active Projects

Innovative solutions in development

100%

Client Satisfaction

Delivering exceptional results

< 2 weeks

Average Delivery

Fast and efficient development

24/7

Support Available

Ongoing maintenance and support

N

AI-powered meal planning with grocery integration

NutriSync
An intelligent meal planning application that uses AI to suggest recipes, manage dietary preferences, and integrate with grocery stores for seamless shopping.
development

Technologies Used

ReactTypeScriptAI/MLNode.jsPostgreSQL

Key Features

  • AI-powered recipe suggestions
  • Dietary preference management
  • Ingredient substitution
  • Recipe scaling (2x, 4x, 1/2)
  • Grocery list generation
  • Kroger & Walmart API integration
  • Direct grocery ordering

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

M

ADHD/Neurodiverse AI Assistant for habit tracking

MindMate
A compassionate AI assistant designed specifically for neurodiverse individuals to help with daily routines, medication reminders, and habit formation.
development

Technologies Used

React NativeTypeScriptAI/MLFirebasePush Notifications

Key Features

  • Gentle habit reminders
  • Medication tracking
  • Meal time suggestions
  • Bathroom break reminders
  • Customizable notification system
  • Progress tracking
  • Mood and energy monitoring

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

A

Chrome extension for neurodiverse-friendly browsing

AccessiView
A browser extension that makes the web more accessible for neurodiverse individuals by offering dyslexia-friendly fonts, reducing animations, and filtering out distracting content.
development

Technologies Used

Chrome ExtensionJavaScriptCSSWeb APIs

Key Features

  • Dyslexia-friendly font options
  • Animation reduction
  • Ad and noise filtering
  • Sensory overload prevention
  • Customizable reading modes
  • Focus enhancement tools
  • Website accessibility improvements

Project Overview

Challenge

Creating innovative solutions that address real-world problems through modern technology.

Solution

Leveraging cutting-edge technologies to build scalable, user-friendly applications.

Impact

Improving user experience and efficiency through intelligent automation and AI integration.

Results

Delivering high-quality solutions that exceed client expectations and drive business growth.

Our Development Process

We follow a systematic approach to deliver exceptional results

Research & Planning

We thoroughly research your requirements and create a detailed project plan with clear milestones.

Design & Development

Our team builds your solution using modern technologies and best practices.

Testing & Deployment

We rigorously test your solution and deploy it with ongoing support and maintenance.

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with the same level of innovation and quality.

\ No newline at end of file diff --git a/projects.txt b/projects.txt index 500388b..b43d259 100755 --- a/projects.txt +++ b/projects.txt @@ -13,7 +13,7 @@ a:I[6874,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/_next/static/css/d0f4d3bb0b85b20d.css","style"] :HL["/_next/static/css/588bd2b89a9a790d.css","style"] -0:{"P":null,"b":"Jcpog2WwpZGCyxiGjUdpQ","p":"","c":["","projects"],"i":false,"f":[[["",{"children":["projects",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["projects","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} +0:{"P":null,"b":"KfXY7tz1ArGuwIJMgic5E","p":"","c":["","projects"],"i":false,"f":[[["",{"children":["projects",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["projects","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} 1a:I[2872,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d58e1160f88e35e1.js","177","static/chunks/app/layout-ff6b5496fe5edda3.js"],"ThemeToggle"] 1b:I[894,[],"ClientPageRoot"] 1c:I[9462,["36","static/chunks/36-0acc6fbd079e7e76.js","893","static/chunks/app/projects/page-4cf9412f1397b98a.js"],"default"] diff --git a/public/brain-favicon.svg b/public/brain-favicon.svg new file mode 100644 index 0000000..188f1f2 --- /dev/null +++ b/public/brain-favicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html index 373a663..e8d8c6f 100755 --- a/public/index.html +++ b/public/index.html @@ -17,7 +17,9 @@ - + + + diff --git a/public/robots.txt b/public/robots.txt old mode 100644 new mode 100755 diff --git a/robots.txt b/robots.txt old mode 100644 new mode 100755 diff --git a/scripts/create-favicon.js b/scripts/create-favicon.js new file mode 100644 index 0000000..2147cc2 --- /dev/null +++ b/scripts/create-favicon.js @@ -0,0 +1,14 @@ +const fs = require('fs'); +const path = require('path'); + +// Simple brain icon as a minimal SVG +const brainSVG = ` + + +`; + +// Write the SVG file +fs.writeFileSync(path.join(__dirname, '../public/brain-favicon.svg'), brainSVG); + +console.log('Brain favicon SVG created successfully!'); +console.log('You can now use this SVG as a favicon by referencing it in your HTML.'); \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3c1bdc2..9dbfeda 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -29,7 +29,7 @@ npm run build # Step 2.5: Inject Google Analytics into all built HTML files echo "" echo "📊 Step 2.5: Injecting Google Analytics into built HTML files..." -node scripts/inject-ga.js +node "$PROJECT_DIR/scripts/inject-ga.js" # Step 3: Create backup of current site echo "" diff --git a/services.html b/services.html index dc10f60..56d201d 100755 --- a/services.html +++ b/services.html @@ -1,4 +1,4 @@ - + -Services - Big Brain Coding

Our Services

We offer comprehensive software development solutions tailored to your needs. From simple websites to complex AI-powered applications, we've got you covered.

Website Design & Development
Modern, responsive websites built with the latest technologies
  • Custom design and development
  • Responsive layouts
  • SEO optimization
  • Performance optimization
  • Content management systems
  • E-commerce integration
Get Started
Website Hosting Services
Reliable hosting solutions with 99.9% uptime guarantee
  • Cloud hosting solutions
  • SSL certificates
  • Domain management
  • Backup services
  • 24/7 monitoring
  • Technical support
Get Started
AI Feature Integration
Intelligent features that enhance user experience and business efficiency
  • Chatbot integration
  • Recommendation systems
  • Data analysis
  • Automation workflows
  • Natural language processing
  • Machine learning models
Get Started
Custom Application Development
Tailored applications to solve your specific business needs
  • Web applications
  • Mobile apps
  • Desktop applications
  • API development
  • Database design
  • Third-party integrations
Get Started
Chrome Extension Development
Browser extensions to enhance productivity and user experience
  • Custom browser extensions
  • Productivity tools
  • Accessibility features
  • Data collection
  • User interface enhancements
  • Cross-browser compatibility
Get Started

Our Process

We follow a proven methodology to deliver exceptional results

01

Discovery & Planning

We understand your requirements and create a detailed project plan

02

Design & Development

Our team builds your solution with modern technologies

03

Testing & Quality Assurance

Rigorous testing ensures your solution works perfectly

04

Deployment & Support

We launch your project and provide ongoing support

Pricing Plans

Choose the perfect plan for your business needs

Starter
$2,500/project
Perfect for small businesses and startups
  • Responsive website design
  • Basic SEO optimization
  • Contact form integration
  • Social media integration
  • 1 month of support
  • Basic analytics setup
Get Started
Most Popular
Professional
$5,000/project
Ideal for growing businesses
  • Everything in Starter
  • Advanced SEO optimization
  • Content management system
  • E-commerce integration
  • 3 months of support
  • Performance optimization
  • Security hardening
Get Started
Enterprise
$10,000+/project
Custom solutions for large organizations
  • Everything in Professional
  • Custom AI integration
  • Advanced analytics
  • Multi-language support
  • API development
  • 6 months of support
  • Priority support
  • Custom features
Get Started

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with modern technology and innovative solutions.

\ No newline at end of file +Services - Big Brain Coding

Our Services

We offer comprehensive software development solutions tailored to your needs. From simple websites to complex AI-powered applications, we've got you covered.

Website Design & Development
Modern, responsive websites built with the latest technologies
  • Custom design and development
  • Responsive layouts
  • SEO optimization
  • Performance optimization
  • Content management systems
  • E-commerce integration
Get Started
Website Hosting Services
Reliable hosting solutions with 99.9% uptime guarantee
  • Cloud hosting solutions
  • SSL certificates
  • Domain management
  • Backup services
  • 24/7 monitoring
  • Technical support
Get Started
AI Feature Integration
Intelligent features that enhance user experience and business efficiency
  • Chatbot integration
  • Recommendation systems
  • Data analysis
  • Automation workflows
  • Natural language processing
  • Machine learning models
Get Started
Custom Application Development
Tailored applications to solve your specific business needs
  • Web applications
  • Mobile apps
  • Desktop applications
  • API development
  • Database design
  • Third-party integrations
Get Started
Chrome Extension Development
Browser extensions to enhance productivity and user experience
  • Custom browser extensions
  • Productivity tools
  • Accessibility features
  • Data collection
  • User interface enhancements
  • Cross-browser compatibility
Get Started

Our Process

We follow a proven methodology to deliver exceptional results

01

Discovery & Planning

We understand your requirements and create a detailed project plan

02

Design & Development

Our team builds your solution with modern technologies

03

Testing & Quality Assurance

Rigorous testing ensures your solution works perfectly

04

Deployment & Support

We launch your project and provide ongoing support

Pricing Plans

Choose the perfect plan for your business needs

Starter
$2,500/project
Perfect for small businesses and startups
  • Responsive website design
  • Basic SEO optimization
  • Contact form integration
  • Social media integration
  • 1 month of support
  • Basic analytics setup
Get Started
Most Popular
Professional
$5,000/project
Ideal for growing businesses
  • Everything in Starter
  • Advanced SEO optimization
  • Content management system
  • E-commerce integration
  • 3 months of support
  • Performance optimization
  • Security hardening
Get Started
Enterprise
$10,000+/project
Custom solutions for large organizations
  • Everything in Professional
  • Custom AI integration
  • Advanced analytics
  • Multi-language support
  • API development
  • 6 months of support
  • Priority support
  • Custom features
Get Started

Ready to Start Your Project?

Let's discuss how we can bring your ideas to life with modern technology and innovative solutions.

\ No newline at end of file diff --git a/services.txt b/services.txt index 520195b..e7975b8 100755 --- a/services.txt +++ b/services.txt @@ -13,7 +13,7 @@ a:I[6874,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d :HL["/_next/static/media/93f479601ee12b01-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}] :HL["/_next/static/css/d0f4d3bb0b85b20d.css","style"] :HL["/_next/static/css/588bd2b89a9a790d.css","style"] -0:{"P":null,"b":"Jcpog2WwpZGCyxiGjUdpQ","p":"","c":["","services"],"i":false,"f":[[["",{"children":["services",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["services","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} +0:{"P":null,"b":"KfXY7tz1ArGuwIJMgic5E","p":"","c":["","services"],"i":false,"f":[[["",{"children":["services",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d0f4d3bb0b85b20d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/588bd2b89a9a790d.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","suppressHydrationWarning":true,"children":["$","$L2",null,{"attribute":"class","defaultTheme":"system","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"options":{"mode":"c15t","backendURL":"https://bryan-wills-111n91xb-europe-onboarding.c15t.dev"},"children":["$","$L4",null,{"children":[["$","$L5",null,{}],["$","$L6",null,{}],["$","div",null,{"className":"flex min-h-screen flex-col","children":[["$","$L7",null,{}],["$","main",null,{"className":"flex-1","children":["$","$L8",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L9",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}],["$","footer",null,{"className":"border-t bg-background","children":["$","div",null,{"className":"container mx-auto px-4 py-12","children":[["$","div",null,{"className":"grid grid-cols-1 gap-8 md:grid-cols-4","children":[["$","div",null,{"className":"space-y-4","children":[["$","$La",null,{"href":"/","className":"flex items-center space-x-2","children":[["$","svg",null,{"ref":"$undefined","xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-brain h-8 w-8 text-primary","aria-hidden":"true","children":[["$","path","l5xja",{"d":"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"}],["$","path","ep3f8r",{"d":"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"}],["$","path","1p4c4q",{"d":"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"}],["$","path","tmeiqw",{"d":"M17.599 6.5a3 3 0 0 0 .399-1.375"}],["$","path","105sqy",{"d":"M6.003 5.125A3 3 0 0 0 6.401 6.5"}],["$","path","ql3yin",{"d":"M3.477 10.896a4 4 0 0 1 .585-.396"}],["$","path","1qfode",{"d":"M19.938 10.5a4 4 0 0 1 .585.396"}],["$","path","2e4loj",{"d":"M6 18a4 4 0 0 1-1.967-.516"}],["$","path","159ez6",{"d":"M19.967 17.484A4 4 0 0 1 18 18"}],"$undefined"]}],["$","span",null,{"className":"text-xl font-bold","children":"Big Brain Coding"}]]}],["$","p",null,{"className":"text-sm text-muted-foreground","children":"A software development company specializing in modern web technologies, AI integration, and custom application development."}],["$","div",null,{"className":"flex space-x-4 ","children":[["$","$La","GitHub",{"href":"https://github.com/bigbraincoding","target":"_blank","rel":"noopener noreferrer","className":"text-muted-foreground transition-colors hover:text-gray-800 dark:hover:text-gray-200","aria-label":"GitHub","children":["$","svg",null,{"stroke":"currentColor","fill":"currentColor","strokeWidth":"0","viewBox":"0 0 496 512","children":["$undefined",["$Lb"]],"className":"$undefined","style":{"color":"$undefined"},"height":20,"width":20,"xmlns":"http://www.w3.org/2000/svg"}]}],"$Lc","$Ld","$Le","$Lf"]}]]}],"$L10","$L11","$L12"]}],"$L13"]}]}],"$L14"]}]]}]}]}]}]]}]]}],{"children":["services","$L15",{"children":["__PAGE__","$L16",{},null,false]},null,false]},null,false],"$L17",false]],"m":"$undefined","G":["$18",[]],"s":false,"S":true} 1a:I[2872,["36","static/chunks/36-0acc6fbd079e7e76.js","758","static/chunks/758-d58e1160f88e35e1.js","177","static/chunks/app/layout-ff6b5496fe5edda3.js"],"ThemeToggle"] 1b:I[894,[],"ClientPageRoot"] 1c:I[9502,["36","static/chunks/36-0acc6fbd079e7e76.js","763","static/chunks/app/services/page-d4d9c62eaa67d639.js"],"default"] diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 76a8138..6ee77f2 100755 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -179,7 +179,7 @@ export default function AboutPage() { variants={staggerContainer} className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3" > - {values.map((value, index) => { + {values.map((value) => { const IconComponent = value.icon return ( @@ -236,7 +236,7 @@ export default function AboutPage() { variants={staggerContainer} className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3" > - {teamMembers.map((member, index) => ( + {teamMembers.map((member) => (
- {milestones.map((milestone, index) => ( + {milestones.map((milestone) => ( {/* Timeline Dot */}
{/* Content */} -
+
diff --git a/src/app/analytics/ip/[ip]/page.tsx b/src/app/analytics/ip/[ip]/page.tsx new file mode 100644 index 0000000..47a4971 --- /dev/null +++ b/src/app/analytics/ip/[ip]/page.tsx @@ -0,0 +1,449 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useParams } from 'next/navigation'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Calendar, Download, FileText, BarChart3, Clock, Globe, User, Activity } from 'lucide-react'; +import { IPAnalytics, NGINXLogEntry } from '@/lib/nginxLogParser'; +import { formatDuration, formatDate, exportIPAnalyticsToCSV, exportIPAnalyticsToXLSX, exportIPLogsToCSV, exportIPLogsToXLSX, downloadCSV } from '@/lib/exportUtils'; + +export default function IPAnalyticsPage() { + const params = useParams(); + const ipAddress = params?.ip as string; + + const [analytics, setAnalytics] = useState(null); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState<'analytics' | 'logs'>('analytics'); + const [dateRange, setDateRange] = useState<{ start: string; end: string }>({ + start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], + end: new Date().toISOString().split('T')[0] + }); + const [exportLoading, setExportLoading] = useState(null); + + const fetchIPAnalytics = useCallback(async () => { + if (!ipAddress) return; + try { + setLoading(true); + 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(); + setAnalytics(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }, [ipAddress]); + + const fetchIPLogs = useCallback(async () => { + if (!ipAddress) return; + try { + 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); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } + }, [ipAddress]); + + useEffect(() => { + fetchIPAnalytics(); + fetchIPLogs(); + }, [fetchIPAnalytics, fetchIPLogs]); + + if (!ipAddress) { + return ( +
+ IP address not found +
+ ); + } + + const handleExportAnalytics = async (format: 'csv' | 'xlsx') => { + if (!analytics) return; + + setExportLoading(`analytics-${format}`); + + try { + if (format === 'csv') { + const csvContent = exportIPAnalyticsToCSV(analytics); + downloadCSV(csvContent, `ip-analytics-${ipAddress}-${new Date().toISOString().split('T')[0]}.csv`); + } else { + exportIPAnalyticsToXLSX(analytics); + } + } catch (error) { + console.error('Export failed:', error); + } finally { + setExportLoading(null); + } + }; + + const handleExportLogs = async (format: 'csv' | 'xlsx') => { + if (logs.length === 0) return; + + setExportLoading(`logs-${format}`); + + try { + if (format === 'csv') { + const csvContent = exportIPLogsToCSV(logs, ipAddress); + downloadCSV(csvContent, `ip-logs-${ipAddress}-${new Date().toISOString().split('T')[0]}.csv`); + } else { + exportIPLogsToXLSX(logs, ipAddress); + } + } catch (error) { + console.error('Export failed:', error); + } finally { + setExportLoading(null); + } + }; + + const getEngagementColor = (score: number): string => { + if (score >= 80) return 'bg-green-500'; + if (score >= 60) return 'bg-yellow-500'; + if (score >= 40) return 'bg-orange-500'; + return 'bg-red-500'; + }; + + if (loading) { + return ( +
+
+
+
+
+ ); + } + + if (error) { + return ( +
+ + + {error} + + +
+ ); + } + + if (!analytics) { + return ( +
+ + + No analytics data found for IP address {ipAddress} + + +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

IP Analytics: {ipAddress}

+ + {analytics.engagementScore}% Engagement + +
+
+ + +
+
+ + {/* Date Range Selection */} + + + Date Range + + +
+
+ + ) => setDateRange(prev => ({ ...prev, start: e.target.value }))} + /> +
+
+ + ) => setDateRange(prev => ({ ...prev, end: e.target.value }))} + /> +
+ + +
+
+
+ + {/* Analytics Summary */} +
+ + + Total Visits + + +
{analytics.totalVisits}
+
+
+ + + + Sessions + + +
{analytics.sessions}
+
+
+ + + + Avg Time on Site + + +
{formatDuration(analytics.averageTimeOnSite)}
+
+
+ + + + Engagement Score + + +
{analytics.engagementScore}%
+ +
+
+
+ + {/* Detailed Analytics */} + + + Overview + Pages + Sessions + Logs ({logs.length}) + Technical + + + +
+ + + Visit Timeline + + +
+ First Visit: +
{formatDate(analytics.firstVisit)}
+
+
+ Last Visit: +
{formatDate(analytics.lastVisit)}
+
+
+
+ + + + Device & Browser + + +
+ {Object.entries(analytics.devices).map(([device, count]) => ( +
+ {device} + {count} +
+ ))} + {Object.entries(analytics.browsers).map(([browser, count]) => ( +
+ {browser} + {count} +
+ ))} +
+
+
+
+
+ + + + + Pages Visited + + +
+ {Object.entries(analytics.pages) + .sort(([, a], [, b]) => b - a) + .map(([page, count]) => ( +
+ {page} +
+ + {count} +
+
+ ))} +
+
+
+
+ + + + + Session Details + + +
+ {analytics.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} + + ))} +
+
+ ))} +
+
+
+
+ + + + + + All Logs ({logs.length} entries) +
+ + +
+
+
+ +
+ {logs.map((log, index) => ( +
+
+ {formatDate(log.timestamp)} + = 400 ? 'destructive' : 'default'}> + {log.statusCode} + +
+
{log.method} {log.path}
+
{log.userAgent}
+
+ ))} +
+
+
+
+ + + + + Technical Information + + +
+
+ IP Address: +
{analytics.ipAddress}
+
+
+ Total Requests: +
{analytics.totalVisits}
+
+
+ Unique Sessions: +
{analytics.sessions}
+
+
+ Average Session Duration: +
{formatDuration(analytics.averageTimeOnSite)}
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/analytics/page.tsx b/src/app/analytics/page.tsx new file mode 100755 index 0000000..61f7aaf --- /dev/null +++ b/src/app/analytics/page.tsx @@ -0,0 +1,11 @@ +'use client' + +import EnhancedAnalyticsDashboard from '@/components/analytics/EnhancedAnalyticsDashboard' + +export default function AnalyticsPage() { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/src/app/api/analytics/dashboard/route.ts b/src/app/api/analytics/dashboard/route.ts new file mode 100755 index 0000000..a7d43dc --- /dev/null +++ b/src/app/api/analytics/dashboard/route.ts @@ -0,0 +1,152 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { readdir, readFile } from 'fs/promises'; +import { join } from 'path'; +import { existsSync } from 'fs'; + +interface TrackingEvent { + timestamp: string; + sessionId: string; + eventType: string; + pageUrl: string; + ipAddress: string; + userAgent: string; + deviceInfo: { + browser: string; + deviceType: string; + screenWidth: number; + screenHeight: number; + language: string; + }; + timeOnPage?: number; + scrollDepth?: number; +} + +interface IPSummary { + ip: string; + totalVisits: number; + uniqueSessions: number; + pages: { [key: string]: number }; + devices: { [key: string]: number }; + browsers: { [key: string]: number }; + averageTimeOnPage: number; + lastVisit: string; + firstVisit: string; +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + let date = body.date; + + // If no date provided, use today's date + if (!date) { + const today = new Date(); + date = today.toISOString().split('T')[0]; // Format: YYYY-MM-DD + } + + const [year, month, day] = date.split('-'); + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs/bigbraincoding.com', year, month, day); + + if (!existsSync(logDir)) { + return NextResponse.json({ + summary: [], + events: [], + totalVisitors: 0, + totalSessions: 0, + totalPageViews: 0 + }); + } + + const files = await readdir(logDir); + const jsonFiles = files.filter(file => file.endsWith('.json')); + + const events: TrackingEvent[] = []; + const ipSummaries: { [key: string]: IPSummary } = {}; + const sessionSet = new Set(); + + for (const file of jsonFiles) { + try { + const content = await readFile(join(logDir, file), 'utf-8'); + const event: TrackingEvent = JSON.parse(content); + events.push(event); + sessionSet.add(event.sessionId); + + // Initialize IP summary if not exists + if (!ipSummaries[event.ipAddress]) { + ipSummaries[event.ipAddress] = { + ip: event.ipAddress, + totalVisits: 0, + uniqueSessions: 0, + pages: {}, + devices: {}, + browsers: {}, + averageTimeOnPage: 0, + lastVisit: event.timestamp, + firstVisit: event.timestamp + }; + } + + // Update IP summary + const summary = ipSummaries[event.ipAddress]; + summary.totalVisits++; + summary.lastVisit = event.timestamp; + summary.firstVisit = event.timestamp < summary.firstVisit ? event.timestamp : summary.firstVisit; + + // Track pages + const pageKey = event.pageUrl.split('?')[0]; // Remove query params + summary.pages[pageKey] = (summary.pages[pageKey] || 0) + 1; + + // Track devices + const deviceType = event.deviceInfo.deviceType || 'unknown'; + summary.devices[deviceType] = (summary.devices[deviceType] || 0) + 1; + + // Track browsers + const browser = event.deviceInfo.browser || 'unknown'; + summary.browsers[browser] = (summary.browsers[browser] || 0) + 1; + + // Track time on page + if (event.timeOnPage) { + summary.averageTimeOnPage = (summary.averageTimeOnPage + event.timeOnPage) / 2; + } + + } catch (error) { + console.error(`Error reading file ${file}:`, error); + } + } + + // Calculate unique sessions per IP + const ipSessions: { [key: string]: Set } = {}; + events.forEach(event => { + if (!ipSessions[event.ipAddress]) { + ipSessions[event.ipAddress] = new Set(); + } + ipSessions[event.ipAddress].add(event.sessionId); + }); + + // Update unique sessions count + Object.keys(ipSummaries).forEach(ip => { + ipSummaries[ip].uniqueSessions = ipSessions[ip]?.size || 0; + }); + + const summary = Object.values(ipSummaries).sort((a, b) => b.totalVisits - a.totalVisits); + + return NextResponse.json({ + summary, + events: events.slice(-100), // Last 100 events + totalVisitors: summary.length, + totalSessions: sessionSet.size, + totalPageViews: events.length + }); + + } catch (error) { + console.error('Analytics API error:', error); + return NextResponse.json({ + error: 'Failed to load analytics data', + summary: [], + events: [], + totalVisitors: 0, + totalSessions: 0, + totalPageViews: 0 + }, { 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 new file mode 100644 index 0000000..a4a9da7 --- /dev/null +++ b/src/app/api/analytics/ip-analytics/route.ts @@ -0,0 +1,112 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { join } from 'path'; +import { existsSync } from 'fs'; +import nginxLogParser from '@/lib/nginxLogParser'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { ipAddress, dateRange, logType = 'all' } = body; + + if (!ipAddress) { + return NextResponse.json( + { error: 'IP address is required' }, + { status: 400 } + ); + } + + // Define log file paths + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs'); + const logFiles = [ + join(logDir, 'bigbraincoding.com_access.log'), + join(logDir, 'bigbraincoding.com_tracking.log'), + join(logDir, 'bigbraincoding.com_ip_tracking.log') + ]; + + // Filter log files based on type + let selectedLogFiles: string[] = []; + switch (logType) { + case 'access': + selectedLogFiles = [logFiles[0]]; + break; + case 'tracking': + selectedLogFiles = [logFiles[1]]; + break; + case 'ip_tracking': + selectedLogFiles = [logFiles[2]]; + break; + default: + selectedLogFiles = logFiles.filter(file => existsSync(file)); + } + + if (selectedLogFiles.length === 0) { + return NextResponse.json({ + analytics: null, + error: 'No log files found' + }); + } + + // Parse log files + const entries = await nginxLogParser.parseLogFiles(selectedLogFiles); + + // Filter by date range if provided + let filteredEntries = entries; + if (dateRange && dateRange.start && dateRange.end) { + filteredEntries = nginxLogParser.filterByDateRange(entries, dateRange.start, dateRange.end); + } + + // Get detailed analytics for the specific IP + const analytics = nginxLogParser.getIPAnalytics(filteredEntries, ipAddress); + + if (!analytics) { + return NextResponse.json({ + analytics: null, + error: 'IP address not found in logs' + }); + } + + return NextResponse.json({ + analytics, + logType, + filesProcessed: selectedLogFiles + }); + } catch (error) { + console.error('IP Analytics API error:', error); + return NextResponse.json( + { error: 'Failed to get IP analytics' }, + { status: 500 } + ); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const ipAddress = searchParams.get('ip'); + const startDate = searchParams.get('start'); + const endDate = searchParams.get('end'); + + if (!ipAddress) { + return NextResponse.json( + { error: 'IP address is required' }, + { status: 400 } + ); + } + + const body: Record = { ipAddress }; + if (startDate && endDate) { + body.dateRange = { start: startDate, end: endDate }; + } + + return POST(new NextRequest(request.url, { + method: 'POST', + body: JSON.stringify(body) + })); + } catch (error) { + console.error('IP Analytics API GET error:', error); + return NextResponse.json( + { error: 'Failed to fetch IP analytics' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/analytics/ip-logs/route.ts b/src/app/api/analytics/ip-logs/route.ts new file mode 100644 index 0000000..a259b8a --- /dev/null +++ b/src/app/api/analytics/ip-logs/route.ts @@ -0,0 +1,113 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { join } from 'path'; +import { existsSync } from 'fs'; +import nginxLogParser from '@/lib/nginxLogParser'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { ipAddress, dateRange, logType = 'all' } = body; + + if (!ipAddress) { + return NextResponse.json( + { error: 'IP address is required' }, + { status: 400 } + ); + } + + // Define log file paths + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs'); + const logFiles = [ + join(logDir, 'bigbraincoding.com_access.log'), + join(logDir, 'bigbraincoding.com_tracking.log'), + join(logDir, 'bigbraincoding.com_ip_tracking.log') + ]; + + // Filter log files based on type + let selectedLogFiles: string[] = []; + switch (logType) { + case 'access': + selectedLogFiles = [logFiles[0]]; + break; + case 'tracking': + selectedLogFiles = [logFiles[1]]; + break; + case 'ip_tracking': + selectedLogFiles = [logFiles[2]]; + break; + default: + selectedLogFiles = logFiles.filter(file => existsSync(file)); + } + + if (selectedLogFiles.length === 0) { + return NextResponse.json({ + entries: [], + error: 'No log files found' + }); + } + + // Parse log files + const entries = await nginxLogParser.parseLogFiles(selectedLogFiles); + + // Filter by date range if provided + let filteredEntries = entries; + if (dateRange && dateRange.start && dateRange.end) { + filteredEntries = nginxLogParser.filterByDateRange(entries, dateRange.start, dateRange.end); + } + + // Get entries for the specific IP + const ipEntries = nginxLogParser.getEntriesByIP(filteredEntries, ipAddress); + + if (ipEntries.length === 0) { + return NextResponse.json({ + entries: [], + error: 'IP address not found in logs' + }); + } + + return NextResponse.json({ + entries: ipEntries, + totalEntries: ipEntries.length, + logType, + filesProcessed: selectedLogFiles + }); + } catch (error) { + console.error('IP Logs API error:', error); + return NextResponse.json( + { error: 'Failed to get IP logs' }, + { status: 500 } + ); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const ipAddress = searchParams.get('ip'); + const startDate = searchParams.get('start'); + const endDate = searchParams.get('end'); + + if (!ipAddress) { + return NextResponse.json( + { error: 'IP address is required' }, + { status: 400 } + ); + } + + const body: Record = { ipAddress }; + if (startDate && endDate) { + body.dateRange = { start: startDate, end: endDate }; + } + + return POST(new NextRequest(request.url, { + method: 'POST', + body: JSON.stringify(body) + })); + } catch (error) { + console.error('IP Logs API GET error:', error); + return NextResponse.json( + { error: 'Failed to fetch IP logs' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/analytics/marketing-intelligence/route.ts b/src/app/api/analytics/marketing-intelligence/route.ts new file mode 100644 index 0000000..12d806c --- /dev/null +++ b/src/app/api/analytics/marketing-intelligence/route.ts @@ -0,0 +1,130 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { readdir, readFile } from 'fs/promises'; +import { join } from 'path'; +import { existsSync } from 'fs'; +import marketingIntelligenceService from '@/lib/marketingIntelligence'; + +interface TrackingEvent { + timestamp: string; + sessionId: string; + eventType: string; + pageUrl: string; + ipAddress: string; + userAgent: string; + deviceInfo: { + browser: string; + deviceType: string; + screenWidth: number; + screenHeight: number; + language: string; + }; + timeOnPage?: number; + scrollDepth?: number; + engagement?: { + mouseMovements: number; + clicks: number; + scrollEvents: number; + }; +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + let date = body.date; + + // If no date provided, use today's date + if (!date) { + const today = new Date(); + date = today.toISOString().split('T')[0]; // Format: YYYY-MM-DD + } + + const [year, month, day] = date.split('-'); + const logDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs/bigbraincoding.com', year, month, day); + + if (!existsSync(logDir)) { + return NextResponse.json({ + visitorProfiles: [], + salesIntelligence: { + highValueVisitors: [], + conversionOpportunities: [], + marketInsights: { + topPerformingPages: [], + commonUserJourneys: [], + devicePreferences: {}, + timeBasedTrends: {} + } + } + }); + } + + const files = await readdir(logDir); + const jsonFiles = files.filter(file => file.endsWith('.json') && !file.includes('summary')); + + const events: TrackingEvent[] = []; + const sessionData: Map; + }> = new Map(); + + // Read and parse all tracking events + for (const file of jsonFiles) { + try { + const content = await readFile(join(logDir, file), 'utf-8'); + const event: TrackingEvent = JSON.parse(content); + events.push(event); + + // Group events by session + const sessionKey = event.sessionId; + if (!sessionData.has(sessionKey)) { + sessionData.set(sessionKey, { + ip: event.ipAddress, + sessionId: event.sessionId, + pages: [], + timeOnSite: 0, + engagementMetrics: { + mouseMovements: 0, + clicks: 0, + scrollEvents: 0 + } + }); + } + + const session = sessionData.get(sessionKey)!; + session.pages.push(event.pageUrl); + session.timeOnSite += event.timeOnPage || 0; + + // Aggregate engagement metrics + if (event.engagement) { + session.engagementMetrics.mouseMovements += event.engagement.mouseMovements || 0; + session.engagementMetrics.clicks += event.engagement.clicks || 0; + session.engagementMetrics.scrollEvents += event.engagement.scrollEvents || 0; + } + } catch (error) { + console.error(`Error reading file ${file}:`, error); + } + } + + // Update visitor profiles with session data + for (const session of sessionData.values()) { + marketingIntelligenceService.updateVisitorProfile(session); + } + + // Get visitor profiles and sales intelligence + const visitorProfiles = marketingIntelligenceService.getVisitorProfiles(); + const salesIntelligence = marketingIntelligenceService.generateSalesIntelligence(); + + return NextResponse.json({ + visitorProfiles, + salesIntelligence + }); + } catch (error) { + console.error('Marketing Intelligence API error:', error); + return NextResponse.json( + { error: 'Failed to generate marketing intelligence' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/analytics/nginx-logs/route.ts b/src/app/api/analytics/nginx-logs/route.ts new file mode 100644 index 0000000..df0ff52 --- /dev/null +++ b/src/app/api/analytics/nginx-logs/route.ts @@ -0,0 +1,121 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { existsSync } from 'fs'; +import nginxLogParser from '@/lib/nginxLogParser'; +import { config } from '@/lib/config'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { dateRange, logType = 'all' } = body; + + // Get log file paths from configuration + const logFiles = config.logs.getLogFiles(); + const logFilePaths = [ + logFiles.access, + logFiles.tracking, + logFiles.ipTracking + ]; + + // Filter log files based on type + let selectedLogFiles: string[] = []; + switch (logType) { + case 'access': + selectedLogFiles = [logFilePaths[0]]; + break; + case 'tracking': + selectedLogFiles = [logFilePaths[1]]; + break; + case 'ip_tracking': + selectedLogFiles = [logFilePaths[2]]; + break; + default: + selectedLogFiles = logFilePaths.filter(file => existsSync(file)); + } + + if (selectedLogFiles.length === 0) { + return NextResponse.json({ + entries: [], + summary: { + totalRequests: 0, + uniqueIPs: [], + statusCodes: {}, + topPaths: {}, + topUserAgents: {}, + topReferers: {}, + averageResponseTime: 0, + totalBytesSent: 0, + timeRange: { start: '', end: '' } + } + }); + } + + // Parse log files + const entries = await nginxLogParser.parseLogFiles(selectedLogFiles); + + // Filter by date range if provided + let filteredEntries = entries; + if (dateRange && dateRange.start && dateRange.end) { + filteredEntries = nginxLogParser.filterByDateRange(entries, dateRange.start, dateRange.end); + } + + // Generate summary + const summary = nginxLogParser.generateSummary(filteredEntries); + + // Get unique IPs with counts + const uniqueIPs = nginxLogParser.getUniqueIPsWithCounts(filteredEntries); + + // Get filtered page analytics (excluding system paths) + const filteredPages = nginxLogParser.getFilteredPageAnalytics(filteredEntries); + + // Separate bot and human traffic + const { humanIPs, botIPs } = nginxLogParser.separateBotTraffic(filteredEntries); + + // Convert Set to Array for JSON serialization + const serializableSummary = { + ...summary, + uniqueIPs: Array.from(summary.uniqueIPs), + filteredPages, + uniqueIPsWithCounts: uniqueIPs, + humanIPs, + botIPs + }; + + return NextResponse.json({ + entries: filteredEntries, + summary: serializableSummary, + logType, + filesProcessed: selectedLogFiles + }); + } catch (error) { + console.error('NGINX Logs API error:', error); + return NextResponse.json( + { error: 'Failed to parse NGINX logs' }, + { status: 500 } + ); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const logType = searchParams.get('type') || 'all'; + const startDate = searchParams.get('start'); + const endDate = searchParams.get('end'); + + const body: Record = { logType }; + if (startDate && endDate) { + body.dateRange = { start: startDate, end: endDate }; + } + + return POST(new NextRequest(request.url, { + method: 'POST', + body: JSON.stringify(body) + })); + } catch (error) { + console.error('NGINX Logs API GET error:', error); + return NextResponse.json( + { error: 'Failed to parse NGINX logs' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/test-ip/route.ts b/src/app/api/test-ip/route.ts new file mode 100755 index 0000000..5a5e3de --- /dev/null +++ b/src/app/api/test-ip/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from 'next/server' + +export async function GET(request: NextRequest) { + const forwarded = request.headers.get('x-forwarded-for') + let realIP = forwarded ? forwarded.split(',')[0] : 'unknown' + + // Strip IPv6 prefix if present (::ffff:) + if (realIP.startsWith('::ffff:')) { + realIP = realIP.substring(7) + } + + const allowedIP = process.env.MY_IP_ADDRESS + + return NextResponse.json({ + yourIP: realIP, + allowedIP: allowedIP, + isAllowed: realIP === allowedIP, + headers: { + 'x-forwarded-for': request.headers.get('x-forwarded-for'), + 'x-real-ip': request.headers.get('x-real-ip'), + 'cf-connecting-ip': request.headers.get('cf-connecting-ip'), + } + }) +} \ No newline at end of file diff --git a/src/app/api/tracking/route.ts b/src/app/api/tracking/route.ts index e67e5e6..704e76c 100755 --- a/src/app/api/tracking/route.ts +++ b/src/app/api/tracking/route.ts @@ -1,7 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; -import { writeFile, mkdir } from 'fs/promises'; +import { writeFile, mkdir, readFile } from 'fs/promises'; import { join } from 'path'; import { existsSync } from 'fs'; +import botDetectionService from '@/lib/botDetection'; export async function POST(request: NextRequest) { try { @@ -9,99 +10,180 @@ export async function POST(request: NextRequest) { // Get client IP address const forwarded = request.headers.get('x-forwarded-for'); - const ip = forwarded ? forwarded.split(',')[0] : 'unknown'; + let ip = forwarded ? forwarded.split(',')[0] : 'unknown'; + + // Strip IPv6 prefix if present (::ffff:) + if (ip.startsWith('::ffff:')) { + ip = ip.substring(7); + } // Add IP address to event event.ipAddress = ip; - // Create timestamp for file naming - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - const hour = String(now.getHours()).padStart(2, '0'); - const minute = String(now.getMinutes()).padStart(2, '0'); - const second = String(now.getSeconds()).padStart(2, '0'); - - // Create directory structure: ~/bigbraincoding.com/YYYY/MM/DD/ - const baseDir = process.env.HOME || process.env.USERPROFILE || '/tmp'; - const logDir = join(baseDir, 'bigbraincoding.com', String(year), month, day); - - // Ensure directory exists - if (!existsSync(logDir)) { - await mkdir(logDir, { recursive: true }); - } + // Bot detection and rate limiting + const botDetectionResult = botDetectionService.detectBot( + ip, + event.userAgent || request.headers.get('user-agent') || '', + { + timeOnPage: event.timeOnPage, + mouseMovements: event.engagement?.mouseMovements || 0 + } + ); - // Create log file name with timestamp - const fileName = `${hour}-${minute}-${second}-${event.sessionId.substring(0, 8)}.json`; - const filePath = join(logDir, fileName); - - // Format the log entry - const logEntry = { - timestamp: event.timestamp, - sessionId: event.sessionId, - eventType: event.eventType, - pageUrl: event.pageUrl, - referrer: event.referrer, - ipAddress: event.ipAddress, - userAgent: event.userAgent, - deviceInfo: event.deviceInfo, - eventData: event.eventData, - timeOnPage: event.timeOnPage, - timeOnPageSeconds: event.timeOnPage ? Math.round(event.timeOnPage / 1000) : undefined, - scrollDepth: event.scrollDepth, - performance: event.performance, - engagement: event.engagement + // Add bot detection data to event + event.botDetection = { + isBot: botDetectionResult.isBot, + confidence: botDetectionResult.confidence, + reason: botDetectionResult.reason, + requiresVerification: botDetectionResult.requiresVerification, + rateLimitExceeded: botDetectionResult.rateLimitExceeded }; - // Write to file - await writeFile(filePath, JSON.stringify(logEntry, null, 2)); + // If bot detected with high confidence, log but don't process normally + if (botDetectionResult.isBot && botDetectionResult.confidence > 0.8) { + console.log('Bot detected:', { + ip, + userAgent: event.userAgent, + confidence: botDetectionResult.confidence, + reason: botDetectionResult.reason + }); + + // Still log the event but mark it as bot activity + event.eventType = `bot_${event.eventType}`; + } - // Also write to a daily summary file - const summaryFileName = `${year}-${month}-${day}-summary.json`; - const summaryPath = join(logDir, summaryFileName); + // If rate limited, return error + if (botDetectionResult.rateLimitExceeded) { + return NextResponse.json( + { + error: 'Rate limit exceeded', + resetTime: Date.now() + 60 * 60 * 1000 // 1 hour from now + }, + { status: 429 } + ); + } - let summary: { events: Array<{ timestamp: string; eventType: string; sessionId: string; pageUrl: string; deviceType: string; browser: string }>; totalEvents: number; uniqueSessions: Set } = { events: [], totalEvents: 0, uniqueSessions: new Set() }; + // Check if we're on Vercel + const isVercel = process.env.VERCEL === '1'; + + if (isVercel) { + // On Vercel, just log to console and return success + // You can integrate with a database service here + console.log('Vercel Tracking Event:', { + timestamp: event.timestamp, + sessionId: event.sessionId, + eventType: event.eventType, + pageUrl: event.pageUrl, + ipAddress: event.ipAddress, // Now includes IP + userAgent: event.userAgent, + deviceInfo: event.deviceInfo, + timeOnPage: event.timeOnPage, + scrollDepth: event.scrollDepth, + botDetection: event.botDetection + }); + return NextResponse.json({ + message: 'Event received (Vercel environment)', + requiresVerification: botDetectionResult.requiresVerification + }); + } - try { - if (existsSync(summaryPath)) { - const summaryData = await import('fs').then(fs => fs.readFileSync(summaryPath, 'utf8')); - summary = JSON.parse(summaryData); - summary.uniqueSessions = new Set(summary.uniqueSessions); - } - } catch { - // If summary file doesn't exist or is corrupted, start fresh - summary = { events: [], totalEvents: 0, uniqueSessions: new Set() }; + // For local/self-hosted environment, write to file + const date = new Date(event.timestamp); + const year = date.getFullYear().toString(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hour = date.getHours().toString().padStart(2, '0'); + const minute = date.getMinutes().toString().padStart(2, '0'); + const second = date.getSeconds().toString().padStart(2, '0'); + + // Corrected base directory for logs + const baseLogDir = join(process.env.HOME || '/home/bryanwi09', 'docker/nginx/logs/bigbraincoding.com'); + const dailyLogDir = join(baseLogDir, year, month, day); + + if (!existsSync(dailyLogDir)) { + await mkdir(dailyLogDir, { recursive: true }); } - // Add event to summary - summary.events.push({ - timestamp: event.timestamp, - eventType: event.eventType, - sessionId: event.sessionId, - pageUrl: event.pageUrl, - deviceType: event.deviceInfo.deviceType, - browser: event.deviceInfo.browser - }); + const fileName = `${hour}-${minute}-${second}-${event.sessionId.substring(0, 8)}-${event.eventType}.json`; + const filePath = join(dailyLogDir, fileName); - summary.totalEvents++; - summary.uniqueSessions.add(event.sessionId); + await writeFile(filePath, JSON.stringify(event, null, 2)); - // Convert Set back to array for JSON serialization - const summaryForFile = { - ...summary, - uniqueSessions: Array.from(summary.uniqueSessions), - lastUpdated: new Date().toISOString() + // Update daily summary + const summaryFileName = `${year}-${month}-${day}-summary.json`; + const summaryFilePath = join(dailyLogDir, summaryFileName); + + const dailySummary = { + totalEvents: 0, + uniqueVisitors: new Set(), + uniqueSessions: new Set(), + pageViews: 0, + clicks: 0, + botEvents: 0, + ipAddresses: {} as { [key: string]: { totalVisits: number, uniqueSessions: Set } } }; - await writeFile(summaryPath, JSON.stringify(summaryForFile, null, 2)); + if (existsSync(summaryFilePath)) { + const existingSummary = await readFile(summaryFilePath, 'utf8'); + const parsedSummary = JSON.parse(existingSummary); + dailySummary.totalEvents = parsedSummary.totalEvents || 0; + dailySummary.pageViews = parsedSummary.pageViews || 0; + dailySummary.clicks = parsedSummary.clicks || 0; + dailySummary.botEvents = parsedSummary.botEvents || 0; + dailySummary.uniqueVisitors = new Set(parsedSummary.uniqueVisitors || []); + dailySummary.uniqueSessions = new Set(parsedSummary.uniqueSessions || []); + for (const ip in parsedSummary.ipAddresses) { + dailySummary.ipAddresses[ip] = { + totalVisits: parsedSummary.ipAddresses[ip].totalVisits || 0, + uniqueSessions: new Set(parsedSummary.ipAddresses[ip].uniqueSessions || []) + }; + } + } + + dailySummary.totalEvents++; + dailySummary.uniqueVisitors.add(event.ipAddress); + dailySummary.uniqueSessions.add(event.sessionId); - return NextResponse.json({ success: true, logged: true }); + if (event.eventType === 'page_view') { + dailySummary.pageViews++; + } else if (event.eventType === 'click') { + dailySummary.clicks++; + } + + // Track bot events separately + if (botDetectionResult.isBot) { + dailySummary.botEvents++; + } + if (!dailySummary.ipAddresses[event.ipAddress]) { + dailySummary.ipAddresses[event.ipAddress] = { totalVisits: 0, uniqueSessions: new Set() }; + } + dailySummary.ipAddresses[event.ipAddress].totalVisits++; + dailySummary.ipAddresses[event.ipAddress].uniqueSessions.add(event.sessionId); + + const serializableSummary = { + ...dailySummary, + uniqueVisitors: Array.from(dailySummary.uniqueVisitors), + uniqueSessions: Array.from(dailySummary.uniqueSessions), + ipAddresses: Object.fromEntries( + Object.entries(dailySummary.ipAddresses).map(([ip, data]) => [ + ip, + { totalVisits: data.totalVisits, uniqueSessions: Array.from(data.uniqueSessions) } + ]) + ) + }; + + await writeFile(summaryFilePath, JSON.stringify(serializableSummary, null, 2)); + + return NextResponse.json({ + message: 'Event received and logged', + requiresVerification: botDetectionResult.requiresVerification, + botDetected: botDetectionResult.isBot + }); } catch (error) { - console.error('Error logging tracking event:', error); + console.error('Tracking API error:', error); return NextResponse.json( - { success: false, error: 'Failed to log tracking event' }, + { error: 'Failed to log event' }, { status: 500 } ); } diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index 001c680..432cb0a 100755 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -81,7 +81,7 @@ export default function ContactPage() { animate="visible" className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4" > - {contactMethods.map((method, index) => { + {contactMethods.map((method) => { const IconComponent = method.icon return ( diff --git a/src/app/layout.tsx b/src/app/layout.tsx index da21684..e03d877 100755 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import Script from "next/script"; import "./globals.css"; import Header from "@/components/layout/Header"; import Footer from "@/components/layout/Footer"; import { ThemeProvider } from "@/components/providers/ThemeProvider"; import { ThemeToggle } from "@/components/ui/theme-toggle"; -import TrackingProvider from "@/components/tracking/TrackingProvider"; +import UpdateBanner from "@/components/shared/UpdateBanner"; import { ConsentManagerDialog, ConsentManagerProvider, @@ -28,6 +29,14 @@ export const metadata: Metadata = { keywords: ["software development", "web design", "AI integration", "Next.js", "React", "TypeScript"], authors: [{ name: "Big Brain Coding" }], creator: "Big Brain Coding", + icons: { + icon: [ + { url: '/brain-favicon.svg', type: 'image/svg+xml' }, + { url: '/favicon.ico', type: 'image/x-icon' } + ], + shortcut: '/brain-favicon.svg', + apple: '/brain-favicon.svg' + }, openGraph: { type: "website", locale: "en_US", @@ -51,7 +60,14 @@ export default function RootLayout({ return ( - {/* Head content will be handled by Next.js */} + - - - -
-
-
{children}
-
- -
-
+ + + +
+
+
{children}
+
+ +
diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index f902a0f..1fdf489 100755 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -74,7 +74,7 @@ export default function ProjectsPage() { animate="visible" className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4" > - {projectStats.map((stat, index) => { + {projectStats.map((stat) => { const IconComponent = stat.icon return ( @@ -107,7 +107,7 @@ export default function ProjectsPage() { animate="visible" className="grid grid-cols-1 gap-12 lg:grid-cols-2" > - {PROJECTS.map((project, index) => ( + {PROJECTS.map((project) => ( - {SERVICES.map((service, index) => { + {SERVICES.map((service) => { const IconComponent = iconMap[service.icon] || Globe return ( @@ -204,7 +204,7 @@ export default function ServicesPage() { variants={staggerContainer} className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4" > - {processSteps.map((step, index) => { + {processSteps.map((step) => { const IconComponent = step.icon || Globe return ( @@ -255,7 +255,7 @@ export default function ServicesPage() { variants={staggerContainer} className="grid grid-cols-1 gap-8 md:grid-cols-3" > - {pricingPlans.map((plan, index) => ( + {pricingPlans.map((plan) => ( ([]) + const [ipSummaries, setIpSummaries] = useState([]) + const [selectedIP, setSelectedIP] = useState('') + const [loading, setLoading] = useState(true) + const [searchIP, setSearchIP] = useState('') + + useEffect(() => { + fetchAnalyticsData() + }, []) + + const fetchAnalyticsData = async () => { + try { + setLoading(true) + // This would be replaced with actual API calls to your tracking data + const response = await fetch('/api/analytics/dashboard', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ dateRange: 'today' }) + }) + + if (response.ok) { + const data = await response.json() + setEvents(data.events || []) + setIpSummaries(data.ipSummaries || []) + } else { + // For now, create some mock data + setEvents([ + { + timestamp: '2025-07-31T01:20:00.000Z', + sessionId: 'test-session', + eventType: 'pageview', + pageUrl: 'https://bigbraincoding.com/test', + ipAddress: '99.125.236.29', + userAgent: 'Mozilla/5.0 (Test Browser)', + deviceInfo: { + browser: 'TestBrowser', + deviceType: 'desktop', + screenWidth: 1920, + screenHeight: 1080, + language: 'en-US' + } + } + ]) + setIpSummaries([ + { + ip: '99.125.236.29', + totalVisits: 1, + uniqueSessions: 1, + pages: { '/test': 1 }, + devices: { 'desktop': 1 }, + browsers: { 'TestBrowser': 1 }, + averageTimeOnPage: 30, + lastVisit: '2025-07-31T01:20:00.000Z', + firstVisit: '2025-07-31T01:20:00.000Z' + } + ]) + } + } catch (error) { + console.error('Error fetching analytics data:', error) + // Create mock data on error + setEvents([ + { + timestamp: '2025-07-31T01:20:00.000Z', + sessionId: 'test-session', + eventType: 'pageview', + pageUrl: 'https://bigbraincoding.com/test', + ipAddress: '99.125.236.29', + userAgent: 'Mozilla/5.0 (Test Browser)', + deviceInfo: { + browser: 'TestBrowser', + deviceType: 'desktop', + screenWidth: 1920, + screenHeight: 1080, + language: 'en-US' + } + } + ]) + setIpSummaries([ + { + ip: '99.125.236.29', + totalVisits: 1, + uniqueSessions: 1, + pages: { '/test': 1 }, + devices: { 'desktop': 1 }, + browsers: { 'TestBrowser': 1 }, + averageTimeOnPage: 30, + lastVisit: '2025-07-31T01:20:00.000Z', + firstVisit: '2025-07-31T01:20:00.000Z' + } + ]) + } finally { + setLoading(false) + } + } + + const getIPDetails = (ip: string) => { + return ipSummaries.find(summary => summary.ip === ip) + } + + const filteredIPs = ipSummaries.filter(summary => + searchIP === '' || summary.ip.includes(searchIP) + ) + + const selectedIPDetails = selectedIP ? getIPDetails(selectedIP) : null + const selectedIPEvents = events.filter(event => event.ipAddress === selectedIP) + + if (loading) { + return ( +
+

Loading analytics data...

+
+ ) + } + + return ( +
+ {/* Search and Filters */} +
+ setSearchIP(e.target.value)} + className="max-w-sm" + /> + +
+ + {/* IP Addresses Summary */} + + + IP Addresses ({filteredIPs.length}) + Click on an IP to see detailed analytics + + +
+ {filteredIPs.map((summary) => ( +
setSelectedIP(summary.ip)} + > +
+
+

{summary.ip}

+

+ {summary.totalVisits} visits • {summary.uniqueSessions} sessions +

+
+
+ + {summary.devices.desktop || 0} desktop + +

+ Last: {new Date(summary.lastVisit).toLocaleDateString()} +

+
+
+
+ ))} +
+
+
+ + {/* Selected IP Details */} + {selectedIPDetails && ( + + + Details for {selectedIP} + Detailed analytics for this IP address + + +
+
+

{selectedIPDetails.totalVisits}

+

Total Visits

+
+
+

{selectedIPDetails.uniqueSessions}

+

Unique Sessions

+
+
+

{Math.round(selectedIPDetails.averageTimeOnPage)}s

+

Avg Time on Page

+
+
+ + {/* Pages Visited */} +
+

Pages Visited

+
+ {Object.entries(selectedIPDetails.pages).map(([page, count]) => ( +
+ {page} + {count} +
+ ))} +
+
+ + {/* Recent Events */} +
+

Recent Events

+
+ {selectedIPEvents.slice(0, 5).map((event, index) => ( +
+
+ {event.eventType} + {new Date(event.timestamp).toLocaleString()} +
+
{event.pageUrl}
+
+ ))} +
+
+
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/components/analytics/EnhancedAnalyticsDashboard.tsx b/src/components/analytics/EnhancedAnalyticsDashboard.tsx new file mode 100644 index 0000000..d73de53 --- /dev/null +++ b/src/components/analytics/EnhancedAnalyticsDashboard.tsx @@ -0,0 +1,381 @@ +'use client' + +import { useState, useEffect, useCallback } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import UniqueIPsList from './UniqueIPsList'; + +interface AnalyticsData { + nginx: { + entries: NGINXLogEntry[]; + summary: { + totalRequests: number; + uniqueIPs: string[]; + uniqueIPsWithCounts?: UniqueIPSummary[]; + statusCodes: Record; + topPaths: Record; + filteredPages?: Record; + topUserAgents: Record; + topReferers: Record; + averageResponseTime: number; + totalBytesSent: number; + timeRange: { + start: string; + end: string; + }; + humanIPs?: UniqueIPSummary[]; + botIPs?: UniqueIPSummary[]; + }; + }; + tracking: { + summary: unknown[]; + events: unknown[]; + totalVisitors: number; + totalSessions: number; + totalPageViews: number; + }; + marketing: { + visitorProfiles: unknown[]; + salesIntelligence: unknown; + }; +} + +interface NGINXLogEntry { + timestamp: string; + ipAddress: string; + method: string; + path: string; + statusCode: number; + bytesSent: number; + referer: string; + userAgent: string; + requestTime: number; + upstreamResponseTime: number; +} + +interface UniqueIPSummary { + ipAddress: string; + totalVisits: number; + sessions: number; + lastVisit: string; + engagementScore: number; +} + +export default function EnhancedAnalyticsDashboard() { + const [analyticsData, setAnalyticsData] = useState(null); + const [loading, setLoading] = useState(true); + const [selectedPlatform, setSelectedPlatform] = useState<'nginx' | 'tracking' | 'marketing'>('nginx'); + const [selectedLogType, setSelectedLogType] = useState<'all' | 'access' | 'tracking' | 'ip_tracking'>('all'); + const [dateRange, setDateRange] = useState({ + start: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0], + end: new Date().toISOString().split('T')[0] + }); + + // New state for IP analytics + const [showUniqueIPsModal, setShowUniqueIPsModal] = useState(false); + + const fetchAnalyticsData = useCallback(async () => { + try { + setLoading(true); + + // Fetch NGINX logs + const nginxResponse = await fetch('/api/analytics/nginx-logs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + logType: selectedLogType, + dateRange + }) + }); + + // Fetch tracking data + const trackingResponse = await fetch('/api/analytics/dashboard', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ dateRange }) + }); + + // Fetch marketing intelligence + const marketingResponse = await fetch('/api/analytics/marketing-intelligence', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ dateRange }) + }); + + const nginxData = nginxResponse.ok ? await nginxResponse.json() : { entries: [], summary: {} }; + const trackingData = trackingResponse.ok ? await trackingResponse.json() : { entries: [], summary: {} }; + const marketingData = marketingResponse.ok ? await marketingResponse.json() : { visitorProfiles: [], salesIntelligence: {} }; + + setAnalyticsData({ + nginx: nginxData, + tracking: trackingData, + marketing: marketingData + }); + } catch (error) { + console.error('Error fetching analytics data:', error); + } finally { + setLoading(false); + } + }, [selectedLogType, dateRange]); + + useEffect(() => { + fetchAnalyticsData(); + }, [fetchAnalyticsData]); + + const handleUniqueVisitorsClick = () => { + setShowUniqueIPsModal(true); + }; + + const formatBytes = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + + if (loading) { + return ( +
+
+
+ ); + } + + if (!analyticsData) { + return ( +
+ No analytics data available +
+ ); + } + + return ( +
+ {/* Header Controls */} +
+
+ + + {selectedPlatform === 'nginx' && ( + + )} +
+ +
+ setDateRange(prev => ({ ...prev, start: e.target.value }))} + className="px-3 py-2 border rounded-md" + /> + to + setDateRange(prev => ({ ...prev, end: e.target.value }))} + className="px-3 py-2 border rounded-md" + /> +
+
+ + {/* NGINX Analytics */} + {selectedPlatform === 'nginx' && analyticsData.nginx && ( +
+ {/* Summary Cards */} +
+ + + Total Requests + + +
{analyticsData.nginx.summary.totalRequests.toLocaleString()}
+
+
+ + + + Unique Visitors + + +
{analyticsData.nginx.summary.uniqueIPs.length}
+
Click to view details
+
+
+ + + + Avg Response Time + + +
{analyticsData.nginx.summary.averageResponseTime.toFixed(2)}ms
+
+
+ + + + Data Transferred + + +
{formatBytes(analyticsData.nginx.summary.totalBytesSent)}
+
+
+
+ + {/* Detailed Analytics */} + + + Pages + Status Codes + Browsers + Referers + + + + + + Top Pages (Filtered) + + +
+ {Object.entries(analyticsData.nginx.summary.filteredPages || {}) + .sort(([, a], [, b]) => (b as number) - (a as number)) + .slice(0, 10) + .map(([page, count]) => ( +
+ {page} +
+ + {count} +
+
+ ))} +
+
+
+
+ + + + + Status Codes + + +
+ {Object.entries(analyticsData.nginx.summary.statusCodes) + .sort(([, a], [, b]) => (b as number) - (a as number)) + .map(([code, count]) => ( +
+ + {code} + +
+ + {count} +
+
+ ))} +
+
+
+
+ + + + + Top Browsers + + +
+ {Object.entries(analyticsData.nginx.summary.topUserAgents) + .sort(([, a], [, b]) => b - a) + .slice(0, 10) + .map(([browser, count]) => ( +
+ {browser} +
+ + {count} +
+
+ ))} +
+
+
+
+ + + + + Top Referers + + +
+ {Object.entries(analyticsData.nginx.summary.topReferers) + .sort(([, a], [, b]) => b - a) + .slice(0, 10) + .map(([referer, count]) => ( +
+ {referer || 'Direct'} +
+ + {count} +
+
+ ))} +
+
+
+
+
+
+ )} + + {/* Unique IPs Modal */} + + + + Unique Visitors Analytics + + {analyticsData.nginx.summary.uniqueIPsWithCounts && ( + + )} + + +
+ ); +} \ No newline at end of file diff --git a/src/components/analytics/IPDetailsModal.tsx b/src/components/analytics/IPDetailsModal.tsx new file mode 100644 index 0000000..c440ab6 --- /dev/null +++ b/src/components/analytics/IPDetailsModal.tsx @@ -0,0 +1,377 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Button } from '@/components/ui/button'; +import { IPAnalytics, SessionDetail, NGINXLogEntry } from '@/lib/nginxLogParser'; +import { formatDuration, formatDate, exportIPAnalyticsToCSV, exportIPLogsToCSV, downloadCSV } from '@/lib/exportUtils'; + +interface IPDetailsModalProps { + isOpen: boolean; + onClose: () => void; + ipAddress: string; + dateRange: { start: string; end: string }; +} + +export default function IPDetailsModal({ isOpen, onClose, ipAddress, dateRange }: IPDetailsModalProps) { + const [analytics, setAnalytics] = useState(null); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (isOpen && ipAddress) { + fetchIPAnalytics(); + fetchIPLogs(); + } + }, [isOpen, ipAddress, dateRange]); + + const fetchIPAnalytics = async () => { + try { + setLoading(true); + setError(null); + + const response = await fetch('/api/analytics/ip-analytics', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ipAddress, + dateRange + }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to fetch IP analytics'); + } + + setAnalytics(data.analytics); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + const fetchIPLogs = async () => { + try { + const response = await fetch('/api/analytics/ip-logs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ipAddress, + dateRange + }) + }); + + const data = await response.json(); + + if (response.ok) { + setLogs(data.entries); + } + } catch (err) { + console.error('Error fetching IP logs:', err); + } + }; + + const handleExportAnalytics = () => { + if (!analytics) return; + + const csvContent = exportIPAnalyticsToCSV(analytics); + downloadCSV(csvContent, `ip-analytics-${ipAddress}-${new Date().toISOString().split('T')[0]}.csv`); + }; + + const handleExportLogs = () => { + if (logs.length === 0) return; + + const csvContent = exportIPLogsToCSV(logs, ipAddress); + downloadCSV(csvContent, `ip-logs-${ipAddress}-${new Date().toISOString().split('T')[0]}.csv`); + }; + + const getEngagementColor = (score: number): string => { + if (score >= 80) return 'bg-green-500'; + if (score >= 60) return 'bg-yellow-500'; + if (score >= 40) return 'bg-orange-500'; + return 'bg-red-500'; + }; + + if (loading) { + return ( + + + + Loading IP Analytics... + +
+
+
+
+
+ ); + } + + if (error) { + return ( + + + + Error + +
+ {error} +
+
+
+ ); + } + + if (!analytics) { + return ( + + + + No Data Found + +
+ No analytics data found for IP address {ipAddress} +
+
+
+ ); + } + + return ( + + + + +
+ IP Analytics: {ipAddress} + + {analytics.engagementScore}% Engagement + +
+
+ + +
+
+
+ +
+ + + Total Visits + + +
{analytics.totalVisits}
+
+
+ + + + Sessions + + +
{analytics.sessions}
+
+
+ + + + Avg Time on Site + + +
{formatDuration(analytics.averageTimeOnSite)}
+
+
+ + + + Engagement Score + + +
{analytics.engagementScore}%
+ +
+
+
+ + + + Overview + Pages + Sessions + Logs + Technical + + + +
+ + + Visit Timeline + + +
+ First Visit: +
{formatDate(analytics.firstVisit)}
+
+
+ Last Visit: +
{formatDate(analytics.lastVisit)}
+
+
+
+ + + + Device & Browser + + +
+ {Object.entries(analytics.devices).map(([device, count]) => ( +
+ {device} + {count} +
+ ))} + {Object.entries(analytics.browsers).map(([browser, count]) => ( +
+ {browser} + {count} +
+ ))} +
+
+
+
+
+ + + + + Pages Visited + + +
+ {Object.entries(analytics.pages) + .sort(([, a], [, b]) => b - a) + .map(([page, count]) => ( +
+ {page} +
+ + {count} +
+
+ ))} +
+
+
+
+ + + + + Session Details + + +
+ {analytics.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} + + ))} +
+
+ ))} +
+
+
+
+ + + + + All Logs ({logs.length} entries) + + +
+ {logs.map((log, index) => ( +
+
+ {formatDate(log.timestamp)} + = 400 ? 'destructive' : 'default'}> + {log.statusCode} + +
+
{log.method} {log.path}
+
{log.userAgent}
+
+ ))} +
+
+
+
+ + + + + Technical Information + + +
+
+ IP Address: +
{analytics.ipAddress}
+
+
+ Total Requests: +
{analytics.totalVisits}
+
+
+ Unique Sessions: +
{analytics.sessions}
+
+
+ Average Session Duration: +
{formatDuration(analytics.averageTimeOnSite)}
+
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/analytics/MarketingIntelligenceDashboard.tsx b/src/components/analytics/MarketingIntelligenceDashboard.tsx new file mode 100644 index 0000000..44e4406 --- /dev/null +++ b/src/components/analytics/MarketingIntelligenceDashboard.tsx @@ -0,0 +1,538 @@ +'use client' + +import { useState, useEffect } from 'react' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Progress } from '@/components/ui/progress' +import { + Users, + TrendingUp, + Target, + Clock, + MapPin, + Activity, + AlertCircle, + CheckCircle, + XCircle +} from 'lucide-react' + +interface VisitorProfile { + ip: string + sessionId: string + firstVisit: string + lastVisit: string + totalVisits: number + totalTimeOnSite: number + pagesVisited: string[] + engagementScore: number + leadScore: number + deviceConsistency: boolean + highEngagementPages: string[] + conversionEvents: string[] + timeBasedPatterns: { + averageSessionDuration: number + preferredVisitTimes: string[] + returnVisitor: boolean + } +} + +interface LeadQualification { + isQualified: boolean + leadScore: number + qualificationReason: string + recommendedAction: string + urgency: 'low' | 'medium' | 'high' + nextBestAction: string +} + +interface SalesIntelligence { + highValueVisitors: VisitorProfile[] + conversionOpportunities: { + visitor: VisitorProfile + opportunity: string + confidence: number + }[] + marketInsights: { + topPerformingPages: string[] + commonUserJourneys: string[][] + devicePreferences: Record + timeBasedTrends: Record + } +} + +export default function MarketingIntelligenceDashboard() { + const [visitorProfiles, setVisitorProfiles] = useState([]) + const [salesIntelligence, setSalesIntelligence] = useState(null) + const [loading, setLoading] = useState(true) + const [selectedVisitor, setSelectedVisitor] = useState(null) + + useEffect(() => { + fetchMarketingIntelligence() + }, []) + + const fetchMarketingIntelligence = async () => { + try { + setLoading(true) + + // This would be replaced with actual API calls + const response = await fetch('/api/analytics/marketing-intelligence', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ dateRange: 'today' }) + }) + + if (response.ok) { + const data = await response.json() + setVisitorProfiles(data.visitorProfiles || []) + setSalesIntelligence(data.salesIntelligence || null) + } else { + // Mock data for demonstration + setVisitorProfiles([ + { + ip: '192.168.1.100', + sessionId: 'session-1', + firstVisit: '2025-07-31T10:00:00.000Z', + lastVisit: '2025-07-31T15:30:00.000Z', + totalVisits: 3, + totalTimeOnSite: 1800000, // 30 minutes + pagesVisited: ['/', '/services', '/projects', '/contact'], + engagementScore: 0.85, + leadScore: 0.78, + deviceConsistency: true, + highEngagementPages: ['/services', '/projects', '/contact'], + conversionEvents: ['contact_page_visited', 'services_page_visited'], + timeBasedPatterns: { + averageSessionDuration: 600000, // 10 minutes + preferredVisitTimes: ['10:00', '14:00', '15:00'], + returnVisitor: true + } + }, + { + ip: '192.168.1.101', + sessionId: 'session-2', + firstVisit: '2025-07-31T12:00:00.000Z', + lastVisit: '2025-07-31T12:15:00.000Z', + totalVisits: 1, + totalTimeOnSite: 900000, // 15 minutes + pagesVisited: ['/', '/about'], + engagementScore: 0.45, + leadScore: 0.32, + deviceConsistency: false, + highEngagementPages: ['/about'], + conversionEvents: [], + timeBasedPatterns: { + averageSessionDuration: 900000, + preferredVisitTimes: ['12:00'], + returnVisitor: false + } + } + ]) + + setSalesIntelligence({ + highValueVisitors: [ + { + ip: '192.168.1.100', + sessionId: 'session-1', + firstVisit: '2025-07-31T10:00:00.000Z', + lastVisit: '2025-07-31T15:30:00.000Z', + totalVisits: 3, + totalTimeOnSite: 1800000, + pagesVisited: ['/', '/services', '/projects', '/contact'], + engagementScore: 0.85, + leadScore: 0.78, + deviceConsistency: true, + highEngagementPages: ['/services', '/projects', '/contact'], + conversionEvents: ['contact_page_visited', 'services_page_visited'], + timeBasedPatterns: { + averageSessionDuration: 600000, + preferredVisitTimes: ['10:00', '14:00', '15:00'], + returnVisitor: true + } + } + ], + conversionOpportunities: [ + { + visitor: { + ip: '192.168.1.101', + sessionId: 'session-2', + firstVisit: '2025-07-31T12:00:00.000Z', + lastVisit: '2025-07-31T12:15:00.000Z', + totalVisits: 1, + totalTimeOnSite: 900000, + pagesVisited: ['/', '/about'], + engagementScore: 0.45, + leadScore: 0.32, + deviceConsistency: false, + highEngagementPages: ['/about'], + conversionEvents: [], + timeBasedPatterns: { + averageSessionDuration: 900000, + preferredVisitTimes: ['12:00'], + returnVisitor: false + } + }, + opportunity: 'Contact page conversion', + confidence: 0.32 + } + ], + marketInsights: { + topPerformingPages: ['/', '/services', '/projects', '/contact', '/about'], + commonUserJourneys: [ + ['/', '/services'], + ['/', '/projects'], + ['/', '/contact'] + ], + devicePreferences: { desktop: 70, mobile: 25, tablet: 5 }, + timeBasedTrends: { '10:00': 3, '12:00': 1, '14:00': 2, '15:00': 1 } + } + }) + } + } catch (error) { + console.error('Error fetching marketing intelligence:', error) + } finally { + setLoading(false) + } + } + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case 'high': return 'bg-red-500' + case 'medium': return 'bg-yellow-500' + case 'low': return 'bg-green-500' + default: return 'bg-gray-500' + } + } + + const getLeadScoreColor = (score: number) => { + if (score >= 0.7) return 'text-green-600' + if (score >= 0.4) return 'text-yellow-600' + return 'text-red-600' + } + + const formatDuration = (ms: number) => { + const minutes = Math.floor(ms / 60000) + const seconds = Math.floor((ms % 60000) / 1000) + return `${minutes}m ${seconds}s` + } + + if (loading) { + return ( +
+
Loading marketing intelligence...
+
+ ) + } + + return ( +
+
+
+

Marketing Intelligence

+

+ Lead qualification and sales intelligence dashboard +

+
+ +
+ + + + Lead Qualification + Sales Intelligence + Market Insights + + + +
+ + + Total Visitors + + + +
{visitorProfiles.length}
+

+ Active in last 24 hours +

+
+
+ + + + Qualified Leads + + + +
+ {visitorProfiles.filter(v => v.leadScore >= 0.6).length} +
+

+ High engagement visitors +

+
+
+ + + + Avg Engagement + + + +
+ {Math.round(visitorProfiles.reduce((acc, v) => acc + v.engagementScore, 0) / visitorProfiles.length * 100)}% +
+

+ Across all visitors +

+
+
+
+ + + + Visitor Profiles + + Detailed visitor analysis and lead qualification + + + +
+ {visitorProfiles.map((visitor) => ( +
setSelectedVisitor(visitor)} + > +
+
+
+
+
{visitor.ip}
+
+ {visitor.totalVisits} visits • {formatDuration(visitor.totalTimeOnSite)} +
+
+
+
+
+ {Math.round(visitor.leadScore * 100)}% +
+
Lead Score
+
+
+ +
+
+ + {visitor.timeBasedPatterns.returnVisitor ? "Return Visitor" : "New Visitor"} + + + {visitor.highEngagementPages.length} key pages + + + {visitor.conversionEvents.length} conversions + +
+ +
+ + Last visit: {new Date(visitor.lastVisit).toLocaleString()} +
+
+
+ ))} +
+
+
+
+ + + {salesIntelligence && ( + <> +
+ + + High Value Visitors + + Visitors with lead score ≥ 60% + + + +
+ {salesIntelligence.highValueVisitors.map((visitor) => ( +
+
+
{visitor.ip}
+
+ {visitor.conversionEvents.join(', ')} +
+
+
+
+ {Math.round(visitor.leadScore * 100)}% +
+
Score
+
+
+ ))} +
+
+
+ + + + Conversion Opportunities + + Visitors ready for follow-up + + + +
+ {salesIntelligence.conversionOpportunities.map((opp) => ( +
+
+
{opp.visitor.ip}
+ + {Math.round(opp.confidence * 100)}% confidence + +
+
+ {opp.opportunity} +
+
+ ))} +
+
+
+
+ + )} +
+ + + {salesIntelligence && ( + <> +
+ + + Top Performing Pages + + Most engaging content + + + +
+ {salesIntelligence.marketInsights.topPerformingPages.map((page, index) => ( +
+
+
+ {index + 1} +
+ {page} +
+
+ ))} +
+
+
+ + + + Common User Journeys + + Most frequent navigation paths + + + +
+ {salesIntelligence.marketInsights.commonUserJourneys.map((journey, index) => ( +
+
+ Journey {index + 1} +
+
+ {journey.join(' → ')} +
+
+ ))} +
+
+
+
+ + )} +
+
+ + {/* Visitor Detail Modal */} + {selectedVisitor && ( +
+
+
+

Visitor Details

+ +
+ +
+
+
+
IP Address
+
{selectedVisitor.ip}
+
+
+
Lead Score
+
+ {Math.round(selectedVisitor.leadScore * 100)}% +
+
+
+
Total Visits
+
{selectedVisitor.totalVisits}
+
+
+
Total Time
+
{formatDuration(selectedVisitor.totalTimeOnSite)}
+
+
+ +
+
Pages Visited
+
+ {selectedVisitor.pagesVisited.map((page) => ( + {page} + ))} +
+
+ +
+
Conversion Events
+
+ {selectedVisitor.conversionEvents.map((event) => ( + {event} + ))} +
+
+ +
+
Engagement Score
+ +
+ {Math.round(selectedVisitor.engagementScore * 100)}% engagement +
+
+
+
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/components/analytics/UniqueIPsList.tsx b/src/components/analytics/UniqueIPsList.tsx new file mode 100644 index 0000000..5373ee2 --- /dev/null +++ b/src/components/analytics/UniqueIPsList.tsx @@ -0,0 +1,274 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { useRouter } from 'next/navigation'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { exportUniqueIPsToCSV, exportUniqueIPsToXLSX, downloadCSV, testXLSXExport } from '@/lib/exportUtils'; +import { UniqueIPSummary } from '@/lib/nginxLogParser'; + +interface UniqueIPsListProps { + uniqueIPs: UniqueIPSummary[]; + humanIPs?: UniqueIPSummary[]; + botIPs?: UniqueIPSummary[]; + dateRange: { start: string; end: string }; +} + +type SortField = 'visits' | 'sessions' | 'engagement' | 'lastVisit'; +type SortOrder = 'asc' | 'desc'; + +export default function UniqueIPsList({ uniqueIPs, humanIPs, botIPs }: UniqueIPsListProps) { + const router = useRouter(); + const [searchTerm, setSearchTerm] = useState(''); + const [sortField, setSortField] = useState('visits'); + const [sortOrder, setSortOrder] = useState('desc'); + const [activeTab, setActiveTab] = useState<'all' | 'human' | 'bot'>('all'); + const [exportLoading, setExportLoading] = useState(null); + + const displayIPs = useMemo(() => { + let ips = uniqueIPs; + + if (activeTab === 'human' && humanIPs) { + ips = humanIPs; + } else if (activeTab === 'bot' && botIPs) { + ips = botIPs; + } + + // Apply search filter + if (searchTerm) { + ips = ips.filter(ip => + ip.ipAddress.toLowerCase().includes(searchTerm.toLowerCase()) + ); + } + + // Apply sorting + ips.sort((a, b) => { + let aValue: string | number; + let bValue: string | number; + + switch (sortField) { + case 'visits': + aValue = a.totalVisits; + bValue = b.totalVisits; + break; + case 'sessions': + aValue = a.sessions; + bValue = b.sessions; + break; + case 'engagement': + aValue = a.engagementScore; + bValue = b.engagementScore; + break; + case 'lastVisit': + aValue = new Date(a.lastVisit).getTime(); + bValue = new Date(b.lastVisit).getTime(); + break; + default: + aValue = a.totalVisits; + bValue = b.totalVisits; + } + + if (sortOrder === 'asc') { + return aValue > bValue ? 1 : -1; + } else { + return aValue < bValue ? 1 : -1; + } + }); + + return ips; + }, [uniqueIPs, humanIPs, botIPs, searchTerm, sortField, sortOrder, activeTab]); + + const formatDate = (dateString: string): string => { + return new Date(dateString).toLocaleString('en-US', { + timeZone: 'America/Kentucky/Louisville', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const getEngagementColor = (score: number): string => { + if (score >= 80) return 'bg-green-500'; + if (score >= 60) return 'bg-yellow-500'; + if (score >= 40) return 'bg-orange-500'; + return 'bg-red-500'; + }; + + const getLeadIndicator = (ip: UniqueIPSummary): boolean => { + // Consider an IP a lead if they have high engagement and multiple sessions + return ip.engagementScore >= 70 && ip.sessions >= 2 && ip.totalVisits >= 5; + }; + + const handleIPClick = (ipAddress: string) => { + router.push(`/analytics/ip/${ipAddress}`); + }; + + const handleExport = async (format: 'csv' | 'xlsx') => { + setExportLoading(format); + + try { + // Debug: Log the data being exported + console.log('Exporting data:', { + format, + displayIPs, + count: displayIPs.length, + activeTab + }); + + if (displayIPs.length === 0) { + alert('No data to export. Please check your filters or date range.'); + return; + } + + if (format === 'csv') { + const csvContent = exportUniqueIPsToCSV(displayIPs); + downloadCSV(csvContent, `unique-ips-${activeTab}-${new Date().toISOString().split('T')[0]}.csv`); + } else { + exportUniqueIPsToXLSX(displayIPs); + } + } catch (error) { + console.error('Export failed:', error); + alert('Export failed. Please try again.'); + } finally { + setExportLoading(null); + } + }; + + return ( +
+
+
+ setSearchTerm(e.target.value)} + className="w-full" + /> +
+
+ + + + + +
+
+ + setActiveTab(value as 'all' | 'human' | 'bot')}> + + All ({uniqueIPs.length}) + Human ({humanIPs?.length || 0}) + Bot ({botIPs?.length || 0}) + + + + + + + + + + + + + + + + {displayIPs.length === 0 && ( + + + {searchTerm ? 'No IP addresses found matching your search.' : 'No IP addresses found.'} + + + )} + +
+ Showing {displayIPs.length} of {uniqueIPs.length} unique IP addresses +
+
+ ); +} + +interface IPListProps { + ips: UniqueIPSummary[]; + onIPClick: (ipAddress: string) => void; + formatDate: (dateString: string) => string; + getEngagementColor: (score: number) => string; + getLeadIndicator: (ip: UniqueIPSummary) => boolean; +} + +function IPList({ ips, onIPClick, formatDate, getEngagementColor, getLeadIndicator }: IPListProps) { + return ( +
+ {ips.map((ip) => ( + onIPClick(ip.ipAddress)}> + +
+
+
+ {ip.ipAddress} + {getLeadIndicator(ip) && ( + + Lead + + )} +
+
+ {ip.totalVisits} visits • {ip.sessions} sessions • Last: {formatDate(ip.lastVisit)} +
+
+
+
+
+ {ip.engagementScore}% +
+
+
+
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/src/components/home/Hero.tsx b/src/components/home/Hero.tsx index 0a4e150..6648aaf 100755 --- a/src/components/home/Hero.tsx +++ b/src/components/home/Hero.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react' import Link from 'next/link' import { motion, AnimatePresence } from 'framer-motion' import { Button } from '@/components/ui/button' -import { HERO_CTAS, PROJECTS } from '@/lib/constants' +import { HERO_CTAS } from '@/lib/constants' import { fadeInUp, staggerContainer } from '@/lib/animations' @@ -20,7 +20,6 @@ export default function Hero() { }, []) const currentHeroCTA = HERO_CTAS[currentCTA] - const currentProject = PROJECTS.find(p => p.id === currentHeroCTA.projectId) return (
diff --git a/src/components/home/Projects.tsx b/src/components/home/Projects.tsx index 00d282c..ef1700d 100755 --- a/src/components/home/Projects.tsx +++ b/src/components/home/Projects.tsx @@ -34,7 +34,7 @@ export default function Projects() { variants={staggerContainer} className="grid grid-cols-1 gap-8 lg:grid-cols-3" > - {PROJECTS.map((project, index) => ( + {PROJECTS.map((project) => ( - {SERVICES.map((service, index) => { + {SERVICES.map((service) => { const IconComponent = iconMap[service.icon as keyof typeof iconMap] return ( diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index b9e3c3e..c87669b 100755 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -7,7 +7,6 @@ import { Menu, X, Brain } from 'lucide-react' import { Button } from '@/components/ui/button' import { NAVIGATION } from '@/lib/constants' - export default function Header() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) diff --git a/src/components/shared/UpdateBanner.tsx b/src/components/shared/UpdateBanner.tsx new file mode 100644 index 0000000..1e4bea2 --- /dev/null +++ b/src/components/shared/UpdateBanner.tsx @@ -0,0 +1,68 @@ +'use client' + +import { useState, useEffect } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { X, Bell } from 'lucide-react' +import { Button } from '@/components/ui/button' + +const BANNER_KEY = 'update-banner-dismissed' +const BANNER_DELAY = 2000 // 2 seconds delay + +export default function UpdateBanner() { + const [isVisible, setIsVisible] = useState(false) + const [isDismissed, setIsDismissed] = useState(false) + + useEffect(() => { + // Check if banner was dismissed within last 24 hours + const dismissedTime = localStorage.getItem(BANNER_KEY) + const now = Date.now() + const twentyFourHours = 24 * 60 * 60 * 1000 + + if (!dismissedTime || (now - parseInt(dismissedTime)) > twentyFourHours) { + // Show banner after delay + const timer = setTimeout(() => { + setIsVisible(true) + // Set CSS custom property for header positioning + document.documentElement.style.setProperty('--banner-height', '60px') + }, BANNER_DELAY) + + return () => clearTimeout(timer) + } + }, []) + + const handleDismiss = () => { + setIsDismissed(true) + localStorage.setItem(BANNER_KEY, Date.now().toString()) + } + + return ( + + {isVisible && !isDismissed && ( + +
+ +

+ 🚀 We're building exciting products to bring convenience to your everyday tasks. Check back frequently for updates! +

+ +
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..4327940 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} \ No newline at end of file diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..e391a79 --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } \ No newline at end of file diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..c546248 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,160 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} \ No newline at end of file diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..5959555 --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file diff --git a/src/lib/botDetection.ts b/src/lib/botDetection.ts new file mode 100644 index 0000000..0f23a5f --- /dev/null +++ b/src/lib/botDetection.ts @@ -0,0 +1,208 @@ +// Bot Detection and Rate Limiting System +export interface BotDetectionConfig { + maxRequestsPerMinute: number; + maxRequestsPerHour: number; + suspiciousUserAgents: string[]; + progressiveVerificationEnabled: boolean; +} + +export interface BotDetectionResult { + isBot: boolean; + confidence: number; + reason: string; + requiresVerification: boolean; + rateLimitExceeded: boolean; +} + +export interface RateLimitInfo { + requestsInLastMinute: number; + requestsInLastHour: number; + isRateLimited: boolean; + resetTime: number; +} + +class BotDetectionService { + private requestCounts: Map = new Map(); + private config: BotDetectionConfig; + + constructor(config: Partial = {}) { + this.config = { + maxRequestsPerMinute: 60, + maxRequestsPerHour: 1000, + suspiciousUserAgents: [ + 'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget', 'python', 'java', + 'go-http-client', 'okhttp', 'apache-httpclient', 'postman', 'insomnia' + ], + progressiveVerificationEnabled: true, + ...config + }; + } + + /** + * Analyze user agent for bot indicators + */ + private analyzeUserAgent(userAgent: string): { isBot: boolean; confidence: number; reason: string } { + const ua = userAgent.toLowerCase(); + + // Check for known bot patterns + for (const pattern of this.config.suspiciousUserAgents) { + if (ua.includes(pattern)) { + return { + isBot: true, + confidence: 0.9, + reason: `Suspicious user agent pattern: ${pattern}` + }; + } + } + + // Check for missing common browser indicators + const hasBrowserIndicators = ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox'); + if (!hasBrowserIndicators && ua.length < 50) { + return { + isBot: true, + confidence: 0.7, + reason: 'Missing browser indicators and short user agent' + }; + } + + // Check for automation tools + const automationTools = ['selenium', 'webdriver', 'phantomjs', 'headless']; + for (const tool of automationTools) { + if (ua.includes(tool)) { + return { + isBot: true, + confidence: 0.95, + reason: `Automation tool detected: ${tool}` + }; + } + } + + return { + isBot: false, + confidence: 0.1, + reason: 'Appears to be legitimate browser' + }; + } + + /** + * Check rate limiting for IP address + */ + private checkRateLimit(ip: string): RateLimitInfo { + const now = Date.now(); + const minuteAgo = now - 60 * 1000; + const hourAgo = now - 60 * 60 * 1000; + + const ipData = this.requestCounts.get(ip) || { minute: 0, hour: 0, lastReset: now }; + + // Reset counters if needed + if (now - ipData.lastReset > 60 * 60 * 1000) { + ipData.minute = 0; + ipData.hour = 0; + ipData.lastReset = now; + } + + // Increment counters + ipData.minute++; + ipData.hour++; + + this.requestCounts.set(ip, ipData); + + const isRateLimited = + ipData.minute > this.config.maxRequestsPerMinute || + ipData.hour > this.config.maxRequestsPerHour; + + return { + requestsInLastMinute: ipData.minute, + requestsInLastHour: ipData.hour, + isRateLimited, + resetTime: ipData.lastReset + 60 * 60 * 1000 + }; + } + + /** + * Main bot detection method + */ + public detectBot(ip: string, userAgent: string, additionalData?: Record): BotDetectionResult { + // Check rate limiting first + const rateLimitInfo = this.checkRateLimit(ip); + + // Analyze user agent + const uaAnalysis = this.analyzeUserAgent(userAgent); + + // Additional heuristics + let confidence = uaAnalysis.confidence; + let reason = uaAnalysis.reason; + let requiresVerification = false; + + // Check for suspicious behavior patterns + if (additionalData) { + // Check for rapid page changes + if (additionalData.timeOnPage && (additionalData.timeOnPage as number) < 1000) { + confidence = Math.min(confidence + 0.2, 1.0); + reason += '; Rapid page navigation detected'; + } + + // Check for missing mouse movements (if available) + if (additionalData.mouseMovements === 0) { + confidence = Math.min(confidence + 0.1, 1.0); + reason += '; No mouse movements detected'; + } + } + + // Determine if verification is required + if (this.config.progressiveVerificationEnabled && confidence > 0.5) { + requiresVerification = true; + } + + return { + isBot: uaAnalysis.isBot || rateLimitInfo.isRateLimited, + confidence, + reason, + requiresVerification, + rateLimitExceeded: rateLimitInfo.isRateLimited + }; + } + + /** + * Generate verification challenge for suspicious activity + */ + public generateVerificationChallenge(): { type: string; challenge: string; expiresAt: number } { + const challenges = [ + { type: 'press_and_hold', challenge: 'Press and hold this button for 3 seconds' }, + { type: 'simple_math', challenge: 'What is 7 + 3?' }, + { type: 'checkbox', challenge: 'Check this box to continue' } + ]; + + const randomChallenge = challenges[Math.floor(Math.random() * challenges.length)]; + + return { + type: randomChallenge.type, + challenge: randomChallenge.challenge, + expiresAt: Date.now() + 5 * 60 * 1000 // 5 minutes + }; + } + + /** + * Clean up old rate limit data + */ + public cleanup(): void { + const now = Date.now(); + const oneHourAgo = now - 60 * 60 * 1000; + + for (const [ip, data] of this.requestCounts.entries()) { + if (data.lastReset < oneHourAgo) { + this.requestCounts.delete(ip); + } + } + } +} + +// Export singleton instance +export const botDetectionService = new BotDetectionService(); + +// Cleanup old data every hour +setInterval(() => { + botDetectionService.cleanup(); +}, 60 * 60 * 1000); + +export default botDetectionService; \ No newline at end of file diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..9a6cc33 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,51 @@ +// Configuration for analytics and logging +import { join } from 'path'; + +export const config = { + // Log file paths - can be overridden by environment variables + logs: { + // Base directory for logs (defaults to user's home directory) + baseDir: process.env.LOGS_BASE_DIR || process.env.HOME || process.env.USERPROFILE || '/home', + + // NGINX log directory relative to base directory + nginxDir: process.env.NGINX_LOGS_DIR || 'docker/nginx/logs', + + // Log file names (can be customized per domain) + accessLog: process.env.ACCESS_LOG_NAME || 'bigbraincoding.com_access.log', + trackingLog: process.env.TRACKING_LOG_NAME || 'bigbraincoding.com_tracking.log', + ipTrackingLog: process.env.IP_TRACKING_LOG_NAME || 'bigbraincoding.com_ip_tracking.log', + + // Get full log directory path + getNginxLogDir: () => { + return join(config.logs.baseDir, config.logs.nginxDir); + }, + + // Get specific log file paths + getLogFiles: () => { + const logDir = config.logs.getNginxLogDir(); + return { + access: join(logDir, config.logs.accessLog), + tracking: join(logDir, config.logs.trackingLog), + ipTracking: join(logDir, config.logs.ipTrackingLog) + }; + } + }, + + // Analytics settings + analytics: { + // Timezone for analytics (defaults to Louisville timezone) + timezone: process.env.ANALYTICS_TIMEZONE || 'America/Kentucky/Louisville', + + // Session timeout in minutes (default: 30 minutes) + sessionTimeout: parseInt(process.env.SESSION_TIMEOUT || '30'), + + // Bot detection settings + botDetection: { + // Minimum indicators to classify as bot + minBotIndicators: parseInt(process.env.MIN_BOT_INDICATORS || '3'), + + // Maximum requests per IP to consider as human + maxHumanRequests: parseInt(process.env.MAX_HUMAN_REQUESTS || '100') + } + } +}; \ No newline at end of file diff --git a/src/lib/exportUtils.ts b/src/lib/exportUtils.ts new file mode 100644 index 0000000..f90852e --- /dev/null +++ b/src/lib/exportUtils.ts @@ -0,0 +1,410 @@ +import { IPAnalytics, UniqueIPSummary, NGINXLogEntry } from './nginxLogParser'; + +export interface ExportOptions { + format: 'csv' | 'xlsx'; + filename?: string; +} + +/** + * Export IP analytics to CSV + */ +export function exportIPAnalyticsToCSV(analytics: IPAnalytics): string { + const lines: string[] = []; + + // Header + lines.push('IP Analytics Report'); + lines.push(''); + + // Basic info + lines.push('IP Address,Value'); + lines.push(`IP Address,${analytics.ipAddress}`); + lines.push(`Total Visits,${analytics.totalVisits}`); + lines.push(`Sessions,${analytics.sessions}`); + lines.push(`Average Time on Site (seconds),${analytics.averageTimeOnSite}`); + lines.push(`Engagement Score,${analytics.engagementScore}%`); + lines.push(`First Visit,${analytics.firstVisit}`); + lines.push(`Last Visit,${analytics.lastVisit}`); + lines.push(''); + + // Pages visited + lines.push('Pages Visited,Count'); + Object.entries(analytics.pages).forEach(([page, count]) => { + lines.push(`${page},${count}`); + }); + lines.push(''); + + // Browsers + lines.push('Browsers,Count'); + Object.entries(analytics.browsers).forEach(([browser, count]) => { + lines.push(`${browser},${count}`); + }); + lines.push(''); + + // Devices + lines.push('Devices,Count'); + Object.entries(analytics.devices).forEach(([device, count]) => { + lines.push(`${device},${count}`); + }); + lines.push(''); + + // Sessions + lines.push('Session Details'); + lines.push('Session ID,Start Time,End Time,Duration (seconds),Total Requests,Pages'); + analytics.sessionDetails.forEach((session) => { + lines.push(`${session.sessionId},${session.startTime},${session.endTime},${session.duration},${session.totalRequests},"${session.pages.join('; ')}"`); + }); + + return lines.join('\n'); +} + +/** + * Export IP analytics to XLSX format via CSV conversion + */ +export function exportIPAnalyticsToXLSX(analytics: IPAnalytics): void { + // Validate input data + if (!analytics || !analytics.ipAddress) { + console.error('Invalid analytics data:', analytics); + alert('Export failed: Invalid data to export'); + return; + } + + console.log('Exporting IP analytics to XLSX via CSV:', { ipAddress: analytics.ipAddress, data: analytics }); + + // Generate CSV content + const csvContent = exportIPAnalyticsToCSV(analytics); + + console.log('CSV content generated:', csvContent); + + // Convert CSV to XLSX + const filename = `ip-analytics-${analytics.ipAddress}-${new Date().toISOString().split('T')[0]}.xlsx`; + csvToXLSX(csvContent, filename); +} + +/** + * Export all logs for an IP to CSV + */ +export function exportIPLogsToCSV(entries: NGINXLogEntry[], ipAddress: string): string { + const lines: string[] = []; + + // Header + lines.push('IP Logs Report'); + lines.push(`IP Address: ${ipAddress}`); + lines.push(''); + + // Column headers + lines.push('Timestamp,Method,Path,Status Code,Bytes Sent,User Agent,Referer'); + + // Data rows + entries.forEach(entry => { + lines.push(`"${entry.timestamp}","${entry.method}","${entry.path}",${entry.statusCode},${entry.bytesSent},"${entry.userAgent}","${entry.referer}"`); + }); + + return lines.join('\n'); +} + +/** + * Export all logs for an IP to XLSX via CSV conversion + */ +export function exportIPLogsToXLSX(entries: NGINXLogEntry[], ipAddress: string): void { + // Validate input data + if (!entries || !Array.isArray(entries) || entries.length === 0) { + console.error('Invalid or empty entries data:', entries); + alert('Export failed: No data to export'); + return; + } + + if (!ipAddress) { + console.error('Invalid IP address:', ipAddress); + alert('Export failed: Invalid IP address'); + return; + } + + console.log('Exporting IP logs to XLSX via CSV:', { ipAddress, count: entries.length, data: entries }); + + // Generate CSV content + const csvContent = exportIPLogsToCSV(entries, ipAddress); + + console.log('CSV content generated:', csvContent); + + // Convert CSV to XLSX + const filename = `ip-logs-${ipAddress}-${new Date().toISOString().split('T')[0]}.xlsx`; + csvToXLSX(csvContent, filename); +} + +/** + * Export unique IPs list to CSV + */ +export function exportUniqueIPsToCSV(uniqueIPs: UniqueIPSummary[]): string { + const lines: string[] = []; + + // Header + lines.push('Unique IPs Report'); + lines.push(''); + + // Column headers + lines.push('IP Address,Total Visits,Sessions,Last Visit,Engagement Score'); + + // Data rows + uniqueIPs.forEach(ip => { + lines.push(`"${ip.ipAddress}",${ip.totalVisits},${ip.sessions},"${ip.lastVisit}",${ip.engagementScore}%`); + }); + + return lines.join('\n'); +} + +/** + * Convert CSV string to XLSX format + */ +export function csvToXLSX(csvContent: string, filename: string): void { + console.log('Converting CSV to XLSX:', { filename, csvLength: csvContent.length }); + + // Dynamically import SheetJS + import('xlsx').then((XLSX) => { + try { + console.log('XLSX library loaded for CSV conversion'); + + // Parse CSV content + const workbook = XLSX.read(csvContent, { type: 'string' }); + + console.log('CSV parsed successfully:', workbook); + + // Write to XLSX format + const wbout = XLSX.write(workbook, { + bookType: 'xlsx', + type: 'binary', + bookSST: false, + compression: true + }); + + console.log('XLSX write completed, output length:', wbout.length); + + // Convert to blob + const buf = new ArrayBuffer(wbout.length); + const view = new Uint8Array(buf); + for (let i = 0; i < wbout.length; i++) { + view[i] = wbout.charCodeAt(i) & 0xFF; + } + + const blob = new Blob([buf], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + + console.log('Blob created, size:', blob.size); + + // Download + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Clean up + URL.revokeObjectURL(url); + + console.log('CSV to XLSX conversion completed successfully'); + } catch (error) { + console.error('Error during CSV to XLSX conversion:', error); + alert('Export failed: Error converting CSV to Excel file'); + } + }).catch((error) => { + console.error('Failed to load XLSX library for CSV conversion:', error); + alert('XLSX export failed. Please install the xlsx library.'); + }); +} + +/** + * Export unique IPs list to XLSX via CSV conversion + */ +export function exportUniqueIPsToXLSX(uniqueIPs: UniqueIPSummary[]): void { + // Validate input data + if (!uniqueIPs || !Array.isArray(uniqueIPs) || uniqueIPs.length === 0) { + console.error('Invalid or empty uniqueIPs data:', uniqueIPs); + alert('Export failed: No data to export'); + return; + } + + console.log('Exporting unique IPs to XLSX via CSV:', { count: uniqueIPs.length, data: uniqueIPs }); + + // Generate CSV content + const csvContent = exportUniqueIPsToCSV(uniqueIPs); + + console.log('CSV content generated:', csvContent); + + // Convert CSV to XLSX + const filename = `unique-ips-${new Date().toISOString().split('T')[0]}.xlsx`; + csvToXLSX(csvContent, filename); +} + +/** + * Test XLSX export with simple data via CSV conversion + */ +export function testXLSXExport(): void { + console.log('Testing XLSX export with simple data via CSV...'); + + const testCSV = `Test Report + +Name,Value +Test 1,100 +Test 2,200 +Test 3,300`; + + csvToXLSX(testCSV, 'test-export.xlsx'); +} + +/** + * Download CSV data + */ +export function downloadCSV(csvContent: string, filename: string): void { + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + + if (link.download !== undefined) { + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } +} + +/** + * Download XLSX data using SheetJS + */ +export function downloadXLSX(workbook: Record, filename: string): void { + // Add debugging information + console.log('Exporting XLSX:', { filename, workbook }); + + // Validate workbook structure + if (!workbook || !workbook.SheetNames || !workbook.Sheets) { + console.error('Invalid workbook structure:', workbook); + alert('Export failed: Invalid data structure'); + return; + } + + // Check if sheets have data + const hasData = Object.values(workbook.Sheets as Record>).some((sheet) => { + const sheetKeys = Object.keys(sheet); + console.log('Sheet keys:', sheetKeys); + return sheetKeys.length > 0; + }); + + if (!hasData) { + console.error('No data found in workbook'); + alert('Export failed: No data to export'); + return; + } + + // Dynamically import SheetJS + import('xlsx').then((XLSX) => { + try { + console.log('XLSX library loaded successfully'); + + // Ensure proper workbook structure + const processedWorkbook = { + SheetNames: workbook.SheetNames as string[], + Sheets: {} as Record> + }; + + // Process each sheet + Object.entries(workbook.Sheets as Record>).forEach(([sheetName, sheetData]) => { + console.log(`Processing sheet: ${sheetName}`, sheetData); + const processedSheet: Record = {}; + + // Convert cell data to proper format + Object.entries(sheetData).forEach(([cellRef, cellData]) => { + if (cellData && typeof cellData === 'object' && 'v' in cellData) { + const cellValue = (cellData as { v: string | number }).v; + processedSheet[cellRef] = { + v: cellValue, + t: typeof cellValue === 'number' ? 'n' : 's' + }; + } + }); + + processedWorkbook.Sheets[sheetName] = processedSheet; + console.log(`Processed sheet ${sheetName}:`, processedSheet); + }); + + console.log('Processed workbook:', processedWorkbook); + + const wbout = XLSX.write(processedWorkbook, { + bookType: 'xlsx', + type: 'binary', + bookSST: false, + compression: true + }); + + console.log('XLSX write completed, output length:', wbout.length); + + // Convert to blob + const buf = new ArrayBuffer(wbout.length); + const view = new Uint8Array(buf); + for (let i = 0; i < wbout.length; i++) { + view[i] = wbout.charCodeAt(i) & 0xFF; + } + + const blob = new Blob([buf], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + + console.log('Blob created, size:', blob.size); + + // Download + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Clean up + URL.revokeObjectURL(url); + + console.log('XLSX export completed successfully'); + } catch (error) { + console.error('Error during XLSX generation:', error); + alert('Export failed: Error generating Excel file'); + } + }).catch((error) => { + console.error('Failed to load XLSX library:', error); + alert('XLSX export failed. Please install the xlsx library.'); + }); +} + +/** + * Format duration for display + */ +export function formatDuration(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.floor(seconds % 60); + + if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s`; + } else if (minutes > 0) { + return `${minutes}m ${secs}s`; + } else { + return `${secs}s`; + } +} + +/** + * Format date for display + */ +export function formatDate(dateString: string): string { + return new Date(dateString).toLocaleString('en-US', { + timeZone: 'America/Kentucky/Louisville', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); +} \ No newline at end of file diff --git a/src/lib/marketingIntelligence.ts b/src/lib/marketingIntelligence.ts new file mode 100644 index 0000000..696b923 --- /dev/null +++ b/src/lib/marketingIntelligence.ts @@ -0,0 +1,489 @@ +// Marketing Intelligence and Lead Qualification System +export interface VisitorProfile { + ip: string; + sessionId: string; + firstVisit: string; + lastVisit: string; + totalVisits: number; + totalTimeOnSite: number; + pagesVisited: string[]; + engagementScore: number; + leadScore: number; + deviceConsistency: boolean; + highEngagementPages: string[]; + conversionEvents: string[]; + timeBasedPatterns: { + averageSessionDuration: number; + preferredVisitTimes: string[]; + returnVisitor: boolean; + }; +} + +export interface LeadQualification { + isQualified: boolean; + leadScore: number; + qualificationReason: string; + recommendedAction: string; + urgency: 'low' | 'medium' | 'high'; + nextBestAction: string; +} + +export interface SalesIntelligence { + highValueVisitors: VisitorProfile[]; + conversionOpportunities: { + visitor: VisitorProfile; + opportunity: string; + confidence: number; + }[]; + marketInsights: { + topPerformingPages: string[]; + commonUserJourneys: string[][]; + devicePreferences: Record; + timeBasedTrends: Record; + }; +} + +class MarketingIntelligenceService { + private visitorProfiles: Map = new Map(); + private highEngagementThresholds = { + timeOnSite: 300000, // 5 minutes + pagesVisited: 3, + engagementScore: 0.7, + leadScore: 0.6 + }; + + /** + * Update visitor profile with new session data + */ + public updateVisitorProfile(sessionData: { + ip: string; + sessionId: string; + pages: string[]; + timeOnSite: number; + engagementMetrics?: Record; + }): void { + const { ip, sessionId, pages, timeOnSite, engagementMetrics } = sessionData; + + const existingProfile = this.visitorProfiles.get(ip); + const now = new Date().toISOString(); + + const engagementScore = this.calculateEngagementScore({ + timeOnSite, + pagesVisited: pages.length, + engagementMetrics + }); + + const leadScore = this.calculateLeadScore({ + engagementScore, + totalVisits: (existingProfile?.totalVisits || 0) + 1, + timeOnSite, + pagesVisited: pages.length, + highValuePages: this.getHighValuePages(pages) + }); + + const profile: VisitorProfile = { + ip, + sessionId, + firstVisit: existingProfile?.firstVisit || now, + lastVisit: now, + totalVisits: (existingProfile?.totalVisits || 0) + 1, + totalTimeOnSite: (existingProfile?.totalTimeOnSite || 0) + timeOnSite, + pagesVisited: [...new Set([...(existingProfile?.pagesVisited || []), ...pages])], + engagementScore, + leadScore, + deviceConsistency: this.checkDeviceConsistency(existingProfile), + highEngagementPages: this.identifyHighEngagementPages(pages), + conversionEvents: this.trackConversionEvents(pages), + timeBasedPatterns: this.analyzeTimePatterns(existingProfile, now, timeOnSite) + }; + + this.visitorProfiles.set(ip, profile); + } + + /** + * Calculate engagement score based on visitor behavior + */ + private calculateEngagementScore(data: { + timeOnSite: number; + pagesVisited: number; + engagementMetrics?: Record; + }): number { + const { timeOnSite, pagesVisited, engagementMetrics } = data; + + let score = 0; + + // Time on site factor (0-40 points) + const timeScore = Math.min(timeOnSite / this.highEngagementThresholds.timeOnSite, 1) * 40; + score += timeScore; + + // Pages visited factor (0-30 points) + const pagesScore = Math.min(pagesVisited / this.highEngagementThresholds.pagesVisited, 1) * 30; + score += pagesScore; + + // Engagement metrics factor (0-30 points) + if (engagementMetrics) { + const scrollDepth = engagementMetrics.scrollDepth || 0; + const clicks = engagementMetrics.clicks || 0; + const mouseMovements = engagementMetrics.mouseMovements || 0; + + const engagementScore = ( + (scrollDepth / 100) * 10 + + Math.min(clicks / 10, 1) * 10 + + Math.min(mouseMovements / 50, 1) * 10 + ); + score += engagementScore; + } + + return Math.min(score / 100, 1); + } + + /** + * Calculate lead score for qualification + */ + private calculateLeadScore(data: { + engagementScore: number; + totalVisits: number; + timeOnSite: number; + pagesVisited: number; + highValuePages: string[]; + }): number { + const { engagementScore, totalVisits, timeOnSite, pagesVisited, highValuePages } = data; + + let score = 0; + + // Engagement factor (0-30 points) + score += engagementScore * 30; + + // Return visitor factor (0-20 points) + if (totalVisits > 1) { + score += Math.min(totalVisits / 5, 1) * 20; + } + + // Time investment factor (0-25 points) + const timeScore = Math.min(timeOnSite / (this.highEngagementThresholds.timeOnSite * 2), 1) * 25; + score += timeScore; + + // High-value page factor (0-25 points) + const highValueScore = Math.min(highValuePages.length / 3, 1) * 25; + score += highValueScore; + + return Math.min(score / 100, 1); + } + + /** + * Identify high-value pages that indicate serious interest + */ + private getHighValuePages(pages: string[]): string[] { + const highValuePagePatterns = [ + '/services', + '/projects', + '/contact', + '/about', + '/pricing', + '/quote' + ]; + + return pages.filter(page => + highValuePagePatterns.some(pattern => page.includes(pattern)) + ); + } + + /** + * Check device consistency across visits + */ + private checkDeviceConsistency(existingProfile?: VisitorProfile): boolean { + // For now, assume consistent if profile exists + // In a real implementation, you'd compare device fingerprints + return !!existingProfile; + } + + /** + * Identify pages that indicate high engagement + */ + private identifyHighEngagementPages(pages: string[]): string[] { + const engagementIndicators = [ + 'contact', + 'services', + 'projects', + 'about', + 'pricing' + ]; + + return pages.filter(page => + engagementIndicators.some(indicator => page.includes(indicator)) + ); + } + + /** + * Track conversion events + */ + private trackConversionEvents(pages: string[]): string[] { + const conversionEvents: string[] = []; + + if (pages.includes('/contact')) { + conversionEvents.push('contact_page_visited'); + } + + if (pages.includes('/services')) { + conversionEvents.push('services_page_visited'); + } + + if (pages.includes('/projects')) { + conversionEvents.push('portfolio_viewed'); + } + + return conversionEvents; + } + + /** + * Analyze time-based patterns + */ + private analyzeTimePatterns( + existingProfile: VisitorProfile | undefined, + currentVisit: string, + sessionDuration: number + ): VisitorProfile['timeBasedPatterns'] { + const now = new Date(currentVisit); + const visitHour = now.getHours(); + + const patterns: VisitorProfile['timeBasedPatterns'] = { + averageSessionDuration: existingProfile?.timeBasedPatterns.averageSessionDuration || sessionDuration, + preferredVisitTimes: existingProfile?.timeBasedPatterns.preferredVisitTimes || [], + returnVisitor: !!existingProfile + }; + + // Update preferred visit times + const hourString = `${visitHour}:00`; + if (!patterns.preferredVisitTimes.includes(hourString)) { + patterns.preferredVisitTimes.push(hourString); + } + + // Update average session duration + if (existingProfile) { + const totalSessions = existingProfile.totalVisits; + patterns.averageSessionDuration = ( + (existingProfile.timeBasedPatterns.averageSessionDuration * (totalSessions - 1) + sessionDuration) / totalSessions + ); + } + + return patterns; + } + + /** + * Qualify a visitor as a lead + */ + public qualifyLead(ip: string): LeadQualification { + const profile = this.visitorProfiles.get(ip); + + if (!profile) { + return { + isQualified: false, + leadScore: 0, + qualificationReason: 'No visitor profile found', + recommendedAction: 'Continue monitoring', + urgency: 'low', + nextBestAction: 'Wait for more engagement' + }; + } + + const isQualified = profile.leadScore >= this.highEngagementThresholds.leadScore; + const urgency = this.determineUrgency(profile); + const recommendedAction = this.getRecommendedAction(profile); + const nextBestAction = this.getNextBestAction(profile); + + return { + isQualified, + leadScore: profile.leadScore, + qualificationReason: this.getQualificationReason(profile), + recommendedAction, + urgency, + nextBestAction + }; + } + + /** + * Determine urgency level for lead + */ + private determineUrgency(profile: VisitorProfile): 'low' | 'medium' | 'high' { + if (profile.leadScore >= 0.8) return 'high'; + if (profile.leadScore >= 0.6) return 'medium'; + return 'low'; + } + + /** + * Get recommended action for lead + */ + private getRecommendedAction(profile: VisitorProfile): string { + if (profile.conversionEvents.includes('contact_page_visited')) { + return 'Follow up on contact form submission'; + } + + if (profile.highEngagementPages.includes('/services')) { + return 'Send personalized service proposal'; + } + + if (profile.highEngagementPages.includes('/projects')) { + return 'Share relevant case studies'; + } + + return 'Send welcome email with value proposition'; + } + + /** + * Get next best action + */ + private getNextBestAction(profile: VisitorProfile): string { + if (!profile.conversionEvents.includes('contact_page_visited')) { + return 'Encourage contact page visit'; + } + + if (!profile.highEngagementPages.includes('/services')) { + return 'Direct to services page'; + } + + if (!profile.highEngagementPages.includes('/projects')) { + return 'Showcase portfolio'; + } + + return 'Maintain relationship with regular updates'; + } + + /** + * Get qualification reason + */ + private getQualificationReason(profile: VisitorProfile): string { + const reasons: string[] = []; + + if (profile.engagementScore > 0.7) { + reasons.push('High engagement'); + } + + if (profile.totalVisits > 2) { + reasons.push('Return visitor'); + } + + if (profile.highEngagementPages.length > 0) { + reasons.push('Viewed key pages'); + } + + if (profile.conversionEvents.length > 0) { + reasons.push('Conversion events triggered'); + } + + return reasons.join(', ') || 'Limited engagement'; + } + + /** + * Generate sales intelligence report + */ + public generateSalesIntelligence(): SalesIntelligence { + const profiles = Array.from(this.visitorProfiles.values()); + + const highValueVisitors = profiles.filter(p => p.leadScore >= 0.6); + + const conversionOpportunities = profiles + .filter(p => p.leadScore >= 0.4 && p.leadScore < 0.6) + .map(visitor => ({ + visitor, + opportunity: this.getConversionOpportunity(visitor), + confidence: visitor.leadScore + })); + + const marketInsights = this.generateMarketInsights(profiles); + + return { + highValueVisitors, + conversionOpportunities, + marketInsights + }; + } + + /** + * Get conversion opportunity for visitor + */ + private getConversionOpportunity(visitor: VisitorProfile): string { + if (!visitor.conversionEvents.includes('contact_page_visited')) { + return 'Contact page conversion'; + } + + if (!visitor.highEngagementPages.includes('/services')) { + return 'Service interest qualification'; + } + + if (!visitor.highEngagementPages.includes('/projects')) { + return 'Portfolio engagement'; + } + + return 'General engagement improvement'; + } + + /** + * Generate market insights from visitor data + */ + private generateMarketInsights(profiles: VisitorProfile[]): SalesIntelligence['marketInsights'] { + const pageVisits: Record = {}; + const devicePreferences: Record = {}; + const timeBasedTrends: Record = {}; + const userJourneys: string[][] = []; + + profiles.forEach(profile => { + // Count page visits + profile.pagesVisited.forEach(page => { + pageVisits[page] = (pageVisits[page] || 0) + 1; + }); + + // Track user journeys + userJourneys.push(profile.pagesVisited); + + // Time-based trends (simplified) + profile.timeBasedPatterns.preferredVisitTimes.forEach(time => { + timeBasedTrends[time] = (timeBasedTrends[time] || 0) + 1; + }); + }); + + return { + topPerformingPages: Object.entries(pageVisits) + .sort(([,a], [,b]) => b - a) + .slice(0, 5) + .map(([page]) => page), + commonUserJourneys: this.findCommonJourneys(userJourneys), + devicePreferences, + timeBasedTrends + }; + } + + /** + * Find common user journey patterns + */ + private findCommonJourneys(journeys: string[][]): string[][] { + const journeyCounts: Record = {}; + + journeys.forEach(journey => { + const key = journey.join(' -> '); + journeyCounts[key] = (journeyCounts[key] || 0) + 1; + }); + + return Object.entries(journeyCounts) + .sort(([,a], [,b]) => b - a) + .slice(0, 3) + .map(([journey]) => journey.split(' -> ')); + } + + /** + * Get all visitor profiles + */ + public getVisitorProfiles(): VisitorProfile[] { + return Array.from(this.visitorProfiles.values()); + } + + /** + * Get profile for specific IP + */ + public getVisitorProfile(ip: string): VisitorProfile | undefined { + return this.visitorProfiles.get(ip); + } +} + +// Export singleton instance +export const marketingIntelligenceService = new MarketingIntelligenceService(); +export default marketingIntelligenceService; \ No newline at end of file diff --git a/src/lib/nginxLogParser.ts b/src/lib/nginxLogParser.ts new file mode 100644 index 0000000..7220c9b --- /dev/null +++ b/src/lib/nginxLogParser.ts @@ -0,0 +1,863 @@ +// NGINX Log Parser for Analytics Dashboard +import { config } from './config'; + +export interface NGINXLogEntry { + timestamp: string; + ipAddress: string; + method: string; + path: string; + statusCode: number; + bytesSent: number; + referer: string; + userAgent: string; + requestTime: number; + upstreamResponseTime: number; + acceptLanguage: string; + acceptEncoding: string; + connection: string; + upgrade: string; + secFetchDest: string; + secFetchMode: string; + secFetchSite: string; + secFetchUser: string; +} + +export interface NGINXLogSummary { + totalRequests: number; + uniqueIPs: Set; + statusCodes: Record; + topPaths: Record; + topUserAgents: Record; + topReferers: Record; + averageResponseTime: number; + totalBytesSent: number; + timeRange: { + start: string; + end: string; + }; +} + +export interface IPAnalytics { + ipAddress: string; + totalVisits: number; + sessions: number; + averageTimeOnSite: number; + pages: Record; + browsers: Record; + devices: Record; + engagementScore: number; + firstVisit: string; + lastVisit: string; + sessionDetails: SessionDetail[]; +} + +export interface SessionDetail { + sessionId: string; + startTime: string; + endTime: string; + duration: number; + pages: string[]; + totalRequests: number; +} + +export interface UniqueIPSummary { + ipAddress: string; + totalVisits: number; + sessions: number; + lastVisit: string; + engagementScore: number; +} + +class NGINXLogParser { + /** + * Convert UTC timestamp to configured timezone + */ + private convertToConfiguredTimezone(utcTimestamp: string): string { + try { + const utcDate = new Date(utcTimestamp); + const configuredDate = new Date(utcDate.toLocaleString('en-US', { timeZone: config.analytics.timezone })); + + // Format as ISO string but with configured timezone + const year = configuredDate.getFullYear(); + const month = String(configuredDate.getMonth() + 1).padStart(2, '0'); + const day = String(configuredDate.getDate()).padStart(2, '0'); + const hours = String(configuredDate.getHours()).padStart(2, '0'); + const minutes = String(configuredDate.getMinutes()).padStart(2, '0'); + const seconds = String(configuredDate.getSeconds()).padStart(2, '0'); + + // Get timezone offset for configured timezone + const timezoneOffset = configuredDate.getTimezoneOffset(); + const offsetHours = Math.abs(Math.floor(timezoneOffset / 60)); + const offsetMinutes = Math.abs(timezoneOffset % 60); + const offsetSign = timezoneOffset > 0 ? '-' : '+'; + + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.000${offsetSign}${String(offsetHours).padStart(2, '0')}:${String(offsetMinutes).padStart(2, '0')}`; + } catch (error) { + console.error('Error converting timestamp to configured timezone:', error); + return utcTimestamp; // Fallback to original timestamp + } + } + + /** + * Parse a single NGINX log line + */ + private parseLogLine(line: string): NGINXLogEntry | null { + try { + // Parse the main log format: combined + tracking format + const regex = /^(\S+) - - \[([^\]]+)\] "([^"]*)" (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" "([^"]*)" rt=([^ ]+) uct="([^"]*)" uht="([^"]*)" urt="([^"]*)" ua="([^"]*)" us="([^"]*)"$/; + const match = line.match(regex); + + if (!match) { + // Try tracking log format + const trackingRegex = /^(\S+) - - \[([^\]]+)\] "([^"]*)" (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" ([^ ]+) - ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$/; + const trackingMatch = line.match(trackingRegex); + + if (trackingMatch) { + const [ + , + ipAddress, + timestamp, + request, + statusCode, + bytesSent, + referer, + userAgent, + xForwardedFor, + requestTime, + acceptLanguage, + acceptEncoding, + connection, + upgrade, + secFetchDest, + secFetchMode, + secFetchSite, + secFetchUser + ] = trackingMatch; + + // Parse the request line + const requestMatch = request.match(/^(\S+) (\S+) (\S+)$/); + if (!requestMatch) { + return null; + } + + const [, method, path, httpVersion] = requestMatch; + + // Extract real IP from X-Forwarded-For header + let realIP = ipAddress; + if (xForwardedFor && xForwardedFor !== '-') { + // X-Forwarded-For can contain multiple IPs, take the first one + const forwardedIPs = xForwardedFor.split(','); + if (forwardedIPs.length > 0) { + realIP = forwardedIPs[0].trim(); + } + } + + return { + timestamp: this.convertToConfiguredTimezone(this.parseTimestamp(timestamp)), + ipAddress: realIP, + method, + path, + statusCode: parseInt(statusCode), + bytesSent: parseInt(bytesSent), + referer: referer === '-' ? '' : referer, + userAgent: userAgent === '-' ? '' : userAgent, + requestTime: parseFloat(requestTime) || 0, + upstreamResponseTime: 0, + acceptLanguage: acceptLanguage === '-' ? '' : acceptLanguage, + acceptEncoding: acceptEncoding === '-' ? '' : acceptEncoding, + connection: connection === '-' ? '' : connection, + upgrade: upgrade === '-' ? '' : upgrade, + secFetchDest: secFetchDest === '-' ? '' : secFetchDest, + secFetchMode: secFetchMode === '-' ? '' : secFetchMode, + secFetchSite: secFetchSite === '-' ? '' : secFetchSite, + secFetchUser: secFetchUser === '-' ? '' : secFetchUser + }; + } + + // Try alternative format for logs without tracking data + const altRegex = /^(\S+) - - \[([^\]]+)\] "([^"]*)" (\d+) (\d+) "([^"]*)" "([^"]*)"$/; + const altMatch = line.match(altRegex); + + if (!altMatch) { + return null; + } + + const [ + , + ipAddress, + timestamp, + request, + statusCode, + bytesSent, + referer, + userAgent + ] = altMatch; + + // Parse the request line + const requestMatch = request.match(/^(\S+) (\S+) (\S+)$/); + if (!requestMatch) { + return null; + } + + const [, method, path, httpVersion] = requestMatch; + + return { + timestamp: this.convertToConfiguredTimezone(this.parseTimestamp(timestamp)), + ipAddress, + method, + path, + statusCode: parseInt(statusCode), + bytesSent: parseInt(bytesSent), + referer: referer === '-' ? '' : referer, + userAgent: userAgent === '-' ? '' : userAgent, + requestTime: 0, + upstreamResponseTime: 0, + acceptLanguage: '', + acceptEncoding: '', + connection: '', + upgrade: '', + secFetchDest: '', + secFetchMode: '', + secFetchSite: '', + secFetchUser: '' + }; + } + + const [ + , + ipAddress, + timestamp, + request, + statusCode, + bytesSent, + referer, + userAgent, + xForwardedFor, + xRealIP, + requestTime, + upstreamConnectTime, + upstreamHeaderTime, + upstreamResponseTime, + userAgent2, + userSession + ] = match; + + // Parse the request line + const requestMatch = request.match(/^(\S+) (\S+) (\S+)$/); + if (!requestMatch) { + return null; + } + + const [, method, path, httpVersion] = requestMatch; + + // Extract real IP from X-Forwarded-For header + let realIP = ipAddress || 'unknown'; + if (xForwardedFor && xForwardedFor !== '-') { + // X-Forwarded-For can contain multiple IPs, take the first one + const forwardedIPs = xForwardedFor.split(','); + if (forwardedIPs.length > 0) { + realIP = forwardedIPs[0].trim(); + } + } + + return { + timestamp: this.convertToConfiguredTimezone(this.parseTimestamp(timestamp)), + ipAddress: realIP, + method, + path, + statusCode: parseInt(statusCode), + bytesSent: parseInt(bytesSent), + referer: referer === '-' ? '' : referer, + userAgent: userAgent === '-' ? '' : userAgent, + requestTime: parseFloat(requestTime) || 0, + upstreamResponseTime: parseFloat(upstreamResponseTime) || 0, + acceptLanguage: '', + acceptEncoding: '', + connection: '', + upgrade: '', + secFetchDest: '', + secFetchMode: '', + secFetchSite: '', + secFetchUser: '' + }; + } catch (error) { + console.error('Error parsing NGINX log line:', error); + return null; + } + } + + /** + * Parse timestamp from NGINX log format + */ + private parseTimestamp(timestamp: string): string { + // Convert from NGINX format to ISO string + // Format: 25/Jul/2025:15:10:42 +0000 + const match = timestamp.match(/^(\d+)\/(\w+)\/(\d+):(\d+):(\d+):(\d+) ([\+\-]\d{4})$/); + + if (!match) { + return new Date().toISOString(); + } + + const [, day, month, year, hour, minute, second, timezone] = match; + + // Convert month name to number + const monthMap: Record = { + 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', + 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', + 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12' + }; + + const monthNum = monthMap[month] || '01'; + const paddedDay = day.padStart(2, '0'); + const paddedHour = hour.padStart(2, '0'); + const paddedMinute = minute.padStart(2, '0'); + const paddedSecond = second.padStart(2, '0'); + + // Create ISO string (assuming UTC for now, we'll convert to ET later) + const utcTimestamp = `${year}-${monthNum}-${paddedDay}T${paddedHour}:${paddedMinute}:${paddedSecond}.000Z`; + + return utcTimestamp; + } + + /** + * Read and parse NGINX log files + */ + public async parseLogFiles(logPaths: string[]): Promise { + const entries: NGINXLogEntry[] = []; + + for (const logPath of logPaths) { + try { + const fs = await import('fs/promises'); + const content = await fs.readFile(logPath, 'utf-8'); + const lines = content.split('\n').filter(line => line.trim()); + + for (const line of lines) { + const entry = this.parseLogLine(line); + if (entry) { + entries.push(entry); + } + } + } catch (error) { + console.error(`Error reading log file ${logPath}:`, error); + } + } + + return entries; + } + + /** + * Generate summary statistics from log entries + */ + public generateSummary(entries: NGINXLogEntry[]): NGINXLogSummary { + const summary: NGINXLogSummary = { + totalRequests: entries.length, + uniqueIPs: new Set(), + statusCodes: {}, + topPaths: {}, + topUserAgents: {}, + topReferers: {}, + averageResponseTime: 0, + totalBytesSent: 0, + timeRange: { + start: '', + end: '' + } + }; + + let totalResponseTime = 0; + let validResponseTimes = 0; + + for (const entry of entries) { + // Count unique IPs + summary.uniqueIPs.add(entry.ipAddress); + + // Count status codes + summary.statusCodes[entry.statusCode] = (summary.statusCodes[entry.statusCode] || 0) + 1; + + // Count paths + summary.topPaths[entry.path] = (summary.topPaths[entry.path] || 0) + 1; + + // Count user agents + if (entry.userAgent) { + const browser = this.extractBrowser(entry.userAgent); + summary.topUserAgents[browser] = (summary.topUserAgents[browser] || 0) + 1; + } + + // Count referers + if (entry.referer) { + const domain = this.extractDomain(entry.referer); + summary.topReferers[domain] = (summary.topReferers[domain] || 0) + 1; + } + + // Calculate response times + if (entry.requestTime > 0) { + totalResponseTime += entry.requestTime; + validResponseTimes++; + } + + // Sum bytes sent + summary.totalBytesSent += entry.bytesSent; + + // Track time range + if (!summary.timeRange.start || entry.timestamp < summary.timeRange.start) { + summary.timeRange.start = entry.timestamp; + } + if (!summary.timeRange.end || entry.timestamp > summary.timeRange.end) { + summary.timeRange.end = entry.timestamp; + } + } + + // Calculate average response time + if (validResponseTimes > 0) { + summary.averageResponseTime = totalResponseTime / validResponseTimes; + } + + return summary; + } + + /** + * Extract browser name from user agent + */ + private extractBrowser(userAgent: string): string { + const ua = userAgent.toLowerCase(); + + if (ua.includes('chrome')) return 'Chrome'; + if (ua.includes('firefox')) return 'Firefox'; + if (ua.includes('safari')) return 'Safari'; + if (ua.includes('edge')) return 'Edge'; + if (ua.includes('opera')) return 'Opera'; + if (ua.includes('bot') || ua.includes('crawler')) return 'Bot'; + + return 'Other'; + } + + /** + * Extract domain from referer URL + */ + private extractDomain(referer: string): string { + try { + const url = new URL(referer); + return url.hostname; + } catch { + return 'Direct'; + } + } + + /** + * Get top N items from a record + */ + public getTopItems(record: Record, limit: number = 10): Array<{ key: string; count: number }> { + return Object.entries(record) + .sort(([, a], [, b]) => b - a) + .slice(0, limit) + .map(([key, count]) => ({ key, count })); + } + + /** + * Filter entries by date range + */ + public filterByDateRange(entries: NGINXLogEntry[], startDate: string, endDate: string): NGINXLogEntry[] { + const start = new Date(startDate + 'T00:00:00.000Z'); + const end = new Date(endDate + 'T23:59:59.999Z'); + + const filtered = entries.filter(entry => { + const entryDate = new Date(entry.timestamp); + const isInRange = entryDate >= start && entryDate <= end; + + return isInRange; + }); + + return filtered; + } + + /** + * Get entries for a specific IP address + */ + public getEntriesByIP(entries: NGINXLogEntry[], ipAddress: string): NGINXLogEntry[] { + return entries.filter(entry => entry.ipAddress === ipAddress); + } + + /** + * Get entries for a specific path + */ + public getEntriesByPath(entries: NGINXLogEntry[], path: string): NGINXLogEntry[] { + return entries.filter(entry => entry.path === path); + } + + /** + * Get all unique IPs with their visit counts and basic analytics + */ + public getUniqueIPsWithCounts(entries: NGINXLogEntry[]): UniqueIPSummary[] { + const ipMap = new Map(); + + for (const entry of entries) { + // Skip Docker container IPs + if (this.isDockerIP(entry.ipAddress)) { + continue; + } + + if (!ipMap.has(entry.ipAddress)) { + ipMap.set(entry.ipAddress, { + ipAddress: entry.ipAddress, + totalVisits: 0, + sessions: 0, + lastVisit: entry.timestamp, + engagementScore: 0 + }); + } + + const summary = ipMap.get(entry.ipAddress)!; + summary.totalVisits++; + summary.lastVisit = entry.timestamp > summary.lastVisit ? entry.timestamp : summary.lastVisit; + } + + // Calculate sessions and engagement scores + for (const [ip, summary] of ipMap) { + const ipEntries = this.getEntriesByIP(entries, ip); + summary.sessions = this.calculateSessions(ipEntries); + summary.engagementScore = this.calculateEngagementScore(ipEntries); + } + + return Array.from(ipMap.values()).sort((a, b) => b.totalVisits - a.totalVisits); + } + + /** + * Check if an IP is a Docker container IP + */ + private isDockerIP(ipAddress: string): boolean { + // Common Docker IP ranges + const dockerRanges = [ + '172.16.', '172.17.', '172.18.', '172.19.', '172.20.', '172.21.', '172.22.', '172.23.', '172.24.', '172.25.', '172.26.', '172.27.', '172.28.', '172.29.', '172.30.', '172.31.', + '10.0.', '10.1.', '10.2.', '10.3.', '10.4.', '10.5.', '10.6.', '10.7.', '10.8.', '10.9.', + '192.168.', + '127.0.0.1', + 'localhost' + ]; + + return dockerRanges.some(range => ipAddress.startsWith(range)); + } + + /** + * Detect if an IP is likely a bot + */ + public isBotIP(entries: NGINXLogEntry[]): boolean { + if (entries.length === 0) return false; + + const ipAddress = entries[0].ipAddress; + const uniqueUserAgents = new Set(entries.map(e => e.userAgent)); + const uniqueDevices = new Set(entries.map(e => this.extractDeviceType(e.userAgent))); + const uniqueBrowsers = new Set(entries.map(e => this.extractBrowser(e.userAgent))); + + // Bot indicators + const indicators = { + multipleUserAgents: uniqueUserAgents.size > 5, + multipleDevices: uniqueDevices.size > 3, + multipleBrowsers: uniqueBrowsers.size > 3, + highRequestRate: entries.length > 100, + hasBotInUserAgent: entries.some(e => e.userAgent.toLowerCase().includes('bot')), + hasCrawlerInUserAgent: entries.some(e => e.userAgent.toLowerCase().includes('crawler')), + hasSpiderInUserAgent: entries.some(e => e.userAgent.toLowerCase().includes('spider')) + }; + + // Count positive indicators + const positiveIndicators = Object.values(indicators).filter(Boolean).length; + + // If 3 or more indicators are positive, consider it a bot + return positiveIndicators >= 3; + } + + /** + * Get bot vs human traffic separation + */ + public separateBotTraffic(entries: NGINXLogEntry[]): { + humanIPs: UniqueIPSummary[]; + botIPs: UniqueIPSummary[]; + } { + const uniqueIPs = this.getUniqueIPsWithCounts(entries); + const humanIPs: UniqueIPSummary[] = []; + const botIPs: UniqueIPSummary[] = []; + + for (const ipSummary of uniqueIPs) { + const ipEntries = this.getEntriesByIP(entries, ipSummary.ipAddress); + + if (this.isBotIP(ipEntries)) { + botIPs.push(ipSummary); + } else { + humanIPs.push(ipSummary); + } + } + + return { humanIPs, botIPs }; + } + + /** + * Get detailed analytics for a specific IP address + */ + public getIPAnalytics(entries: NGINXLogEntry[], ipAddress: string): IPAnalytics | null { + const ipEntries = this.getEntriesByIP(entries, ipAddress); + if (ipEntries.length === 0) return null; + + const sessions = this.getSessionsForIP(ipEntries); + const pages = this.getPageVisits(ipEntries); + const browsers = this.getBrowserUsage(ipEntries); + const devices = this.getDeviceUsage(ipEntries); + + const firstVisit = ipEntries.reduce((earliest, entry) => + entry.timestamp < earliest ? entry.timestamp : earliest, ipEntries[0].timestamp); + const lastVisit = ipEntries.reduce((latest, entry) => + entry.timestamp > latest ? entry.timestamp : latest, ipEntries[0].timestamp); + + const averageTimeOnSite = this.calculateAverageTimeOnSite(sessions); + const engagementScore = this.calculateEngagementScore(ipEntries); + + return { + ipAddress, + totalVisits: ipEntries.length, + sessions: sessions.length, + averageTimeOnSite, + pages, + browsers, + devices, + engagementScore, + firstVisit, + lastVisit, + sessionDetails: sessions + }; + } + + /** + * Calculate sessions for an IP address (with proper session breaks) + */ + private calculateSessions(entries: NGINXLogEntry[]): number { + if (entries.length === 0) return 0; + + const sortedEntries = entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + let sessions = 1; + let lastActivity = new Date(sortedEntries[0].timestamp); + + for (let i = 1; i < sortedEntries.length; i++) { + const currentActivity = new Date(sortedEntries[i].timestamp); + const timeDiff = currentActivity.getTime() - lastActivity.getTime(); + const minutesDiff = timeDiff / (1000 * 60); + + // If more than configured session timeout minutes have passed, it's a new session + if (minutesDiff > config.analytics.sessionTimeout) { + sessions++; + } + lastActivity = currentActivity; + } + + return sessions; + } + + /** + * Get detailed sessions for an IP address + */ + public getSessionsForIP(entries: NGINXLogEntry[]): SessionDetail[] { + if (entries.length === 0) return []; + + const sortedEntries = entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + const sessions: SessionDetail[] = []; + let currentSession: SessionDetail | null = null; + let lastActivity = new Date(sortedEntries[0].timestamp); + + for (const entry of sortedEntries) { + const currentActivity = new Date(entry.timestamp); + const timeDiff = currentActivity.getTime() - lastActivity.getTime(); + const minutesDiff = timeDiff / (1000 * 60); + + // Start new session if more than configured session timeout minutes have passed + if (!currentSession || minutesDiff > config.analytics.sessionTimeout) { + if (currentSession) { + // Finalize previous session + currentSession.endTime = lastActivity.toISOString(); + currentSession.duration = (new Date(currentSession.endTime).getTime() - new Date(currentSession.startTime).getTime()) / 1000; + sessions.push(currentSession); + } + + // Start new session + currentSession = { + sessionId: `${entry.ipAddress}-${currentActivity.getTime()}`, + startTime: entry.timestamp, + endTime: '', + duration: 0, + pages: [], + totalRequests: 0 + }; + } + + // Add to current session (only if it's not a system path) + if (currentSession && this.isActualPage(entry.path)) { + currentSession.totalRequests++; + if (!currentSession.pages.includes(entry.path)) { + currentSession.pages.push(entry.path); + } + } + + lastActivity = currentActivity; + } + + // Finalize last session + if (currentSession) { + currentSession.endTime = lastActivity.toISOString(); + currentSession.duration = (new Date(currentSession.endTime).getTime() - new Date(currentSession.startTime).getTime()) / 1000; + sessions.push(currentSession); + } + + return sessions; + } + + /** + * Get page visits for an IP (filtered to actual pages) + */ + private getPageVisits(entries: NGINXLogEntry[]): Record { + const pages: Record = {}; + + for (const entry of entries) { + // Filter out system paths and focus on actual pages + if (this.isActualPage(entry.path)) { + const cleanPath = this.cleanPagePath(entry.path); + pages[cleanPath] = (pages[cleanPath] || 0) + 1; + } + } + + return pages; + } + + /** + * Check if a path is an actual page (not system files) + */ + private isActualPage(path: string): boolean { + const systemPaths = [ + '/_next/', + '/api/', + '/static/', + '/favicon.ico', + '/robots.txt', + '/sitemap.xml' + ]; + + return !systemPaths.some(systemPath => path.startsWith(systemPath)); + } + + /** + * Clean page path for better analytics + */ + private cleanPagePath(path: string): string { + // Remove query parameters + const cleanPath = path.split('?')[0]; + + // Map common paths to readable names + const pathMap: Record = { + '/': 'Homepage', + '/projects': 'Projects', + '/about': 'About', + '/contact': 'Contact', + '/services': 'Services' + }; + + return pathMap[cleanPath] || cleanPath; + } + + /** + * Get browser usage for an IP + */ + private getBrowserUsage(entries: NGINXLogEntry[]): Record { + const browsers: Record = {}; + + for (const entry of entries) { + const browser = this.extractBrowser(entry.userAgent); + browsers[browser] = (browsers[browser] || 0) + 1; + } + + return browsers; + } + + /** + * Get device usage for an IP + */ + private getDeviceUsage(entries: NGINXLogEntry[]): Record { + const devices: Record = {}; + + for (const entry of entries) { + const deviceType = this.extractDeviceType(entry.userAgent); + devices[deviceType] = (devices[deviceType] || 0) + 1; + } + + return devices; + } + + /** + * Extract device type from user agent + */ + private extractDeviceType(userAgent: string): string { + const ua = userAgent.toLowerCase(); + + if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) { + return 'Mobile'; + } else if (ua.includes('tablet') || ua.includes('ipad')) { + return 'Tablet'; + } else if (ua.includes('bot') || ua.includes('crawler')) { + return 'Bot'; + } else { + return 'Desktop'; + } + } + + /** + * Calculate average time on site from sessions + */ + private calculateAverageTimeOnSite(sessions: SessionDetail[]): number { + if (sessions.length === 0) return 0; + + const totalTime = sessions.reduce((sum, session) => sum + session.duration, 0); + return totalTime / sessions.length; + } + + /** + * Calculate engagement score based on various factors + */ + private calculateEngagementScore(entries: NGINXLogEntry[]): number { + if (entries.length === 0) return 0; + + let score = 0; + + // Base score from number of visits + score += Math.min(entries.length * 10, 100); + + // Bonus for multiple pages visited + const uniquePages = new Set(entries.map(e => e.path).filter(p => this.isActualPage(p))); + score += uniquePages.size * 15; + + // Bonus for longer time on site (if we have session data) + const sessions = this.getSessionsForIP(entries); + if (sessions.length > 0) { + const avgSessionTime = sessions.reduce((sum, s) => sum + s.duration, 0) / sessions.length; + score += Math.min(avgSessionTime / 60 * 20, 50); // Up to 50 points for time + } + + // Bonus for multiple sessions + score += Math.min(sessions.length * 10, 30); + + return Math.min(score, 100); // Cap at 100 + } + + /** + * Get filtered page analytics (excluding system paths) + */ + public getFilteredPageAnalytics(entries: NGINXLogEntry[]): Record { + const pages: Record = {}; + + for (const entry of entries) { + if (this.isActualPage(entry.path)) { + const cleanPath = this.cleanPagePath(entry.path); + pages[cleanPath] = (pages[cleanPath] || 0) + 1; + } + } + + return pages; + } +} + +export const nginxLogParser = new NGINXLogParser(); +export default nginxLogParser; \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100755 index 0000000..ef4c25d --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,4 @@ +// Middleware temporarily disabled - authentication handled in analytics page +export const config = { + matcher: [], +}; \ No newline at end of file diff --git a/vercel-import-env.sh b/vercel-import-env.sh new file mode 100644 index 0000000..269a8fe --- /dev/null +++ b/vercel-import-env.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Set environment target here: development, preview, or production +ENVIRONMENT="production" + +while IFS= read -r line || [ -n "$line" ]; do + # Skip comments and blank lines + if [[ "$line" =~ ^#.* ]] || [[ -z "$line" ]]; then + continue + fi + + # Split key and value + IFS='=' read -r key value <<< "$line" + + if [[ -n "$key" && -n "$value" ]]; then + echo "Adding $key to Vercel ($ENVIRONMENT)" + echo "$value" | vercel env add "$key" "$ENVIRONMENT" + fi +done < .env + + diff --git a/vercel.json b/vercel.json new file mode 100755 index 0000000..fe6dd91 --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "buildCommand": "npm run build", + "outputDirectory": ".next", + "framework": "nextjs", + "installCommand": "npm install" +} \ No newline at end of file