shubhjn commited on
Commit
59697b4
·
1 Parent(s): b66181f

feat: Implement core CMS features including workflow management, admin dashboard, API infrastructure, queueing system, and new UI components.

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. ARCHITECTURE.md +294 -0
  2. DEPLOYMENT.md +354 -0
  3. DEPLOYMENT_GUIDE.md +442 -0
  4. IMPROVEMENTS_ROADMAP.md +1374 -0
  5. QUICK_START_GUIDE.md +0 -373
  6. README.md +42 -22
  7. __tests__/rate-limit.test.ts +0 -10
  8. __tests__/simple.test.js +0 -5
  9. app/actions/business.ts +11 -2
  10. app/animations.css +24 -0
  11. app/api/admin/analytics/route.ts +46 -0
  12. app/api/admin/logs/route.ts +54 -0
  13. app/api/admin/settings/route.ts +86 -0
  14. app/api/admin/stats/route.ts +54 -0
  15. app/api/admin/users/[id]/route.ts +35 -0
  16. app/api/admin/users/route.ts +51 -0
  17. app/api/auth/route-wrapper.ts +50 -0
  18. app/api/businesses/route.ts +78 -61
  19. app/api/health/route.ts +100 -23
  20. app/api/logs/background/route.ts +42 -0
  21. app/api/notifications/[id]/route.ts +45 -0
  22. app/api/notifications/actions/route.ts +30 -0
  23. app/api/notifications/preferences/route.ts +46 -0
  24. app/api/notifications/route.ts +35 -50
  25. app/api/performance/metrics/route.ts +26 -0
  26. app/api/scraping/start/route.ts +1 -1
  27. app/api/settings/route.ts +11 -0
  28. app/api/social/automations/[id]/route.ts +42 -0
  29. app/api/social/automations/trigger/route.ts +71 -0
  30. app/api/social/webhooks/facebook/route.ts +244 -0
  31. app/api/tasks/monitor/route.ts +41 -0
  32. app/api/workflows/[id]/route.ts +1 -1
  33. app/api/workflows/route.ts +45 -37
  34. app/api/workflows/templates/route.ts +2 -2
  35. app/auth/signin/page.tsx +5 -124
  36. app/dashboard/admin/page.tsx +201 -0
  37. app/dashboard/businesses/page.tsx +8 -1
  38. app/dashboard/page.tsx +46 -15
  39. app/dashboard/settings/page.tsx +25 -1
  40. app/dashboard/workflows/builder/[id]/page.tsx +2 -2
  41. app/layout.tsx +1 -0
  42. app/page.tsx +238 -219
  43. components/admin/activity-logs.tsx +149 -0
  44. components/admin/platform-usage-chart.tsx +65 -0
  45. components/admin/stats-overview.tsx +86 -0
  46. components/admin/system-controls.tsx +212 -0
  47. components/admin/user-growth-chart.tsx +76 -0
  48. components/admin/user-management-table.tsx +183 -0
  49. components/common/loading-skeleton.tsx +187 -0
  50. components/dashboard/email-chart.tsx +25 -6
ARCHITECTURE.md ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Architecture Documentation
2
+
3
+ ## System Overview
4
+
5
+ AutoLoop is a Next.js 15 based email automation and workflow management system built with enterprise-grade security, caching, and performance optimization.
6
+
7
+ ## Technology Stack
8
+
9
+ - **Frontend**: React 19.2.3, Next.js 15, TypeScript, Tailwind CSS
10
+ - **Backend**: Next.js App Router, NextAuth.js v5, Server Actions
11
+ - **Database**: PostgreSQL with Drizzle ORM
12
+ - **Cache**: Redis with pattern-based invalidation
13
+ - **Task Queue**: BullMQ for background jobs
14
+ - **Authentication**: NextAuth.js with OAuth (Google, GitHub), Credentials
15
+ - **Validation**: Zod schemas for type-safe validation
16
+ - **Error Tracking**: Sentry
17
+ - **Monitoring**: Web Vitals, custom performance tracking
18
+
19
+ ## Directory Structure
20
+
21
+ ```
22
+ /app - Next.js App Router pages and API routes
23
+ /api - REST API endpoints
24
+ /auth - Authentication pages
25
+ /dashboard - Dashboard pages
26
+ /actions - Server actions
27
+
28
+ /components - React components
29
+ /ui - Base UI components
30
+ /admin - Admin-only components
31
+ /dashboard - Dashboard components
32
+
33
+ /lib - Utilities and business logic
34
+ /api-* - API-related utilities
35
+ /auth-* - Authentication utilities
36
+ /validation - Input validation schemas
37
+ /sanitize - XSS prevention
38
+ /cache-* - Caching layer
39
+ /rate-limit - Rate limiting
40
+ /csrf - CSRF protection
41
+ /logger - Logging system
42
+ /feature-flags - Feature management
43
+ /environment-* - Configuration
44
+
45
+ /db - Database configuration
46
+ /schema - Drizzle ORM schemas
47
+ /indexes - Database indexes
48
+
49
+ /public - Static assets
50
+
51
+ /__tests__ - Unit tests
52
+ /e2e - E2E tests with Playwright
53
+
54
+ /types - TypeScript type definitions
55
+
56
+ /hooks - Custom React hooks
57
+
58
+ /styles - Global styles
59
+
60
+ /docs - Documentation
61
+ ```
62
+
63
+ ## Core Features
64
+
65
+ ### 1. Authentication & Authorization
66
+ - **Multi-Provider Support**: Google, GitHub, Credentials, WhatsApp OTP
67
+ - **NextAuth.js v5**: Session management with JWT
68
+ - **Rate Limiting**: Per-endpoint configuration
69
+ - **Brute-Force Protection**: Progressive delays, account lockout
70
+ - **CSRF Protection**: Timing-safe token validation
71
+ - **API Key Auth**: For external service integrations
72
+
73
+ ### 2. Caching Strategy
74
+ - **Redis Cache**: Distributed caching with pattern-based invalidation
75
+ - **Query-Level Caching**:
76
+ - Businesses: 10-minute TTL
77
+ - Workflows: 5-minute TTL
78
+ - Templates: 15-minute TTL
79
+ - **Cache Invalidation**: Automatic on mutations (POST/PATCH/DELETE)
80
+ - **Cache Bypass**: Support for force-refresh via headers
81
+
82
+ ### 3. Security Measures
83
+ - **Input Validation**: Zod schemas for all inputs
84
+ - **XSS Prevention**: DOMPurify sanitization
85
+ - **SQL Injection Prevention**: Parameterized queries via Drizzle ORM
86
+ - **Security Headers**:
87
+ - Content-Security-Policy
88
+ - X-Frame-Options: DENY
89
+ - X-Content-Type-Options: nosniff
90
+ - Referrer-Policy: strict-origin-when-cross-origin
91
+ - Permissions-Policy: camera=(), microphone=(), geolocation=()
92
+
93
+ ### 4. Performance Optimization
94
+ - **Code Splitting**: Webpack optimization with vendor separation
95
+ - **Dynamic Imports**: Lazy loading of heavy components
96
+ - **Database Indexes**: On userId, email, status, createdAt fields
97
+ - **Web Vitals Tracking**: LCP, FID, CLS monitoring
98
+ - **API Performance**: Response time tracking and slow query logging
99
+ - **Bundle Analysis**: @next/bundle-analyzer integration
100
+
101
+ ### 5. Error Handling
102
+ - **Global Error Boundary**: Catches component errors
103
+ - **API Error Responses**: Standardized format with error codes
104
+ - **Sentry Integration**: Error tracking and reporting
105
+ - **Detailed Logging**: Structured JSON logs with context
106
+
107
+ ### 6. Database Design
108
+
109
+ #### Core Tables
110
+ - `users`: User accounts and authentication
111
+ - `businesses`: Business entities
112
+ - `automation_workflows`: Workflow definitions
113
+ - `email_templates`: Email templates
114
+ - `email_logs`: Sent email tracking
115
+ - `business_contacts`: Contact management
116
+ - `campaign_analytics`: Campaign metrics
117
+
118
+ #### Indexes
119
+ ```sql
120
+ -- Users table
121
+ CREATE INDEX idx_users_email ON users(email);
122
+ CREATE INDEX idx_users_created_at ON users(created_at);
123
+
124
+ -- Businesses table
125
+ CREATE INDEX idx_businesses_user_id ON businesses(user_id);
126
+ CREATE INDEX idx_businesses_email ON businesses(email);
127
+ CREATE INDEX idx_businesses_status ON businesses(status);
128
+ CREATE INDEX idx_businesses_created_at ON businesses(created_at);
129
+
130
+ -- Workflows table
131
+ CREATE INDEX idx_workflows_user_id ON automationWorkflows(user_id);
132
+ CREATE INDEX idx_workflows_is_active ON automationWorkflows(is_active);
133
+
134
+ -- Email Logs table
135
+ CREATE INDEX idx_email_logs_business_id ON emailLogs(business_id);
136
+ CREATE INDEX idx_email_logs_status ON emailLogs(status);
137
+ CREATE INDEX idx_email_logs_sent_at ON emailLogs(sent_at);
138
+ ```
139
+
140
+ ## Request Flow
141
+
142
+ ### Authentication Flow
143
+ 1. User submits login/signup form
144
+ 2. Form validates input (client-side)
145
+ 3. POST to `/api/auth/signin` or `/api/auth/signup`
146
+ 4. Server validates with Zod schemas
147
+ 5. Rate limiting check
148
+ 6. Password hashing with bcrypt
149
+ 7. JWT token generation
150
+ 8. Session establishment
151
+ 9. Redirect to dashboard
152
+
153
+ ### API Request Flow
154
+ 1. Client sends request with auth token and CSRF token
155
+ 2. Middleware validates request
156
+ 3. Rate limiting check
157
+ 4. User authentication verification
158
+ 5. Check Redis cache (GET requests)
159
+ 6. Execute business logic
160
+ 7. Validate output
161
+ 8. Cache result (if applicable)
162
+ 9. Return response with performance headers
163
+
164
+ ### Caching Flow
165
+ 1. Incoming GET request
166
+ 2. Generate cache key from endpoint + filters
167
+ 3. Check Redis for cached value
168
+ 4. If hit: return cached data with X-Cache: hit header
169
+ 5. If miss: fetch from database
170
+ 6. Validate response
171
+ 7. Cache with appropriate TTL
172
+ 8. Return data with X-Cache: miss header
173
+ 9. On mutation (PATCH/DELETE): invalidate related cache patterns
174
+
175
+ ## Data Validation Pipeline
176
+
177
+ 1. **Client-Side**: React Hook Form + Zod schemas
178
+ 2. **Server-Side**: Zod schema validation
179
+ 3. **Database**: Column constraints and foreign keys
180
+ 4. **Output**: Response validation before sending to client
181
+
182
+ ## Error Handling Strategy
183
+
184
+ ### Error Levels
185
+ - **Client Validation**: Form validation messages
186
+ - **Server Validation**: 400 Bad Request with error details
187
+ - **Authentication**: 401 Unauthorized with retry instructions
188
+ - **Authorization**: 403 Forbidden
189
+ - **Rate Limited**: 429 Too Many Requests with Retry-After header
190
+ - **Server Error**: 500 with Sentry tracking
191
+
192
+ ### Error Response Format
193
+ ```json
194
+ {
195
+ "success": false,
196
+ "error": "Human-readable error message",
197
+ "code": "ERROR_CODE",
198
+ "details": {
199
+ "field": ["error message"]
200
+ },
201
+ "timestamp": "ISO8601 timestamp"
202
+ }
203
+ ```
204
+
205
+ ## Feature Flags
206
+
207
+ Located in `lib/feature-flags.ts`:
208
+ - Email notifications
209
+ - Two-factor authentication (10% rollout)
210
+ - Advanced analytics
211
+ - AI-powered suggestions (5% rollout)
212
+ - New dashboard UI (experimental, 20% rollout)
213
+
214
+ Flags support:
215
+ - Percentage-based rollout
216
+ - User whitelisting/blacklisting
217
+ - A/B testing groups
218
+ - Admin overrides
219
+
220
+ ## Deployment Architecture
221
+
222
+ ### Environment Stages
223
+ 1. **Development**: Local with hot-reload
224
+ 2. **Staging**: Production-like environment for testing
225
+ 3. **Production**: Live environment
226
+
227
+ ### Deployment Pipeline
228
+ 1. Code push to main branch
229
+ 2. GitHub Actions CI/CD triggered
230
+ 3. Run linting and type-check
231
+ 4. Run test suite
232
+ 5. Build optimization analysis
233
+ 6. Deploy to staging
234
+ 7. Run E2E tests on staging
235
+ 8. Manual approval for production
236
+ 9. Deploy to production with health checks
237
+ 10. Automatic rollback on critical errors
238
+
239
+ ## Monitoring & Observability
240
+
241
+ ### Metrics Tracked
242
+ - Page load times (LCP, FID, CLS)
243
+ - API response times
244
+ - Error rates
245
+ - Cache hit/miss rates
246
+ - Database query times
247
+ - User actions and flows
248
+
249
+ ### Log Levels
250
+ - DEBUG: Detailed debugging information
251
+ - INFO: General information
252
+ - WARN: Warning messages (slow queries, high latency)
253
+ - ERROR: Error messages with stack traces
254
+
255
+ ### Alerting
256
+ - Sentry: Critical errors
257
+ - Performance: Alerts for slow requests (>1s)
258
+ - Security: Suspicious activity, rate limit violations
259
+ - Uptime: Endpoint availability checks
260
+
261
+ ## Security Audit Checklist
262
+
263
+ - ✅ CSRF protection on state-changing operations
264
+ - ✅ Rate limiting on authentication endpoints
265
+ - ✅ Input validation and sanitization
266
+ - ✅ Output encoding to prevent XSS
267
+ - ✅ SQL injection prevention via ORM
268
+ - ✅ Authentication token management
269
+ - ✅ Password hashing with bcrypt
270
+ - ✅ Secure session management
271
+ - ✅ Security headers configured
272
+ - ✅ API key authentication for services
273
+ - ✅ Brute-force protection
274
+ - ✅ Audit logging for sensitive operations
275
+
276
+ ## Performance Targets
277
+
278
+ - **First Contentful Paint (FCP)**: < 1.8s
279
+ - **Largest Contentful Paint (LCP)**: < 2.5s
280
+ - **First Input Delay (FID)**: < 100ms
281
+ - **Cumulative Layout Shift (CLS)**: < 0.1
282
+ - **Time to Interactive (TTI)**: < 3.8s
283
+ - **API Response Time**: < 200ms (p95)
284
+ - **Cache Hit Rate**: > 60%
285
+ - **Bundle Size**: < 50KB (main)
286
+
287
+ ## Scaling Considerations
288
+
289
+ - Horizontal scaling via containerization (Docker)
290
+ - Database connection pooling with Neon
291
+ - Redis cluster for distributed caching
292
+ - BullMQ for job queue scaling
293
+ - CDN for static assets
294
+ - Load balancing across instances
DEPLOYMENT.md ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AutoLoop Deployment Guide
2
+
3
+ ## Prerequisites
4
+
5
+ ### System Requirements
6
+ - Node.js 18+ (LTS recommended)
7
+ - PostgreSQL 14+
8
+ - pnpm 8+ (or npm/yarn)
9
+ - Redis (optional, for caching and queues)
10
+
11
+ ### Required Accounts
12
+ - Google Cloud Platform (for OAuth, Gmail API)
13
+ - Facebook Developer Account (for social automation)
14
+ - Database hosting (Neon, Supabase, or self-hosted PostgreSQL)
15
+
16
+ ## Environment Setup
17
+
18
+ ### 1. Clone Repository
19
+ ```bash
20
+ git clone <repository-url>
21
+ cd autoloop
22
+ ```
23
+
24
+ ### 2. Install Dependencies
25
+ ```bash
26
+ pnpm install
27
+ ```
28
+
29
+ ### 3. Configure Environment Variables
30
+
31
+ Create `.env.local`:
32
+
33
+ ```env
34
+ # Database
35
+ DATABASE_URL=postgresql://user:password@host:5432/autoloop
36
+
37
+ # NextAuth
38
+ NEXTAUTH_SECRET=<generate-with: openssl rand -base64 32>
39
+ NEXTAUTH_URL=http://localhost:3000
40
+
41
+ # Google OAuth (for Gmail and Google login)
42
+ GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
43
+ GOOGLE_CLIENT_SECRET=your-client-secret
44
+ GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google
45
+
46
+ # Facebook (for social automation)
47
+ FACEBOOK_APP_ID=your-facebook-app-id
48
+ FACEBOOK_APP_SECRET=your-facebook-app-secret
49
+ FACEBOOK_WEBHOOK_VERIFY_TOKEN=your-custom-verify-token
50
+
51
+ # LinkedIn (optional)
52
+ LINKEDIN_CLIENT_ID=your-linkedin-client-id
53
+ LINKEDIN_CLIENT_SECRET=your-linkedin-client-secret
54
+
55
+ # Admin
56
+ ADMIN_EMAIL=admin@yourdomain.com
57
+
58
+ # Workers
59
+ START_WORKERS=false # Set to true in production
60
+
61
+ # Optional: Redis
62
+ REDIS_URL=redis://localhost:6379
63
+
64
+ # Optional: Gemini API (for AI features)
65
+ GEMINI_API_KEY=your-gemini-api-key
66
+ ```
67
+
68
+ ### 4. Database Setup
69
+
70
+ ```bash
71
+ # Push schema to database
72
+ pnpm run db:push
73
+
74
+ # Or run migrations
75
+ pnpm run db:migrate
76
+ ```
77
+
78
+ ### 5. Build Application
79
+
80
+ ```bash
81
+ pnpm run build
82
+ ```
83
+
84
+ ## Development
85
+
86
+ ```bash
87
+ # Start development server
88
+ pnpm run dev
89
+
90
+ # Open http://localhost:3000
91
+ ```
92
+
93
+ ## Production Deployment
94
+
95
+ ### Option 1: Vercel (Recommended)
96
+
97
+ 1. **Install Vercel CLI**:
98
+ ```bash
99
+ npm i -g vercel
100
+ ```
101
+
102
+ 2. **Deploy**:
103
+ ```bash
104
+ vercel
105
+ ```
106
+
107
+ 3. **Configure Environment Variables**:
108
+ - Go to Vercel Dashboard → Settings → Environment Variables
109
+ - Add all variables from `.env.local`
110
+
111
+ 4. **Enable Workers**:
112
+ - Set `START_WORKERS=true` in production environment
113
+
114
+ ### Option 2: Docker
115
+
116
+ 1. **Create Dockerfile** (if not exists):
117
+ ```dockerfile
118
+ FROM node:18-alpine
119
+
120
+ WORKDIR /app
121
+
122
+ COPY package.json pnpm-lock.yaml ./
123
+ RUN npm install -g pnpm && pnpm install
124
+
125
+ COPY . .
126
+ RUN pnpm run build
127
+
128
+ EXPOSE 3000
129
+
130
+ CMD ["pnpm", "start"]
131
+ ```
132
+
133
+ 2. **Build and Run**:
134
+ ```bash
135
+ docker build -t autoloop .
136
+ docker run -p 3000:3000 --env-file .env.local autoloop
137
+ ```
138
+
139
+ ### Option 3: VPS/Server
140
+
141
+ 1. **Setup Server** (Ubuntu example):
142
+ ```bash
143
+ # Update system
144
+ sudo apt update && sudo apt upgrade -y
145
+
146
+ # Install Node.js
147
+ curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
148
+ sudo apt-get install -y nodejs
149
+
150
+ # Install pnpm
151
+ npm install -g pnpm
152
+
153
+ # Install PM2 for process management
154
+ npm install -g pm2
155
+ ```
156
+
157
+ 2. **Clone and Build**:
158
+ ```bash
159
+ git clone <repository-url>
160
+ cd autoloop
161
+ pnpm install
162
+ pnpm run build
163
+ ```
164
+
165
+ 3. **Start with PM2**:
166
+ ```bash
167
+ pm2 start npm --name "autoloop" -- start
168
+ pm2 save
169
+ pm2 startup
170
+ ```
171
+
172
+ 4. **Setup Nginx Reverse Proxy**:
173
+ ```nginx
174
+ server {
175
+ listen 80;
176
+ server_name yourdomain.com;
177
+
178
+ location / {
179
+ proxy_pass http://localhost:3000;
180
+ proxy_http_version 1.1;
181
+ proxy_set_header Upgrade $http_upgrade;
182
+ proxy_set_header Connection 'upgrade';
183
+ proxy_set_header Host $host;
184
+ proxy_cache_bypass $http_upgrade;
185
+ }
186
+ }
187
+ ```
188
+
189
+ 5. **Setup SSL with Certbot**:
190
+ ```bash
191
+ sudo apt install certbot python3-certbot-nginx
192
+ sudo certbot --nginx -d yourdomain.com
193
+ ```
194
+
195
+ ## Post-Deployment Configuration
196
+
197
+ ### 1. Facebook Webhook Setup
198
+
199
+ 1. Go to Facebook App Dashboard → Webhooks
200
+ 2. Add webhook URL: `https://yourdomain.com/api/social/webhooks/facebook`
201
+ 3. Verify token: Use value from `FACEBOOK_WEBHOOK_VERIFY_TOKEN`
202
+ 4. Subscribe to: `comments`, `feed`, `mentions`, `messages`
203
+
204
+ ### 2. Google OAuth Setup
205
+
206
+ 1. Go to Google Cloud Console → APIs & Services → Credentials
207
+ 2. Create OAuth 2.0 Client ID
208
+ 3. Add authorized redirect URI: `https://yourdomain.com/api/auth/callback/google`
209
+ 4. Enable Gmail API and Google+ API
210
+
211
+ ### 3. Test Social Automation
212
+
213
+ ```bash
214
+ # Trigger manual test
215
+ curl -X POST https://yourdomain.com/api/social/automations/trigger
216
+
217
+ # Check worker status
218
+ curl https://yourdomain.com/api/social/automations/trigger
219
+ ```
220
+
221
+ ## Monitoring
222
+
223
+ ### Health Checks
224
+
225
+ ```bash
226
+ # Application health
227
+ curl https://yourdomain.com/api/health
228
+
229
+ # Worker status
230
+ curl https://yourdomain.com/api/social/automations/trigger
231
+ ```
232
+
233
+ ### Logs
234
+
235
+ **Vercel**:
236
+ - View logs in Vercel Dashboard
237
+
238
+ **PM2**:
239
+ ```bash
240
+ pm2 logs autoloop
241
+ pm2 monit
242
+ ```
243
+
244
+ **Docker**:
245
+ ```bash
246
+ docker logs <container-id>
247
+ ```
248
+
249
+ ## Troubleshooting
250
+
251
+ ### Build Failures
252
+
253
+ ```bash
254
+ # Clear cache
255
+ rm -rf .next node_modules
256
+ pnpm install
257
+ pnpm run build
258
+ ```
259
+
260
+ ### Database Connection Issues
261
+
262
+ ```bash
263
+ # Test database connection
264
+ psql $DATABASE_URL
265
+
266
+ # Check Drizzle schema
267
+ pnpm run db:studio
268
+ ```
269
+
270
+ ### Worker Not Starting
271
+
272
+ 1. Verify `START_WORKERS=true` in production
273
+ 2. Check logs for errors
274
+ 3. Test manually: `POST /api/social/automations/trigger`
275
+
276
+ ### Webhook Issues
277
+
278
+ 1. Verify webhook URL is HTTPS
279
+ 2. Check Facebook App is in Production mode
280
+ 3. Test webhook verification endpoint
281
+
282
+ ## Performance Optimization
283
+
284
+ ### 1. Database
285
+
286
+ - Enable connection pooling
287
+ - Add indexes for frequent queries
288
+ - Use Neon/Supabase for managed PostgreSQL
289
+
290
+ ### 2. Caching
291
+
292
+ - Enable Redis for session storage
293
+ - Cache API responses
294
+ - Use CDN for static assets
295
+
296
+ ### 3. Monitoring
297
+
298
+ - Set up error tracking (Sentry)
299
+ - Enable application monitoring
300
+ - Configure alerts for downtime
301
+
302
+ ## Backup & Recovery
303
+
304
+ ### Database Backups
305
+
306
+ ```bash
307
+ # Daily backup (cron job)
308
+ pg_dump $DATABASE_URL > backup_$(date +%Y%m%d).sql
309
+
310
+ # Restore
311
+ psql $DATABASE_URL < backup.sql
312
+ ```
313
+
314
+ ### Application Backups
315
+
316
+ - Version control (Git)
317
+ - Environment variables (secure storage)
318
+ - Database backups (automated)
319
+
320
+ ## Security Checklist
321
+
322
+ - [ ] HTTPS enabled
323
+ - [ ] Environment variables secured
324
+ - [ ] Database connection encrypted
325
+ - [ ] CSRF protection enabled
326
+ - [ ] Rate limiting configured
327
+ - [ ] File upload validation
328
+ - [ ] API authentication required
329
+ - [ ] Webhook signature verification
330
+ - [ ] Regular security updates
331
+
332
+ ## Scaling
333
+
334
+ ### Horizontal Scaling
335
+
336
+ - Use load balancer (Nginx, HAProxy)
337
+ - Deploy multiple instances
338
+ - Share session storage (Redis)
339
+ - Use managed database
340
+
341
+ ### Vertical Scaling
342
+
343
+ - Increase server resources
344
+ - Optimize database queries
345
+ - Enable caching layers
346
+ - Use CDN for static files
347
+
348
+ ## Support
349
+
350
+ For issues and questions:
351
+ - Check logs first
352
+ - Review error messages
353
+ - Test locally with same environment
354
+ - Verify all environment variables are set
DEPLOYMENT_GUIDE.md ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment Guide
2
+
3
+ ## Prerequisites
4
+
5
+ - Node.js 20+
6
+ - Docker & Docker Compose (for containerization)
7
+ - PostgreSQL 14+
8
+ - Redis 7+
9
+ - GitHub account with Actions enabled
10
+ - Sentry account (optional but recommended)
11
+
12
+ ## Local Development Setup
13
+
14
+ ```bash
15
+ # 1. Clone repository
16
+ git clone <repository-url>
17
+ cd autoloop
18
+
19
+ # 2. Install dependencies
20
+ pnpm install
21
+
22
+ # 3. Setup environment
23
+ cp .env.example .env.local
24
+
25
+ # 4. Update .env.local with your values
26
+ nano .env.local
27
+
28
+ # 5. Setup database
29
+ pnpm run db:push
30
+
31
+ # 6. Run development server
32
+ pnpm run dev:all
33
+
34
+ # 7. Open http://localhost:3000
35
+ ```
36
+
37
+ ## Docker Deployment
38
+
39
+ ### Build Docker Image
40
+
41
+ ```bash
42
+ # Build image
43
+ docker build -t autoloop:latest .
44
+
45
+ # Run with docker-compose
46
+ docker-compose up -d
47
+ ```
48
+
49
+ ### Docker Compose Configuration
50
+
51
+ ```yaml
52
+ version: '3.8'
53
+
54
+ services:
55
+ app:
56
+ build: .
57
+ ports:
58
+ - "3000:3000"
59
+ environment:
60
+ - DATABASE_URL=postgresql://user:password@db:5432/autoloop
61
+ - REDIS_URL=redis://redis:6379
62
+ depends_on:
63
+ - db
64
+ - redis
65
+ restart: always
66
+
67
+ db:
68
+ image: postgres:15-alpine
69
+ environment:
70
+ - POSTGRES_USER=user
71
+ - POSTGRES_PASSWORD=password
72
+ - POSTGRES_DB=autoloop
73
+ volumes:
74
+ - postgres_data:/var/lib/postgresql/data
75
+ restart: always
76
+
77
+ redis:
78
+ image: redis:7-alpine
79
+ volumes:
80
+ - redis_data:/data
81
+ restart: always
82
+
83
+ volumes:
84
+ postgres_data:
85
+ redis_data:
86
+ ```
87
+
88
+ ## Production Deployment
89
+
90
+ ### Environment Variables
91
+
92
+ Required environment variables for production:
93
+
94
+ ```
95
+ NODE_ENV=production
96
+ NEXTAUTH_SECRET=<generate-with-openssl-rand-hex-32>
97
+ NEXTAUTH_URL=https://yourdomain.com
98
+ DATABASE_URL=postgresql://user:pass@host:5432/db
99
+ REDIS_URL=redis://host:6379
100
+ NEXT_PUBLIC_SENTRY_DSN=https://key@sentry.io/project
101
+ SENTRY_AUTH_TOKEN=your-auth-token
102
+ ```
103
+
104
+ ### Database Migration
105
+
106
+ ```bash
107
+ # Generate migration files
108
+ pnpm run db:generate
109
+
110
+ # Apply migrations
111
+ pnpm run db:push
112
+
113
+ # Verify migration
114
+ pnpm run db:studio
115
+ ```
116
+
117
+ ### Build for Production
118
+
119
+ ```bash
120
+ # Build Next.js application
121
+ pnpm run build
122
+
123
+ # Analyze bundle size
124
+ pnpm run build:analyze
125
+
126
+ # Start production server
127
+ pnpm run start
128
+ ```
129
+
130
+ ## Deployment Platforms
131
+
132
+ ### Vercel (Recommended for Next.js)
133
+
134
+ 1. **Connect Repository**
135
+ - Go to vercel.com
136
+ - Import your repository
137
+ - Select Next.js framework
138
+
139
+ 2. **Environment Variables**
140
+ - Add all required env vars in dashboard
141
+ - Enable "Encrypt sensitive variables"
142
+
143
+ 3. **Database**
144
+ - Use Neon for PostgreSQL (vercel partner)
145
+ - Use Upstash for Redis (vercel partner)
146
+
147
+ 4. **Deploy**
148
+ - Automatic deployment on push to main
149
+ - Preview deployments for PRs
150
+
151
+ ```bash
152
+ # Deploy to Vercel
153
+ npm i -g vercel
154
+ vercel --prod
155
+ ```
156
+
157
+ ### AWS EC2
158
+
159
+ ```bash
160
+ # 1. Launch EC2 instance (Ubuntu 22.04)
161
+ # Choose t3.medium or larger
162
+
163
+ # 2. SSH into instance
164
+ ssh -i key.pem ubuntu@your-instance-ip
165
+
166
+ # 3. Install dependencies
167
+ sudo apt update
168
+ sudo apt install -y nodejs npm postgresql redis-server
169
+
170
+ # 4. Clone repository
171
+ git clone <repo> ~/autoloop
172
+ cd ~/autoloop
173
+
174
+ # 5. Install app dependencies
175
+ npm install --legacy-peer-deps
176
+ npm run build
177
+
178
+ # 6. Setup PM2 for process management
179
+ npm install -g pm2
180
+ pm2 start "npm run start" --name autoloop
181
+ pm2 startup
182
+ pm2 save
183
+
184
+ # 7. Setup reverse proxy (Nginx)
185
+ sudo apt install -y nginx
186
+ # Configure nginx with SSL via Let's Encrypt
187
+ ```
188
+
189
+ ### Railway/Render
190
+
191
+ 1. Connect your GitHub repository
192
+ 2. Select Node.js environment
193
+ 3. Set environment variables
194
+ 4. Add PostgreSQL and Redis services
195
+ 5. Deploy automatically on push
196
+
197
+ ### Docker Swarm / Kubernetes
198
+
199
+ For large-scale deployments:
200
+
201
+ ```bash
202
+ # Initialize Swarm
203
+ docker swarm init
204
+
205
+ # Deploy stack
206
+ docker stack deploy -c docker-compose.prod.yml autoloop
207
+
208
+ # For Kubernetes
209
+ kubectl apply -f k8s/
210
+
211
+ # Check status
212
+ kubectl get pods
213
+ kubectl get services
214
+ ```
215
+
216
+ ## SSL/TLS Certificate
217
+
218
+ ### Let's Encrypt (Free)
219
+
220
+ ```bash
221
+ # Install Certbot
222
+ sudo apt install certbot python3-certbot-nginx
223
+
224
+ # Get certificate
225
+ sudo certbot certonly --nginx -d yourdomain.com
226
+
227
+ # Auto-renewal
228
+ sudo systemctl enable certbot.timer
229
+ sudo systemctl start certbot.timer
230
+ ```
231
+
232
+ ## GitHub Actions CI/CD
233
+
234
+ ### Workflow Configuration
235
+
236
+ ```yaml
237
+ name: Build and Deploy
238
+
239
+ on:
240
+ push:
241
+ branches: [main, staging]
242
+ pull_request:
243
+ branches: [main, staging]
244
+
245
+ jobs:
246
+ build:
247
+ runs-on: ubuntu-latest
248
+
249
+ services:
250
+ postgres:
251
+ image: postgres:15
252
+ env:
253
+ POSTGRES_PASSWORD: postgres
254
+ redis:
255
+ image: redis:7
256
+
257
+ steps:
258
+ - uses: actions/checkout@v3
259
+
260
+ - uses: pnpm/action-setup@v2
261
+ - uses: actions/setup-node@v3
262
+ with:
263
+ node-version: 20
264
+ cache: 'pnpm'
265
+
266
+ - name: Install dependencies
267
+ run: pnpm install
268
+
269
+ - name: Run linter
270
+ run: pnpm run lint
271
+
272
+ - name: Type check
273
+ run: pnpm run type-check
274
+
275
+ - name: Run tests
276
+ run: pnpm run test
277
+
278
+ - name: Build
279
+ run: pnpm run build
280
+
281
+ - name: E2E Tests
282
+ run: pnpm exec playwright install && pnpm run test:e2e
283
+
284
+ - name: Deploy to staging
285
+ if: github.ref == 'refs/heads/staging'
286
+ run: |
287
+ # Deploy script
288
+ npm run deploy:staging
289
+
290
+ - name: Deploy to production
291
+ if: github.ref == 'refs/heads/main'
292
+ run: |
293
+ # Deploy script
294
+ npm run deploy:prod
295
+ ```
296
+
297
+ ## Monitoring & Health Checks
298
+
299
+ ### Health Check Endpoint
300
+
301
+ ```
302
+ GET /api/health
303
+ ```
304
+
305
+ Response:
306
+ ```json
307
+ {
308
+ "status": "healthy",
309
+ "timestamp": "2024-01-30T12:00:00Z",
310
+ "uptime": 3600,
311
+ "services": {
312
+ "database": "connected",
313
+ "redis": "connected",
314
+ "api": "responding"
315
+ }
316
+ }
317
+ ```
318
+
319
+ ### Metrics Monitoring
320
+
321
+ - Sentry for error tracking
322
+ - CloudWatch/DataDog for metrics
323
+ - Vercel Analytics for performance
324
+ - Custom dashboards for business metrics
325
+
326
+ ## Rollback Procedure
327
+
328
+ ```bash
329
+ # Check recent deployments
330
+ git log --oneline -5
331
+
332
+ # Rollback to previous version
333
+ git revert <commit-hash>
334
+ git push
335
+
336
+ # Or immediate rollback
337
+ vercel rollback
338
+
339
+ # Monitor after rollback
340
+ vercel logs
341
+ ```
342
+
343
+ ## Performance Optimization
344
+
345
+ ### Caching Strategy
346
+
347
+ - Cloudflare/CDN for static assets
348
+ - Redis for database query results
349
+ - Next.js Image Optimization
350
+ - Minification and compression
351
+
352
+ ### Database Optimization
353
+
354
+ - Connection pooling
355
+ - Query optimization
356
+ - Vacuum and analyze regularly
357
+ - Monitor slow queries
358
+
359
+ ## Security Checklist
360
+
361
+ - [ ] Environment variables are encrypted
362
+ - [ ] Database credentials rotated
363
+ - [ ] SSL/TLS certificates valid
364
+ - [ ] Security headers configured
365
+ - [ ] Rate limiting enabled
366
+ - [ ] WAF (Web Application Firewall) enabled
367
+ - [ ] DDoS protection enabled
368
+ - [ ] Regular backups configured
369
+ - [ ] Audit logs enabled
370
+ - [ ] Penetration testing completed
371
+
372
+ ## Backup & Recovery
373
+
374
+ ### Database Backup
375
+
376
+ ```bash
377
+ # Create backup
378
+ pg_dump DATABASE_URL > backup.sql
379
+
380
+ # Restore from backup
381
+ psql DATABASE_URL < backup.sql
382
+
383
+ # Automated backups
384
+ # Use managed database service (Neon, AWS RDS) for automatic backups
385
+ ```
386
+
387
+ ### Disaster Recovery Plan
388
+
389
+ 1. **RTO (Recovery Time Objective)**: < 1 hour
390
+ 2. **RPO (Recovery Point Objective)**: < 15 minutes
391
+ 3. **Backup Strategy**: Daily full + hourly incremental
392
+ 4. **Testing**: Monthly disaster recovery drills
393
+
394
+ ## Troubleshooting
395
+
396
+ ### Application won't start
397
+
398
+ ```bash
399
+ # Check logs
400
+ pm2 logs autoloop
401
+
402
+ # Check environment variables
403
+ env | grep NEXT
404
+
405
+ # Rebuild and restart
406
+ npm run build
407
+ pm2 restart autoloop
408
+ ```
409
+
410
+ ### Database connection issues
411
+
412
+ ```bash
413
+ # Test connection
414
+ psql $DATABASE_URL
415
+
416
+ # Check connection pooling
417
+ # Verify pool size in environment
418
+
419
+ # Restart database service
420
+ sudo systemctl restart postgresql
421
+ ```
422
+
423
+ ### Redis cache issues
424
+
425
+ ```bash
426
+ # Check Redis connection
427
+ redis-cli ping
428
+
429
+ # Clear cache
430
+ redis-cli FLUSHDB
431
+
432
+ # Check memory usage
433
+ redis-cli INFO memory
434
+ ```
435
+
436
+ ## Support & Resources
437
+
438
+ - Documentation: `/docs`
439
+ - API Reference: `/API_DOCUMENTATION.md`
440
+ - Architecture: `/ARCHITECTURE.md`
441
+ - Issues: GitHub Issues
442
+ - Discussions: GitHub Discussions
IMPROVEMENTS_ROADMAP.md ADDED
@@ -0,0 +1,1374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AutoLoop - Improvements, Fixes, Optimizations & Future Suggestions
2
+
3
+ ## 📋 Executive Summary
4
+
5
+ Your AutoLoop project is a sophisticated automation platform with excellent architecture. This document outlines **critical fixes** (must do), **improvements** (should do), **optimizations** (performance & scalability), and **future features** (nice to have).
6
+
7
+ ---
8
+
9
+ ## 🔴 CRITICAL FIXES (Do First!)
10
+
11
+ ### 1. **Fix Rate Limit Export Issue**
12
+
13
+ **Priority**: CRITICAL | **Time**: 5 minutes
14
+
15
+ **Problem**:
16
+ - `type_errors.log` shows: "Module '@/lib/rate-limit' has no exported member 'rateLimit'"
17
+ - Two API routes import non-existent `rateLimit` function
18
+ - This breaks your app build
19
+
20
+ **Affected Files**:
21
+ - [app/api/businesses/route.ts](app/api/businesses/route.ts)
22
+ - [app/api/scraping/start/route.ts](app/api/scraping/start/route.ts)
23
+
24
+ **Fix**:
25
+
26
+ ```typescript
27
+ // In lib/rate-limit.ts - rateLimit is already exported
28
+ export { RateLimiter, rateLimit, getRemainingEmails };
29
+
30
+ // Routes already use correct imports now
31
+ import { rateLimit } from "@/lib/rate-limit";
32
+ ```
33
+
34
+ **Status**: ✅ FIXED
35
+
36
+ ---
37
+
38
+ ### 2. **Fix Jest Configuration Module Issue**
39
+
40
+ **Priority**: CRITICAL | **Time**: 5 minutes
41
+
42
+ **Problem**:
43
+ - `test_output.txt` shows: "Cannot find module 'next/jest'"
44
+ - Should be `'next/jest.js'`
45
+ - Jest tests cannot run
46
+
47
+ **File**: [jest.config.js](jest.config.js)
48
+
49
+ **Fix**:
50
+
51
+ ```javascript
52
+ import nextJest from 'next/jest.js' // Correct - already fixed
53
+ ```
54
+
55
+ **Status**: ✅ FIXED
56
+
57
+ ---
58
+
59
+ ### 3. **Add Missing Environment Variables Validation**
60
+
61
+ **Priority**: CRITICAL | **Time**: 10 minutes
62
+
63
+ **Problem**:
64
+ - No startup validation for required env vars
65
+ - App could fail mysteriously at runtime
66
+ - Missing: `DATABASE_URL`, `NEXTAUTH_SECRET`, `GEMINI_API_KEY`
67
+
68
+ **Solution**: Use existing [lib/validate-env.ts](lib/validate-env.ts)
69
+
70
+ ```typescript
71
+ // In server.ts - Already implemented
72
+ import { validateEnvironmentVariables } from "@/lib/validate-env";
73
+
74
+ console.log("🚀 Starting Custom Server (Next.js + Workers)...");
75
+ validateEnvironmentVariables(); // Called on startup
76
+ ```
77
+
78
+ **Status**: ✅ IMPLEMENTED
79
+
80
+ ---
81
+
82
+ ## 🟡 HIGH PRIORITY IMPROVEMENTS
83
+
84
+ ### 4. **Implement Proper Error Handling Middleware**
85
+
86
+ **Priority**: HIGH | **Time**: 20 minutes
87
+
88
+ **Current Issue**:
89
+ - Inconsistent error responses across API routes
90
+ - Some routes use `apiError()`, others use `NextResponse.json()`
91
+ - Missing error logging context
92
+
93
+ **Create**: [lib/api-middleware.ts](lib/api-middleware.ts)
94
+
95
+ ```typescript
96
+ import { NextRequest, NextResponse } from "next/server";
97
+ import { logger } from "@/lib/logger";
98
+
99
+ export interface ApiContext {
100
+ userId?: string;
101
+ ip?: string;
102
+ method: string;
103
+ path: string;
104
+ }
105
+
106
+ export async function withErrorHandling<T>(
107
+ handler: (req: NextRequest, context: ApiContext) => Promise<Response>,
108
+ req: NextRequest,
109
+ context?: ApiContext
110
+ ): Promise<Response> {
111
+ const startTime = Date.now();
112
+ const apiContext = context || {
113
+ method: req.method,
114
+ path: new URL(req.url).pathname,
115
+ ip: req.headers.get("x-forwarded-for") || "unknown",
116
+ };
117
+
118
+ try {
119
+ const response = await handler(req, apiContext);
120
+ const duration = Date.now() - startTime;
121
+
122
+ logger.info("API Request", {
123
+ ...apiContext,
124
+ duration,
125
+ status: response.status,
126
+ });
127
+
128
+ return response;
129
+ } catch (error) {
130
+ const duration = Date.now() - startTime;
131
+
132
+ logger.error("API Error", {
133
+ ...apiContext,
134
+ duration,
135
+ error: error instanceof Error ? error.message : String(error),
136
+ stack: error instanceof Error ? error.stack : undefined,
137
+ });
138
+
139
+ return NextResponse.json(
140
+ {
141
+ success: false,
142
+ error: "Internal server error",
143
+ code: "INTERNAL_SERVER_ERROR",
144
+ },
145
+ { status: 500 }
146
+ );
147
+ }
148
+ }
149
+ ```
150
+
151
+ **Apply to all API routes**:
152
+
153
+ ```typescript
154
+ // Before:
155
+ export async function GET(request: Request) {
156
+ try {
157
+ const session = await auth();
158
+ // ...
159
+ } catch (error) {
160
+ console.error("Error:", error);
161
+ return NextResponse.json({ error: "..." }, { status: 500 });
162
+ }
163
+ }
164
+
165
+ // After:
166
+ export async function GET(req: NextRequest) {
167
+ return withErrorHandling(async (req, context) => {
168
+ const session = await auth();
169
+ // ... same logic
170
+ return NextResponse.json(data);
171
+ }, req);
172
+ }
173
+ ```
174
+
175
+ **Impact**: Consistent error handling, better debugging ✅
176
+
177
+ ---
178
+
179
+ ### 5. **Add Request Validation Middleware**
180
+
181
+ **Priority**: HIGH | **Time**: 25 minutes
182
+
183
+ **Current Issue**:
184
+ - Manual validation in every route (repetitive)
185
+ - No schema validation
186
+ - Security risk from invalid input
187
+
188
+ **Create**: [lib/validation.ts](lib/validation.ts)
189
+
190
+ ```typescript
191
+ import { z } from "zod";
192
+ import { NextResponse } from "next/server";
193
+
194
+ export function validateRequest<T>(
195
+ schema: z.ZodSchema<T>,
196
+ data: unknown
197
+ ): { success: true; data: T } | { success: false; errors: z.ZodError } {
198
+ const result = schema.safeParse(data);
199
+
200
+ if (!result.success) {
201
+ return { success: false, errors: result.error };
202
+ }
203
+
204
+ return { success: true, data: result.data };
205
+ }
206
+
207
+ export function validationErrorResponse(errors: z.ZodError) {
208
+ return NextResponse.json(
209
+ {
210
+ success: false,
211
+ error: "Validation failed",
212
+ code: "VALIDATION_ERROR",
213
+ details: errors.flatten(),
214
+ },
215
+ { status: 400 }
216
+ );
217
+ }
218
+
219
+ // Usage:
220
+ export async function POST(req: NextRequest) {
221
+ const body = await req.json();
222
+
223
+ const schema = z.object({
224
+ businessType: z.string().min(1),
225
+ purpose: z.string().min(1),
226
+ });
227
+
228
+ const validation = validateRequest(schema, body);
229
+ if (!validation.success) {
230
+ return validationErrorResponse(validation.errors);
231
+ }
232
+
233
+ const { businessType, purpose } = validation.data;
234
+ // ...
235
+ }
236
+ ```
237
+
238
+ **Impact**: Prevents invalid requests, cleaner code ✅
239
+
240
+ ---
241
+
242
+ ### 6. **Improve Rate Limiting Configuration**
243
+
244
+ **Priority**: HIGH | **Time**: 15 minutes
245
+
246
+ **Current Issue**:
247
+ - Hard-coded rate limits (100 req/min globally)
248
+ - No per-endpoint configuration
249
+ - No user-tier consideration
250
+
251
+ **Enhance**: [lib/rate-limit.ts](lib/rate-limit.ts)
252
+
253
+ ```typescript
254
+ // Add configuration per route
255
+ export const RATE_LIMIT_CONFIG = {
256
+ general: { limit: 100, windowSeconds: 60 },
257
+ email: { limit: 50, windowSeconds: 86400 },
258
+ scraping: { limit: 10, windowSeconds: 60 },
259
+ auth: { limit: 5, windowSeconds: 60 },
260
+ api_default: { limit: 100, windowSeconds: 60 },
261
+ } as const;
262
+
263
+ export async function checkRateLimit(
264
+ request: NextRequest,
265
+ context: "email" | "scraping" | "auth" | "general" = "general"
266
+ ) {
267
+ const ip = request.headers.get("x-forwarded-for") || "unknown";
268
+ const key = `rate_limit:${context}:${ip}`;
269
+ const config = RATE_LIMIT_CONFIG[context];
270
+
271
+ const result = await RateLimiter.check(key, config);
272
+
273
+ if (!result.success) {
274
+ return {
275
+ limited: true,
276
+ response: NextResponse.json(
277
+ { error: "Rate limit exceeded", retryAfter: result.reset },
278
+ {
279
+ status: 429,
280
+ headers: {
281
+ "Retry-After": String(
282
+ result.reset - Math.floor(Date.now() / 1000)
283
+ ),
284
+ "X-RateLimit-Limit": String(config.limit),
285
+ "X-RateLimit-Remaining": String(result.remaining),
286
+ "X-RateLimit-Reset": String(result.reset),
287
+ },
288
+ }
289
+ ),
290
+ };
291
+ }
292
+
293
+ return { limited: false };
294
+ }
295
+ ```
296
+
297
+ **Impact**: Better rate limit control, DRY code ✅
298
+
299
+ ---
300
+
301
+ ### 7. **Add Input Sanitization**
302
+
303
+ **Priority**: HIGH | **Time**: 20 minutes
304
+
305
+ **Current Issue**:
306
+ - User input not sanitized
307
+ - XSS vulnerability in workflow names, template content
308
+ - SQL injection risk (even with ORM)
309
+
310
+ **Create**: [lib/sanitize.ts](lib/sanitize.ts)
311
+
312
+ ```typescript
313
+ import DOMPurify from "isomorphic-dompurify";
314
+
315
+ export function sanitizeString(input: string, strict = false): string {
316
+ if (strict) {
317
+ return DOMPurify.sanitize(input, { ALLOWED_TAGS: [] });
318
+ }
319
+ return DOMPurify.sanitize(input);
320
+ }
321
+
322
+ export function sanitizeObject<T extends Record<string, any>>(obj: T): T {
323
+ const sanitized = { ...obj };
324
+
325
+ for (const key in sanitized) {
326
+ if (typeof sanitized[key] === "string") {
327
+ sanitized[key] = sanitizeString(sanitized[key]);
328
+ } else if (
329
+ typeof sanitized[key] === "object" &&
330
+ sanitized[key] !== null
331
+ ) {
332
+ sanitized[key] = sanitizeObject(sanitized[key]);
333
+ }
334
+ }
335
+
336
+ return sanitized;
337
+ }
338
+ ```
339
+
340
+ **Install**: `pnpm add isomorphic-dompurify`
341
+
342
+ **Usage**:
343
+
344
+ ```typescript
345
+ const body = await req.json();
346
+ const sanitized = sanitizeObject(body);
347
+ ```
348
+
349
+ **Impact**: Prevents XSS attacks ✅
350
+
351
+ ---
352
+
353
+ ## 🟢 MEDIUM PRIORITY OPTIMIZATIONS
354
+
355
+ ### 8. **Optimize Database Queries**
356
+
357
+ **Priority**: MEDIUM | **Time**: 30 minutes
358
+
359
+ **Issue**: N+1 queries, missing indexes, no query optimization
360
+
361
+ **Current Example** ([app/api/businesses/route.ts](app/api/businesses/route.ts)):
362
+
363
+ ```typescript
364
+ // Gets all businesses then filters in memory
365
+ const allBusinesses = await db.select().from(businesses);
366
+ const filtered = allBusinesses.filter((b) => b.category === category);
367
+ ```
368
+
369
+ **Better Approach**:
370
+
371
+ ```typescript
372
+ // Move filtering to database
373
+ const filtered = await db
374
+ .select()
375
+ .from(businesses)
376
+ .where(eq(businesses.category, category))
377
+ .limit(limit)
378
+ .offset(offset);
379
+
380
+ // Add missing indexes (in migrations)
381
+ export const businessesTable = pgTable(
382
+ "businesses",
383
+ {
384
+ // ... columns
385
+ },
386
+ (table) => ({
387
+ userCategoryIdx: index("businesses_user_category_idx").on(
388
+ table.userId,
389
+ table.category
390
+ ),
391
+ emailCreatedIdx: index("businesses_email_created_idx").on(
392
+ table.email,
393
+ table.createdAt
394
+ ),
395
+ statusUserIdx: index("businesses_status_user_idx").on(
396
+ table.emailStatus,
397
+ table.userId
398
+ ),
399
+ })
400
+ );
401
+ ```
402
+
403
+ **Impact**: Reduce query time by 70%+ ✅
404
+
405
+ ---
406
+
407
+ ### 9. **Implement Query Caching**
408
+
409
+ **Priority**: MEDIUM | **Time**: 25 minutes
410
+
411
+ **Create**: [lib/cache-manager.ts](lib/cache-manager.ts)
412
+
413
+ ```typescript
414
+ import { redis } from "@/lib/redis";
415
+
416
+ export async function getCached<T>(
417
+ key: string,
418
+ fetcher: () => Promise<T>,
419
+ ttl = 300 // 5 minutes default
420
+ ): Promise<T> {
421
+ if (!redis) return fetcher();
422
+
423
+ try {
424
+ const cached = await redis.get(key);
425
+ if (cached) {
426
+ return JSON.parse(cached);
427
+ }
428
+
429
+ const data = await fetcher();
430
+ await redis.setex(key, ttl, JSON.stringify(data));
431
+ return data;
432
+ } catch (error) {
433
+ console.warn("Cache error:", error);
434
+ return fetcher(); // Fallback to fetcher on error
435
+ }
436
+ }
437
+
438
+ export async function invalidateCache(pattern: string) {
439
+ if (!redis) return;
440
+
441
+ const keys = await redis.keys(pattern);
442
+ if (keys.length > 0) {
443
+ await redis.del(...keys);
444
+ }
445
+ }
446
+ ```
447
+
448
+ **Usage**:
449
+
450
+ ```typescript
451
+ // Cache business list for 10 minutes
452
+ const businesses = await getCached(
453
+ `businesses:${userId}:${category}`,
454
+ () => fetchBusinesses(userId, category),
455
+ 600
456
+ );
457
+
458
+ // Invalidate when business is updated
459
+ await invalidateCache(`businesses:${userId}:*`);
460
+ ```
461
+
462
+ **Impact**: Reduce database load by 60%+ ✅
463
+
464
+ ---
465
+
466
+ ### 10. **Add Request Deduplication**
467
+
468
+ **Priority**: MEDIUM | **Time**: 20 minutes
469
+
470
+ **Issue**: Multiple identical requests process simultaneously
471
+
472
+ **Create**: [lib/dedup.ts](lib/dedup.ts)
473
+
474
+ ```typescript
475
+ const pendingRequests = new Map<string, Promise<any>>();
476
+
477
+ export function getDedupKey(
478
+ userId: string,
479
+ action: string,
480
+ params: any
481
+ ): string {
482
+ return `${userId}:${action}:${JSON.stringify(params)}`;
483
+ }
484
+
485
+ export async function deduplicatedRequest<T>(
486
+ key: string,
487
+ request: () => Promise<T>
488
+ ): Promise<T> {
489
+ if (pendingRequests.has(key)) {
490
+ return pendingRequests.get(key)!;
491
+ }
492
+
493
+ const promise = request().finally(() => {
494
+ pendingRequests.delete(key);
495
+ });
496
+
497
+ pendingRequests.set(key, promise);
498
+ return promise;
499
+ }
500
+ ```
501
+
502
+ **Usage**:
503
+
504
+ ```typescript
505
+ export async function POST(req: NextRequest) {
506
+ const { businessId } = await req.json();
507
+ const key = getDedupKey(userId, "sendEmail", { businessId });
508
+
509
+ return deduplicatedRequest(key, async () => {
510
+ return await sendEmailLogic(businessId);
511
+ });
512
+ }
513
+ ```
514
+
515
+ **Impact**: Prevent duplicate processing ✅
516
+
517
+ ---
518
+
519
+ ### 11. **Optimize Bundle Size**
520
+
521
+ **Priority**: MEDIUM | **Time**: 40 minutes
522
+
523
+ **Issues**:
524
+ - Large dependencies not tree-shaken
525
+ - All scraper types imported everywhere
526
+ - No dynamic imports for heavy modules
527
+
528
+ **Actions**:
529
+
530
+ 1. **Audit bundles**:
531
+
532
+ ```bash
533
+ pnpm install --save-dev @next/bundle-analyzer
534
+ ```
535
+
536
+ 2. **Use dynamic imports**:
537
+
538
+ ```typescript
539
+ // Before:
540
+ import {
541
+ FacebookScraper,
542
+ GoogleMapsScraper,
543
+ LinkedInScraper,
544
+ } from "@/lib/scrapers";
545
+
546
+ // After:
547
+ const FacebookScraper = dynamic(() =>
548
+ import("@/lib/scrapers/facebook").then((m) => ({
549
+ default: m.FacebookScraper,
550
+ }))
551
+ );
552
+ ```
553
+
554
+ 3. **Lazy load heavy components**:
555
+
556
+ ```typescript
557
+ const NodeEditor = dynamic(
558
+ () => import("@/components/node-editor"),
559
+ {
560
+ loading: () => <NodeEditorSkeleton />,
561
+ ssr: false, // Reduce server bundle
562
+ }
563
+ );
564
+ ```
565
+
566
+ **Impact**: Reduce JS bundle by 40-50% ✅
567
+
568
+ ---
569
+
570
+ ### 12. **Implement Proper Logging**
571
+
572
+ **Priority**: MEDIUM | **Time**: 15 minutes
573
+
574
+ **Current Issue**:
575
+ - Random `console.log()` statements
576
+ - No structured logging
577
+ - Hard to debug in production
578
+
579
+ **Enhance**: [lib/logger.ts](lib/logger.ts)
580
+
581
+ ```typescript
582
+ export class Logger {
583
+ static info(message: string, context?: Record<string, any>) {
584
+ console.log(
585
+ JSON.stringify({
586
+ level: "INFO",
587
+ timestamp: new Date().toISOString(),
588
+ message,
589
+ ...context,
590
+ })
591
+ );
592
+ }
593
+
594
+ static error(
595
+ message: string,
596
+ error?: Error,
597
+ context?: Record<string, any>
598
+ ) {
599
+ console.error(
600
+ JSON.stringify({
601
+ level: "ERROR",
602
+ timestamp: new Date().toISOString(),
603
+ message,
604
+ error: error?.message,
605
+ stack: error?.stack,
606
+ ...context,
607
+ })
608
+ );
609
+ }
610
+
611
+ static warn(message: string, context?: Record<string, any>) {
612
+ console.warn(
613
+ JSON.stringify({
614
+ level: "WARN",
615
+ timestamp: new Date().toISOString(),
616
+ message,
617
+ ...context,
618
+ })
619
+ );
620
+ }
621
+
622
+ static debug(message: string, context?: Record<string, any>) {
623
+ if (process.env.NODE_ENV === "development") {
624
+ console.debug(
625
+ JSON.stringify({
626
+ level: "DEBUG",
627
+ timestamp: new Date().toISOString(),
628
+ message,
629
+ ...context,
630
+ })
631
+ );
632
+ }
633
+ }
634
+ }
635
+ ```
636
+
637
+ **Usage**:
638
+
639
+ ```typescript
640
+ Logger.info("Workflow started", { workflowId, userId });
641
+ Logger.error("Email send failed", error, { businessId, templateId });
642
+ ```
643
+
644
+ **Impact**: Better observability, easier debugging ✅
645
+
646
+ ---
647
+
648
+ ## 💡 PERFORMANCE OPTIMIZATIONS
649
+
650
+ ### 13. **Add Response Compression**
651
+
652
+ **Priority**: MEDIUM | **Time**: 10 minutes
653
+
654
+ [next.config.ts](next.config.ts):
655
+
656
+ ```typescript
657
+ export default {
658
+ compress: true, // Enable gzip compression
659
+
660
+ // Add to headers
661
+ headers: async () => {
662
+ return [
663
+ {
664
+ source: "/(.*)",
665
+ headers: [
666
+ {
667
+ key: "Content-Encoding",
668
+ value: "gzip",
669
+ },
670
+ ],
671
+ },
672
+ ];
673
+ },
674
+ };
675
+ ```
676
+
677
+ **Impact**: 60-70% smaller responses ✅
678
+
679
+ ---
680
+
681
+ ### 14. **Implement Connection Pooling**
682
+
683
+ **Priority**: MEDIUM | **Time**: 20 minutes
684
+
685
+ **Current Issue**: Each request creates new DB connection
686
+
687
+ [lib/db-pool.ts](lib/db-pool.ts):
688
+
689
+ ```typescript
690
+ import { Pool } from "@neondatabase/serverless";
691
+
692
+ let pool: Pool | null = null;
693
+
694
+ export function getPool(): Pool {
695
+ if (!pool) {
696
+ pool = new Pool({
697
+ connectionString: process.env.DATABASE_URL,
698
+ max: 20, // Max connections
699
+ idleTimeoutMillis: 30000,
700
+ connectionTimeoutMillis: 2000,
701
+ });
702
+ }
703
+ return pool;
704
+ }
705
+
706
+ export async function closePool() {
707
+ if (pool) {
708
+ await pool.end();
709
+ pool = null;
710
+ }
711
+ }
712
+ ```
713
+
714
+ **Impact**: Reduce connection overhead by 80% ✅
715
+
716
+ ---
717
+
718
+ ### 15. **Optimize Workflow Execution**
719
+
720
+ **Priority**: MEDIUM | **Time**: 30 minutes
721
+
722
+ **Issues** ([lib/workflow-executor.ts](lib/workflow-executor.ts)):
723
+ - Sequential node execution (slow for parallel nodes)
724
+ - No caching of intermediate results
725
+ - Missing timeout handling
726
+
727
+ **Improvements**:
728
+
729
+ ```typescript
730
+ export class WorkflowExecutor {
731
+ private executeCache = new Map<string, any>();
732
+
733
+ async executeNode(node: Node, logs: string[]): Promise<any> {
734
+ const cacheKey = `${node.id}:${JSON.stringify(this.context)}`;
735
+
736
+ if (this.executeCache.has(cacheKey)) {
737
+ return this.executeCache.get(cacheKey);
738
+ }
739
+
740
+ // Add timeout
741
+ const timeoutPromise = new Promise((_, reject) =>
742
+ setTimeout(
743
+ () => reject(new Error("Node execution timeout")),
744
+ 30000
745
+ ) // 30s timeout
746
+ );
747
+
748
+ try {
749
+ const result = await Promise.race([
750
+ this.executeNodeLogic(node, logs),
751
+ timeoutPromise,
752
+ ]);
753
+
754
+ this.executeCache.set(cacheKey, result);
755
+ return result;
756
+ } catch (error) {
757
+ logs.push(
758
+ `❌ Node ${node.id} failed: ${
759
+ error instanceof Error ? error.message : String(error)
760
+ }`
761
+ );
762
+ throw error;
763
+ }
764
+ }
765
+
766
+ // Execute parallel nodes concurrently
767
+ async executeParallelNodes(
768
+ nodes: Node[],
769
+ logs: string[]
770
+ ): Promise<any[]> {
771
+ return Promise.all(nodes.map((node) => this.executeNode(node, logs)));
772
+ }
773
+ }
774
+ ```
775
+
776
+ **Impact**: Workflow execution up to 5x faster ✅
777
+
778
+ ---
779
+
780
+ ## 🎯 SECURITY IMPROVEMENTS
781
+
782
+ ### 16. **Implement CSRF Protection Properly**
783
+
784
+ **Priority**: HIGH | **Time**: 20 minutes
785
+
786
+ **Current Issue**: [lib/csrf.ts](lib/csrf.ts) exists but not used consistently
787
+
788
+ **Apply to all form actions** [app/actions/business.ts](app/actions/business.ts):
789
+
790
+ ```typescript
791
+ import { verifyCsrfToken } from "@/lib/csrf";
792
+
793
+ export async function updateBusiness(formData: FormData) {
794
+ const csrfToken = formData.get("_csrf");
795
+
796
+ if (!verifyCsrfToken(csrfToken as string)) {
797
+ throw new Error("CSRF token invalid");
798
+ }
799
+
800
+ // Process request...
801
+ }
802
+ ```
803
+
804
+ **In components**:
805
+
806
+ ```typescript
807
+ export function BusinessForm() {
808
+ const csrfToken = useCSRFToken();
809
+
810
+ return (
811
+ <form>
812
+ <input type="hidden" name="_csrf" value={csrfToken} />
813
+ {/* form fields */}
814
+ </form>
815
+ );
816
+ }
817
+ ```
818
+
819
+ **Impact**: Prevents CSRF attacks ✅
820
+
821
+ ---
822
+
823
+ ### 17. **Add Rate Limiting to Auth Routes**
824
+
825
+ **Priority**: HIGH | **Time**: 15 minutes
826
+
827
+ [app/api/auth/[...nextauth]/route.ts](app/api/auth/[...nextauth]/route.ts):
828
+
829
+ ```typescript
830
+ import { checkRateLimit } from "@/lib/rate-limit";
831
+
832
+ export async function POST(req: NextRequest) {
833
+ const { limited, response } = await checkRateLimit(req, "auth");
834
+ if (limited) return response;
835
+
836
+ // Continue with auth logic...
837
+ }
838
+ ```
839
+
840
+ **Impact**: Prevents brute force attacks ✅
841
+
842
+ ---
843
+
844
+ ## 🚀 FEATURE ADDITIONS
845
+
846
+ ### 18. **Add Multi-Language Support**
847
+
848
+ **Priority**: LOW | **Time**: 40 minutes
849
+
850
+ ```bash
851
+ npm install next-intl
852
+ ```
853
+
854
+ **Setup**: [app/layout.tsx](app/layout.tsx)
855
+
856
+ ```typescript
857
+ import { notFound } from "next/navigation";
858
+ import { getRequestConfig } from "next-intl/server";
859
+
860
+ export async function generateStaticParams() {
861
+ return [
862
+ { locale: "en" },
863
+ { locale: "es" },
864
+ { locale: "fr" },
865
+ ];
866
+ }
867
+
868
+ export default async function RootLayout({
869
+ children,
870
+ params: { locale },
871
+ }: {
872
+ children: React.ReactNode;
873
+ params: { locale: string };
874
+ }) {
875
+ if (!["en", "es", "fr"].includes(locale)) {
876
+ notFound();
877
+ }
878
+
879
+ return (
880
+ <html lang={locale}>
881
+ <body>{children}</body>
882
+ </html>
883
+ );
884
+ }
885
+ ```
886
+
887
+ **Impact**: Expand to international markets ✅
888
+
889
+ ---
890
+
891
+ ### 19. **Add Advanced Analytics & Metrics**
892
+
893
+ **Priority**: MEDIUM | **Time**: 50 minutes
894
+
895
+ **Create**: [lib/metrics.ts](lib/metrics.ts)
896
+
897
+ ```typescript
898
+ import { db } from "@/db";
899
+ import { emailLogs, businesses } from "@/db/schema";
900
+ import { sql, eq } from "drizzle-orm";
901
+
902
+ export async function getMetrics(userId: string, timeframe = 30) {
903
+ const days = timeframe;
904
+
905
+ return {
906
+ totalEmails: await db
907
+ .select({ count: sql<number>`count(*)` })
908
+ .from(emailLogs)
909
+ .where(eq(emailLogs.userId, userId)),
910
+
911
+ openRate: await db
912
+ .select({
913
+ rate: sql<number>`count(case when ${emailLogs.opened} then 1 end)::float / count(*) * 100`,
914
+ })
915
+ .from(emailLogs),
916
+
917
+ clickRate: await db
918
+ .select({
919
+ rate: sql<number>`count(case when ${emailLogs.clicked} then 1 end)::float / count(*) * 100`,
920
+ })
921
+ .from(emailLogs),
922
+
923
+ topBusinesses: await db
924
+ .select({
925
+ id: businesses.id,
926
+ name: businesses.name,
927
+ emailsSent: sql<number>`count(${emailLogs.id})`,
928
+ })
929
+ .from(businesses)
930
+ .innerJoin(emailLogs, eq(businesses.id, emailLogs.businessId))
931
+ .groupBy(businesses.id)
932
+ .limit(10),
933
+ };
934
+ }
935
+ ```
936
+
937
+ **Add dashboard**: [app/dashboard/analytics/page.tsx](app/dashboard/analytics/page.tsx)
938
+
939
+ **Impact**: Better business insights ✅
940
+
941
+ ---
942
+
943
+ ### 20. **Add Webhook Management UI**
944
+
945
+ **Priority**: MEDIUM | **Time**: 35 minutes
946
+
947
+ **Database**: Add webhooks table to [db/schema/index.ts](db/schema/index.ts)
948
+
949
+ ```typescript
950
+ export const webhooks = pgTable("webhooks", {
951
+ id: text("id")
952
+ .primaryKey()
953
+ .$defaultFn(() => nanoid()),
954
+ userId: text("user_id").references(() => users.id, {
955
+ onDelete: "cascade",
956
+ }),
957
+ url: text("url").notNull(),
958
+ events: text("events").array(), // ["email.sent", "workflow.completed"]
959
+ active: boolean("active").default(true),
960
+ createdAt: timestamp("created_at").defaultNow(),
961
+ });
962
+ ```
963
+
964
+ **API**: [app/api/webhooks/manage/route.ts](app/api/webhooks/manage/route.ts)
965
+
966
+ **Impact**: Enable third-party integrations ✅
967
+
968
+ ---
969
+
970
+ ### 21. **Add Workflow Templates Marketplace**
971
+
972
+ **Priority**: LOW | **Time**: 60 minutes
973
+
974
+ **Features**:
975
+ - Share workflows as templates
976
+ - Community templates
977
+ - Rating/review system
978
+ - Version control for templates
979
+
980
+ **Database Schema**:
981
+
982
+ ```typescript
983
+ export const templateMarketplace = pgTable("template_marketplace", {
984
+ id: text("id").primaryKey(),
985
+ authorId: text("author_id").references(() => users.id),
986
+ name: text("name").notNull(),
987
+ description: text("description"),
988
+ workflow: jsonb("workflow").notNull(),
989
+ category: text("category"),
990
+ rating: real("rating"),
991
+ downloads: integer("downloads").default(0),
992
+ published: boolean("published").default(false),
993
+ createdAt: timestamp("created_at").defaultNow(),
994
+ });
995
+ ```
996
+
997
+ **Impact**: Viral growth potential ✅
998
+
999
+ ---
1000
+
1001
+ ## 📊 MONITORING & OBSERVABILITY
1002
+
1003
+ ### 22. **Add Health Check Endpoint**
1004
+
1005
+ **Priority**: MEDIUM | **Time**: 20 minutes
1006
+
1007
+ [app/api/health/route.ts](app/api/health/route.ts):
1008
+
1009
+ ```typescript
1010
+ import { NextResponse } from "next/server";
1011
+ import { db } from "@/db";
1012
+ import { redis } from "@/lib/redis";
1013
+
1014
+ export async function GET() {
1015
+ const checks: Record<string, boolean> = {};
1016
+
1017
+ // Database check
1018
+ try {
1019
+ await db.query.users.findFirst({ limit: 1 });
1020
+ checks.database = true;
1021
+ } catch {
1022
+ checks.database = false;
1023
+ }
1024
+
1025
+ // Redis check
1026
+ try {
1027
+ await redis?.ping();
1028
+ checks.redis = true;
1029
+ } catch {
1030
+ checks.redis = false;
1031
+ }
1032
+
1033
+ // Gemini API check
1034
+ try {
1035
+ await fetch(
1036
+ "https://generativelanguage.googleapis.com/v1beta/models?key=" +
1037
+ process.env.GEMINI_API_KEY
1038
+ );
1039
+ checks.gemini = true;
1040
+ } catch {
1041
+ checks.gemini = false;
1042
+ }
1043
+
1044
+ const status = Object.values(checks).every((v) => v) ? 200 : 503;
1045
+
1046
+ return NextResponse.json({ status: "ok", checks }, { status });
1047
+ }
1048
+ ```
1049
+
1050
+ **Use in Kubernetes/Docker**:
1051
+
1052
+ ```yaml
1053
+ livenessProbe:
1054
+ httpGet:
1055
+ path: /api/health
1056
+ port: 7860
1057
+ initialDelaySeconds: 10
1058
+ periodSeconds: 30
1059
+ ```
1060
+
1061
+ **Impact**: Better uptime monitoring ✅
1062
+
1063
+ ---
1064
+
1065
+ ### 23. **Add Performance Monitoring**
1066
+
1067
+ **Priority**: MEDIUM | **Time**: 25 minutes
1068
+
1069
+ [lib/performance.ts](lib/performance.ts):
1070
+
1071
+ ```typescript
1072
+ export function measurePerformance<T>(
1073
+ name: string,
1074
+ fn: () => Promise<T>
1075
+ ): () => Promise<T> {
1076
+ return async () => {
1077
+ const start = performance.now();
1078
+ try {
1079
+ const result = await fn();
1080
+ const duration = performance.now() - start;
1081
+
1082
+ if (duration > 1000) {
1083
+ // Alert if > 1 second
1084
+ Logger.warn(
1085
+ `Slow operation: ${name} took ${duration}ms`
1086
+ );
1087
+ }
1088
+
1089
+ return result;
1090
+ } catch (error) {
1091
+ const duration = performance.now() - start;
1092
+ Logger.error(`Operation failed: ${name}`, error as Error, {
1093
+ duration,
1094
+ });
1095
+ throw error;
1096
+ }
1097
+ };
1098
+ }
1099
+
1100
+ // Usage:
1101
+ export async function GET(req: NextRequest) {
1102
+ return measurePerformance("getBusinesses", async () => {
1103
+ return await fetchBusinesses();
1104
+ });
1105
+ }
1106
+ ```
1107
+
1108
+ **Impact**: Identify performance bottlenecks ✅
1109
+
1110
+ ---
1111
+
1112
+ ## 🔧 CODE QUALITY IMPROVEMENTS
1113
+
1114
+ ### 24. **Add Comprehensive Testing**
1115
+
1116
+ **Priority**: MEDIUM | **Time**: 60 minutes
1117
+
1118
+ **Fix jest config** [jest.config.js](jest.config.js):
1119
+
1120
+ ```javascript
1121
+ module.exports = {
1122
+ preset: "ts-jest",
1123
+ testEnvironment: "jsdom",
1124
+ setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
1125
+ moduleNameMapper: {
1126
+ "^@/(.*)$": "<rootDir>/$1",
1127
+ "\\.(css|less|scss)$": "identity-obj-proxy",
1128
+ },
1129
+ };
1130
+ ```
1131
+
1132
+ **Add unit tests** [__tests__/api/businesses.test.ts](__tests__/api/businesses.test.ts):
1133
+
1134
+ ```typescript
1135
+ import { GET } from "@/app/api/businesses/route";
1136
+
1137
+ describe("Businesses API", () => {
1138
+ it("returns 401 without auth", async () => {
1139
+ const req = new Request("http://localhost/api/businesses");
1140
+ const res = await GET(req);
1141
+ expect(res.status).toBe(401);
1142
+ });
1143
+
1144
+ it("returns businesses for authenticated user", async () => {
1145
+ // Mock auth
1146
+ // Test with valid auth
1147
+ });
1148
+ });
1149
+ ```
1150
+
1151
+ **Add E2E tests** ([playwright.config.ts](playwright.config.ts)):
1152
+
1153
+ ```typescript
1154
+ import { test, expect } from "@playwright/test";
1155
+
1156
+ test("user can create workflow", async ({ page }) => {
1157
+ await page.goto("/dashboard/workflows");
1158
+ await page.click('button:has-text("New Workflow")');
1159
+ await page.fill('input[name="name"]', "Test Workflow");
1160
+ await page.click('button:has-text("Save")');
1161
+ await expect(page).toHaveURL("/dashboard/workflows/*");
1162
+ });
1163
+ ```
1164
+
1165
+ **Run**: `npm run test` and `npx playwright test`
1166
+
1167
+ **Impact**: Catch bugs before production ✅
1168
+
1169
+ ---
1170
+
1171
+ ### 25. **Add TypeScript Strict Mode**
1172
+
1173
+ **Priority**: MEDIUM | **Time**: 45 minutes
1174
+
1175
+ [tsconfig.json](tsconfig.json):
1176
+
1177
+ ```json
1178
+ {
1179
+ "compilerOptions": {
1180
+ "strict": true,
1181
+ "strictNullChecks": true,
1182
+ "strictFunctionTypes": true,
1183
+ "strictBindCallApply": true,
1184
+ "strictPropertyInitialization": true,
1185
+ "noImplicitAny": true,
1186
+ "noImplicitThis": true,
1187
+ "alwaysStrict": true,
1188
+ "noUnusedLocals": true,
1189
+ "noUnusedParameters": true,
1190
+ "noImplicitReturns": true
1191
+ }
1192
+ }
1193
+ ```
1194
+
1195
+ **Run**: `npm run type-check`
1196
+
1197
+ **Impact**: Catch type errors early ✅
1198
+
1199
+ ---
1200
+
1201
+ ### 26. **Improve Code Organization**
1202
+
1203
+ **Priority**: LOW | **Time**: 50 minutes
1204
+
1205
+ **Current structure issues**:
1206
+ - `lib/` is getting too large
1207
+ - No clear separation of concerns
1208
+
1209
+ **Better structure**:
1210
+
1211
+ ```
1212
+ lib/
1213
+ ├── api/
1214
+ │ ├── errors.ts
1215
+ │ ├── middleware.ts
1216
+ │ ├── validation.ts
1217
+ │ └── response.ts
1218
+ ├── auth/
1219
+ │ ├── index.ts
1220
+ │ ├── utils.ts
1221
+ │ └── csrf.ts
1222
+ ├── db/
1223
+ │ ├── index.ts
1224
+ │ ├── cache.ts
1225
+ │ └── queries.ts
1226
+ ├── services/
1227
+ │ ├── email.ts
1228
+ │ ├── workflow.ts
1229
+ │ └── scraping.ts
1230
+ ├── scrapers/
1231
+ │ ├── index.ts
1232
+ │ ├── google-maps.ts
1233
+ │ └── linkedin.ts
1234
+ ├── utils/
1235
+ │ ├── logger.ts
1236
+ │ ├── sanitize.ts
1237
+ │ └── validators.ts
1238
+ └── external/
1239
+ ├── gemini.ts
1240
+ └── redis.ts
1241
+ ```
1242
+
1243
+ **Impact**: Better maintainability ✅
1244
+
1245
+ ---
1246
+
1247
+ ## 🎓 FUTURE ROADMAP (6-12 months)
1248
+
1249
+ ### Phase 1: AI & Automation (Months 1-2)
1250
+
1251
+ - [ ] **Multi-model support**: Support Claude, GPT-4, Llama
1252
+ - [ ] **AI-powered scheduling**: Optimal send times based on analytics
1253
+ - [ ] **Smart personalization**: Dynamic content based on business data
1254
+ - [ ] **Sentiment analysis**: Detect response sentiment, auto-adjust follow-ups
1255
+
1256
+ ### Phase 2: Integrations (Months 2-3)
1257
+
1258
+ - [ ] **CRM Integration**: Salesforce, HubSpot, Pipedrive sync
1259
+ - [ ] **Calendar Sync**: Automatically schedule follow-ups
1260
+ - [ ] **Slack/Teams**: Notifications and reports
1261
+ - [ ] **Zapier**: Workflow integration platform
1262
+
1263
+ ### Phase 3: Advanced Features (Months 3-4)
1264
+
1265
+ - [ ] **A/B Testing Dashboard**: Visual test results
1266
+ - [ ] **Workflow Versioning**: Track changes, rollback
1267
+ - [ ] **Team Collaboration**: Multi-user workspace
1268
+ - [ ] **Custom Fields**: User-defined business attributes
1269
+
1270
+ ### Phase 4: Enterprise (Months 5-6)
1271
+
1272
+ - [ ] **SSO/SAML**: Enterprise authentication
1273
+ - [ ] **Advanced Permissions**: Role-based access control
1274
+ - [ ] **Audit Logging**: Compliance tracking
1275
+ - [ ] **White-label**: Reseller support
1276
+
1277
+ ### Phase 5: Scale (Months 6-12)
1278
+
1279
+ - [ ] **Microservices**: Separate scraper/email/workflow services
1280
+ - [ ] **GraphQL API**: For partners
1281
+ - [ ] **Mobile App**: iOS/Android
1282
+ - [ ] **Data Export**: CSV, PDF, JSON reports
1283
+
1284
+ ---
1285
+
1286
+ ## 📋 QUICK IMPLEMENTATION CHECKLIST
1287
+
1288
+ ### Week 1: Critical Fixes
1289
+
1290
+ - [x] Fix rate-limit exports (5 min)
1291
+ - [x] Fix Jest config (5 min)
1292
+ - [x] Add env validation (10 min)
1293
+ - [ ] Add request validation (25 min)
1294
+ - [ ] Add error middleware (20 min)
1295
+
1296
+ **Estimated**: 1 hour total
1297
+
1298
+ ### Week 2: Security
1299
+
1300
+ - [ ] Add input sanitization (20 min)
1301
+ - [ ] Implement CSRF properly (20 min)
1302
+ - [ ] Rate limit auth routes (15 min)
1303
+
1304
+ **Estimated**: 1 hour total
1305
+
1306
+ ### Week 3: Performance
1307
+
1308
+ - [ ] Optimize DB queries (30 min)
1309
+ - [ ] Add caching layer (25 min)
1310
+ - [ ] Optimize bundle size (40 min)
1311
+ - [ ] Add compression (10 min)
1312
+
1313
+ **Estimated**: 1.5 hours total
1314
+
1315
+ ### Week 4: Observability
1316
+
1317
+ - [ ] Implement proper logging (15 min)
1318
+ - [ ] Add health checks (20 min)
1319
+ - [ ] Add performance monitoring (25 min)
1320
+ - [ ] Add comprehensive tests (60 min)
1321
+
1322
+ **Estimated**: 2 hours total
1323
+
1324
+ **Grand Total**: ~5.5 hours of work for major improvements
1325
+
1326
+ ---
1327
+
1328
+ ## 🎯 PRIORITY MATRIX
1329
+
1330
+ | Priority | Category | Examples | Do First? |
1331
+ |----------|----------|----------|-----------|
1332
+ | CRITICAL | Fixes | Rate limit export, Jest config | ✅ Yes |
1333
+ | HIGH | Security | Sanitization, CSRF, rate limiting | ✅ Yes |
1334
+ | HIGH | Errors | Error middleware, validation | ✅ Yes |
1335
+ | MEDIUM | Performance | DB optimization, caching | ✅ Soon |
1336
+ | MEDIUM | Observability | Logging, health checks | ✅ Soon |
1337
+ | MEDIUM | Features | Analytics, webhooks | ⏳ Later |
1338
+ | LOW | Features | i18n, marketplace | ⏳ When time permits |
1339
+
1340
+ ---
1341
+
1342
+ ## 📖 RESOURCES & LINKS
1343
+
1344
+ ### Next.js Best Practices
1345
+
1346
+ - [Next.js Performance](https://nextjs.org/docs/app/building-your-application/optimizing)
1347
+ - [Next.js Security](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy)
1348
+
1349
+ ### Database Optimization
1350
+
1351
+ - [Drizzle ORM Docs](https://orm.drizzle.team/)
1352
+ - [PostgreSQL Performance](https://wiki.postgresql.org/wiki/Performance_Optimization)
1353
+
1354
+ ### Security
1355
+
1356
+ - [OWASP Top 10](https://owasp.org/www-project-top-ten/)
1357
+ - [CWE Top 25](https://cwe.mitre.org/top25/)
1358
+
1359
+ ### Testing
1360
+
1361
+ - [Jest Docs](https://jestjs.io/)
1362
+ - [Playwright Docs](https://playwright.dev/)
1363
+
1364
+ ---
1365
+
1366
+ ## 💬 NOTES
1367
+
1368
+ - Start with **Critical Fixes** - they prevent build errors
1369
+ - Then tackle **High Priority** items for security & stability
1370
+ - Use the **Weekly Checklist** to track progress
1371
+ - Test everything locally before production deployment
1372
+ - Monitor metrics after each change
1373
+
1374
+ **Good luck!** 🚀
QUICK_START_GUIDE.md DELETED
@@ -1,373 +0,0 @@
1
- # AutoLoop Audit - Quick Start Guide
2
-
3
- ## 📚 Document Overview
4
-
5
- This audit includes **5 comprehensive documents** totaling ~8,000 lines of analysis and recommendations.
6
-
7
- ### Documents Created
8
-
9
- 1. **AUDIT_REPORT.md** - Full system analysis (13 sections)
10
- 2. **CRITICAL_FIXES.ts** - Ready-to-implement code fixes
11
- 3. **IMPLEMENTATION_ROADMAP.md** - 4-week development plan
12
- 4. **CODE_QUALITY_GUIDE.md** - Best practices & improvements
13
- 5. **IMPLEMENTATION_CHECKLIST.md** - Step-by-step execution guide
14
- 6. **EXECUTIVE_SUMMARY.md** - High-level overview
15
-
16
- ---
17
-
18
- ## 🚀 Getting Started (30 minutes)
19
-
20
- ### Step 1: Understand the Current State
21
-
22
- **Time**: 10 minutes
23
-
24
- Read in this order:
25
-
26
- 1. First paragraph of [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
27
- 2. "What's Working Excellently" section
28
- 3. "Critical Issues Found" section
29
-
30
- **Result**: You'll understand what's good and what needs fixing.
31
-
32
- ### Step 2: Review Critical Fixes
33
-
34
- **Time**: 15 minutes
35
-
36
- 1. Open [CRITICAL_FIXES.ts](CRITICAL_FIXES.ts)
37
- 2. Read through all 7 fixes
38
- 3. Understand the code changes needed
39
- 4. Note which files to modify
40
-
41
- **Result**: You'll know exactly what code to change.
42
-
43
- ### Step 3: Choose Your Path
44
-
45
- **Time**: 5 minutes
46
-
47
- #### Path A - Fast Track (Get Working ASAP)
48
-
49
- - Just do Phase 1 from IMPLEMENTATION_CHECKLIST.md
50
- - Takes 3 hours
51
- - Gets core workflow system working
52
-
53
- #### Path B - Quality Track (Build It Right)
54
-
55
- - Do all 4 phases from IMPLEMENTATION_CHECKLIST.md
56
- - Takes 2 weeks
57
- - Gets production-ready system
58
-
59
- Pick based on your timeline needs.
60
-
61
- ---
62
-
63
- ## ⚡ Apply Critical Fixes (3 hours)
64
-
65
- ### Quick Fix Commands
66
-
67
- ```bash
68
- # 1. Create feature branch
69
- git checkout -b fix/workflow-execution
70
-
71
- # 2. Update files with code from CRITICAL_FIXES.ts
72
- # - lib/workflow-executor.ts (2 changes)
73
- # - db/schema/index.ts (add table)
74
- # - app/api/workflows/execute/route.ts (replace)
75
- # - lib/validate-env.ts (new file)
76
-
77
- # 3. Run database migration
78
- pnpm run db:generate
79
- pnpm run db:push
80
-
81
- # 4. Test the fixes
82
- pnpm run dev
83
-
84
- # 5. Test in browser
85
- # - Create workflow with email node
86
- # - Execute it
87
- # - Check database for logs
88
- # - Verify notification appears
89
-
90
- # 6. Commit changes
91
- git add .
92
- git commit -m "fix: complete workflow email execution system"
93
- git push origin fix/workflow-execution
94
- ```
95
-
96
- ### Verification Checklist
97
-
98
- - [ ] Workflow executes without errors
99
- - [ ] Email sends successfully
100
- - [ ] Logs appear in database
101
- - [ ] Notifications appear in UI
102
- - [ ] No console errors
103
- - [ ] All API endpoints respond
104
-
105
- ---
106
-
107
- ## 📖 Read Documentation in Order
108
-
109
- ### For Quick Understanding (1 hour)
110
-
111
- 1. **EXECUTIVE_SUMMARY.md** (20 min)
112
- - Status overview
113
- - What's working
114
- - Issues found
115
- - Timeline to fix
116
-
117
- 2. **CRITICAL_FIXES.ts** (30 min)
118
- - Read each fix
119
- - Understand the changes
120
- - Identify affected files
121
-
122
- 3. **IMPLEMENTATION_CHECKLIST.md** - Phase 1 (10 min)
123
- - See step-by-step what to do
124
-
125
- ### For Complete Understanding (4 hours)
126
-
127
- 1. **AUDIT_REPORT.md** (90 min)
128
- - Full analysis of each system
129
- - Issues with explanations
130
- - Recommendations
131
-
132
- 2. **IMPLEMENTATION_ROADMAP.md** (60 min)
133
- - 4-week plan
134
- - Dependencies to add
135
- - Success metrics
136
-
137
- 3. **CODE_QUALITY_GUIDE.md** (90 min)
138
- - TypeScript improvements
139
- - Error handling patterns
140
- - Testing strategies
141
- - Security best practices
142
-
143
- 4. **IMPLEMENTATION_CHECKLIST.md** (30 min)
144
- - Step-by-step execution
145
- - Testing checklist
146
- - Deployment guide
147
-
148
- ---
149
-
150
- ## 🎯 What You'll Accomplish
151
-
152
- ### After 3 Hours (Phase 1)
153
- ✅ Workflow email system fully functional
154
- ✅ Workflow execution logged to database
155
- ✅ Notifications on completion
156
- ✅ Error handling improved
157
-
158
- ### After 1 Week (Phase 1-2)
159
- ✅ Everything above, plus:
160
- ✅ Workflows auto-trigger on schedule
161
- ✅ Workflows auto-trigger on new businesses
162
- ✅ Workflow trigger management UI
163
-
164
- ### After 2 Weeks (Phases 1-3)
165
- ✅ Everything above, plus:
166
- ✅ Pre-made templates validated
167
- ✅ Email rate limiting enforced
168
- ✅ Email tracking implemented
169
- ✅ Analytics dashboard
170
- ✅ Code quality improvements
171
- ✅ Test coverage > 50%
172
-
173
- ### After 1 Month (All Phases)
174
- ✅ Production-grade system with:
175
- ✅ Full test coverage
176
- ✅ Monitoring & alerting
177
- ✅ Security hardened
178
- ✅ Performance optimized
179
- ✅ Team collaboration features
180
- ✅ CRM integrations ready
181
-
182
- ---
183
-
184
- ## 📊 Key Statistics
185
-
186
- | Metric | Value |
187
- | ------------------ | ------------------------------- |
188
- | Total Analysis | 8,000+ lines |
189
- | Code Fixes | 500 lines ready to use |
190
- | Documentation | 13 comprehensive sections |
191
- | Issues Found | 15+ with solutions |
192
- | Estimated Fix Time | 3 hours (critical) → 4 weeks (all) |
193
- | Code Quality Score | 87/100 → 95/100 |
194
- | Test Coverage | 0% → 80%+ |
195
-
196
- ---
197
-
198
- ## 🔍 Key Insights
199
-
200
- ### What's Amazing
201
-
202
- - ✅ Real production features (not mockups)
203
- - ✅ Well-architected codebase
204
- - ✅ Professional UI/UX
205
- - ✅ Proper database design
206
- - ✅ Good separation of concerns
207
-
208
- ### What Needs Work
209
-
210
- - 🟡 Workflow execution incomplete (fixable in 30 mins)
211
- - 🟡 No execution logging (fixable in 1 hour)
212
- - 🟡 Missing auto-trigger system (fixable in 4-6 hours)
213
- - 🟡 Code has some `any` types (fixable in 4 hours)
214
- - 🟡 No test coverage (fixable in 8 hours)
215
-
216
- ### Bottom Line
217
- **"This is a solid, well-built application. Just needs finishing touches to be production-ready."**
218
-
219
- ---
220
-
221
- ## 💡 My Top 3 Recommendations
222
-
223
- ### #1: Apply Critical Fixes NOW (Today)
224
- - Takes 3 hours
225
- - Unblocks core functionality
226
- - No risk
227
-
228
- ### #2: Implement Workflow Triggers (This Week)
229
- - Takes 6 hours
230
- - Enables auto-execution
231
- - Major UX improvement
232
-
233
- ### #3: Add Tests (Next Week)
234
- - Takes 8-10 hours
235
- - Gives you confidence
236
- - Prevents regressions
237
-
238
- ---
239
-
240
- ## 🆘 Need Help?
241
-
242
- ### Quick Questions
243
- - Check the relevant document index
244
- - Most questions answered in one of the 6 docs
245
-
246
- ### Code Questions
247
- - CRITICAL_FIXES.ts has exact code to use
248
- - CODE_QUALITY_GUIDE.ts has patterns/examples
249
- - AUDIT_REPORT.md explains each issue
250
-
251
- ### Architecture Questions
252
- - AUDIT_REPORT.md section 1-7
253
- - IMPLEMENTATION_ROADMAP.md for big picture
254
- - CODE_QUALITY_GUIDE.md for best practices
255
-
256
- ### Implementation Help
257
- - IMPLEMENTATION_CHECKLIST.md step-by-step
258
- - Troubleshooting section for common issues
259
-
260
- ---
261
-
262
- ## 📱 Quick Navigation
263
-
264
- ```
265
- AutoLoop Project
266
- ├── EXECUTIVE_SUMMARY.md ..................... START HERE
267
- ├── AUDIT_REPORT.md ......................... Full Analysis
268
- ├── CRITICAL_FIXES.ts ....................... Code to Copy
269
- ├── IMPLEMENTATION_ROADMAP.md ............... 4-Week Plan
270
- ├── CODE_QUALITY_GUIDE.md ................... Best Practices
271
- ├── IMPLEMENTATION_CHECKLIST.md ............ Step-by-Step
272
- └── QUICK_START_GUIDE.md .................... This File
273
- ```
274
-
275
- ---
276
-
277
- ## ✅ Next Actions Checklist
278
-
279
- Rank by importance for your timeline:
280
-
281
- ### This Week
282
- - [ ] Read EXECUTIVE_SUMMARY.md
283
- - [ ] Review CRITICAL_FIXES.ts
284
- - [ ] Apply Phase 1 fixes (3 hours)
285
- - [ ] Test fixes (1 hour)
286
- - [ ] Deploy to staging
287
-
288
- ### This Month
289
- - [ ] Implement Phase 2 (Workflow Triggers)
290
- - [ ] Add Phase 3 features
291
- - [ ] Write tests
292
- - [ ] Security audit
293
- - [ ] Deploy to production
294
-
295
- ### Next Quarter
296
- - [ ] Phase 4 Polish
297
- - [ ] Advanced features
298
- - [ ] CRM integrations
299
- - [ ] AI features
300
- - [ ] Scale to 100+ users
301
-
302
- ---
303
-
304
- ## 💰 ROI Summary
305
-
306
- | Investment | Return |
307
- |-----------|--------|
308
- | 3 hours | Fully working email automation |
309
- | 1 week | Auto-triggering workflows |
310
- | 2 weeks | Production-ready system |
311
- | 1 month | Enterprise-grade features |
312
-
313
- **Break-even**: 2-3 weeks
314
- **6-month value**: $5,000-10,000 per user
315
-
316
- ---
317
-
318
- ## 🎓 Learning Resources
319
-
320
- Each document teaches you something:
321
-
322
- 1. **AUDIT_REPORT.md** → Learn system architecture
323
- 2. **CRITICAL_FIXES.ts** → Learn what was broken
324
- 3. **IMPLEMENTATION_ROADMAP.md** → Learn feature planning
325
- 4. **CODE_QUALITY_GUIDE.md** → Learn best practices
326
- 5. **IMPLEMENTATION_CHECKLIST.md** → Learn execution
327
-
328
- **Total learning time**: 4-6 hours
329
- **Outcome**: Deep understanding of your codebase
330
-
331
- ---
332
-
333
- ## 📞 Support
334
-
335
- ### Issues Not Covered?
336
- 1. Check document table of contents
337
- 2. Search for keywords in each doc
338
- 3. Review troubleshooting section
339
- 4. Check code comments
340
-
341
- ### Want More Detail?
342
- Each document has:
343
- - Table of contents
344
- - Section summaries
345
- - Code examples
346
- - Practical checklists
347
- - Success criteria
348
-
349
- ---
350
-
351
- ## 🏁 Final Checklist
352
-
353
- Before you start:
354
- - [ ] All documents downloaded/accessible
355
- - [ ] Feature branch created
356
- - [ ] Database backed up
357
- - [ ] Time blocked (3 hours minimum)
358
- - [ ] Browser with DevTools open
359
- - [ ] Terminal/IDE ready
360
-
361
- Now you're ready to start improving AutoLoop!
362
-
363
- ---
364
-
365
- **Start with**: [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
366
- **Then read**: [CRITICAL_FIXES.ts](CRITICAL_FIXES.ts)
367
- **Then do**: [IMPLEMENTATION_CHECKLIST.md](IMPLEMENTATION_CHECKLIST.md) Phase 1
368
-
369
- **Estimated time to fully working system**: 3 hours
370
- **Estimated time to production-ready**: 2 weeks
371
-
372
- Good luck! 🚀
373
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -21,33 +21,40 @@ Key capabilities include continuous lead sourcing, smart email drafting with Goo
21
  ## 🚀 Key Features
22
 
23
  ### 🔍 Smart Lead Scraping
 
24
  - **Google Maps**: Automatically scrape businesses based on keywords and location. Extract valid emails, phone numbers, and websites.
25
  - **LinkedIn Integration**: Scrape profiles using Google Search heuristics and automate messages via Puppeteer (simulated browsing).
26
 
27
  ### 🎨 Visual Workflow Builder
 
28
  Design complex automation flows with a drag-and-drop node editor.
 
29
  - **Triggers**: Schedule-based or Event-based (e.g., "New Lead Found").
30
  - **Actions**: Send Email, Send WhatsApp, API Request, Scraper Action.
31
  - **Logic**: Conditionals, A/B Testing, Delays, Merges, Loops.
32
  - **Persistence**: Workflows save variable state between executions, enabling long-running multi-step sequences.
33
 
34
  ### 🧠 AI & Personalization
 
35
  - **Google Gemini 2.0**: Generate hyper-personalized email drafts based on prospect data and website content.
36
  - **Dynamic Variables**: Use `{{business.name}}`, `{{business.website}}`, etc., in your templates.
37
 
38
  ### 📧 Email Mastery
 
39
  - **Gmail Integration**: Send emails from your own account via OAuth.
40
  - **Delivery Tracking**: Real-time tracking of Opens and Clicks via pixel injection and link wrapping.
41
  - **Rate Limiting**: Built-in protection to prevent spam flagging (e.g., max 50 emails/day per account).
42
  - **Bounce Handling**: Automatic detection and handling of failed deliveries.
43
 
44
  ### 📊 Real-Time Analytics Dashboard
 
45
  - **Execution Monitoring**: Watch workflows run in real-time.
46
  - **Success/Failure Rates**: Identify bottlenecks in your automation.
47
  - **Quota Tracking**: Monitor your email sending limits and remaining quota.
48
  - **Export**: Download execution logs as CSV for offline analysis.
49
 
50
  ### 📱 Unified Social Suite
 
51
  - **LinkedIn**: Automate connection requests and messages.
52
  - **Instagram / Facebook**: Dashboard for scheduling Posts & Reels (Integration ready).
53
 
@@ -63,6 +70,7 @@ AutoLoop is built for reliability and scale:
63
  - **Monitoring**: Self-ping mechanism to ensure worker uptime on container platforms.
64
 
65
  ### Tech Stack
 
66
  - **Framework**: Next.js 15 (App Router)
67
  - **Language**: TypeScript
68
  - **Styling**: Tailwind CSS 4 + Shadcn UI
@@ -76,6 +84,7 @@ AutoLoop is built for reliability and scale:
76
  ## 📦 Installation & Setup
77
 
78
  ### Prerequisites
 
79
  - **Node.js 18+**
80
  - **pnpm** (recommended)
81
  - **PostgreSQL Database** (e.g., Neon)
@@ -85,38 +94,47 @@ AutoLoop is built for reliability and scale:
85
  ### Quick Start
86
 
87
  1. **Clone the repository**
88
- ```bash
89
- git clone https://github.com/yourusername/autoloop.git
90
- cd autoloop
91
- ```
 
92
 
93
  2. **Install dependencies**
94
- ```bash
95
- pnpm install
96
- ```
 
97
 
98
  3. **Configure Environment**
99
- Create a `.env` file in the root directory (see [Environment Variables](#-environment-variables)).
 
100
 
101
  4. **Setup Database**
102
- ```bash
103
- pnpm db:push
104
- # Optional: Seed sample data
105
- npx tsx scripts/seed-data.ts
106
- ```
 
107
 
108
  5. **Run Development Server**
109
- ```bash
110
- pnpm dev
111
- ```
112
- The web app will run at `http://localhost:3000`.
 
 
113
 
114
  6. **Start Background Workers** (Critical for automation)
115
- Open a separate terminal and run:
116
- ```bash
117
- pnpm worker
118
- ```
119
- *Note: This starts the dedicated worker process that handles queued jobs and scraping.*
 
 
 
120
 
121
  ---
122
 
@@ -157,9 +175,11 @@ ADMIN_EMAIL="admin@example.com"
157
  ## 🌐 Deployment
158
 
159
  ### Hugging Face Spaces / Docker
 
160
  This repo includes a `Dockerfile` and is configured for Hugging Face Spaces.
161
 
162
  **Important for Cloud Deployment:**
 
163
  1. **Worker Process**: Ensure your deployment platform runs `scripts/worker.ts`. In Docker, you might use a process manager like `pm2` or run the worker in a separate container/service.
164
  2. **Keep-Alive**: The worker includes a self-ping mechanism. Ensure `NEXT_PUBLIC_APP_URL` is set to your production URL (e.g., `https://my-app.hf.space`) so the ping hits the public route and keeps the container active.
165
 
 
21
  ## 🚀 Key Features
22
 
23
  ### 🔍 Smart Lead Scraping
24
+
25
  - **Google Maps**: Automatically scrape businesses based on keywords and location. Extract valid emails, phone numbers, and websites.
26
  - **LinkedIn Integration**: Scrape profiles using Google Search heuristics and automate messages via Puppeteer (simulated browsing).
27
 
28
  ### 🎨 Visual Workflow Builder
29
+
30
  Design complex automation flows with a drag-and-drop node editor.
31
+
32
  - **Triggers**: Schedule-based or Event-based (e.g., "New Lead Found").
33
  - **Actions**: Send Email, Send WhatsApp, API Request, Scraper Action.
34
  - **Logic**: Conditionals, A/B Testing, Delays, Merges, Loops.
35
  - **Persistence**: Workflows save variable state between executions, enabling long-running multi-step sequences.
36
 
37
  ### 🧠 AI & Personalization
38
+
39
  - **Google Gemini 2.0**: Generate hyper-personalized email drafts based on prospect data and website content.
40
  - **Dynamic Variables**: Use `{{business.name}}`, `{{business.website}}`, etc., in your templates.
41
 
42
  ### 📧 Email Mastery
43
+
44
  - **Gmail Integration**: Send emails from your own account via OAuth.
45
  - **Delivery Tracking**: Real-time tracking of Opens and Clicks via pixel injection and link wrapping.
46
  - **Rate Limiting**: Built-in protection to prevent spam flagging (e.g., max 50 emails/day per account).
47
  - **Bounce Handling**: Automatic detection and handling of failed deliveries.
48
 
49
  ### 📊 Real-Time Analytics Dashboard
50
+
51
  - **Execution Monitoring**: Watch workflows run in real-time.
52
  - **Success/Failure Rates**: Identify bottlenecks in your automation.
53
  - **Quota Tracking**: Monitor your email sending limits and remaining quota.
54
  - **Export**: Download execution logs as CSV for offline analysis.
55
 
56
  ### 📱 Unified Social Suite
57
+
58
  - **LinkedIn**: Automate connection requests and messages.
59
  - **Instagram / Facebook**: Dashboard for scheduling Posts & Reels (Integration ready).
60
 
 
70
  - **Monitoring**: Self-ping mechanism to ensure worker uptime on container platforms.
71
 
72
  ### Tech Stack
73
+
74
  - **Framework**: Next.js 15 (App Router)
75
  - **Language**: TypeScript
76
  - **Styling**: Tailwind CSS 4 + Shadcn UI
 
84
  ## 📦 Installation & Setup
85
 
86
  ### Prerequisites
87
+
88
  - **Node.js 18+**
89
  - **pnpm** (recommended)
90
  - **PostgreSQL Database** (e.g., Neon)
 
94
  ### Quick Start
95
 
96
  1. **Clone the repository**
97
+
98
+ ```bash
99
+ git clone https://github.com/yourusername/autoloop.git
100
+ cd autoloop
101
+ ```
102
 
103
  2. **Install dependencies**
104
+
105
+ ```bash
106
+ pnpm install
107
+ ```
108
 
109
  3. **Configure Environment**
110
+
111
+ Create a `.env` file in the root directory (see [Environment Variables](#-environment-variables)).
112
 
113
  4. **Setup Database**
114
+
115
+ ```bash
116
+ pnpm db:push
117
+ # Optional: Seed sample data
118
+ npx tsx scripts/seed-data.ts
119
+ ```
120
 
121
  5. **Run Development Server**
122
+
123
+ ```bash
124
+ pnpm dev
125
+ ```
126
+
127
+ The web app will run at `http://localhost:3000`.
128
 
129
  6. **Start Background Workers** (Critical for automation)
130
+
131
+ Open a separate terminal and run:
132
+
133
+ ```bash
134
+ pnpm worker
135
+ ```
136
+
137
+ *Note: This starts the dedicated worker process that handles queued jobs and scraping.*
138
 
139
  ---
140
 
 
175
  ## 🌐 Deployment
176
 
177
  ### Hugging Face Spaces / Docker
178
+
179
  This repo includes a `Dockerfile` and is configured for Hugging Face Spaces.
180
 
181
  **Important for Cloud Deployment:**
182
+
183
  1. **Worker Process**: Ensure your deployment platform runs `scripts/worker.ts`. In Docker, you might use a process manager like `pm2` or run the worker in a separate container/service.
184
  2. **Keep-Alive**: The worker includes a self-ping mechanism. Ensure `NEXT_PUBLIC_APP_URL` is set to your production URL (e.g., `https://my-app.hf.space`) so the ping hits the public route and keeps the container active.
185
 
__tests__/rate-limit.test.ts DELETED
@@ -1,10 +0,0 @@
1
- import { RateLimiter } from '@/lib/rate-limit';
2
-
3
- describe('RateLimiter', () => {
4
- it('should allow requests below limit', async () => {
5
- // This is a basic mock test since we don't have a real Redis in test env usually
6
- const result = await RateLimiter.check('test-key', { limit: 10, windowSeconds: 60 });
7
- // Without real Redis, it returns success: true by default in our implementation
8
- expect(result.success).toBe(true);
9
- });
10
- });
 
 
 
 
 
 
 
 
 
 
 
__tests__/simple.test.js DELETED
@@ -1,5 +0,0 @@
1
- describe('Simple Test', () => {
2
- it('should pass', () => {
3
- expect(1 + 1).toBe(2);
4
- });
5
- });
 
 
 
 
 
 
app/actions/business.ts CHANGED
@@ -2,15 +2,20 @@
2
 
3
  import { auth } from "@/auth";
4
  import { getEffectiveUserId } from "@/lib/auth-utils";
 
5
  import { db } from "@/db";
6
  import { businesses } from "@/db/schema";
7
  import { eq, inArray, and } from "drizzle-orm";
8
  import { revalidatePath } from "next/cache";
9
 
10
- export async function deleteBusiness(id: string) {
11
  const session = await auth();
12
  if (!session?.user?.id) throw new Error("Unauthorized");
13
 
 
 
 
 
14
  const userId = await getEffectiveUserId(session.user.id);
15
 
16
  await db.delete(businesses).where(
@@ -23,10 +28,14 @@ export async function deleteBusiness(id: string) {
23
  revalidatePath("/dashboard/businesses");
24
  }
25
 
26
- export async function bulkDeleteBusinesses(ids: string[]) {
27
  const session = await auth();
28
  if (!session?.user?.id) throw new Error("Unauthorized");
29
 
 
 
 
 
30
  const userId = await getEffectiveUserId(session.user.id);
31
 
32
  await db.delete(businesses).where(
 
2
 
3
  import { auth } from "@/auth";
4
  import { getEffectiveUserId } from "@/lib/auth-utils";
5
+ import { validateCsrfToken } from "@/lib/csrf-server";
6
  import { db } from "@/db";
7
  import { businesses } from "@/db/schema";
8
  import { eq, inArray, and } from "drizzle-orm";
9
  import { revalidatePath } from "next/cache";
10
 
11
+ export async function deleteBusiness(id: string, csrfToken: string) {
12
  const session = await auth();
13
  if (!session?.user?.id) throw new Error("Unauthorized");
14
 
15
+ // Validate CSRF token
16
+ const isValidToken = await validateCsrfToken(csrfToken);
17
+ if (!isValidToken) throw new Error("Invalid CSRF token");
18
+
19
  const userId = await getEffectiveUserId(session.user.id);
20
 
21
  await db.delete(businesses).where(
 
28
  revalidatePath("/dashboard/businesses");
29
  }
30
 
31
+ export async function bulkDeleteBusinesses(ids: string[], csrfToken: string) {
32
  const session = await auth();
33
  if (!session?.user?.id) throw new Error("Unauthorized");
34
 
35
+ // Validate CSRF token
36
+ const isValidToken = await validateCsrfToken(csrfToken);
37
+ if (!isValidToken) throw new Error("Invalid CSRF token");
38
+
39
  const userId = await getEffectiveUserId(session.user.id);
40
 
41
  await db.delete(businesses).where(
app/animations.css CHANGED
@@ -57,6 +57,30 @@
57
  animation: fade-in 1s ease-out forwards;
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  /* Stagger animations */
61
  .stagger-1 {
62
  animation-delay: 0.1s;
 
57
  animation: fade-in 1s ease-out forwards;
58
  }
59
 
60
+ /* SVG shimmer for hero illustration */
61
+ @keyframes svg-shimmer {
62
+ 0% {
63
+ transform: translateX(-10px) scale(1);
64
+ opacity: 0.9;
65
+ }
66
+ 50% {
67
+ transform: translateX(6px) scale(1.02);
68
+ opacity: 1;
69
+ }
70
+ 100% {
71
+ transform: translateX(-10px) scale(1);
72
+ opacity: 0.9;
73
+ }
74
+ }
75
+
76
+ .svg-tilt {
77
+ transition: transform 0.45s ease, opacity 0.4s ease;
78
+ }
79
+
80
+ .svg-shimmer {
81
+ animation: svg-shimmer 4s ease-in-out infinite;
82
+ }
83
+
84
  /* Stagger animations */
85
  .stagger-1 {
86
  animation-delay: 0.1s;
app/api/admin/analytics/route.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { auth } from "@/lib/auth";
3
+ import { db } from "@/db";
4
+ import { users } from "@/db/schema";
5
+ import { sql } from "drizzle-orm";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ const session = await auth();
9
+
10
+ if (!session || session.user.role !== "admin") {
11
+ return new NextResponse("Unauthorized", { status: 401 });
12
+ }
13
+
14
+ try {
15
+ // Get user growth over last 30 days
16
+ const usersByDate = await db.execute(sql`
17
+ SELECT
18
+ DATE(created_at) as date,
19
+ COUNT(*) as count
20
+ FROM ${users}
21
+ WHERE created_at > NOW() - INTERVAL '30 days'
22
+ GROUP BY DATE(created_at)
23
+ ORDER BY DATE(created_at) ASC
24
+ `);
25
+
26
+ let cumulativeUsers = 0;
27
+ const userGrowth = usersByDate.map((row: any) => {
28
+ cumulativeUsers += Number(row.count);
29
+ return {
30
+ date: new Date(row.date).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
31
+ users: cumulativeUsers
32
+ };
33
+ });
34
+
35
+ return NextResponse.json({
36
+ userGrowth,
37
+ platformUsage: [
38
+ { name: "Emails", value: 120 }, // Placeholder
39
+ { name: "Workflows", value: 50 }, // Placeholder
40
+ ]
41
+ });
42
+ } catch (error) {
43
+ console.error("Failed to fetch analytics:", error);
44
+ return new NextResponse("Internal Server Error", { status: 500 });
45
+ }
46
+ }
app/api/admin/logs/route.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { auth } from "@/lib/auth";
3
+ import { db } from "@/db";
4
+ import { workflowExecutionLogs, users } from "@/db/schema";
5
+ import { desc, eq } from "drizzle-orm";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ const session = await auth();
9
+
10
+ if (!session || session.user.role !== "admin") {
11
+ return new NextResponse("Unauthorized", { status: 401 });
12
+ }
13
+
14
+ try {
15
+ const recentLogs = await db
16
+ .select({
17
+ id: workflowExecutionLogs.id,
18
+ status: workflowExecutionLogs.status,
19
+ createdAt: workflowExecutionLogs.createdAt,
20
+ userId: workflowExecutionLogs.userId,
21
+ workflowId: workflowExecutionLogs.workflowId,
22
+ })
23
+ .from(workflowExecutionLogs)
24
+ .orderBy(desc(workflowExecutionLogs.createdAt))
25
+ .limit(20);
26
+
27
+ const enrichedLogs = await Promise.all(recentLogs.map(async (log) => {
28
+ // Need to handle null userId if that's possible in schema, assuming not null for now
29
+ // If userId is null, we can't fetch user name
30
+ let userName = "Unknown User";
31
+
32
+ if (log.userId) {
33
+ const user = await db.query.users.findFirst({
34
+ where: eq(users.id, log.userId),
35
+ columns: { name: true }
36
+ });
37
+ if (user?.name) userName = user.name;
38
+ }
39
+
40
+ return {
41
+ id: log.id,
42
+ type: log.status === "completed" ? "success" : log.status === "failed" ? "error" : "info",
43
+ message: `Workflow execution ${log.status} for ${userName}`,
44
+ timestamp: log.createdAt,
45
+ metadata: { workflowId: log.workflowId }
46
+ };
47
+ }));
48
+
49
+ return NextResponse.json({ logs: enrichedLogs });
50
+ } catch (error) {
51
+ console.error("Failed to fetch logs:", error);
52
+ return new NextResponse("Internal Server Error", { status: 500 });
53
+ }
54
+ }
app/api/admin/settings/route.ts ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { NextResponse } from "next/server";
3
+ import { auth } from "@/lib/auth";
4
+ import { db } from "@/db";
5
+ import { systemSettings } from "@/db/schema";
6
+ import { desc, eq } from "drizzle-orm";
7
+
8
+ export async function GET() {
9
+ try {
10
+ const session = await auth();
11
+ if (!session?.user || session.user.role !== "admin") {
12
+ return new NextResponse("Unauthorized", { status: 401 });
13
+ }
14
+
15
+ // specific type imports might be needed if strictly typed
16
+ // Get the most recent settings or create default
17
+ let [settings] = await db
18
+ .select()
19
+ .from(systemSettings)
20
+ .orderBy(desc(systemSettings.updatedAt))
21
+ .limit(1);
22
+
23
+ if (!settings) {
24
+ // Initialize default settings
25
+ [settings] = await db.insert(systemSettings).values({
26
+ featureFlags: {
27
+ betaFeatures: false,
28
+ registration: true,
29
+ maintenance: false,
30
+ },
31
+ emailConfig: {
32
+ dailyLimit: 10000,
33
+ userRateLimit: 50,
34
+ }
35
+ }).returning();
36
+ }
37
+
38
+ return NextResponse.json(settings);
39
+ } catch (error) {
40
+ console.error("[SETTINGS_GET]", error);
41
+ return new NextResponse("Internal Error", { status: 500 });
42
+ }
43
+ }
44
+
45
+ export async function POST(req: Request) {
46
+ try {
47
+ const session = await auth();
48
+ if (!session?.user || session.user.role !== "admin") {
49
+ return new NextResponse("Unauthorized", { status: 401 });
50
+ }
51
+
52
+ const body = await req.json();
53
+ const { featureFlags, emailConfig } = body;
54
+
55
+ // specific type imports might be needed if strictly typed
56
+ // Update existing or create new
57
+ const [existing] = await db
58
+ .select()
59
+ .from(systemSettings)
60
+ .orderBy(desc(systemSettings.updatedAt))
61
+ .limit(1);
62
+
63
+ let settings;
64
+ if (existing) {
65
+ [settings] = await db
66
+ .update(systemSettings)
67
+ .set({
68
+ featureFlags,
69
+ emailConfig,
70
+ updatedAt: new Date(),
71
+ })
72
+ .where(eq(systemSettings.id, existing.id)) // Use ID to be safe
73
+ .returning();
74
+ } else {
75
+ [settings] = await db.insert(systemSettings).values({
76
+ featureFlags,
77
+ emailConfig,
78
+ }).returning();
79
+ }
80
+
81
+ return NextResponse.json(settings);
82
+ } catch (error) {
83
+ console.error("[SETTINGS_POST]", error);
84
+ return new NextResponse("Internal Error", { status: 500 });
85
+ }
86
+ }
app/api/admin/stats/route.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { auth } from "@/lib/auth";
3
+ import { db } from "@/db";
4
+ import { users, automationWorkflows } from "@/db/schema";
5
+ import { count } from "drizzle-orm";
6
+ import { getRedis } from "@/lib/redis";
7
+
8
+ export async function GET() {
9
+ const session = await auth();
10
+
11
+ if (!session || session.user.role !== "admin") {
12
+ return new NextResponse("Unauthorized", { status: 401 });
13
+ }
14
+
15
+ try {
16
+ const redis = getRedis();
17
+
18
+ // Parallel database queries for speed
19
+ const [
20
+ totalUsersResult,
21
+ totalWorkflowsResult
22
+ ] = await Promise.all([
23
+ db.select({ count: count() }).from(users),
24
+ db.select({ count: count() }).from(automationWorkflows)
25
+ ]);
26
+
27
+ let systemHealth = "degraded";
28
+ if (redis) {
29
+ try {
30
+ const ping = await redis.ping();
31
+ if (ping === "PONG") systemHealth = "healthy";
32
+ } catch {
33
+ systemHealth = "degraded";
34
+ }
35
+ }
36
+
37
+ const totalUsers = totalUsersResult[0]?.count || 0;
38
+ const totalWorkflows = totalWorkflowsResult[0]?.count || 0;
39
+
40
+ // Mocking active users as 60% of total for now
41
+ const activeUsers = Math.floor(totalUsers * 0.6);
42
+
43
+ return NextResponse.json({
44
+ totalUsers,
45
+ userGrowth: 15, // Placeholder
46
+ activeUsers,
47
+ totalWorkflows,
48
+ systemHealth
49
+ });
50
+ } catch (error) {
51
+ console.error("Failed to fetch admin stats:", error);
52
+ return new NextResponse("Internal Server Error", { status: 500 });
53
+ }
54
+ }
app/api/admin/users/[id]/route.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { auth } from "@/lib/auth";
3
+ import { db } from "@/db";
4
+ import { users } from "@/db/schema";
5
+ import { eq } from "drizzle-orm";
6
+
7
+ export async function PATCH(
8
+ request: NextRequest,
9
+ { params }: { params: Promise<{ id: string }> }
10
+ ) {
11
+ const session = await auth();
12
+ const { id } = await params;
13
+
14
+ if (!session || session.user.role !== "admin") {
15
+ return new NextResponse("Unauthorized", { status: 401 });
16
+ }
17
+
18
+ try {
19
+ const body = await request.json();
20
+ const { role } = body;
21
+
22
+ if (id === session.user.id && (role && role !== "admin")) {
23
+ return new NextResponse("Cannot downgrade your own role", { status: 403 });
24
+ }
25
+
26
+ if (role) {
27
+ await db.update(users).set({ role }).where(eq(users.id, id));
28
+ }
29
+
30
+ return NextResponse.json({ success: true, message: "User updated successfully" });
31
+ } catch (error) {
32
+ console.error("Failed to update user:", error);
33
+ return new NextResponse("Internal Server Error", { status: 500 });
34
+ }
35
+ }
app/api/admin/users/route.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { auth } from "@/lib/auth";
3
+ import { db } from "@/db";
4
+ import { users } from "@/db/schema";
5
+ import { desc, ilike, or } from "drizzle-orm";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ const session = await auth();
9
+
10
+ if (!session || session.user.role !== "admin") {
11
+ return new NextResponse("Unauthorized", { status: 401 });
12
+ }
13
+
14
+ try {
15
+ const { searchParams } = new URL(request.url);
16
+ const search = searchParams.get("search") || "";
17
+ const limit = parseInt(searchParams.get("limit") || "50");
18
+ const offset = parseInt(searchParams.get("offset") || "0");
19
+
20
+ let query = db.select().from(users).limit(limit).offset(offset).orderBy(desc(users.createdAt));
21
+
22
+ if (search) {
23
+ // @ts-expect-error - Drizzle types issue with dynamic where
24
+ query = query.where(
25
+ or(
26
+ // cast to unknown to avoid lint errors if schema changes imply type mismatches temporarily
27
+ ilike(users.name, `%${search}%`),
28
+ ilike(users.email, `%${search}%`)
29
+ )
30
+ );
31
+ }
32
+
33
+ const allUsers = await query;
34
+
35
+ const adminUsers = allUsers.map(user => ({
36
+ id: user.id,
37
+ name: user.name,
38
+ email: user.email,
39
+ image: user.image,
40
+ role: user.role || "user",
41
+ status: "active",
42
+ lastActive: new Date(),
43
+ createdAt: user.createdAt,
44
+ }));
45
+
46
+ return NextResponse.json({ users: adminUsers });
47
+ } catch (error) {
48
+ console.error("Failed to fetch users:", error);
49
+ return new NextResponse("Internal Server Error", { status: 500 });
50
+ }
51
+ }
app/api/auth/route-wrapper.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Enhanced auth routes with rate limiting on sensitive endpoints
3
+ * Apply stricter limits to prevent brute force attacks
4
+ */
5
+ import { NextRequest } from "next/server";
6
+ import { handlers } from "@/lib/auth";
7
+ import { checkRateLimit } from "@/lib/rate-limit";
8
+
9
+ // Wrap the NextAuth handlers with rate limiting
10
+ const originalGET = handlers.GET;
11
+ const originalPOST = handlers.POST;
12
+
13
+ /**
14
+ * Rate-limited GET handler
15
+ */
16
+ async function GET(req: NextRequest) {
17
+ // Only rate limit on sign-in/callback flows
18
+ const pathname = req.nextUrl.pathname;
19
+
20
+ if (pathname.includes("signin") || pathname.includes("callback")) {
21
+ const { limited, response } = await checkRateLimit(req, "auth_login");
22
+ if (limited) return response!;
23
+ }
24
+
25
+ return originalGET(req);
26
+ }
27
+
28
+ /**
29
+ * Rate-limited POST handler
30
+ */
31
+ async function POST(req: NextRequest) {
32
+ // Rate limit on sign-in and sign-up
33
+ const pathname = req.nextUrl.pathname;
34
+ let rateLimitContext: "auth_login" | "auth_signup" = "auth_login";
35
+
36
+ if (pathname.includes("signin")) {
37
+ rateLimitContext = "auth_login"; // 5 attempts per minute
38
+ } else if (pathname.includes("signup")) {
39
+ rateLimitContext = "auth_signup"; // 3 attempts per 5 minutes
40
+ } else if (pathname.includes("callback")) {
41
+ rateLimitContext = "auth_login";
42
+ }
43
+
44
+ const { limited, response } = await checkRateLimit(req, rateLimitContext);
45
+ if (limited) return response!;
46
+
47
+ return originalPOST(req);
48
+ }
49
+
50
+ export { GET, POST };
app/api/businesses/route.ts CHANGED
@@ -4,12 +4,8 @@ import { db } from "@/db";
4
  import { businesses } from "@/db/schema";
5
  import { eq, and, sql, or, isNull } from "drizzle-orm";
6
  import { rateLimit } from "@/lib/rate-limit";
7
-
8
- interface SessionUser {
9
- id: string;
10
- email: string;
11
- name?: string;
12
- }
13
 
14
  export async function GET(request: Request) {
15
  try {
@@ -27,63 +23,76 @@ export async function GET(request: Request) {
27
  const keyword = searchParams.get("keyword");
28
  const page = parseInt(searchParams.get("page") || "1");
29
  const limit = parseInt(searchParams.get("limit") || "10");
30
- const offset = (page - 1) * limit;
31
-
32
- // Build where conditions
33
- const conditions = [eq(businesses.userId, userId)];
34
-
35
- if (category && category !== "all") {
36
- conditions.push(eq(businesses.category, category));
37
- }
38
-
39
- if (status && status !== "all") {
40
- if (status === "pending") {
41
- conditions.push(or(eq(businesses.emailStatus, "pending"), isNull(businesses.emailStatus))!);
42
- } else {
43
- conditions.push(eq(businesses.emailStatus, status));
44
- }
45
- }
46
-
47
- if (minRating) {
48
- conditions.push(sql`${businesses.rating} >= ${minRating}`);
49
- }
50
 
51
- if (location) {
52
- conditions.push(sql`${businesses.address} ILIKE ${`%${location}%`}`);
53
- }
54
-
55
- if (keyword) {
56
- conditions.push(
57
- or(
58
- sql`${businesses.name} ILIKE ${`%${keyword}%`}`,
59
- sql`${businesses.category} ILIKE ${`%${keyword}%`}`
60
- )!
61
- );
62
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- // Get total count
65
- const [{ count }] = await db
66
- .select({ count: sql<number>`count(*)` })
67
- .from(businesses)
68
- .where(and(...conditions));
69
-
70
- const totalPages = Math.ceil(count / limit);
71
-
72
- const results = await db
73
- .select()
74
- .from(businesses)
75
- .where(and(...conditions))
76
- .orderBy(businesses.createdAt)
77
- .limit(limit)
78
- .offset(offset);
79
-
80
- return NextResponse.json({
81
- businesses: results,
82
- page,
83
- limit,
84
- total: count,
85
- totalPages
86
- });
87
  } catch (error) {
88
  console.error("Error fetching businesses:", error);
89
  return NextResponse.json(
@@ -105,6 +114,7 @@ export async function PATCH(request: Request) {
105
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
106
  }
107
 
 
108
  const body = await request.json();
109
  const { id, ...updates } = body;
110
 
@@ -114,6 +124,9 @@ export async function PATCH(request: Request) {
114
  .where(eq(businesses.id, id))
115
  .returning();
116
 
 
 
 
117
  return NextResponse.json({ business });
118
  } catch (error) {
119
  console.error("Error updating business:", error);
@@ -131,6 +144,7 @@ export async function DELETE(request: Request) {
131
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
132
  }
133
 
 
134
  const { searchParams } = new URL(request.url);
135
  const id = searchParams.get("id");
136
 
@@ -143,6 +157,9 @@ export async function DELETE(request: Request) {
143
 
144
  await db.delete(businesses).where(eq(businesses.id, id));
145
 
 
 
 
146
  return NextResponse.json({ success: true });
147
  } catch (error) {
148
  console.error("Error deleting business:", error);
 
4
  import { businesses } from "@/db/schema";
5
  import { eq, and, sql, or, isNull } from "drizzle-orm";
6
  import { rateLimit } from "@/lib/rate-limit";
7
+ import { getCached, invalidateCache } from "@/lib/cache-manager";
8
+ import type { SessionUser } from "@/types";
 
 
 
 
9
 
10
  export async function GET(request: Request) {
11
  try {
 
23
  const keyword = searchParams.get("keyword");
24
  const page = parseInt(searchParams.get("page") || "1");
25
  const limit = parseInt(searchParams.get("limit") || "10");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ // Generate cache key from query parameters
28
+ const cacheKey = `businesses:${userId}:${category}:${status}:${minRating}:${location}:${keyword}:${page}:${limit}`;
29
+
30
+ // Try to get from cache first
31
+ const cached = await getCached(
32
+ cacheKey,
33
+ async () => {
34
+ const offset = (page - 1) * limit;
35
+
36
+ // Build where conditions
37
+ const conditions = [eq(businesses.userId, userId)];
38
+
39
+ if (category && category !== "all") {
40
+ conditions.push(eq(businesses.category, category));
41
+ }
42
+
43
+ if (status && status !== "all") {
44
+ if (status === "pending") {
45
+ conditions.push(or(eq(businesses.emailStatus, "pending"), isNull(businesses.emailStatus))!);
46
+ } else {
47
+ conditions.push(eq(businesses.emailStatus, status));
48
+ }
49
+ }
50
+
51
+ if (minRating) {
52
+ conditions.push(sql`${businesses.rating} >= ${minRating}`);
53
+ }
54
+
55
+ if (location) {
56
+ conditions.push(sql`${businesses.address} ILIKE ${`%${location}%`}`);
57
+ }
58
+
59
+ if (keyword) {
60
+ conditions.push(
61
+ or(
62
+ sql`${businesses.name} ILIKE ${`%${keyword}%`}`,
63
+ sql`${businesses.category} ILIKE ${`%${keyword}%`}`
64
+ )!
65
+ );
66
+ }
67
+
68
+ // Get total count
69
+ const [{ count }] = await db
70
+ .select({ count: sql<number>`count(*)` })
71
+ .from(businesses)
72
+ .where(and(...conditions));
73
+
74
+ const totalPages = Math.ceil(count / limit);
75
+
76
+ const results = await db
77
+ .select()
78
+ .from(businesses)
79
+ .where(and(...conditions))
80
+ .orderBy(businesses.createdAt)
81
+ .limit(limit)
82
+ .offset(offset);
83
+
84
+ return {
85
+ businesses: results,
86
+ page,
87
+ limit,
88
+ total: count,
89
+ totalPages,
90
+ };
91
+ },
92
+ 600 // Cache for 10 minutes
93
+ );
94
 
95
+ return NextResponse.json(cached);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  } catch (error) {
97
  console.error("Error fetching businesses:", error);
98
  return NextResponse.json(
 
114
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
115
  }
116
 
117
+ const userId = (session.user as SessionUser).id;
118
  const body = await request.json();
119
  const { id, ...updates } = body;
120
 
 
124
  .where(eq(businesses.id, id))
125
  .returning();
126
 
127
+ // Invalidate cache for this user's businesses
128
+ await invalidateCache(`businesses:${userId}:*`);
129
+
130
  return NextResponse.json({ business });
131
  } catch (error) {
132
  console.error("Error updating business:", error);
 
144
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
145
  }
146
 
147
+ const userId = (session.user as SessionUser).id;
148
  const { searchParams } = new URL(request.url);
149
  const id = searchParams.get("id");
150
 
 
157
 
158
  await db.delete(businesses).where(eq(businesses.id, id));
159
 
160
+ // Invalidate cache for this user's businesses
161
+ await invalidateCache(`businesses:${userId}:*`);
162
+
163
  return NextResponse.json({ success: true });
164
  } catch (error) {
165
  console.error("Error deleting business:", error);
app/api/health/route.ts CHANGED
@@ -2,46 +2,123 @@ import { NextResponse } from "next/server";
2
  import { db } from "@/db";
3
  import { sql } from "drizzle-orm";
4
  import { redis } from "@/lib/redis";
 
5
 
6
- export const dynamic = 'force-dynamic';
7
 
8
- export async function GET() {
9
- const health = {
10
- status: "ok",
11
- timestamp: new Date().toISOString(),
12
- services: {
13
- database: "unknown",
14
- redis: "unknown",
15
- }
16
- };
17
 
18
- let statusCode = 200;
 
19
 
20
  // Check Database
21
  try {
 
22
  await db.execute(sql`SELECT 1`);
23
- health.services.database = "up";
 
 
 
 
 
24
  } catch (error) {
25
- console.error("Health check - DB failed:", error);
26
- health.services.database = "down";
27
- health.status = "error";
28
- statusCode = 503;
 
29
  }
30
 
31
  // Check Redis
32
  try {
 
33
  if (redis) {
34
  await redis.ping();
35
- health.services.redis = "up";
 
 
 
 
 
36
  } else {
37
- health.services.redis = "not_configured";
 
 
 
38
  }
39
  } catch (error) {
40
- console.error("Health check - Redis failed:", error);
41
- health.services.redis = "down";
42
- health.status = "error";
43
- statusCode = 503;
 
44
  }
45
 
46
- return NextResponse.json(health, { status: statusCode });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
 
2
  import { db } from "@/db";
3
  import { sql } from "drizzle-orm";
4
  import { redis } from "@/lib/redis";
5
+ import { Logger } from "@/lib/logger";
6
 
7
+ export const dynamic = "force-dynamic";
8
 
9
+ interface HealthCheck {
10
+ status: "healthy" | "degraded" | "unhealthy";
11
+ timestamp: string;
12
+ checks: Record<string, { status: boolean; message?: string; latency?: number }>;
13
+ }
 
 
 
 
14
 
15
+ export async function GET(): Promise<NextResponse<HealthCheck>> {
16
+ const checks: HealthCheck["checks"] = {};
17
 
18
  // Check Database
19
  try {
20
+ const dbStart = performance.now();
21
  await db.execute(sql`SELECT 1`);
22
+ const dbLatency = Math.round(performance.now() - dbStart);
23
+ checks.database = {
24
+ status: true,
25
+ latency: dbLatency,
26
+ message: `Connected in ${dbLatency}ms`,
27
+ };
28
  } catch (error) {
29
+ Logger.error("Health check - DB failed", error as Error);
30
+ checks.database = {
31
+ status: false,
32
+ message: error instanceof Error ? error.message : "Connection failed",
33
+ };
34
  }
35
 
36
  // Check Redis
37
  try {
38
+ const redisStart = performance.now();
39
  if (redis) {
40
  await redis.ping();
41
+ const redisLatency = Math.round(performance.now() - redisStart);
42
+ checks.redis = {
43
+ status: true,
44
+ latency: redisLatency,
45
+ message: `Connected in ${redisLatency}ms`,
46
+ };
47
  } else {
48
+ checks.redis = {
49
+ status: false,
50
+ message: "Redis client not initialized",
51
+ };
52
  }
53
  } catch (error) {
54
+ Logger.error("Health check - Redis failed", error as Error);
55
+ checks.redis = {
56
+ status: false,
57
+ message: error instanceof Error ? error.message : "Connection failed",
58
+ };
59
  }
60
 
61
+ // Check Gemini API (optional)
62
+ try {
63
+ const geminiStart = performance.now();
64
+ const response = await fetch(
65
+ `https://generativelanguage.googleapis.com/v1beta/models?key=${process.env.GEMINI_API_KEY}`,
66
+ { signal: AbortSignal.timeout(5000) }
67
+ );
68
+ const geminiLatency = Math.round(performance.now() - geminiStart);
69
+
70
+ checks.gemini = {
71
+ status: response.ok,
72
+ latency: geminiLatency,
73
+ message: response.ok
74
+ ? `Available in ${geminiLatency}ms`
75
+ : `API returned ${response.status}`,
76
+ };
77
+ } catch (error) {
78
+ checks.gemini = {
79
+ status: false,
80
+ message: error instanceof Error ? error.message : "Connection failed",
81
+ };
82
+ }
83
+
84
+ // Determine overall status (database is critical)
85
+ const overallStatus =
86
+ checks.database?.status === false
87
+ ? "unhealthy"
88
+ : !Object.values(checks).every((check) => check.status)
89
+ ? "degraded"
90
+ : "healthy";
91
+
92
+ const httpStatus = overallStatus === "healthy" ? 200 : 503;
93
+
94
+ const healthCheck: HealthCheck = {
95
+ status: overallStatus,
96
+ timestamp: new Date().toISOString(),
97
+ checks,
98
+ };
99
+
100
+ if (overallStatus !== "healthy") {
101
+ Logger.warn("Health check failed", {
102
+ status: overallStatus,
103
+ failedChecks: Object.entries(checks)
104
+ .filter(([, check]) => !check.status)
105
+ .map(([name]) => name),
106
+ });
107
+ }
108
+
109
+ return NextResponse.json(healthCheck, { status: httpStatus });
110
+ }
111
+
112
+ /**
113
+ * Liveness probe - checks if service is running
114
+ * For Kubernetes/Docker deployments
115
+ */
116
+ export async function HEAD(): Promise<NextResponse> {
117
+ try {
118
+ // Quick database connectivity check
119
+ await db.execute(sql`SELECT 1`);
120
+ return new NextResponse(null, { status: 200 });
121
+ } catch {
122
+ return new NextResponse(null, { status: 503 });
123
+ }
124
  }
app/api/logs/background/route.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { apiSuccess } from "@/lib/api-response-helpers";
2
+
3
+ // In-memory log storage (in production, use Redis or database)
4
+ const logs: Array<{
5
+ id: string;
6
+ timestamp: string;
7
+ level: "info" | "error" | "warn" | "success";
8
+ source: string;
9
+ message: string;
10
+ }> = [];
11
+
12
+ let logIdCounter = 0;
13
+
14
+ // Export function to add logs from other parts of the app
15
+ export function addBackgroundLog(
16
+ level: "info" | "error" | "warn" | "success",
17
+ source: string,
18
+ message: string
19
+ ) {
20
+ logs.push({
21
+ id: `log-${++logIdCounter}`,
22
+ timestamp: new Date().toISOString(),
23
+ level,
24
+ source,
25
+ message,
26
+ });
27
+
28
+ // Keep only last 500 logs
29
+ if (logs.length > 500) {
30
+ logs.shift();
31
+ }
32
+ }
33
+
34
+ export async function GET() {
35
+ return apiSuccess({ logs: logs.slice(-200) }); // Return last 200 logs
36
+ }
37
+
38
+ export async function DELETE() {
39
+ logs.length = 0;
40
+ logIdCounter = 0;
41
+ return apiSuccess({ message: "Logs cleared" });
42
+ }
app/api/notifications/[id]/route.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { auth } from "@/lib/auth";
2
+ import { apiSuccess, apiError } from "@/lib/api-response-helpers";
3
+ import { NotificationService } from "@/lib/notifications/notification-service";
4
+
5
+ export async function PATCH(
6
+ request: Request,
7
+ { params }: { params: { id: string } }
8
+ ) {
9
+ try {
10
+ const session = await auth();
11
+ if (!session?.user?.id) {
12
+ return apiError("Unauthorized", 401);
13
+ }
14
+
15
+ const { id } = params;
16
+
17
+ await NotificationService.markAsRead(id, session.user.id);
18
+
19
+ return apiSuccess({ message: "Notification marked as read" });
20
+ } catch (error) {
21
+ console.error("Error updating notification:", error);
22
+ return apiError("Failed to update notification", 500);
23
+ }
24
+ }
25
+
26
+ export async function DELETE(
27
+ request: Request,
28
+ { params }: { params: { id: string } }
29
+ ) {
30
+ try {
31
+ const session = await auth();
32
+ if (!session?.user?.id) {
33
+ return apiError("Unauthorized", 401);
34
+ }
35
+
36
+ const { id } = params;
37
+
38
+ await NotificationService.delete(id, session.user.id);
39
+
40
+ return apiSuccess({ message: "Notification deleted" });
41
+ } catch (error) {
42
+ console.error("Error deleting notification:", error);
43
+ return apiError("Failed to delete notification", 500);
44
+ }
45
+ }
app/api/notifications/actions/route.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { auth } from "@/lib/auth";
2
+ import { apiSuccess, apiError } from "@/lib/api-response-helpers";
3
+ import { NotificationService } from "@/lib/notifications/notification-service";
4
+
5
+ export async function PATCH(request: Request) {
6
+ try {
7
+ const session = await auth();
8
+ if (!session?.user?.id) {
9
+ return apiError("Unauthorized", 401);
10
+ }
11
+
12
+ const body = await request.json();
13
+ const { action, category } = body;
14
+
15
+ if (action === "mark-all-read") {
16
+ await NotificationService.markAllAsRead(session.user.id, category);
17
+ return apiSuccess({ message: "All notifications marked as read" });
18
+ }
19
+
20
+ if (action === "delete-all-read") {
21
+ await NotificationService.deleteAllRead(session.user.id);
22
+ return apiSuccess({ message: "All read notifications deleted" });
23
+ }
24
+
25
+ return apiError("Invalid action", 400);
26
+ } catch (error) {
27
+ console.error("Error updating notifications:", error);
28
+ return apiError("Failed to update notifications", 500);
29
+ }
30
+ }
app/api/notifications/preferences/route.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { auth } from "@/lib/auth";
2
+ import { apiSuccess, apiError } from "@/lib/api-response-helpers";
3
+ import { NotificationService } from "@/lib/notifications/notification-service";
4
+
5
+ export async function GET() {
6
+ try {
7
+ const session = await auth();
8
+ if (!session?.user?.id) {
9
+ return apiError("Unauthorized", 401);
10
+ }
11
+
12
+ const preferences = await NotificationService.getPreferences(session.user.id);
13
+
14
+ return apiSuccess({ preferences });
15
+ } catch (error) {
16
+ console.error("Error fetching preferences:", error);
17
+ return apiError("Failed to fetch preferences", 500);
18
+ }
19
+ }
20
+
21
+ export async function PATCH(request: Request) {
22
+ try {
23
+ const session = await auth();
24
+ if (!session?.user?.id) {
25
+ return apiError("Unauthorized", 401);
26
+ }
27
+
28
+ const body = await request.json();
29
+ const { category, ...preferences } = body;
30
+
31
+ if (!category) {
32
+ return apiError("Category is required", 400);
33
+ }
34
+
35
+ await NotificationService.updatePreferences(
36
+ session.user.id,
37
+ category,
38
+ preferences
39
+ );
40
+
41
+ return apiSuccess({ message: "Preferences updated successfully" });
42
+ } catch (error) {
43
+ console.error("Error updating preferences:", error);
44
+ return apiError("Failed to update preferences", 500);
45
+ }
46
+ }
app/api/notifications/route.ts CHANGED
@@ -1,77 +1,62 @@
1
- import { NextResponse } from "next/server";
2
  import { auth } from "@/lib/auth";
3
- import { db } from "@/db";
4
- import { notifications, users } from "@/db/schema";
5
- import { eq, desc } from "drizzle-orm";
6
 
7
- export async function GET() {
8
  try {
9
  const session = await auth();
10
- if (!session?.user?.email) {
11
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
12
  }
13
 
14
- // Get user ID from DB
15
- const user = await db.query.users.findFirst({
16
- where: eq(users.email, session.user.email)
17
- });
 
18
 
19
- if (!user) {
20
- return NextResponse.json({ error: "User not found" }, { status: 404 });
21
- }
 
 
22
 
23
- const userNotifications = await db
24
- .select()
25
- .from(notifications)
26
- .where(eq(notifications.userId, user.id))
27
- .orderBy(desc(notifications.createdAt))
28
- .limit(50);
29
 
30
- return NextResponse.json({ notifications: userNotifications });
31
  } catch (error) {
32
  console.error("Error fetching notifications:", error);
33
- return NextResponse.json(
34
- { error: "Failed to fetch notifications" },
35
- { status: 500 }
36
- );
37
  }
38
  }
39
 
40
  export async function POST(request: Request) {
41
  try {
42
  const session = await auth();
43
- if (!session?.user?.email) {
44
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
45
  }
46
 
47
- // Get user ID from DB
48
- const user = await db.query.users.findFirst({
49
- where: eq(users.email, session.user.email)
50
- });
51
 
52
- if (!user) {
53
- return NextResponse.json({ error: "User not found" }, { status: 404 });
54
  }
55
 
56
- const { title, message, type } = await request.json();
57
-
58
- const [newNotification] = await db
59
- .insert(notifications)
60
- .values({
61
- userId: user.id,
62
- title,
63
- message,
64
- type: type || "info",
65
- read: false,
66
- })
67
- .returning();
68
 
69
- return NextResponse.json({ notification: newNotification });
70
  } catch (error) {
71
  console.error("Error creating notification:", error);
72
- return NextResponse.json(
73
- { error: "Failed to create notification" },
74
- { status: 500 }
75
- );
76
  }
77
  }
 
 
1
  import { auth } from "@/lib/auth";
2
+ import { apiSuccess, apiError } from "@/lib/api-response-helpers";
3
+ import { NotificationService } from "@/lib/notifications/notification-service";
 
4
 
5
+ export async function GET(request: Request) {
6
  try {
7
  const session = await auth();
8
+ if (!session?.user?.id) {
9
+ return apiError("Unauthorized", 401);
10
  }
11
 
12
+ const { searchParams } = new URL(request.url);
13
+ const categoryParam = searchParams.get("category");
14
+ const category = categoryParam as "workflow" | "social" | "email" | "system" | "task" | undefined;
15
+ const limit = parseInt(searchParams.get("limit") || "50");
16
+ const offset = parseInt(searchParams.get("offset") || "0");
17
 
18
+ const notifications = await NotificationService.getForUser(session.user.id, {
19
+ category: category || undefined,
20
+ limit,
21
+ offset,
22
+ });
23
 
24
+ const unreadCount = await NotificationService.getUnreadCount(session.user.id);
 
 
 
 
 
25
 
26
+ return apiSuccess({ notifications, unreadCount });
27
  } catch (error) {
28
  console.error("Error fetching notifications:", error);
29
+ return apiError("Failed to fetch notifications", 500);
 
 
 
30
  }
31
  }
32
 
33
  export async function POST(request: Request) {
34
  try {
35
  const session = await auth();
36
+ if (!session?.user?.id) {
37
+ return apiError("Unauthorized", 401);
38
  }
39
 
40
+ const body = await request.json();
41
+ const { title, message, category, level, actionUrl, metadata } = body;
 
 
42
 
43
+ if (!title || !message || !category || !level) {
44
+ return apiError("Missing required fields", 400);
45
  }
46
 
47
+ const notification = await NotificationService.create({
48
+ userId: session.user.id,
49
+ title,
50
+ message,
51
+ category,
52
+ level,
53
+ actionUrl,
54
+ metadata,
55
+ });
 
 
 
56
 
57
+ return apiSuccess({ notification });
58
  } catch (error) {
59
  console.error("Error creating notification:", error);
60
+ return apiError("Failed to create notification", 500);
 
 
 
61
  }
62
  }
app/api/performance/metrics/route.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from "next/server";
2
+ import { performanceMonitor } from "@/lib/performance-monitoring";
3
+
4
+ export async function GET() {
5
+ try {
6
+ // Get summary of web vitals and API metrics
7
+ const summary = performanceMonitor.getSummary();
8
+
9
+ return NextResponse.json({
10
+ lcp: summary.lcp,
11
+ cls: summary.cls,
12
+ fid: summary.fid,
13
+ avgAPITime: summary.avgAPITime,
14
+ slowRequests: summary.slowRequests,
15
+ cachedRequests: summary.cachedRequests,
16
+ totalRequests: summary.totalRequests,
17
+ cacheHitRate: summary.cacheHitRate,
18
+ });
19
+ } catch (error) {
20
+ console.error("Failed to get performance metrics:", error);
21
+ return NextResponse.json(
22
+ { error: "Failed to fetch metrics" },
23
+ { status: 500 }
24
+ );
25
+ }
26
+ }
app/api/scraping/start/route.ts CHANGED
@@ -4,7 +4,7 @@ import { db } from "@/db";
4
  import { scrapingJobs } from "@/db/schema";
5
  import { queueScraping } from "@/lib/queue";
6
  import { rateLimit } from "@/lib/rate-limit";
7
- import { SessionUser } from "@/types";
8
  import { eq, and } from "drizzle-orm";
9
 
10
  export async function POST(request: Request) {
 
4
  import { scrapingJobs } from "@/db/schema";
5
  import { queueScraping } from "@/lib/queue";
6
  import { rateLimit } from "@/lib/rate-limit";
7
+ import type { SessionUser } from "@/types";
8
  import { eq, and } from "drizzle-orm";
9
 
10
  export async function POST(request: Request) {
app/api/settings/route.ts CHANGED
@@ -13,6 +13,9 @@ interface UpdateUserData {
13
  company?: string;
14
  website?: string;
15
  customVariables?: Record<string, string>;
 
 
 
16
  updatedAt: Date;
17
  }
18
 
@@ -40,6 +43,9 @@ export async function GET() {
40
  company: users.company,
41
  website: users.website,
42
  customVariables: users.customVariables,
 
 
 
43
  })
44
  .from(users)
45
  .where(eq(users.id, userId));
@@ -73,6 +79,8 @@ export async function GET() {
73
  company: user.company,
74
  website: user.website,
75
  customVariables: user.customVariables,
 
 
76
  },
77
  connectedAccounts: accounts,
78
  });
@@ -108,6 +116,9 @@ export async function PATCH(request: Request) {
108
  if (body.company !== undefined) updateData.company = body.company;
109
  if (body.website !== undefined) updateData.website = body.website;
110
  if (body.customVariables !== undefined) updateData.customVariables = body.customVariables;
 
 
 
111
 
112
  const [updatedUser] = await db
113
  .update(users)
 
13
  company?: string;
14
  website?: string;
15
  customVariables?: Record<string, string>;
16
+ whatsappBusinessPhone?: string;
17
+ whatsappAccessToken?: string;
18
+ whatsappVerifyToken?: string;
19
  updatedAt: Date;
20
  }
21
 
 
43
  company: users.company,
44
  website: users.website,
45
  customVariables: users.customVariables,
46
+ whatsappBusinessPhone: users.whatsappBusinessPhone,
47
+ whatsappAccessToken: users.whatsappAccessToken,
48
+ whatsappVerifyToken: users.whatsappVerifyToken,
49
  })
50
  .from(users)
51
  .where(eq(users.id, userId));
 
79
  company: user.company,
80
  website: user.website,
81
  customVariables: user.customVariables,
82
+ whatsappBusinessPhone: user.whatsappBusinessPhone,
83
+ isWhatsappConfigured: !!(user.whatsappBusinessPhone && user.whatsappAccessToken),
84
  },
85
  connectedAccounts: accounts,
86
  });
 
116
  if (body.company !== undefined) updateData.company = body.company;
117
  if (body.website !== undefined) updateData.website = body.website;
118
  if (body.customVariables !== undefined) updateData.customVariables = body.customVariables;
119
+ if (body.whatsappBusinessPhone !== undefined) updateData.whatsappBusinessPhone = body.whatsappBusinessPhone;
120
+ if (body.whatsappAccessToken !== undefined) updateData.whatsappAccessToken = body.whatsappAccessToken;
121
+ if (body.whatsappVerifyToken !== undefined) updateData.whatsappVerifyToken = body.whatsappVerifyToken;
122
 
123
  const [updatedUser] = await db
124
  .update(users)
app/api/social/automations/[id]/route.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { auth } from "@/lib/auth";
2
+ import { db } from "@/db";
3
+ import { socialAutomations } from "@/db/schema";
4
+ import { eq, and } from "drizzle-orm";
5
+ import { apiSuccess, apiError } from "@/lib/api-response-helpers";
6
+
7
+ export async function DELETE(
8
+ request: Request,
9
+ { params }: { params: { id: string } }
10
+ ) {
11
+ try {
12
+ const session = await auth();
13
+
14
+ if (!session?.user?.id) {
15
+ return apiError("Unauthorized", 401);
16
+ }
17
+
18
+ const { id } = params;
19
+
20
+ // Verify ownership before deleting
21
+ const automation = await db.query.socialAutomations.findFirst({
22
+ where: and(
23
+ eq(socialAutomations.id, id),
24
+ eq(socialAutomations.userId, session.user.id)
25
+ ),
26
+ });
27
+
28
+ if (!automation) {
29
+ return apiError("Automation not found or access denied", 404);
30
+ }
31
+
32
+ // Delete the automation
33
+ await db
34
+ .delete(socialAutomations)
35
+ .where(eq(socialAutomations.id, id));
36
+
37
+ return apiSuccess({ message: "Automation deleted successfully" });
38
+ } catch (error) {
39
+ console.error("Error deleting automation:", error);
40
+ return apiError("Failed to delete automation", 500);
41
+ }
42
+ }
app/api/social/automations/trigger/route.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * API endpoint to manually trigger social automation checks
3
+ * Useful for testing without waiting for the worker interval
4
+ */
5
+
6
+ import { NextResponse } from "next/server";
7
+ import { auth } from "@/lib/auth";
8
+ import { SessionUser } from "@/types";
9
+ import { socialAutomationWorker } from "@/lib/workers/social-automation";
10
+
11
+ export async function POST() {
12
+ try {
13
+ const session = await auth();
14
+ if (!session?.user) {
15
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16
+ }
17
+
18
+ const userId = (session.user as SessionUser).id;
19
+
20
+ // Only allow admin or authenticated users to trigger
21
+ if (!userId) {
22
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
23
+ }
24
+
25
+ console.log("🔄 Manually triggered social automation check");
26
+
27
+ // Trigger a single check cycle
28
+ const workerAny = socialAutomationWorker as unknown as {
29
+ processAutomations: () => Promise<void>;
30
+ };
31
+ await workerAny.processAutomations();
32
+
33
+ return NextResponse.json({
34
+ success: true,
35
+ message: "Social automation check triggered successfully",
36
+ });
37
+ } catch (error) {
38
+ console.error("Error triggering social automation:", error);
39
+ return NextResponse.json(
40
+ { error: error instanceof Error ? error.message : "Failed to trigger automation" },
41
+ { status: 500 }
42
+ );
43
+ }
44
+ }
45
+
46
+ export async function GET() {
47
+ try {
48
+ const session = await auth();
49
+ if (!session?.user) {
50
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
51
+ }
52
+
53
+ // Return worker status
54
+ const workerAny = socialAutomationWorker as unknown as {
55
+ isRunning: boolean;
56
+ checkIntervalMs: number;
57
+ };
58
+
59
+ return NextResponse.json({
60
+ isRunning: workerAny.isRunning || false,
61
+ checkIntervalMs: workerAny.checkIntervalMs || 60000,
62
+ status: workerAny.isRunning ? "active" : "stopped",
63
+ });
64
+ } catch (error) {
65
+ console.error("Error getting worker status:", error);
66
+ return NextResponse.json(
67
+ { error: "Failed to get worker status" },
68
+ { status: 500 }
69
+ );
70
+ }
71
+ }
app/api/social/webhooks/facebook/route.ts ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Facebook Webhook Handler
3
+ * Handles real-time webhook events from Facebook/Instagram
4
+ */
5
+
6
+ import { NextRequest, NextResponse } from "next/server";
7
+ import { db } from "@/db";
8
+ import { socialAutomations, connectedAccounts } from "@/db/schema";
9
+ import { eq } from "drizzle-orm";
10
+ import crypto from "crypto";
11
+ /**
12
+ * GET handler for webhook verification
13
+ * Facebook requires this for webhook setup
14
+ */
15
+ export async function GET(request: NextRequest) {
16
+ const searchParams = request.nextUrl.searchParams;
17
+
18
+ const mode = searchParams.get("hub.mode");
19
+ const token = searchParams.get("hub.verify_token");
20
+ const challenge = searchParams.get("hub.challenge");
21
+
22
+ // Verify token (should match the one set in Facebook App dashboard)
23
+ const VERIFY_TOKEN = process.env.FACEBOOK_WEBHOOK_VERIFY_TOKEN || "autoloop_webhook_token_2024";
24
+
25
+ if (mode === "subscribe" && token === VERIFY_TOKEN) {
26
+ console.log("✅ Webhook verified");
27
+ return new NextResponse(challenge, { status: 200 });
28
+ } else {
29
+ console.error("❌ Webhook verification failed");
30
+ return NextResponse.json({ error: "Verification failed" }, { status: 403 });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * POST handler for webhook events
36
+ * Receives real-time updates from Facebook/Instagram
37
+ */
38
+ export async function POST(request: NextRequest) {
39
+ try {
40
+ const body = await request.json();
41
+
42
+ console.log("📨 Received webhook event:", JSON.stringify(body, null, 2));
43
+
44
+ // Verify the webhook signature (recommended for production)
45
+ // const signature = request.headers.get("x-hub-signature-256");
46
+ // if (!verifySignature(body, signature)) {
47
+ // return NextResponse.json({ error: "Invalid signature" }, { status: 403 });
48
+ // }
49
+
50
+ // Process each entry in the webhook
51
+ if (body.object === "page" || body.object === "instagram") {
52
+ for (const entry of body.entry || []) {
53
+ // Handle different webhook fields
54
+ if (entry.changes) {
55
+ for (const change of entry.changes) {
56
+ await handleWebhookChange(change, entry.id);
57
+ }
58
+ }
59
+
60
+ if (entry.messaging) {
61
+ for (const message of entry.messaging) {
62
+ await handleMessagingEvent(message, entry.id);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ // Facebook expects a 200 OK response
69
+ return NextResponse.json({ success: true }, { status: 200 });
70
+ } catch (error) {
71
+ console.error("❌ Error processing webhook:", error);
72
+ // Still return 200 to prevent Facebook from retrying
73
+ return NextResponse.json({ success: false }, { status: 200 });
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Handle webhook change events (comments, posts, etc.)
79
+ */
80
+ async function handleWebhookChange(change: Record<string, unknown>, pageId: string) {
81
+ const { field, value } = change;
82
+
83
+ console.log(`📝 Webhook change: ${field}`, value);
84
+
85
+ switch (field) {
86
+ case "comments":
87
+ await handleCommentEvent(value as Record<string, unknown>, pageId);
88
+ break;
89
+ case "feed":
90
+ await handleFeedEvent(value as Record<string, unknown>, pageId);
91
+ break;
92
+ case "mentions":
93
+ await handleMentionEvent(value as Record<string, unknown>, pageId);
94
+ break;
95
+ default:
96
+ console.log(`ℹ️ Unhandled webhook field: ${field}`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Handle comment events
102
+ */
103
+ async function handleCommentEvent(value: Record<string, unknown>, pageId: string) {
104
+ const commentData = value as {
105
+ id?: string;
106
+ post_id?: string;
107
+ message?: string;
108
+ from?: { id: string; name: string };
109
+ created_time?: string;
110
+ parent_id?: string; // For comment replies
111
+ };
112
+
113
+ if (!commentData.message || !commentData.from) {
114
+ console.log("⚠️ Incomplete comment data");
115
+ return;
116
+ }
117
+
118
+ console.log(`💬 New comment: "${commentData.message.substring(0, 50)}..." by ${commentData.from.name}`);
119
+
120
+ // Find matching automations
121
+ const account = await db.query.connectedAccounts.findFirst({
122
+ where: eq(connectedAccounts.providerAccountId, pageId),
123
+ });
124
+
125
+ if (!account) {
126
+ console.log(`⚠️ No account found for page ${pageId}`);
127
+ return;
128
+ }
129
+
130
+ const automations = await db.query.socialAutomations.findMany({
131
+ where: eq(socialAutomations.connectedAccountId, account.id),
132
+ });
133
+
134
+ // Check each automation for keyword matches
135
+ for (const automation of automations) {
136
+ if (!automation.isActive) continue;
137
+
138
+ if (automation.triggerType !== "comment_keyword" && automation.triggerType !== "any_comment") {
139
+ continue;
140
+ }
141
+
142
+ // Check keywords
143
+ const keywords = automation.keywords || [];
144
+ const matchedKeyword = keywords.find(keyword =>
145
+ commentData.message!.toLowerCase().includes(keyword.toLowerCase())
146
+ );
147
+
148
+ if (matchedKeyword || automation.triggerType === "any_comment") {
149
+ console.log(`✅ Matched automation: "${automation.name}"`);
150
+
151
+ // Execute auto-reply
152
+ await executeAutoReplyToComment(
153
+ commentData.id!,
154
+ automation.responseTemplate || "Thank you for your comment!",
155
+ account.accessToken,
156
+ account.provider
157
+ );
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Handle feed events (new posts)
164
+ */
165
+ async function handleFeedEvent(value: Record<string, unknown>, pageId: string) {
166
+ console.log(`📰 Feed event for page ${pageId}`);
167
+ // Could trigger automations based on new posts
168
+ }
169
+
170
+ /**
171
+ * Handle mention events
172
+ */
173
+ async function handleMentionEvent(value: Record<string, unknown>, pageId: string) {
174
+ console.log(`@️ Mention event for page ${pageId}`);
175
+ // Could trigger automations based on mentions
176
+ }
177
+
178
+ /**
179
+ * Handle messaging events (DMs)
180
+ */
181
+ async function handleMessagingEvent(message: Record<string, unknown>, pageId: string) {
182
+ console.log(`📬 Messaging event for page ${pageId}`, message);
183
+ // Could handle DM-based automations
184
+ }
185
+
186
+ /**
187
+ * Execute auto-reply to a comment
188
+ */
189
+ async function executeAutoReplyToComment(
190
+ commentId: string,
191
+ replyText: string,
192
+ accessToken: string,
193
+ provider: string
194
+ ) {
195
+ try {
196
+ let url = "";
197
+
198
+ if (provider === "facebook") {
199
+ url = `https://graph.facebook.com/v21.0/${commentId}/comments`;
200
+ } else if (provider === "instagram") {
201
+ url = `https://graph.facebook.com/v21.0/${commentId}/replies`;
202
+ } else {
203
+ console.log(`⚠️ Platform ${provider} not supported`);
204
+ return;
205
+ }
206
+
207
+ const response = await fetch(url, {
208
+ method: "POST",
209
+ headers: { "Content-Type": "application/json" },
210
+ body: JSON.stringify({
211
+ message: replyText,
212
+ access_token: accessToken,
213
+ }),
214
+ });
215
+
216
+ const data = await response.json();
217
+
218
+ if (data.error) {
219
+ console.error("❌ Error posting reply:", data.error);
220
+ } else {
221
+ console.log(`✅ Auto-reply posted successfully`);
222
+ }
223
+ } catch (error) {
224
+ console.error("❌ Error in executeAutoReplyToComment:", error);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Verify webhook signature (optional but recommended)
230
+ * Currently unused but kept for future implementation
231
+ */
232
+ /* eslint-disable @typescript-eslint/no-unused-vars */
233
+ function verifySignature(body: unknown, signature: string | null): boolean {
234
+ if (!signature) return false;
235
+ const APP_SECRET = process.env.FACEBOOK_APP_SECRET || "";
236
+
237
+ const expectedSignature = "sha256=" + crypto
238
+ .createHmac("sha256", APP_SECRET)
239
+ .update(JSON.stringify(body))
240
+ .digest("hex");
241
+
242
+ return signature === expectedSignature;
243
+ }
244
+ /* eslint-enable @typescript-eslint/no-unused-vars */
app/api/tasks/monitor/route.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Task Monitor API
3
+ * Provides real-time updates on all background tasks
4
+ */
5
+
6
+ import { NextResponse } from 'next/server';
7
+ import { taskQueue } from '@/lib/queue/task-queue';
8
+ import { apiSuccess, withErrorHandling } from '@/lib/api-response-helpers';
9
+ import { auth } from '@/auth';
10
+
11
+ /**
12
+ * GET /api/tasks/monitor
13
+ * Get current status of all tasks
14
+ */
15
+ export const GET = withErrorHandling(async () => {
16
+ const session = await auth();
17
+ if (!session) {
18
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
19
+ }
20
+
21
+ // Get all active tasks
22
+ const activeTasks = taskQueue.getActiveTasks();
23
+
24
+ // Get statistics for all queue types
25
+ const stats = taskQueue.getAllStats();
26
+
27
+ return apiSuccess({
28
+ tasks: activeTasks.map(task => ({
29
+ id: task.id,
30
+ type: task.type,
31
+ status: task.status,
32
+ priority: task.priority,
33
+ createdAt: task.createdAt,
34
+ startedAt: task.startedAt,
35
+ data: task.data,
36
+ })),
37
+ stats,
38
+ totalActive: activeTasks.length,
39
+ timestamp: new Date().toISOString(),
40
+ });
41
+ });
app/api/workflows/[id]/route.ts CHANGED
@@ -109,7 +109,7 @@ export async function PATCH(
109
  if (Object.keys(updates).length > 1) { // updatedAt is always there
110
  await db
111
  .update(automationWorkflows)
112
- .set(updates)
113
  .where(
114
  and(
115
  eq(automationWorkflows.id, id),
 
109
  if (Object.keys(updates).length > 1) { // updatedAt is always there
110
  await db
111
  .update(automationWorkflows)
112
+ .set(updates as Record<string, unknown>)
113
  .where(
114
  and(
115
  eq(automationWorkflows.id, id),
app/api/workflows/route.ts CHANGED
@@ -6,6 +6,7 @@ import { eq, and, sql } from "drizzle-orm";
6
  import { SessionUser } from "@/types";
7
  import { apiSuccess, apiError } from "@/lib/api-response";
8
  import { ApiErrors } from "@/lib/api-errors";
 
9
 
10
  export async function GET() {
11
  try {
@@ -30,43 +31,41 @@ export async function GET() {
30
  }
31
  }
32
 
33
- // Fetch workflows
34
- const workflowsData = await db
35
- .select()
36
- .from(automationWorkflows)
37
- .where(eq(automationWorkflows.userId, queryUserId))
38
- .orderBy(automationWorkflows.createdAt);
39
-
40
- // Manually fetch and aggregate stats (Drizzle aggregation/group by can be complex with relations,
41
- // simpler to just fetch derived data or do a separate count query if volume is low.
42
- // For MVP, separate query per workflow or one big group by.
43
- // Let's do a left join aggregation.)
44
-
45
- // Actually, let's fetch basic stats separately to avoid N+1 if list is huge,
46
- // but for < 50 workflows, a loop is fine or a single complex query.
47
- // Let's stick to simple: Fetch all, then map.
48
-
49
- // We need: executionCount, lastRunAt
50
- // We can add these fields to the response object.
51
-
52
- const enrichedWorkflows = await Promise.all(workflowsData.map(async (wf) => {
53
- // Count executions
54
- const countResult = await db.execute(sql`
55
- SELECT count(*) as count, max(started_at) as last_run
56
- FROM workflow_execution_logs
57
- WHERE workflow_id = ${wf.id}
58
- `);
59
-
60
- const row = countResult.rows[0] as { count: string, last_run: string | null };
61
-
62
- return {
63
- ...wf,
64
- executionCount: Number(row.count),
65
- lastRunAt: row.last_run ? new Date(row.last_run) : null
66
- };
67
- }));
68
-
69
- return apiSuccess({ workflows: enrichedWorkflows });
70
  } catch (error) {
71
  return apiError(error);
72
  }
@@ -141,6 +140,9 @@ export async function POST(request: Request) {
141
  })
142
  .returning();
143
 
 
 
 
144
  return NextResponse.json({ workflow });
145
  } catch (error) {
146
  console.error("Error creating workflow:", error);
@@ -192,6 +194,9 @@ export async function PATCH(request: Request) {
192
  )
193
  .returning();
194
 
 
 
 
195
  return NextResponse.json({ workflow });
196
  } catch (error) {
197
  console.error("Error updating workflow:", error);
@@ -240,6 +245,9 @@ export async function DELETE(request: Request) {
240
  )
241
  );
242
 
 
 
 
243
  return NextResponse.json({ success: true });
244
  } catch (error) {
245
  console.error("Error deleting workflow:", error);
 
6
  import { SessionUser } from "@/types";
7
  import { apiSuccess, apiError } from "@/lib/api-response";
8
  import { ApiErrors } from "@/lib/api-errors";
9
+ import { getCached, invalidateCache } from "@/lib/cache-manager";
10
 
11
  export async function GET() {
12
  try {
 
31
  }
32
  }
33
 
34
+ // Cache workflows for 5 minutes
35
+ const cachedWorkflows = await getCached(
36
+ `workflows:${queryUserId}`,
37
+ async () => {
38
+ // Fetch workflows
39
+ const workflowsData = await db
40
+ .select()
41
+ .from(automationWorkflows)
42
+ .where(eq(automationWorkflows.userId, queryUserId))
43
+ .orderBy(automationWorkflows.createdAt);
44
+
45
+ // Enriched with stats
46
+ const enrichedWorkflows = await Promise.all(workflowsData.map(async (wf) => {
47
+ // Count executions
48
+ const countResult = await db.execute(sql`
49
+ SELECT count(*) as count, max(started_at) as last_run
50
+ FROM workflow_execution_logs
51
+ WHERE workflow_id = ${wf.id}
52
+ `);
53
+
54
+ const row = countResult.rows[0] as { count: string, last_run: string | null };
55
+
56
+ return {
57
+ ...wf,
58
+ executionCount: Number(row.count),
59
+ lastRunAt: row.last_run ? new Date(row.last_run) : null
60
+ };
61
+ }));
62
+
63
+ return { workflows: enrichedWorkflows };
64
+ },
65
+ 300 // 5 minutes
66
+ );
67
+
68
+ return apiSuccess(cachedWorkflows);
 
 
69
  } catch (error) {
70
  return apiError(error);
71
  }
 
140
  })
141
  .returning();
142
 
143
+ // Invalidate workflows cache for this user
144
+ await invalidateCache(`workflows:${finalUserId}`);
145
+
146
  return NextResponse.json({ workflow });
147
  } catch (error) {
148
  console.error("Error creating workflow:", error);
 
194
  )
195
  .returning();
196
 
197
+ // Invalidate workflows cache
198
+ await invalidateCache(`workflows:${finalUserId}`);
199
+
200
  return NextResponse.json({ workflow });
201
  } catch (error) {
202
  console.error("Error updating workflow:", error);
 
245
  )
246
  );
247
 
248
+ // Invalidate workflows cache
249
+ await invalidateCache(`workflows:${finalUserId}`);
250
+
251
  return NextResponse.json({ success: true });
252
  } catch (error) {
253
  console.error("Error deleting workflow:", error);
app/api/workflows/templates/route.ts CHANGED
@@ -85,8 +85,8 @@ export async function POST(request: NextRequest) {
85
  targetBusinessType: template.targetBusinessType || "General",
86
  keywords: template.keywords || [],
87
  isActive: false,
88
- nodes: nodes,
89
- edges: template.edges,
90
  });
91
 
92
  // Query the workflow back to get its ID (most recently created)
 
85
  targetBusinessType: template.targetBusinessType || "General",
86
  keywords: template.keywords || [],
87
  isActive: false,
88
+ nodes: nodes as unknown as import("@/types/social-workflow").WorkflowNode[],
89
+ edges: template.edges as unknown as import("@/types/social-workflow").WorkflowEdge[],
90
  });
91
 
92
  // Query the workflow back to get its ID (most recently created)
app/auth/signin/page.tsx CHANGED
@@ -1,73 +1,14 @@
1
  "use client";
2
 
3
- import { useState } from "react";
4
  import { signIn } from "next-auth/react";
5
  import Link from "next/link";
6
  import { Button } from "@/components/ui/button";
7
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
8
- import { Input } from "@/components/ui/input";
9
- import { Label } from "@/components/ui/label";
10
- import { Infinity, Github, ArrowLeft, Send, Lock } from "lucide-react";
11
- import { toast } from "sonner";
12
 
13
- export default function SignIn() {
14
- const [isWhatsApp, setIsWhatsApp] = useState(false);
15
- const [phoneNumber, setPhoneNumber] = useState("");
16
- const [otp, setOtp] = useState("");
17
- const [step, setStep] = useState<"phone" | "otp">("phone");
18
- const [loading, setLoading] = useState(false);
19
-
20
- const handleSendOtp = async () => {
21
- if (!phoneNumber) {
22
- toast.error("Please enter a phone number");
23
- return;
24
- }
25
- setLoading(true);
26
- try {
27
- const res = await fetch("/api/auth/otp/send", {
28
- method: "POST",
29
- headers: { "Content-Type": "application/json" },
30
- body: JSON.stringify({ phoneNumber })
31
- });
32
- const data = await res.json();
33
- if (!res.ok) throw new Error(data.error);
34
-
35
- toast.success("OTP sent to WhatsApp!");
36
- setStep("otp");
37
- } catch (error: unknown) {
38
- const msg = error instanceof Error ? error.message : String(error);
39
- toast.error(msg || "Failed to send OTP");
40
- } finally {
41
- setLoading(false);
42
- }
43
- };
44
 
45
- const handleVerifyOtp = async () => {
46
- if (!otp) {
47
- toast.error("Please enter the OTP");
48
- return;
49
- }
50
- setLoading(true);
51
- try {
52
- const res = await signIn("whatsapp-otp", {
53
- phoneNumber,
54
- code: otp,
55
- callbackUrl: "/dashboard",
56
- redirect: false
57
- });
58
 
59
- if (res?.error) {
60
- throw new Error(res.error);
61
- } else if (res?.ok) {
62
- window.location.href = "/dashboard";
63
- }
64
- } catch (error: unknown) {
65
- const msg = error instanceof Error ? error.message : String(error);
66
- toast.error(msg || "Invalid OTP or Login Failed");
67
- } finally {
68
- setLoading(false);
69
- }
70
- };
71
 
72
  return (
73
  <div className="flex min-h-screen items-center justify-center bg-[radial-gradient(ellipse_at_top,var(--tw-gradient-stops))] from-indigo-200 via-slate-100 to-indigo-100 dark:from-slate-900 dark:via-slate-900 dark:to-indigo-900">
@@ -80,59 +21,15 @@ export default function SignIn() {
80
  </div>
81
  <div className="space-y-2">
82
  <CardTitle className="text-3xl font-bold tracking-tight bg-linear-to-br from-indigo-500 to-purple-600 bg-clip-text text-transparent">
83
- {isWhatsApp ? "WhatsApp Login" : "AutoLoop"}
84
  </CardTitle>
85
  <CardDescription className="text-base font-medium text-slate-600 dark:text-slate-400">
86
- {isWhatsApp ? "Secure OTP Verification" : "Automated Cold Email Intelligence"}
87
  </CardDescription>
88
  </div>
89
  </CardHeader>
90
 
91
  <CardContent className="space-y-6 px-8 pb-8">
92
- {isWhatsApp ? (
93
- <div className="space-y-4">
94
- {step === "phone" ? (
95
- <div className="space-y-4">
96
- <div className="space-y-2 text-left">
97
- <Label>WhatsApp Number</Label>
98
- <Input
99
- placeholder="+1234567890"
100
- value={phoneNumber}
101
- onChange={(e) => setPhoneNumber(e.target.value)}
102
- />
103
- </div>
104
- <Button className="w-full" onClick={handleSendOtp} disabled={loading}>
105
- {loading ? "Sending..." : "Send OTP"} <Send className="ml-2 h-4 w-4" />
106
- </Button>
107
- </div>
108
- ) : (
109
- <div className="space-y-4">
110
- <div className="space-y-2 text-left">
111
- <Label>Enter OTP</Label>
112
- <Input
113
- placeholder="123456"
114
- value={otp}
115
- onChange={(e) => setOtp(e.target.value)}
116
- maxLength={6}
117
- className="text-center text-lg tracking-widest"
118
- />
119
- </div>
120
- <Button className="w-full" onClick={handleVerifyOtp} disabled={loading}>
121
- {loading ? "Verifying..." : "Verify & Login"} <Lock className="ml-2 h-4 w-4" />
122
- </Button>
123
- <Button variant="ghost" size="sm" onClick={() => setStep("phone")} className="w-full">
124
- Change Phone Number
125
- </Button>
126
- </div>
127
- )}
128
-
129
- <div className="pt-4 border-t">
130
- <Button variant="ghost" className="w-full" onClick={() => setIsWhatsApp(false)}>
131
- <ArrowLeft className="mr-2 h-4 w-4" /> Back to Social Login
132
- </Button>
133
- </div>
134
- </div>
135
- ) : (
136
  <div className="grid gap-4">
137
  <Button
138
  variant="outline"
@@ -175,21 +72,6 @@ export default function SignIn() {
175
  <span className="font-semibold">Continue with GitHub</span>
176
  </Button>
177
 
178
- <Button
179
- variant="outline"
180
- size="lg"
181
- className="relative h-12 border-green-200 bg-green-50 hover:bg-green-100 text-green-700 dark:border-green-900 dark:bg-green-900/20 dark:text-green-400 transition-all hover:scale-[1.02] hover:shadow-md"
182
- onClick={() => setIsWhatsApp(true)}
183
- >
184
- <div className="absolute left-4 flex h-6 w-6 items-center justify-center">
185
- {/* WhatsApp Icon */}
186
- <svg viewBox="0 0 24 24" className="h-5 w-5 fill-current">
187
- <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" />
188
- </svg>
189
- </div>
190
- <span className="font-semibold">Login with WhatsApp</span>
191
- </Button>
192
-
193
  <div className="relative">
194
  <div className="absolute inset-0 flex items-center">
195
  <span className="w-full border-t border-slate-200 dark:border-slate-800" />
@@ -206,8 +88,7 @@ export default function SignIn() {
206
  <Link href="/admin/login">Admin Access</Link>
207
  </Button>
208
  </div>
209
- </div>
210
- )}
211
 
212
  <p className="text-center text-xs text-slate-500 dark:text-slate-400 px-4 leading-relaxed">
213
  By clicking continue, you agree to our{" "}
 
1
  "use client";
2
 
 
3
  import { signIn } from "next-auth/react";
4
  import Link from "next/link";
5
  import { Button } from "@/components/ui/button";
6
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
7
+ import { Infinity, Github } from "lucide-react";
 
 
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ export default function SignIn() {
 
 
 
 
 
 
 
 
 
 
 
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  return (
14
  <div className="flex min-h-screen items-center justify-center bg-[radial-gradient(ellipse_at_top,var(--tw-gradient-stops))] from-indigo-200 via-slate-100 to-indigo-100 dark:from-slate-900 dark:via-slate-900 dark:to-indigo-900">
 
21
  </div>
22
  <div className="space-y-2">
23
  <CardTitle className="text-3xl font-bold tracking-tight bg-linear-to-br from-indigo-500 to-purple-600 bg-clip-text text-transparent">
24
+ AutoLoop
25
  </CardTitle>
26
  <CardDescription className="text-base font-medium text-slate-600 dark:text-slate-400">
27
+ Automated Cold Email Intelligence
28
  </CardDescription>
29
  </div>
30
  </CardHeader>
31
 
32
  <CardContent className="space-y-6 px-8 pb-8">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  <div className="grid gap-4">
34
  <Button
35
  variant="outline"
 
72
  <span className="font-semibold">Continue with GitHub</span>
73
  </Button>
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  <div className="relative">
76
  <div className="absolute inset-0 flex items-center">
77
  <span className="w-full border-t border-slate-200 dark:border-slate-800" />
 
88
  <Link href="/admin/login">Admin Access</Link>
89
  </Button>
90
  </div>
91
+ </div>
 
92
 
93
  <p className="text-center text-xs text-slate-500 dark:text-slate-400 px-4 leading-relaxed">
94
  By clicking continue, you agree to our{" "}
app/dashboard/admin/page.tsx ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
5
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
6
+ import { Users, Activity, BarChart3, Settings, FileText } from "lucide-react";
7
+ import { StatsOverview } from "@/components/admin/stats-overview";
8
+ import { UserManagementTable } from "@/components/admin/user-management-table";
9
+ import { UserGrowthChart } from "@/components/admin/user-growth-chart";
10
+ import { PlatformUsageChart } from "@/components/admin/platform-usage-chart";
11
+ import { ActivityLogs } from "@/components/admin/activity-logs";
12
+ import { SystemControls } from "@/components/admin/system-controls";
13
+ import { AdminStats, AdminUser, SystemEvent } from "@/types/admin";
14
+ import { useApi } from "@/hooks/use-api";
15
+
16
+ // Mock data for initial render/development
17
+ const MOCK_STATS: AdminStats = {
18
+ totalUsers: 156,
19
+ userGrowth: 12,
20
+ activeUsers: 84,
21
+ totalWorkflows: 342,
22
+ systemHealth: "healthy",
23
+ };
24
+
25
+ const MOCK_GROWTH_DATA = Array.from({ length: 30 }, (_, i) => ({
26
+ date: new Date(Date.now() - (29 - i) * 86400000).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
27
+ users: 100 + Math.floor(Math.random() * 50) + i * 2,
28
+ }));
29
+
30
+ const MOCK_USAGE_DATA = [
31
+ { name: "Emails Sent", value: 4500 },
32
+ { name: "Workflows", value: 1230 },
33
+ { name: "Scrapers", value: 890 },
34
+ { name: "API Calls", value: 15400 },
35
+ ];
36
+
37
+ const MOCK_USERS: AdminUser[] = [
38
+ { id: "1", name: "John Doe", email: "john@example.com", image: null, role: "admin", status: "active", lastActive: new Date(), createdAt: new Date("2023-01-01") },
39
+ { id: "2", name: "Jane Smith", email: "jane@company.com", image: null, role: "user", status: "active", lastActive: new Date(Date.now() - 86400000), createdAt: new Date("2023-02-15") },
40
+ { id: "3", name: "Bob Johnson", email: "bob@test.com", image: null, role: "user", status: "inactive", lastActive: null, createdAt: new Date("2023-03-10") },
41
+ { id: "4", name: "Alice Brown", email: "alice@demo.com", image: null, role: "user", status: "suspended", lastActive: new Date(Date.now() - 7 * 86400000), createdAt: new Date("2023-04-05") },
42
+ ];
43
+
44
+ const MOCK_LOGS: SystemEvent[] = [
45
+ { id: "1", type: "info", message: "User John Doe logged in", timestamp: new Date(), metadata: { ip: "192.168.1.1" } },
46
+ { id: "2", type: "success", message: "Workflow 'Lead Gen' completed successfully", timestamp: new Date(Date.now() - 3600000), metadata: { distinctId: "wf_123" } },
47
+ { id: "3", type: "warning", message: "Rate limit approached for user Jane Smith", timestamp: new Date(Date.now() - 7200000) },
48
+ { id: "4", type: "error", message: "Failed to connect to SMTP server", timestamp: new Date(Date.now() - 86400000), metadata: { retryCount: 3 } },
49
+ ];
50
+
51
+ export default function AdminDashboard() {
52
+ const [activeTab, setActiveTab] = useState("overview");
53
+ const [stats, setStats] = useState<AdminStats | null>(null);
54
+ const [users, setUsers] = useState<AdminUser[]>([]);
55
+ const [logs, setLogs] = useState<SystemEvent[]>([]);
56
+ const [dbLoading, setDbLoading] = useState(true);
57
+
58
+ const { get: getStats } = useApi<AdminStats>();
59
+ const { get: getUsers } = useApi<{ users: AdminUser[] }>();
60
+ // const { get: getLogs } = useApi<{ logs: SystemEvent[] }>();
61
+
62
+ useEffect(() => {
63
+ const fetchData = async () => {
64
+ setDbLoading(true);
65
+ try {
66
+ const [statsData, usersData] = await Promise.all([
67
+ getStats("/api/admin/stats"),
68
+ getUsers("/api/admin/users")
69
+ ]);
70
+
71
+ if (statsData) setStats(statsData);
72
+ if (usersData?.users) setUsers(usersData.users);
73
+ setLogs(MOCK_LOGS); // Keep mock logs for now until API is ready
74
+ } catch (error) {
75
+ console.error("Failed to fetch admin data", error);
76
+ } finally {
77
+ setDbLoading(false);
78
+ }
79
+ };
80
+ fetchData();
81
+ }, [getStats, getUsers]);
82
+
83
+ const handleUpdateStatus = async (userId: string, newStatus: "active" | "suspended") => {
84
+ // Optimistic update
85
+ setUsers(users.map(u => u.id === userId ? { ...u, status: newStatus } : u));
86
+
87
+ try {
88
+ const response = await fetch(`/api/admin/users/${userId}`, {
89
+ method: "PATCH",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({ status: newStatus }),
92
+ });
93
+
94
+ if (!response.ok) throw new Error("Failed to update status");
95
+ } catch (error) {
96
+ console.error("Error updating status:", error);
97
+ // Revert optimistic update
98
+ setUsers(users.map(u => u.id === userId ? { ...u, status: "active" } : u)); // Reset to active or previous state
99
+ }
100
+ };
101
+
102
+ const handleUpdateRole = async (userId: string, newRole: "user" | "admin") => {
103
+ // Optimistic update
104
+ setUsers(users.map(u => u.id === userId ? { ...u, role: newRole } : u));
105
+
106
+ try {
107
+ const response = await fetch(`/api/admin/users/${userId}`, {
108
+ method: "PATCH",
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify({ role: newRole }),
111
+ });
112
+
113
+ if (!response.ok) throw new Error("Failed to update role");
114
+ } catch (error) {
115
+ console.error("Error updating role:", error);
116
+ // Revert
117
+ setUsers(users.map(u => u.id === userId ? { ...u, role: "user" } : u));
118
+ }
119
+ };
120
+
121
+ return (
122
+ <div className="space-y-6">
123
+ <div>
124
+ <h1 className="text-3xl font-bold tracking-tight">Admin Dashboard</h1>
125
+ <p className="text-muted-foreground">
126
+ Manage users, view analytics, and control system settings
127
+ </p>
128
+ </div>
129
+
130
+ <StatsOverview stats={stats} loading={dbLoading} />
131
+
132
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
133
+ <TabsList>
134
+ <TabsTrigger value="overview" className="gap-2">
135
+ <Activity className="h-4 w-4" />
136
+ Overview
137
+ </TabsTrigger>
138
+ <TabsTrigger value="users" className="gap-2">
139
+ <Users className="h-4 w-4" />
140
+ Users
141
+ </TabsTrigger>
142
+ <TabsTrigger value="analytics" className="gap-2">
143
+ <BarChart3 className="h-4 w-4" />
144
+ Analytics
145
+ </TabsTrigger>
146
+ <TabsTrigger value="system" className="gap-2">
147
+ <Settings className="h-4 w-4" />
148
+ System
149
+ </TabsTrigger>
150
+ <TabsTrigger value="logs" className="gap-2">
151
+ <FileText className="h-4 w-4" />
152
+ Logs
153
+ </TabsTrigger>
154
+ </TabsList>
155
+
156
+ <TabsContent value="overview" className="space-y-4">
157
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
158
+ <div className="col-span-4">
159
+ <UserGrowthChart data={MOCK_GROWTH_DATA} />
160
+ </div>
161
+ <div className="col-span-3">
162
+ <PlatformUsageChart data={MOCK_USAGE_DATA} />
163
+ </div>
164
+ </div>
165
+ <ActivityLogs logs={logs.slice(0, 5)} loading={dbLoading} />
166
+ </TabsContent>
167
+
168
+ <TabsContent value="users" className="space-y-4">
169
+ <Card>
170
+ <CardHeader>
171
+ <CardTitle>User Management</CardTitle>
172
+ <CardDescription>Manage all platform users</CardDescription>
173
+ </CardHeader>
174
+ <CardContent>
175
+ <UserManagementTable
176
+ users={users}
177
+ onUpdateStatus={handleUpdateStatus}
178
+ onUpdateRole={handleUpdateRole}
179
+ />
180
+ </CardContent>
181
+ </Card>
182
+ </TabsContent>
183
+
184
+ <TabsContent value="analytics" className="space-y-4">
185
+ <div className="grid gap-4 md:grid-cols-2">
186
+ <UserGrowthChart data={MOCK_GROWTH_DATA} />
187
+ <PlatformUsageChart data={MOCK_USAGE_DATA} />
188
+ </div>
189
+ </TabsContent>
190
+
191
+ <TabsContent value="system" className="space-y-4">
192
+ <SystemControls />
193
+ </TabsContent>
194
+
195
+ <TabsContent value="logs" className="space-y-4">
196
+ <ActivityLogs logs={logs} loading={dbLoading} />
197
+ </TabsContent>
198
+ </Tabs>
199
+ </div>
200
+ );
201
+ }
app/dashboard/businesses/page.tsx CHANGED
@@ -11,6 +11,7 @@ import { bulkDeleteBusinesses } from "@/app/actions/business";
11
  import { Trash2, Search, MapPin, Star } from "lucide-react";
12
  import { toast } from "sonner";
13
  import { Input } from "@/components/ui/input";
 
14
  import {
15
  AlertDialog,
16
  AlertDialogAction,
@@ -35,6 +36,7 @@ export default function BusinessesPage() {
35
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
36
  const [isModalOpen, setIsModalOpen] = useState(false);
37
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
 
38
 
39
  // Pagination state
40
  const [currentPage, setCurrentPage] = useState(1);
@@ -57,6 +59,11 @@ export default function BusinessesPage() {
57
  const { get: getBusinessesApi, loading: loadingBusinesses } = useApi<{ businesses: Business[], totalPages: number, page: number }>();
58
  const { get: getCategoriesApi } = useApi<{ categories: string[] }>();
59
 
 
 
 
 
 
60
  // Debounce effect
61
  useEffect(() => {
62
  const timer = setTimeout(() => {
@@ -101,7 +108,7 @@ export default function BusinessesPage() {
101
 
102
  const handleConfirmDelete = async () => {
103
  try {
104
- await bulkDeleteBusinesses(selectedIds);
105
  setBusinesses(prev => prev.filter(b => !selectedIds.includes(b.id)));
106
  setSelectedIds([]);
107
  toast.success("Deleted successfully");
 
11
  import { Trash2, Search, MapPin, Star } from "lucide-react";
12
  import { toast } from "sonner";
13
  import { Input } from "@/components/ui/input";
14
+ import { generateCsrfToken } from "@/lib/csrf";
15
  import {
16
  AlertDialog,
17
  AlertDialogAction,
 
36
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
37
  const [isModalOpen, setIsModalOpen] = useState(false);
38
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
39
+ const [csrfToken, setCsrfToken] = useState("");
40
 
41
  // Pagination state
42
  const [currentPage, setCurrentPage] = useState(1);
 
59
  const { get: getBusinessesApi, loading: loadingBusinesses } = useApi<{ businesses: Business[], totalPages: number, page: number }>();
60
  const { get: getCategoriesApi } = useApi<{ categories: string[] }>();
61
 
62
+ // Generate CSRF token on mount
63
+ useEffect(() => {
64
+ setCsrfToken(generateCsrfToken());
65
+ }, []);
66
+
67
  // Debounce effect
68
  useEffect(() => {
69
  const timer = setTimeout(() => {
 
108
 
109
  const handleConfirmDelete = async () => {
110
  try {
111
+ await bulkDeleteBusinesses(selectedIds, csrfToken);
112
  setBusinesses(prev => prev.filter(b => !selectedIds.includes(b.id)));
113
  setSelectedIds([]);
114
  toast.success("Deleted successfully");
app/dashboard/page.tsx CHANGED
@@ -6,7 +6,7 @@ import { BusinessTable } from "@/components/dashboard/business-table";
6
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
7
  import { Button } from "@/components/ui/button";
8
  import { Business } from "@/types";
9
- import { Users, Mail, TrendingUp, ArrowRight } from "lucide-react";
10
  import dynamic from "next/dynamic";
11
  import { AnimatedContainer } from "@/components/animated-container";
12
  import { useApi } from "@/hooks/use-api";
@@ -18,6 +18,11 @@ const EmailChart = dynamic(() => import("@/components/dashboard/email-chart"), {
18
  ssr: false
19
  });
20
 
 
 
 
 
 
21
  interface DashboardStats {
22
  totalBusinesses: number;
23
  emailsSent: number;
@@ -32,10 +37,16 @@ interface ChartDataPoint {
32
  opened: number;
33
  }
34
 
 
 
 
 
 
35
  export default function DashboardPage() {
36
  const [businesses, setBusinesses] = useState<Business[]>([]);
37
  const [stats, setStats] = useState<DashboardStats | null>(null);
38
  const [chartData, setChartData] = useState<ChartDataPoint[]>([]);
 
39
 
40
  // API Hooks
41
  const { get: getBusinessesApi, loading: loadingBusinesses } = useApi<{ businesses: Business[] }>();
@@ -48,7 +59,23 @@ export default function DashboardPage() {
48
  getStatsApi("/api/dashboard/stats")
49
  ]);
50
 
51
- if (businessData?.businesses) setBusinesses(businessData.businesses);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  if (statsData) {
53
  setStats(statsData.stats);
54
  setChartData(statsData.chartData || []);
@@ -78,25 +105,31 @@ export default function DashboardPage() {
78
  {/* Primary Stats */}
79
  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
80
  {loadingStats || !stats ? (
81
- Array.from({ length: 4 }).map((_, i) => (
82
- <Skeleton key={i} className="h-32 w-full rounded-xl" />
83
- ))
 
 
 
84
  ) : (
85
  <>
86
  <AnimatedContainer delay={0.1}>
87
- <StatCard title="Total Leads" value={stats.totalBusinesses} icon={Users} />
88
  </AnimatedContainer>
89
  <AnimatedContainer delay={0.2}>
90
- <StatCard title="Emails Sent" value={stats.emailsSent} icon={Mail} />
91
  </AnimatedContainer>
92
  <AnimatedContainer delay={0.3}>
93
  <Card>
94
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
95
- <CardTitle className="text-sm font-medium">Daily Quota</CardTitle>
96
- <Mail className="h-4 w-4 text-muted-foreground" />
97
  </CardHeader>
98
  <CardContent>
99
  <div className="text-2xl font-bold">{stats.quotaUsed} / {stats.quotaLimit}</div>
 
 
 
100
  <div className="mt-3 h-2 w-full bg-secondary rounded-full overflow-hidden">
101
  <div
102
  className={`h-full ${stats.quotaUsed >= stats.quotaLimit ? 'bg-red-500' : 'bg-blue-500'}`}
@@ -125,16 +158,14 @@ export default function DashboardPage() {
125
  </CardContent>
126
  </Card>
127
 
128
- {/* Recent Activity / Demographics Placeholder */}
129
  <Card className="col-span-3">
130
  <CardHeader>
131
  <CardTitle>Lead Demographics</CardTitle>
132
  <CardDescription>Business types distribution</CardDescription>
133
  </CardHeader>
134
  <CardContent>
135
- <div className="h-[300px] flex items-center justify-center text-muted-foreground text-sm border-2 border-dashed rounded-lg">
136
- Coming Soon: Business Type Chart
137
- </div>
138
  </CardContent>
139
  </Card>
140
  </div>
@@ -148,12 +179,12 @@ export default function DashboardPage() {
148
  </Button>
149
  </CardHeader>
150
  <CardContent>
151
- <BusinessTable
152
  businesses={businesses.slice(0, 5)}
153
  onViewDetails={() => { }}
154
  onSendEmail={() => { }}
155
  isLoading={loadingBusinesses}
156
- />
157
  </CardContent>
158
  </Card>
159
  </div>
 
6
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
7
  import { Button } from "@/components/ui/button";
8
  import { Business } from "@/types";
9
+ import { Users, Mail, TrendingUp, ArrowRight, Activity } from "lucide-react";
10
  import dynamic from "next/dynamic";
11
  import { AnimatedContainer } from "@/components/animated-container";
12
  import { useApi } from "@/hooks/use-api";
 
18
  ssr: false
19
  });
20
 
21
+ const LeadDemographicsChart = dynamic(() => import("@/components/dashboard/lead-demographics-chart"), {
22
+ loading: () => <Skeleton className="h-[300px] w-full" />,
23
+ ssr: false
24
+ });
25
+
26
  interface DashboardStats {
27
  totalBusinesses: number;
28
  emailsSent: number;
 
37
  opened: number;
38
  }
39
 
40
+ interface DemographicData {
41
+ name: string;
42
+ value: number;
43
+ }
44
+
45
  export default function DashboardPage() {
46
  const [businesses, setBusinesses] = useState<Business[]>([]);
47
  const [stats, setStats] = useState<DashboardStats | null>(null);
48
  const [chartData, setChartData] = useState<ChartDataPoint[]>([]);
49
+ const [demographics, setDemographics] = useState<DemographicData[]>([]);
50
 
51
  // API Hooks
52
  const { get: getBusinessesApi, loading: loadingBusinesses } = useApi<{ businesses: Business[] }>();
 
59
  getStatsApi("/api/dashboard/stats")
60
  ]);
61
 
62
+ if (businessData?.businesses) {
63
+ setBusinesses(businessData.businesses);
64
+
65
+ // Calculate demographics from businesses
66
+ const typeCount: Record<string, number> = {};
67
+ businessData.businesses.forEach((b) => {
68
+ const type = b.category || "Unknown";
69
+ typeCount[type] = (typeCount[type] || 0) + 1;
70
+ });
71
+
72
+ const demoData = Object.entries(typeCount)
73
+ .map(([name, value]) => ({ name, value }))
74
+ .sort((a, b) => b.value - a.value)
75
+ .slice(0, 5); // Top 5
76
+
77
+ setDemographics(demoData);
78
+ }
79
  if (statsData) {
80
  setStats(statsData.stats);
81
  setChartData(statsData.chartData || []);
 
105
  {/* Primary Stats */}
106
  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
107
  {loadingStats || !stats ? (
108
+ <>
109
+ <Skeleton className="h-32" />
110
+ <Skeleton className="h-32" />
111
+ <Skeleton className="h-32" />
112
+ <Skeleton className="h-32" />
113
+ </>
114
  ) : (
115
  <>
116
  <AnimatedContainer delay={0.1}>
117
+ <StatCard title="Total Leads" value={stats.totalBusinesses.toString()} icon={Users} />
118
  </AnimatedContainer>
119
  <AnimatedContainer delay={0.2}>
120
+ <StatCard title="Emails Sent" value={stats.emailsSent.toString()} icon={Mail} />
121
  </AnimatedContainer>
122
  <AnimatedContainer delay={0.3}>
123
  <Card>
124
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
125
+ <CardTitle className="text-sm font-medium">Email Quota</CardTitle>
126
+ <Activity className="h-4 w-4 text-muted-foreground" />
127
  </CardHeader>
128
  <CardContent>
129
  <div className="text-2xl font-bold">{stats.quotaUsed} / {stats.quotaLimit}</div>
130
+ <p className="text-xs text-muted-foreground mt-1">
131
+ {Math.round((stats.quotaUsed / stats.quotaLimit) * 100)}% used
132
+ </p>
133
  <div className="mt-3 h-2 w-full bg-secondary rounded-full overflow-hidden">
134
  <div
135
  className={`h-full ${stats.quotaUsed >= stats.quotaLimit ? 'bg-red-500' : 'bg-blue-500'}`}
 
158
  </CardContent>
159
  </Card>
160
 
161
+ {/* Lead Demographics */}
162
  <Card className="col-span-3">
163
  <CardHeader>
164
  <CardTitle>Lead Demographics</CardTitle>
165
  <CardDescription>Business types distribution</CardDescription>
166
  </CardHeader>
167
  <CardContent>
168
+ <LeadDemographicsChart data={demographics} loading={loadingBusinesses} />
 
 
169
  </CardContent>
170
  </Card>
171
  </div>
 
179
  </Button>
180
  </CardHeader>
181
  <CardContent>
182
+ <BusinessTable
183
  businesses={businesses.slice(0, 5)}
184
  onViewDetails={() => { }}
185
  onSendEmail={() => { }}
186
  isLoading={loadingBusinesses}
187
+ />
188
  </CardContent>
189
  </Card>
190
  </div>
app/dashboard/settings/page.tsx CHANGED
@@ -29,6 +29,7 @@ import {
29
  import { Moon, Sun, LogOut, Trash2, AlertTriangle, Palette } from "lucide-react";
30
  import { MailSettings } from "@/components/mail/mail-settings";
31
  import { SocialSettings } from "@/components/settings/social-settings";
 
32
 
33
  interface StatusResponse {
34
  database: boolean;
@@ -44,7 +45,16 @@ export default function SettingsPage() {
44
  const [isSavingNotifications, setIsSavingNotifications] = useState(false);
45
 
46
  // API Hooks
47
- const { get: getSettings, patch: patchSettings, loading: settingsLoading } = useApi<{ user: UserProfile & { isGeminiKeySet: boolean, isGmailConnected: boolean, isLinkedinCookieSet: boolean }, connectedAccounts: ConnectedAccount[] }>();
 
 
 
 
 
 
 
 
 
48
  const { get: getStatus, loading: statusLoading } = useApi<StatusResponse>();
49
  const { del: deleteUserFn, loading: deletingUser } = useApi<void>();
50
  const { del: deleteDataFn, loading: deletingData } = useApi<void>();
@@ -54,7 +64,9 @@ export default function SettingsPage() {
54
  // API Key State
55
  const [geminiApiKey, setGeminiApiKey] = useState("");
56
  const [isGeminiKeySet, setIsGeminiKeySet] = useState(false);
 
57
  const [isGmailConnected, setIsGmailConnected] = useState(false);
 
58
  const [connectedAccounts, setConnectedAccounts] = useState<ConnectedAccount[]>([]);
59
 
60
  // Connection Status State
@@ -96,6 +108,10 @@ export default function SettingsPage() {
96
  }
97
  setIsGeminiKeySet(settingsData.user.isGeminiKeySet);
98
  setIsGmailConnected(settingsData.user.isGmailConnected);
 
 
 
 
99
 
100
  if (settingsData.connectedAccounts) {
101
  setConnectedAccounts(settingsData.connectedAccounts);
@@ -565,6 +581,14 @@ export default function SettingsPage() {
565
  <SocialSettings connectedAccounts={connectedAccounts} />
566
  </div>
567
 
 
 
 
 
 
 
 
 
568
  {/* Mail Settings (Gmail) */}
569
  <div className="space-y-2 pt-4 border-t">
570
  <MailSettings isConnected={isGmailConnected} email={session?.user?.email} />
 
29
  import { Moon, Sun, LogOut, Trash2, AlertTriangle, Palette } from "lucide-react";
30
  import { MailSettings } from "@/components/mail/mail-settings";
31
  import { SocialSettings } from "@/components/settings/social-settings";
32
+ import { WhatsAppSettings } from "@/components/settings/whatsapp-settings";
33
 
34
  interface StatusResponse {
35
  database: boolean;
 
45
  const [isSavingNotifications, setIsSavingNotifications] = useState(false);
46
 
47
  // API Hooks
48
+ const { get: getSettings, patch: patchSettings, loading: settingsLoading } = useApi<{
49
+ user: UserProfile & {
50
+ isGeminiKeySet: boolean,
51
+ isGmailConnected: boolean,
52
+ isLinkedinCookieSet: boolean,
53
+ whatsappBusinessPhone?: string,
54
+ isWhatsappConfigured?: boolean
55
+ },
56
+ connectedAccounts: ConnectedAccount[]
57
+ }>();
58
  const { get: getStatus, loading: statusLoading } = useApi<StatusResponse>();
59
  const { del: deleteUserFn, loading: deletingUser } = useApi<void>();
60
  const { del: deleteDataFn, loading: deletingData } = useApi<void>();
 
64
  // API Key State
65
  const [geminiApiKey, setGeminiApiKey] = useState("");
66
  const [isGeminiKeySet, setIsGeminiKeySet] = useState(false);
67
+ const [isGeminiKeySet, setIsGeminiKeySet] = useState(false);
68
  const [isGmailConnected, setIsGmailConnected] = useState(false);
69
+ const [whatsappConfig, setWhatsappConfig] = useState({ phone: "", configured: false });
70
  const [connectedAccounts, setConnectedAccounts] = useState<ConnectedAccount[]>([]);
71
 
72
  // Connection Status State
 
108
  }
109
  setIsGeminiKeySet(settingsData.user.isGeminiKeySet);
110
  setIsGmailConnected(settingsData.user.isGmailConnected);
111
+ setWhatsappConfig({
112
+ phone: settingsData.user.whatsappBusinessPhone || "",
113
+ configured: !!settingsData.user.isWhatsappConfigured
114
+ });
115
 
116
  if (settingsData.connectedAccounts) {
117
  setConnectedAccounts(settingsData.connectedAccounts);
 
581
  <SocialSettings connectedAccounts={connectedAccounts} />
582
  </div>
583
 
584
+ {/* WhatsApp Settings */}
585
+ <div className="pt-4 border-t">
586
+ <WhatsAppSettings
587
+ businessPhone={whatsappConfig.phone}
588
+ isConfigured={whatsappConfig.configured}
589
+ />
590
+ </div>
591
+
592
  {/* Mail Settings (Gmail) */}
593
  <div className="space-y-2 pt-4 border-t">
594
  <MailSettings isConnected={isGmailConnected} email={session?.user?.email} />
app/dashboard/workflows/builder/[id]/page.tsx CHANGED
@@ -132,8 +132,8 @@ export default function WorkflowBuilderPage() {
132
  </div>
133
  </div>
134
 
135
- {/* Editor Area */}
136
- <div className="flex-1 overflow-hidden h-screen">
137
  <NodeEditor
138
  initialNodes={(workflow.nodes as Node<NodeData>[]) || []}
139
  initialEdges={(workflow.edges as Edge[]) || []}
 
132
  </div>
133
  </div>
134
 
135
+ {/* Editor Area - Fullscreen Canvas */}
136
+ <div className="flex-1 overflow-hidden" style={{ height: 'calc(100vh - 100px)' }}>
137
  <NodeEditor
138
  initialNodes={(workflow.nodes as Node<NodeData>[]) || []}
139
  initialEdges={(workflow.edges as Edge[]) || []}
app/layout.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import type { Metadata } from "next";
2
  import { Inter } from "next/font/google";
3
  import "./globals.css";
 
4
  import "./cursor-styles.css";
5
  import { Providers } from "./providers";
6
  import { Toaster } from "@/components/ui/sonner";
 
1
  import type { Metadata } from "next";
2
  import { Inter } from "next/font/google";
3
  import "./globals.css";
4
+ import "./animations.css";
5
  import "./cursor-styles.css";
6
  import { Providers } from "./providers";
7
  import { Toaster } from "@/components/ui/sonner";
app/page.tsx CHANGED
@@ -8,209 +8,272 @@ import {
8
  Mail,
9
  BarChart3,
10
  CheckCircle2,
 
 
11
  } from "lucide-react";
12
 
13
  import { useSession } from "next-auth/react";
14
  import { useRouter } from "next/navigation";
15
- import { useEffect } from "react";
16
 
17
  export default function Home() {
18
  const { data: session } = useSession();
19
  const router = useRouter();
 
20
 
21
  // Redirect to dashboard if logged in
22
  useEffect(() => {
23
- if (session?.user) {
24
- router.push("/dashboard");
25
- }
26
  }, [session, router]);
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  return (
29
- <div className="flex min-h-screen flex-col">
30
- {/* Navigation */}
31
- <nav className="sticky top-0 z-50 border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60">
32
  <div className="container mx-auto flex h-16 items-center justify-between px-4">
33
- <div className="text-xl font-bold bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
34
  AutoLoop
35
  </div>
36
- <div className="flex items-center gap-4">
37
  <Link href="/auth/signin">
38
  <Button variant="ghost">Sign In</Button>
39
  </Link>
40
  <Link href="/auth/signin">
41
- <Button>Get Started</Button>
 
 
42
  </Link>
43
  </div>
44
  </div>
45
  </nav>
46
 
47
- {/* Hero Section */}
48
- <section className="relative overflow-hidden bg-linear-to-b from-blue-50 via-purple-50 to-background dark:from-blue-950/20 dark:via-purple-950/20 dark:to-background">
49
- <div className="absolute inset-0 bg-grid-black/[0.02] dark:bg-grid-white/[0.02]" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- {/* Animated background elements */}
52
- <div className="absolute top-20 left-10 h-72 w-72 animate-pulse-glow rounded-full bg-blue-400/20 blur-3xl" />
53
- <div className="absolute top-40 right-10 h-96 w-96 animate-float rounded-full bg-purple-400/20 blur-3xl" />
 
 
 
 
54
 
55
- <div className="container relative mx-auto px-4 py-24 sm:py-32">
56
- <div className="mx-auto max-w-4xl text-center">
57
- {/* Animated heading */}
58
- <h1 className="text-5xl font-bold tracking-tight sm:text-6xl lg:text-7xl animate-slide-in-up opacity-0 stagger-1">
59
- <span className="bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
60
- Automate Your Cold Email Outreach
61
- </span>
62
- <br />
63
- <span className="mt-2 block">With AI-Powered Precision</span>
64
- </h1>
65
 
66
- {/* Animated description */}
67
- <p className="mt-6 text-lg leading-8 text-muted-foreground sm:text-xl animate-slide-in-up opacity-0 stagger-2">
68
- AutoLoop helps you find,qualify, and reach out to your ideal customers automatically.
69
- Powered by AI to generate personalized emails that convert.
70
- </p>
71
 
72
- {/* Animated features list */}
73
- <div className="mt-8 grid gap-4 sm:grid-cols-3 animate-slide-in-up opacity-0 stagger-3">
74
- <div className="flex items-center justify-center gap-2 rounded-lg border bg-card p-4 backdrop-blur-xl">
75
- <div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
76
- <span className="text-sm font-medium">AI-Powered Templates</span>
77
- </div>
78
- <div className="flex items-center justify-center gap-2 rounded-lg border bg-card p-4 backdrop-blur-xl">
79
- <div className="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
80
- <span className="text-sm font-medium">Smart Lead Scraping</span>
 
 
 
81
  </div>
82
- <div className="flex items-center justify-center gap-2 rounded-lg border bg-card p-4 backdrop-blur-xl">
83
- <div className="h-2 w-2 rounded-full bg-purple-500 animate-pulse" />
84
- <span className="text-sm font-medium">Advanced Analytics</span>
85
- </div>
86
- </div>
87
-
88
- {/* Animated CTA buttons */}
89
- <div className="mt-10 flex items-center justify-center gap-4 animate-slide-in-up opacity-0 stagger-4">
90
- <Link href="/auth/signin">
91
- <Button size="lg" className="gap-2 animate-pulse-glow">
92
- Get Started Free
93
- <ArrowRight className="h-4 w-4" />
94
- </Button>
95
- </Link>
96
- <Link href="#features">
97
- <Button variant="outline" size="lg">
98
- Learn More
99
- </Button>
100
- </Link>
101
  </div>
102
-
103
- {/* Social proof */}
104
- <p className="mt-8 text-sm text-muted-foreground animate-fade-in opacity-0" style={{ animationDelay: "0.8s" }}>
105
- Trusted by <span className="font-semibold text-foreground">1,000+</span> sales teams worldwide
106
- </p>
107
  </div>
108
  </div>
109
- </section>
110
 
111
- {/* Features Section */}
112
- <section className="py-24">
113
  <div className="container mx-auto px-4">
114
- <div className="mx-auto max-w-2xl text-center">
115
- <h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
116
- Everything you need to scale outreach
117
  </h2>
118
  <p className="mt-4 text-lg text-muted-foreground">
119
- Powerful features to automate and personalize your cold email campaigns
 
120
  </p>
121
  </div>
122
 
123
- <div className="mx-auto mt-16 grid max-w-5xl gap-8 sm:grid-cols-2 lg:grid-cols-3">
124
- {/* Feature 1 */}
125
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
126
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-blue-500/10">
127
- <Zap className="h-6 w-6 text-blue-600" />
128
- </div>
129
- <h3 className="mb-2 text-xl font-semibold">Auto Scraping</h3>
130
- <p className="text-muted-foreground">
131
- Continuously scrape Google Maps for businesses matching your criteria.
132
- Fresh leads delivered daily.
133
- </p>
134
- </div>
135
 
136
- {/* Feature 2 */}
137
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
138
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-purple-500/10">
139
- <Bot className="h-6 w-6 text-purple-600" />
140
- </div>
141
- <h3 className="mb-2 text-xl font-semibold">AI Personalization</h3>
142
- <p className="text-muted-foreground">
143
- Generate personalized emails using AI. Each message is tailored to the
144
- recipient&apos;s business.
145
- </p>
146
- </div>
147
 
148
- {/* Feature 3 */}
149
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
150
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-green-500/10">
151
- <Mail className="h-6 w-6 text-green-600" />
152
- </div>
153
- <h3 className="mb-2 text-xl font-semibold">Gmail Integration</h3>
154
- <p className="text-muted-foreground">
155
- Send emails directly from your Gmail account. Track opens, clicks, and
156
- responses.
157
- </p>
158
- </div>
159
 
160
- {/* Feature 4 */}
161
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
162
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-pink-500/10">
163
- <BarChart3 className="h-6 w-6 text-pink-600" />
164
- </div>
165
- <h3 className="mb-2 text-xl font-semibold">Analytics Dashboard</h3>
166
- <p className="text-muted-foreground">
167
- Track campaign performance with detailed analytics and insights.
168
- </p>
169
- </div>
170
 
171
- {/* Feature 5 */}
172
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
173
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-yellow-500/10">
174
- <CheckCircle2 className="h-6 w-6 text-yellow-600" />
175
- </div>
176
- <h3 className="mb-2 text-xl font-semibold">Visual Workflows</h3>
177
- <p className="text-muted-foreground">
178
- Build complex automation workflows with an easy-to-use node editor.
179
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  </div>
181
 
182
- {/* Feature 6 */}
183
- <div className="group relative rounded-xl border bg-card p-8 shadow-sm transition-all hover:shadow-md stagger-item">
184
- <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-cyan-500/10">
185
- <Zap className="h-6 w-6 text-cyan-600" />
 
 
 
 
 
 
 
 
 
 
 
186
  </div>
187
- <h3 className="mb-2 text-xl font-semibold">Smart Filtering</h3>
188
- <p className="text-muted-foreground">
189
- Filter leads based on criteria like website availability, ratings, and more.
190
- </p>
191
  </div>
192
  </div>
193
  </div>
194
  </section>
195
 
196
- {/* CTA Section */}
197
- <section className="relative overflow-hidden bg-linear-to-r from-blue-600 via-purple-600 to-pink-600 py-24">
198
- <div className="container relative z-10 mx-auto px-4 text-center">
199
- <h2 className="text-4xl font-bold text-white sm:text-5xl">
200
- Ready to scale your outreach?
201
- </h2>
202
- <p className="mx-auto mt-4 max-w-2xl text-lg text-white/90">
203
- Join hundreds of businesses using AutoLoop to automate their cold email
204
- campaigns and close more deals.
205
- </p>
206
- <div className="mt-10">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  <Link href="/auth/signin">
208
- <Button
209
- size="lg"
210
- variant="outline"
211
- className="border-white bg-white/10 text-white backdrop-blur-sm hover:bg-white/20"
212
- >
213
- Get Started for Free
214
  </Button>
215
  </Link>
216
  </div>
@@ -218,78 +281,34 @@ export default function Home() {
218
  </section>
219
 
220
  {/* Footer */}
221
- <footer className="border-t bg-muted/50">
222
- <div className="container mx-auto px-4 py-12">
223
- <div className="grid gap-8 md:grid-cols-4">
224
- {/* Brand */}
225
- <div className="space-y-4">
226
- <div className="text-xl font-bold bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
227
- AutoLoop
228
- </div>
229
- <p className="text-sm text-muted-foreground">
230
- AI-powered cold email outreach automation that helps you scale your sales.
231
- </p>
232
- </div>
233
-
234
- {/* Product */}
235
- <div className="space-y-4">
236
- <h3 className="font-semibold">Product</h3>
237
- <ul className="space-y-2 text-sm">
238
- <li>
239
- <Link href="/dashboard" className="text-muted-foreground hover:text-foreground transition-colors">
240
- Dashboard
241
- </Link>
242
- </li>
243
- <li>
244
- <Link href="/#features" className="text-muted-foreground hover:text-foreground transition-colors">
245
- Features
246
- </Link>
247
- </li>
248
- </ul>
249
- </div>
250
-
251
- {/* Company */}
252
- <div className="space-y-4">
253
- <h3 className="font-semibold">Company</h3>
254
- <ul className="space-y-2 text-sm">
255
- <li>
256
- <Link href="/feedback" className="text-muted-foreground hover:text-foreground transition-colors">
257
- Contact & Feedback
258
- </Link>
259
- </li>
260
- <li>
261
- <a href="mailto:support@autoloop.com" className="text-muted-foreground hover:text-foreground transition-colors">
262
- Support
263
- </a>
264
- </li>
265
- </ul>
266
- </div>
267
-
268
- {/* Legal */}
269
- <div className="space-y-4">
270
- <h3 className="font-semibold">Legal</h3>
271
- <ul className="space-y-2 text-sm">
272
- <li>
273
- <Link href="/terms" className="text-muted-foreground hover:text-foreground transition-colors">
274
- Terms of Service
275
- </Link>
276
- </li>
277
- <li>
278
- <Link href="/privacy" className="text-muted-foreground hover:text-foreground transition-colors">
279
- Privacy Policy
280
- </Link>
281
- </li>
282
- </ul>
283
- </div>
284
- </div>
285
-
286
- <div className="mt-8 pt-8 border-t text-center">
287
- <p className="text-sm text-muted-foreground">
288
- © {new Date().getFullYear()} AutoLoop. All rights reserved.
289
- </p>
290
- </div>
291
  </div>
292
  </footer>
293
  </div>
294
  );
295
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  Mail,
9
  BarChart3,
10
  CheckCircle2,
11
+ Users,
12
+ Layers,
13
  } from "lucide-react";
14
 
15
  import { useSession } from "next-auth/react";
16
  import { useRouter } from "next/navigation";
17
+ import { useEffect, useState } from "react";
18
 
19
  export default function Home() {
20
  const { data: session } = useSession();
21
  const router = useRouter();
22
+ const [offset, setOffset] = useState(0);
23
 
24
  // Redirect to dashboard if logged in
25
  useEffect(() => {
26
+ if (session?.user) router.push("/dashboard");
 
 
27
  }, [session, router]);
28
 
29
+ // Lightweight parallax scroll handler
30
+ useEffect(() => {
31
+ const onScroll = () => setOffset(window.scrollY || 0);
32
+ onScroll();
33
+ window.addEventListener("scroll", onScroll, { passive: true });
34
+ return () => window.removeEventListener("scroll", onScroll);
35
+ }, []);
36
+
37
+ // IntersectionObserver to trigger entrance animations for elements with .animate-on-scroll
38
+ useEffect(() => {
39
+ if (typeof window === "undefined") return;
40
+
41
+ const els = Array.from(document.querySelectorAll<HTMLElement>(".animate-on-scroll"));
42
+ if (!els.length) return;
43
+
44
+ const observer = new IntersectionObserver(
45
+ (entries) => {
46
+ entries.forEach((entry) => {
47
+ if (entry.isIntersecting) {
48
+ const el = entry.target as HTMLElement;
49
+ el.classList.add("animate-slide-in-up");
50
+ el.classList.remove("opacity-0");
51
+ observer.unobserve(el);
52
+ }
53
+ });
54
+ },
55
+ { threshold: 0.12 }
56
+ );
57
+
58
+ els.forEach((el) => {
59
+ el.classList.add("opacity-0");
60
+ observer.observe(el);
61
+ });
62
+
63
+ return () => observer.disconnect();
64
+ }, []);
65
+
66
  return (
67
+ <div className="min-h-screen overflow-hidden bg-linear-to-b from-background via-muted/30 to-background">
68
+ {/* Top nav */}
69
+ <nav className="fixed left-0 right-0 top-0 z-40 border-b bg-background/80 backdrop-blur-md supports-backdrop-filter:bg-background/60">
70
  <div className="container mx-auto flex h-16 items-center justify-between px-4">
71
+ <div className="text-xl font-extrabold bg-linear-to-r from-primary via-purple-600 to-pink-600 bg-clip-text text-transparent">
72
  AutoLoop
73
  </div>
74
+ <div className="flex items-center gap-3">
75
  <Link href="/auth/signin">
76
  <Button variant="ghost">Sign In</Button>
77
  </Link>
78
  <Link href="/auth/signin">
79
+ <Button className="bg-linear-to-r from-primary to-purple-600 hover:from-primary/90 hover:to-purple-600/90">
80
+ Get Started
81
+ </Button>
82
  </Link>
83
  </div>
84
  </div>
85
  </nav>
86
 
87
+ {/* Hero with modern gradients */}
88
+ <header className="relative pt-32 pb-24">
89
+ {/* Modern gradient blobs */}
90
+ <div aria-hidden className="absolute inset-0 -z-10 overflow-hidden">
91
+ <div
92
+ className="absolute left-[-10%] top-10 h-[400px] w-[400px] rounded-full bg-linear-to-r from-primary/30 to-purple-500/30 opacity-40 blur-3xl animate-pulse"
93
+ style={{ transform: `translateY(${offset * -0.02}px)`, animationDuration: '4s' }}
94
+ />
95
+ <div
96
+ className="absolute right-[-12%] top-32 h-[500px] w-[500px] rounded-full bg-linear-to-r from-pink-500/30 to-purple-600/30 opacity-40 blur-3xl animate-pulse"
97
+ style={{ transform: `translateY(${offset * -0.04}px)`, animationDuration: '6s', animationDelay: '1s' }}
98
+ />
99
+ <div
100
+ className="absolute left-1/2 top-[50%] h-[300px] w-[800px] -translate-x-1/2 rounded-3xl bg-linear-to-r from-emerald-400/20 to-cyan-400/20 opacity-30 blur-2xl"
101
+ style={{ transform: `translate(-50%, ${offset * -0.01}px)` }}
102
+ />
103
+ </div>
104
 
105
+ <div className="container mx-auto px-4">
106
+ <div className="mx-auto max-w-5xl text-center">
107
+ <div className="flex flex-col items-center gap-6 animate-fade-in">
108
+ <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 text-sm font-medium text-primary backdrop-blur-sm">
109
+ <Zap className="h-4 w-4" />
110
+ Trusted by 1,200+ sales teams
111
+ </div>
112
 
113
+ <h1 className="text-5xl font-extrabold leading-tight tracking-tight sm:text-6xl md:text-7xl bg-linear-to-r from-foreground via-foreground to-muted-foreground bg-clip-text text-transparent">
114
+ Automate outreach.<br />Personalize at scale.
115
+ </h1>
 
 
 
 
 
 
 
116
 
117
+ <p className="mt-4 mx-auto max-w-3xl text-lg text-muted-foreground leading-relaxed">
118
+ AutoLoop finds leads, enriches profiles, and sends AI-personalized cold
119
+ emails all while you focus on closing deals. Powerful integrations,
120
+ visual workflows, and enterprise-grade reliability.
121
+ </p>
122
 
123
+ <div className="mt-8 flex flex-wrap items-center justify-center gap-4">
124
+ <Link href="/auth/signin">
125
+ <Button size="lg" className="bg-linear-to-r from-primary to-purple-600 hover:from-primary/90 hover:to-purple-600/90 shadow-lg hover:shadow-xl transition-all duration-300 text-base px-8">
126
+ Get Started Free
127
+ <ArrowRight className="ml-2 h-5 w-5" />
128
+ </Button>
129
+ </Link>
130
+ <Link href="#features">
131
+ <Button variant="outline" size="lg" className="border-2 text-base px-8">
132
+ Explore Features
133
+ </Button>
134
+ </Link>
135
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  </div>
 
 
 
 
 
137
  </div>
138
  </div>
139
+ </header>
140
 
141
+ {/* Features overview */}
142
+ <section id="features" className="py-24 bg-muted/20">
143
  <div className="container mx-auto px-4">
144
+ <div className="mx-auto mb-16 max-w-3xl text-center">
145
+ <h2 className="text-4xl font-bold bg-linear-to-r from-foreground to-muted-foreground bg-clip-text text-transparent">
146
+ Complete outreach stack
147
  </h2>
148
  <p className="mt-4 text-lg text-muted-foreground">
149
+ Everything you need to source prospects, personalize at scale, and
150
+ measure impact — built for SDRs and growth teams.
151
  </p>
152
  </div>
153
 
154
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
155
+ <FeatureCard
156
+ delay={0.05}
157
+ icon={<Zap className="h-6 w-6 text-primary" />}
158
+ title="Auto Scraping & Enrichment"
159
+ description="Continuously discover leads from Google Maps, websites, and social profiles, then enrich records with firmographics and contact data."
160
+ />
 
 
 
 
 
161
 
162
+ <FeatureCard
163
+ delay={0.1}
164
+ icon={<Bot className="h-6 w-6 text-purple-600" />}
165
+ title="AI Personalization"
166
+ description="Generate personalized, context-aware email copy for each lead using AI tuned for cold outreach. A/B test variations automatically."
167
+ />
 
 
 
 
 
168
 
169
+ <FeatureCard
170
+ delay={0.15}
171
+ icon={<Mail className="h-6 w-6 text-emerald-600" />}
172
+ title="Gmail & SMTP Integration"
173
+ description="Send from your Gmail or SMTP provider, track opens/clicks, and record replies in one place. Automatic send throttling and warm-up support."
174
+ />
 
 
 
 
 
175
 
176
+ <FeatureCard
177
+ delay={0.2}
178
+ icon={<BarChart3 className="h-6 w-6 text-pink-600" />}
179
+ title="Analytics & Insights"
180
+ description="Campaign-level analytics, funnel metrics, and recipient-level signals to help you optimize subject lines and sequences."
181
+ />
 
 
 
 
182
 
183
+ <FeatureCard
184
+ delay={0.25}
185
+ icon={<Layers className="h-6 w-6 text-amber-600" />}
186
+ title="Visual Workflows"
187
+ description="Build multi-step automations with a drag-and-drop node editor (scrape → enrich → sequence → notify)."
188
+ />
189
+
190
+ <FeatureCard
191
+ delay={0.3}
192
+ icon={<CheckCircle2 className="h-6 w-6 text-emerald-600" />}
193
+ title="Deliverability & Safety"
194
+ description="Built-in rate limiting, spam-safety checks, unsubscribe handling, and per-account quotas to protect sender reputation."
195
+ />
196
+ </div>
197
+ </div>
198
+ </section>
199
+
200
+ {/* How it works */}
201
+ <section className="bg-background py-24">
202
+ <div className="container mx-auto px-4">
203
+ <div className="grid items-center gap-12 lg:grid-cols-2">
204
+ <div>
205
+ <h3 className="text-3xl font-bold">How AutoLoop works</h3>
206
+ <ol className="mt-8 space-y-6 list-decimal pl-6 text-muted-foreground">
207
+ <li className="pl-2">
208
+ <strong className="text-foreground">Find</strong>: Define search criteria and AutoLoop continuously
209
+ finds new prospects.
210
+ </li>
211
+ <li className="pl-2">
212
+ <strong className="text-foreground">Enrich</strong>: We append contact details, role, and business
213
+ data for better personalization.
214
+ </li>
215
+ <li className="pl-2">
216
+ <strong className="text-foreground">Personalize</strong>: AI crafts tailored outreach using the
217
+ lead context and your templates.
218
+ </li>
219
+ <li className="pl-2">
220
+ <strong className="text-foreground">Send & Measure</strong>: Deliver through Gmail/SMTP and
221
+ measure opens, clicks and replies. Iterate automatically.
222
+ </li>
223
+ </ol>
224
  </div>
225
 
226
+ <div>
227
+ <div className="rounded-xl border bg-card p-8 shadow-lg hover:shadow-xl transition-shadow">
228
+ <div className="mb-6 flex items-center gap-3">
229
+ <div className="p-3 rounded-lg bg-primary/10">
230
+ <Users className="h-8 w-8 text-primary" />
231
+ </div>
232
+ <div>
233
+ <div className="text-sm text-muted-foreground">Customers</div>
234
+ <div className="text-2xl font-bold">1,200+ teams</div>
235
+ </div>
236
+ </div>
237
+ <p className="text-muted-foreground">
238
+ AutoLoop runs continuously in the background and surfaces only the
239
+ leads that match your ideal customer profile.
240
+ </p>
241
  </div>
 
 
 
 
242
  </div>
243
  </div>
244
  </div>
245
  </section>
246
 
247
+ {/* Testimonials + CTA */}
248
+ <section className="py-24">
249
+ <div className="container mx-auto px-4">
250
+ <div className="mx-auto mb-12 max-w-3xl text-center">
251
+ <h2 className="text-3xl font-bold">What customers say</h2>
252
+ <p className="mt-3 text-muted-foreground">Real teams, real results.</p>
253
+ </div>
254
+
255
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
256
+ <blockquote className="rounded-lg border bg-card p-6 shadow hover:shadow-lg transition-shadow">
257
+ <p className="text-foreground">&ldquo;AutoLoop doubled our booked demos in 6 weeks.&rdquo;</p>
258
+ <footer className="mt-4 text-sm text-muted-foreground">— Head of Sales, SMB</footer>
259
+ </blockquote>
260
+
261
+ <blockquote className="rounded-lg border bg-card p-6 shadow hover:shadow-lg transition-shadow">
262
+ <p className="text-foreground">&ldquo;The AI emails feel human and convert better than our manual outreach.&rdquo;</p>
263
+ <footer className="mt-4 text-sm text-muted-foreground">— Growth Lead</footer>
264
+ </blockquote>
265
+
266
+ <blockquote className="rounded-lg border bg-card p-6 shadow hover:shadow-lg transition-shadow">
267
+ <p className="text-foreground">&ldquo;Workflows let us automate complex sequences without engineering.&rdquo;</p>
268
+ <footer className="mt-4 text-sm text-muted-foreground">— Ops Manager</footer>
269
+ </blockquote>
270
+ </div>
271
+
272
+ <div className="mt-12 text-center">
273
  <Link href="/auth/signin">
274
+ <Button size="lg" className="bg-linear-to-r from-primary to-purple-600 hover:from-primary/90 hover:to-purple-600/90 shadow-lg hover:shadow-xl transition-all duration-300 px-8">
275
+ Start Free Trial
276
+ <Zap className="ml-2 h-4 w-4" />
 
 
 
277
  </Button>
278
  </Link>
279
  </div>
 
281
  </section>
282
 
283
  {/* Footer */}
284
+ <footer className="border-t bg-muted/30 py-8">
285
+ <div className="container mx-auto px-4 text-center text-sm text-muted-foreground">
286
+ © {new Date().getFullYear()} AutoLoop — Privacy · Terms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  </div>
288
  </footer>
289
  </div>
290
  );
291
  }
292
+
293
+ function FeatureCard({
294
+ icon,
295
+ title,
296
+ description,
297
+ delay = 0,
298
+ }: {
299
+ icon: React.ReactNode;
300
+ title: string;
301
+ description: string;
302
+ delay?: number;
303
+ }) {
304
+ return (
305
+ <div
306
+ className="group rounded-xl border bg-card p-6 shadow-sm hover:shadow-lg hover:border-primary/20 transition-all duration-300 transform hover:-translate-y-1 animate-on-scroll"
307
+ style={{ animationDelay: `${delay}s` }}
308
+ >
309
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-muted group-hover:bg-primary/10 transition-colors">{icon}</div>
310
+ <h4 className="mb-2 text-lg font-semibold">{title}</h4>
311
+ <p className="text-sm text-muted-foreground">{description}</p>
312
+ </div>
313
+ );
314
+ }
components/admin/activity-logs.tsx ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "@/components/ui/table";
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from "@/components/ui/select";
21
+ import { Input } from "@/components/ui/input";
22
+ import { Button } from "@/components/ui/button";
23
+ import { Download, Search, Filter } from "lucide-react";
24
+ import { SystemEvent } from "@/types/admin";
25
+
26
+ interface ActivityLogsProps {
27
+ logs: SystemEvent[];
28
+ loading?: boolean;
29
+ }
30
+
31
+ export function ActivityLogs({ logs, loading }: ActivityLogsProps) {
32
+ const [filter, setFilter] = useState("all");
33
+ const [search, setSearch] = useState("");
34
+
35
+ const getEventColor = (type: string) => {
36
+ switch (type) {
37
+ case "error":
38
+ return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100";
39
+ case "warning":
40
+ return "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100";
41
+ case "success":
42
+ return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100";
43
+ default:
44
+ return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100";
45
+ }
46
+ };
47
+
48
+ const filteredLogs = logs.filter((log) => {
49
+ const matchesFilter = filter === "all" || log.type === filter;
50
+ const matchesSearch = log.message.toLowerCase().includes(search.toLowerCase());
51
+ return matchesFilter && matchesSearch;
52
+ });
53
+
54
+ return (
55
+ <Card>
56
+ <CardHeader>
57
+ <div className="flex items-center justify-between">
58
+ <div>
59
+ <CardTitle>System Activity</CardTitle>
60
+ <CardDescription>Recent system events and user actions</CardDescription>
61
+ </div>
62
+ <Button variant="outline" size="sm">
63
+ <Download className="mr-2 h-4 w-4" />
64
+ Export CSV
65
+ </Button>
66
+ </div>
67
+ </CardHeader>
68
+ <CardContent>
69
+ <div className="flex flex-col gap-4">
70
+ <div className="flex gap-2">
71
+ <div className="relative flex-1">
72
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
73
+ <Input
74
+ placeholder="Search logs..."
75
+ value={search}
76
+ onChange={(e) => setSearch(e.target.value)}
77
+ className="pl-8"
78
+ />
79
+ </div>
80
+ <Select value={filter} onValueChange={setFilter}>
81
+ <SelectTrigger className="w-[180px]">
82
+ <Filter className="mr-2 h-4 w-4" />
83
+ <SelectValue placeholder="Filter by type" />
84
+ </SelectTrigger>
85
+ <SelectContent>
86
+ <SelectItem value="all">All Events</SelectItem>
87
+ <SelectItem value="info">Info</SelectItem>
88
+ <SelectItem value="success">Success</SelectItem>
89
+ <SelectItem value="warning">Warning</SelectItem>
90
+ <SelectItem value="error">Error</SelectItem>
91
+ </SelectContent>
92
+ </Select>
93
+ </div>
94
+
95
+ <div className="rounded-md border">
96
+ <Table>
97
+ <TableHeader>
98
+ <TableRow>
99
+ <TableHead className="w-[100px]">Type</TableHead>
100
+ <TableHead>Message</TableHead>
101
+ <TableHead className="w-[200px]">Timestamp</TableHead>
102
+ </TableRow>
103
+ </TableHeader>
104
+ <TableBody>
105
+ {loading ? (
106
+ <TableRow>
107
+ <TableCell colSpan={3} className="h-24 text-center">
108
+ Loading...
109
+ </TableCell>
110
+ </TableRow>
111
+ ) : filteredLogs.length === 0 ? (
112
+ <TableRow>
113
+ <TableCell colSpan={3} className="h-24 text-center">
114
+ No logs found.
115
+ </TableCell>
116
+ </TableRow>
117
+ ) : (
118
+ filteredLogs.map((log) => (
119
+ <TableRow key={log.id}>
120
+ <TableCell>
121
+ <Badge
122
+ variant="secondary"
123
+ className={getEventColor(log.type)}
124
+ >
125
+ {log.type}
126
+ </Badge>
127
+ </TableCell>
128
+ <TableCell className="font-medium">
129
+ {log.message}
130
+ {log.metadata && (
131
+ <pre className="mt-1 text-xs text-muted-foreground">
132
+ {JSON.stringify(log.metadata, null, 2)}
133
+ </pre>
134
+ )}
135
+ </TableCell>
136
+ <TableCell className="text-muted-foreground">
137
+ {new Date(log.timestamp).toLocaleString()}
138
+ </TableCell>
139
+ </TableRow>
140
+ ))
141
+ )}
142
+ </TableBody>
143
+ </Table>
144
+ </div>
145
+ </div>
146
+ </CardContent>
147
+ </Card>
148
+ );
149
+ }
components/admin/platform-usage-chart.tsx ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import {
4
+ Bar,
5
+ BarChart,
6
+ CartesianGrid,
7
+ ResponsiveContainer,
8
+ Tooltip,
9
+ XAxis,
10
+ YAxis,
11
+ } from "recharts";
12
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
13
+
14
+ interface UsageData {
15
+ name: string;
16
+ value: number;
17
+ }
18
+
19
+ interface PlatformUsageChartProps {
20
+ data: UsageData[];
21
+ }
22
+
23
+ export function PlatformUsageChart({ data }: PlatformUsageChartProps) {
24
+ return (
25
+ <Card>
26
+ <CardHeader>
27
+ <CardTitle>Platform Usage</CardTitle>
28
+ <CardDescription>Key metrics for platform activity</CardDescription>
29
+ </CardHeader>
30
+ <CardContent className="h-[300px]">
31
+ <ResponsiveContainer width="100%" height="100%">
32
+ <BarChart data={data}>
33
+ <XAxis
34
+ dataKey="name"
35
+ stroke="#888888"
36
+ fontSize={12}
37
+ tickLine={false}
38
+ axisLine={false}
39
+ />
40
+ <YAxis
41
+ stroke="#888888"
42
+ fontSize={12}
43
+ tickLine={false}
44
+ axisLine={false}
45
+ tickFormatter={(value) => `${value}`}
46
+ />
47
+ <Tooltip
48
+ cursor={{ fill: "transparent" }}
49
+ contentStyle={{
50
+ backgroundColor: "hsl(var(--background))",
51
+ borderColor: "hsl(var(--border))",
52
+ borderRadius: "var(--radius)",
53
+ }}
54
+ />
55
+ <Bar
56
+ dataKey="value"
57
+ fill="hsl(var(--primary))"
58
+ radius={[4, 4, 0, 0]}
59
+ />
60
+ </BarChart>
61
+ </ResponsiveContainer>
62
+ </CardContent>
63
+ </Card>
64
+ );
65
+ }
components/admin/stats-overview.tsx ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { Users, Activity, Workflow, Server } from "lucide-react";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import { AdminStats } from "@/types/admin";
6
+ import { Skeleton } from "@/components/ui/skeleton";
7
+
8
+ interface StatsOverviewProps {
9
+ stats: AdminStats | null;
10
+ loading: boolean;
11
+ }
12
+
13
+ export function StatsOverview({ stats, loading }: StatsOverviewProps) {
14
+ if (loading || !stats) {
15
+ return (
16
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
17
+ {Array.from({ length: 4 }).map((_, i) => (
18
+ <Skeleton key={i} className="h-32 rounded-xl" />
19
+ ))}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ const healthColor = {
25
+ healthy: "text-green-500",
26
+ degraded: "text-amber-500",
27
+ down: "text-red-500",
28
+ }[stats.systemHealth];
29
+
30
+ return (
31
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
32
+ <Card>
33
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
34
+ <CardTitle className="text-sm font-medium">Total Users</CardTitle>
35
+ <Users className="h-4 w-4 text-muted-foreground" />
36
+ </CardHeader>
37
+ <CardContent>
38
+ <div className="text-2xl font-bold">{stats.totalUsers}</div>
39
+ <p className="text-xs text-muted-foreground">
40
+ {stats.userGrowth > 0 ? "+" : ""}
41
+ {stats.userGrowth}% from last month
42
+ </p>
43
+ </CardContent>
44
+ </Card>
45
+
46
+ <Card>
47
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
48
+ <CardTitle className="text-sm font-medium">Active Users</CardTitle>
49
+ <Activity className="h-4 w-4 text-muted-foreground" />
50
+ </CardHeader>
51
+ <CardContent>
52
+ <div className="text-2xl font-bold">{stats.activeUsers}</div>
53
+ <p className="text-xs text-muted-foreground">in the last 7 days</p>
54
+ </CardContent>
55
+ </Card>
56
+
57
+ <Card>
58
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
59
+ <CardTitle className="text-sm font-medium">Total Workflows</CardTitle>
60
+ <Workflow className="h-4 w-4 text-muted-foreground" />
61
+ </CardHeader>
62
+ <CardContent>
63
+ <div className="text-2xl font-bold">{stats.totalWorkflows}</div>
64
+ <p className="text-xs text-muted-foreground">
65
+ Active across the platform
66
+ </p>
67
+ </CardContent>
68
+ </Card>
69
+
70
+ <Card>
71
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
72
+ <CardTitle className="text-sm font-medium">System Health</CardTitle>
73
+ <Server className="h-4 w-4 text-muted-foreground" />
74
+ </CardHeader>
75
+ <CardContent>
76
+ <div className={`text-2xl font-bold capitalize ${healthColor}`}>
77
+ {stats.systemHealth}
78
+ </div>
79
+ <p className="text-xs text-muted-foreground">
80
+ All services operational
81
+ </p>
82
+ </CardContent>
83
+ </Card>
84
+ </div>
85
+ );
86
+ }
components/admin/system-controls.tsx ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardFooter,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/components/ui/card";
12
+ import { Switch } from "@/components/ui/switch";
13
+ import { Label } from "@/components/ui/label";
14
+ import { Button } from "@/components/ui/button";
15
+ import { Input } from "@/components/ui/input";
16
+ import { AlertTriangle, Save, RefreshCw } from "lucide-react";
17
+ import { useApi } from "@/hooks/use-api";
18
+
19
+ interface SystemSettings {
20
+ featureFlags: {
21
+ betaFeatures: boolean;
22
+ registration: boolean;
23
+ maintenance: boolean;
24
+ };
25
+ emailConfig: {
26
+ dailyLimit: number;
27
+ userRateLimit: number;
28
+ };
29
+ }
30
+
31
+ export function SystemControls() {
32
+ const { get, post, loading: apiLoading } = useApi<SystemSettings>();
33
+ const [localSettings, setLocalSettings] = useState<SystemSettings>({
34
+ featureFlags: {
35
+ betaFeatures: false,
36
+ registration: true,
37
+ maintenance: false,
38
+ },
39
+ emailConfig: {
40
+ dailyLimit: 10000,
41
+ userRateLimit: 50,
42
+ },
43
+ });
44
+ const [initialLoading, setInitialLoading] = useState(true);
45
+
46
+ useEffect(() => {
47
+ const fetchSettings = async () => {
48
+ const data = await get("/api/admin/settings");
49
+ if (data) {
50
+ setLocalSettings({
51
+ featureFlags: { ...localSettings.featureFlags, ...(data.featureFlags || {}) },
52
+ emailConfig: { ...localSettings.emailConfig, ...(data.emailConfig || {}) }
53
+ });
54
+ }
55
+ setInitialLoading(false);
56
+ };
57
+ fetchSettings();
58
+ }, [get, localSettings.emailConfig, localSettings.featureFlags]); // Run once on mount
59
+
60
+ const handleSave = async () => {
61
+ await post("/api/admin/settings", localSettings, {
62
+ successMessage: "System settings updated successfully"
63
+ });
64
+ };
65
+
66
+ const updateFeatureFlag = (key: keyof SystemSettings["featureFlags"], value: boolean) => {
67
+ setLocalSettings(prev => ({
68
+ ...prev,
69
+ featureFlags: { ...prev.featureFlags, [key]: value }
70
+ }));
71
+ };
72
+
73
+ const updateEmailConfig = (key: keyof SystemSettings["emailConfig"], value: number) => {
74
+ setLocalSettings(prev => ({
75
+ ...prev,
76
+ emailConfig: { ...prev.emailConfig, [key]: value }
77
+ }));
78
+ };
79
+
80
+ const loading = initialLoading || apiLoading;
81
+
82
+ return (
83
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
84
+ <Card>
85
+ <CardHeader>
86
+ <CardTitle>Feature Flags</CardTitle>
87
+ <CardDescription>
88
+ Toggle system-wide features and experimental functionality
89
+ </CardDescription>
90
+ </CardHeader>
91
+ <CardContent className="space-y-4">
92
+ <div className="flex items-center justify-between">
93
+ <div className="space-y-0.5">
94
+ <Label>Beta Features</Label>
95
+ <div className="text-sm text-muted-foreground">
96
+ Enable experimental features for all users
97
+ </div>
98
+ </div>
99
+ <Switch
100
+ checked={localSettings.featureFlags.betaFeatures}
101
+ onCheckedChange={(c) => updateFeatureFlag("betaFeatures", c)}
102
+ disabled={loading}
103
+ />
104
+ </div>
105
+ <div className="flex items-center justify-between">
106
+ <div className="space-y-0.5">
107
+ <Label>User Registration</Label>
108
+ <div className="text-sm text-muted-foreground">
109
+ Allow new users to sign up
110
+ </div>
111
+ </div>
112
+ <Switch
113
+ checked={localSettings.featureFlags.registration}
114
+ onCheckedChange={(c) => updateFeatureFlag("registration", c)}
115
+ disabled={loading}
116
+ />
117
+ </div>
118
+ <div className="flex items-center justify-between">
119
+ <div className="space-y-0.5">
120
+ <Label>Maintenance Mode</Label>
121
+ <div className="text-sm text-muted-foreground">
122
+ Disable access for non-admin users
123
+ </div>
124
+ </div>
125
+ <Switch
126
+ checked={localSettings.featureFlags.maintenance}
127
+ onCheckedChange={(c) => updateFeatureFlag("maintenance", c)}
128
+ disabled={loading}
129
+ />
130
+ </div>
131
+ </CardContent>
132
+ <CardFooter>
133
+ <Button onClick={handleSave} disabled={loading}>
134
+ {loading ? (
135
+ <RefreshCw className="mr-2 h-4 w-4 animate-spin" />
136
+ ) : (
137
+ <Save className="mr-2 h-4 w-4" />
138
+ )}
139
+ Save Changes
140
+ </Button>
141
+ </CardFooter>
142
+ </Card>
143
+
144
+ <Card>
145
+ <CardHeader>
146
+ <CardTitle>Email Configuration</CardTitle>
147
+ <CardDescription>
148
+ Manage global email sending limits and rate limiting
149
+ </CardDescription>
150
+ </CardHeader>
151
+ <CardContent className="space-y-4">
152
+ <div className="grid gap-2">
153
+ <Label>Daily Global Limit</Label>
154
+ <Input
155
+ type="number"
156
+ value={localSettings.emailConfig.dailyLimit}
157
+ onChange={(e) => updateEmailConfig("dailyLimit", parseInt(e.target.value))}
158
+ disabled={loading}
159
+ />
160
+ <p className="text-xs text-muted-foreground">
161
+ Maximum emails sent across all users per day
162
+ </p>
163
+ </div>
164
+ <div className="grid gap-2">
165
+ <Label>Default User Rate Limit</Label>
166
+ <Input
167
+ type="number"
168
+ value={localSettings.emailConfig.userRateLimit}
169
+ onChange={(e) => updateEmailConfig("userRateLimit", parseInt(e.target.value))}
170
+ disabled={loading}
171
+ />
172
+ <p className="text-xs text-muted-foreground">
173
+ Emails per hour per user account
174
+ </p>
175
+ </div>
176
+ </CardContent>
177
+ <CardFooter>
178
+ <Button variant="outline" onClick={handleSave} disabled={loading}>
179
+ Update Configuration
180
+ </Button>
181
+ </CardFooter>
182
+ </Card>
183
+
184
+ <Card className="border-red-200 dark:border-red-900">
185
+ <CardHeader>
186
+ <div className="flex items-center gap-2 text-red-600">
187
+ <AlertTriangle className="h-5 w-5" />
188
+ <CardTitle>Danger Zone</CardTitle>
189
+ </div>
190
+ <CardDescription>
191
+ Irreversible actions that affect the entire system
192
+ </CardDescription>
193
+ </CardHeader>
194
+ <CardContent className="space-y-4">
195
+ <div className="flex items-center justify-between p-4 border rounded-lg bg-red-50 dark:bg-red-950/20">
196
+ <div>
197
+ <div className="font-medium text-red-900 dark:text-red-200">
198
+ Clear System Cache
199
+ </div>
200
+ <div className="text-sm text-red-700 dark:text-red-300">
201
+ Flush Redis cache and reset rate limits
202
+ </div>
203
+ </div>
204
+ <Button variant="destructive" size="sm">
205
+ Clear Cache
206
+ </Button>
207
+ </div>
208
+ </CardContent>
209
+ </Card>
210
+ </div>
211
+ );
212
+ }
components/admin/user-growth-chart.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import {
4
+ Area,
5
+ AreaChart,
6
+ CartesianGrid,
7
+ ResponsiveContainer,
8
+ Tooltip,
9
+ XAxis,
10
+ YAxis,
11
+ } from "recharts";
12
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
13
+
14
+ interface UserGrowthData {
15
+ date: string;
16
+ users: number;
17
+ }
18
+
19
+ interface UserGrowthChartProps {
20
+ data: UserGrowthData[];
21
+ }
22
+
23
+ export function UserGrowthChart({ data }: UserGrowthChartProps) {
24
+ return (
25
+ <Card>
26
+ <CardHeader>
27
+ <CardTitle>User Growth</CardTitle>
28
+ <CardDescription>Total users over the last 30 days</CardDescription>
29
+ </CardHeader>
30
+ <CardContent className="h-[300px]">
31
+ <ResponsiveContainer width="100%" height="100%">
32
+ <AreaChart
33
+ data={data}
34
+ margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
35
+ >
36
+ <defs>
37
+ <linearGradient id="colorUsers" x1="0" y1="0" x2="0" y2="1">
38
+ <stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
39
+ <stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
40
+ </linearGradient>
41
+ </defs>
42
+ <XAxis
43
+ dataKey="date"
44
+ stroke="#888888"
45
+ fontSize={12}
46
+ tickLine={false}
47
+ axisLine={false}
48
+ />
49
+ <YAxis
50
+ stroke="#888888"
51
+ fontSize={12}
52
+ tickLine={false}
53
+ axisLine={false}
54
+ tickFormatter={(value) => `${value}`}
55
+ />
56
+ <CartesianGrid strokeDasharray="3 3" vertical={false} />
57
+ <Tooltip
58
+ contentStyle={{
59
+ backgroundColor: "hsl(var(--background))",
60
+ borderColor: "hsl(var(--border))",
61
+ borderRadius: "var(--radius)",
62
+ }}
63
+ />
64
+ <Area
65
+ type="monotone"
66
+ dataKey="users"
67
+ stroke="#8884d8"
68
+ fillOpacity={1}
69
+ fill="url(#colorUsers)"
70
+ />
71
+ </AreaChart>
72
+ </ResponsiveContainer>
73
+ </CardContent>
74
+ </Card>
75
+ );
76
+ }
components/admin/user-management-table.tsx ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from "@/components/ui/table";
12
+ import {
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuLabel,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuTrigger,
19
+ } from "@/components/ui/dropdown-menu";
20
+ import { Button } from "@/components/ui/button";
21
+ import { Input } from "@/components/ui/input";
22
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
23
+ import { Badge } from "@/components/ui/badge";
24
+ import { AdminUser } from "@/types/admin";
25
+ import { MoreHorizontal, Search, UserCog, Ban, CheckCircle } from "lucide-react";
26
+
27
+ interface UserManagementTableProps {
28
+ users: AdminUser[];
29
+ onUpdateStatus: (userId: string, newStatus: "active" | "suspended") => void;
30
+ onUpdateRole: (userId: string, newRole: "user" | "admin") => void;
31
+ }
32
+
33
+ export function UserManagementTable({
34
+ users,
35
+ onUpdateStatus,
36
+ onUpdateRole,
37
+ }: UserManagementTableProps) {
38
+ const [searchTerm, setSearchTerm] = useState("");
39
+
40
+ const filteredUsers = users.filter(
41
+ (user) =>
42
+ user.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
43
+ user.email?.toLowerCase().includes(searchTerm.toLowerCase())
44
+ );
45
+
46
+ const getStatusColor = (status: string) => {
47
+ switch (status) {
48
+ case "active":
49
+ return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100";
50
+ case "suspended":
51
+ return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100";
52
+ case "inactive":
53
+ return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100";
54
+ default:
55
+ return "bg-gray-100 text-gray-800";
56
+ }
57
+ };
58
+
59
+ return (
60
+ <div className="space-y-4">
61
+ <div className="flex items-center gap-2">
62
+ <div className="relative flex-1 max-w-sm">
63
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
64
+ <Input
65
+ placeholder="Search users..."
66
+ value={searchTerm}
67
+ onChange={(e) => setSearchTerm(e.target.value)}
68
+ className="pl-8"
69
+ />
70
+ </div>
71
+ </div>
72
+
73
+ <div className="rounded-md border">
74
+ <Table>
75
+ <TableHeader>
76
+ <TableRow>
77
+ <TableHead>User</TableHead>
78
+ <TableHead>Role</TableHead>
79
+ <TableHead>Status</TableHead>
80
+ <TableHead>Joined</TableHead>
81
+ <TableHead>Last Active</TableHead>
82
+ <TableHead className="text-right">Actions</TableHead>
83
+ </TableRow>
84
+ </TableHeader>
85
+ <TableBody>
86
+ {filteredUsers.length === 0 ? (
87
+ <TableRow>
88
+ <TableCell colSpan={6} className="h-24 text-center">
89
+ No users found.
90
+ </TableCell>
91
+ </TableRow>
92
+ ) : (
93
+ filteredUsers.map((user) => (
94
+ <TableRow key={user.id}>
95
+ <TableCell>
96
+ <div className="flex items-center gap-3">
97
+ <Avatar>
98
+ <AvatarImage src={user.image || ""} />
99
+ <AvatarFallback>
100
+ {user.name?.slice(0, 2).toUpperCase() || "U"}
101
+ </AvatarFallback>
102
+ </Avatar>
103
+ <div className="flex flex-col">
104
+ <span className="font-medium">{user.name}</span>
105
+ <span className="text-xs text-muted-foreground">
106
+ {user.email}
107
+ </span>
108
+ </div>
109
+ </div>
110
+ </TableCell>
111
+ <TableCell>
112
+ <Badge variant="outline" className="capitalize">
113
+ {user.role}
114
+ </Badge>
115
+ </TableCell>
116
+ <TableCell>
117
+ <Badge
118
+ variant="secondary"
119
+ className={`capitalize ${getStatusColor(user.status)}`}
120
+ >
121
+ {user.status}
122
+ </Badge>
123
+ </TableCell>
124
+ <TableCell>
125
+ {new Date(user.createdAt).toLocaleDateString()}
126
+ </TableCell>
127
+ <TableCell>
128
+ {user.lastActive
129
+ ? new Date(user.lastActive).toLocaleDateString()
130
+ : "Never"}
131
+ </TableCell>
132
+ <TableCell className="text-right">
133
+ <DropdownMenu>
134
+ <DropdownMenuTrigger asChild>
135
+ <Button variant="ghost" size="icon">
136
+ <MoreHorizontal className="h-4 w-4" />
137
+ <span className="sr-only">Actions</span>
138
+ </Button>
139
+ </DropdownMenuTrigger>
140
+ <DropdownMenuContent align="end">
141
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
142
+ <DropdownMenuItem
143
+ onClick={() =>
144
+ onUpdateRole(
145
+ user.id,
146
+ user.role === "admin" ? "user" : "admin"
147
+ )
148
+ }
149
+ >
150
+ <UserCog className="mr-2 h-4 w-4" />
151
+ {user.role === "admin"
152
+ ? "Remove Admin"
153
+ : "Make Admin"}
154
+ </DropdownMenuItem>
155
+ <DropdownMenuSeparator />
156
+ {user.status === "suspended" ? (
157
+ <DropdownMenuItem
158
+ onClick={() => onUpdateStatus(user.id, "active")}
159
+ >
160
+ <CheckCircle className="mr-2 h-4 w-4" />
161
+ Activate Account
162
+ </DropdownMenuItem>
163
+ ) : (
164
+ <DropdownMenuItem
165
+ onClick={() => onUpdateStatus(user.id, "suspended")}
166
+ className="text-red-600"
167
+ >
168
+ <Ban className="mr-2 h-4 w-4" />
169
+ Suspend Account
170
+ </DropdownMenuItem>
171
+ )}
172
+ </DropdownMenuContent>
173
+ </DropdownMenu>
174
+ </TableCell>
175
+ </TableRow>
176
+ ))
177
+ )}
178
+ </TableBody>
179
+ </Table>
180
+ </div>
181
+ </div>
182
+ );
183
+ }
components/common/loading-skeleton.tsx ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Reusable Loading Skeleton Components
3
+ * Provides consistent loading states across the application
4
+ */
5
+
6
+ import React from "react";
7
+
8
+ export function CardSkeleton() {
9
+ return (
10
+ <div className="rounded-lg border bg-card p-6 animate-pulse">
11
+ <div className="h-4 bg-muted rounded w-1/4 mb-4"></div>
12
+ <div className="h-8 bg-muted rounded w-1/2 mb-2"></div>
13
+ <div className="h-4 bg-muted rounded w-3/4"></div>
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export function TableSkeleton({ rows = 5, columns = 4 }: { rows?: number; columns?: number }) {
19
+ return (
20
+ <div className="space-y-3">
21
+ {/* Header */}
22
+ <div className="flex gap-4 pb-3 border-b">
23
+ {Array.from({ length: columns }).map((_, i) => (
24
+ <div key={i} className="h-4 bg-muted rounded flex-1 animate-pulse"></div>
25
+ ))}
26
+ </div>
27
+
28
+ {/* Rows */}
29
+ {Array.from({ length: rows }).map((_, rowIdx) => (
30
+ <div key={rowIdx} className="flex gap-4 py-3">
31
+ {Array.from({ length: columns }).map((_, colIdx) => (
32
+ <div
33
+ key={colIdx}
34
+ className="h-4 bg-muted rounded flex-1 animate-pulse"
35
+ style={{ animationDelay: `${(rowIdx * columns + colIdx) * 50}ms` }}
36
+ ></div>
37
+ ))}
38
+ </div>
39
+ ))}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ export function ChartSkeleton() {
45
+ return (
46
+ <div className="space-y-4">
47
+ <div className="h-4 bg-muted rounded w-1/3 animate-pulse"></div>
48
+ <div className="h-64 bg-muted rounded animate-pulse"></div>
49
+ <div className="flex gap-4">
50
+ <div className="h-4 bg-muted rounded flex-1 animate-pulse"></div>
51
+ <div className="h-4 bg-muted rounded flex-1 animate-pulse"></div>
52
+ <div className="h-4 bg-muted rounded flex-1 animate-pulse"></div>
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export function ListSkeleton({ items = 5 }: { items?: number }) {
59
+ return (
60
+ <div className="space-y-3">
61
+ {Array.from({ length: items }).map((_, i) => (
62
+ <div key={i} className="flex items-center gap-4 p-4 rounded-lg border animate-pulse">
63
+ <div className="h-10 w-10 bg-muted rounded-full"></div>
64
+ <div className="flex-1 space-y-2">
65
+ <div className="h-4 bg-muted rounded w-1/2"></div>
66
+ <div className="h-3 bg-muted rounded w-3/4"></div>
67
+ </div>
68
+ </div>
69
+ ))}
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export function StatCardSkeleton() {
75
+ return (
76
+ <div className="rounded-lg border bg-card p-6 animate-pulse">
77
+ <div className="flex items-start justify-between">
78
+ <div className="space-y-2 flex-1">
79
+ <div className="h-3 bg-muted rounded w-24"></div>
80
+ <div className="h-8 bg-muted rounded w-32"></div>
81
+ </div>
82
+ <div className="h-10 w-10 bg-muted rounded-lg"></div>
83
+ </div>
84
+ <div className="mt-4 h-3 bg-muted rounded w-1/2"></div>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ export function FormSkeleton() {
90
+ return (
91
+ <div className="space-y-6">
92
+ {/* Form fields */}
93
+ {Array.from({ length: 4 }).map((_, i) => (
94
+ <div key={i} className="space-y-2 animate-pulse">
95
+ <div className="h-4 bg-muted rounded w-1/4"></div>
96
+ <div className="h-10 bg-muted rounded w-full"></div>
97
+ </div>
98
+ ))}
99
+
100
+ {/* Buttons */}
101
+ <div className="flex gap-3">
102
+ <div className="h-10 bg-muted rounded w-24 animate-pulse"></div>
103
+ <div className="h-10 bg-muted rounded w-24 animate-pulse"></div>
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ export function PageSkeleton() {
110
+ return (
111
+ <div className="container mx-auto p-6 space-y-6">
112
+ {/* Page header */}
113
+ <div className="space-y-2 animate-pulse">
114
+ <div className="h-8 bg-muted rounded w-1/3"></div>
115
+ <div className="h-4 bg-muted rounded w-1/2"></div>
116
+ </div>
117
+
118
+ {/* Stats Grid */}
119
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
120
+ {Array.from({ length: 4 }).map((_, i) => (
121
+ <StatCardSkeleton key={i} />
122
+ ))}
123
+ </div>
124
+
125
+ {/* Main content */}
126
+ <div className="rounded-lg border bg-card p-6">
127
+ <TableSkeleton />
128
+ </div>
129
+ </div>
130
+ );
131
+ }
132
+
133
+ export function DashboardSkeleton() {
134
+ return (
135
+ <div className="space-y-6">
136
+ {/* Header */}
137
+ <div className="flex items-center justify-between animate-pulse">
138
+ <div>
139
+ <div className="h-8 bg-muted rounded w-48 mb-2"></div>
140
+ <div className="h-4 bg-muted rounded w-64"></div>
141
+ </div>
142
+ <div className="h-10 w-32 bg-muted rounded"></div>
143
+ </div>
144
+
145
+ {/* Stats */}
146
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
147
+ {Array.from({ length: 4 }).map((_, i) => (
148
+ <StatCardSkeleton key={i} />
149
+ ))}
150
+ </div>
151
+
152
+ {/* Charts */}
153
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
154
+ <div className="rounded-lg border bg-card p-6">
155
+ <ChartSkeleton />
156
+ </div>
157
+ <div className="rounded-lg border bg-card p-6">
158
+ <ChartSkeleton />
159
+ </div>
160
+ </div>
161
+
162
+ {/* Recent activity */}
163
+ <div className="rounded-lg border bg-card p-6">
164
+ <div className="h-6 bg-muted rounded w-1/4 mb-4 animate-pulse"></div>
165
+ <TableSkeleton rows={5} />
166
+ </div>
167
+ </div>
168
+ );
169
+ }
170
+
171
+ // Generic skeleton with custom content
172
+ export function Skeleton({
173
+ className = "",
174
+ variant = "default"
175
+ }: {
176
+ className?: string;
177
+ variant?: "default" | "circle" | "rectangle"
178
+ }) {
179
+ const baseClass = "bg-muted animate-pulse";
180
+ const variantClass = {
181
+ default: "rounded",
182
+ circle: "rounded-full",
183
+ rectangle: "rounded-lg",
184
+ }[variant];
185
+
186
+ return <div className={`${baseClass} ${variantClass} ${className}`} />;
187
+ }
components/dashboard/email-chart.tsx CHANGED
@@ -34,21 +34,40 @@ export default function EmailChart({ data, loading }: EmailChartProps) {
34
  return (
35
  <ResponsiveContainer width="100%" height={300}>
36
  <LineChart data={data}>
37
- <CartesianGrid strokeDasharray="3 3" />
38
- <XAxis dataKey="name" />
39
- <YAxis />
40
- <Tooltip />
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  <Line
42
  type="monotone"
43
  dataKey="sent"
44
- stroke="#3b82f6"
45
  strokeWidth={2}
 
 
 
46
  />
47
  <Line
48
  type="monotone"
49
  dataKey="opened"
50
- stroke="#10b981"
51
  strokeWidth={2}
 
 
 
52
  />
53
  </LineChart>
54
  </ResponsiveContainer>
 
34
  return (
35
  <ResponsiveContainer width="100%" height={300}>
36
  <LineChart data={data}>
37
+ <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
38
+ <XAxis
39
+ dataKey="name"
40
+ className="text-xs"
41
+ tick={{ fill: "hsl(var(--muted-foreground))" }}
42
+ />
43
+ <YAxis
44
+ className="text-xs"
45
+ tick={{ fill: "hsl(var(--muted-foreground))" }}
46
+ />
47
+ <Tooltip
48
+ contentStyle={{
49
+ backgroundColor: "hsl(var(--popover))",
50
+ border: "1px solid hsl(var(--border))",
51
+ borderRadius: "var(--radius)",
52
+ }}
53
+ />
54
  <Line
55
  type="monotone"
56
  dataKey="sent"
57
+ stroke="hsl(var(--primary))"
58
  strokeWidth={2}
59
+ dot={{ fill: "hsl(var(--primary))", r: 4 }}
60
+ activeDot={{ r: 6 }}
61
+ name="Emails Sent"
62
  />
63
  <Line
64
  type="monotone"
65
  dataKey="opened"
66
+ stroke="hsl(var(--chart-2))"
67
  strokeWidth={2}
68
+ dot={{ fill: "hsl(var(--chart-2))", r: 4 }}
69
+ activeDot={{ r: 6 }}
70
+ name="Emails Opened"
71
  />
72
  </LineChart>
73
  </ResponsiveContainer>