frontend changes
This commit is contained in:
parent
68bec83ba4
commit
99107c06c5
@ -1,231 +0,0 @@
|
|||||||
# Authentication Implementation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document describes the authentication system implementation with proper error handling, separate signup/signin routes, and email verification flow.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Backend (User Auth Service)
|
|
||||||
- **Port**: 8011
|
|
||||||
- **Base URL**: `http://localhost:8011`
|
|
||||||
- **Database**: PostgreSQL
|
|
||||||
- **Features**: JWT authentication, email verification, session management
|
|
||||||
|
|
||||||
### Frontend (Next.js App)
|
|
||||||
- **Port**: 3001
|
|
||||||
- **Base URL**: `http://localhost:3001`
|
|
||||||
- **Framework**: Next.js 15.4.6 with TypeScript
|
|
||||||
- **UI**: Tailwind CSS + shadcn/ui components
|
|
||||||
|
|
||||||
## Routes Structure
|
|
||||||
|
|
||||||
### Frontend Routes
|
|
||||||
```
|
|
||||||
/signup - User registration page
|
|
||||||
/signin - User login page
|
|
||||||
/auth - Redirects to /signin (legacy support)
|
|
||||||
/verify-email - Email verification page (handled by backend redirect)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend API Endpoints
|
|
||||||
```
|
|
||||||
POST /api/auth/register - User registration
|
|
||||||
POST /api/auth/login - User login
|
|
||||||
GET /api/auth/verify-email - Email verification (redirects to frontend)
|
|
||||||
POST /api/auth/logout - User logout
|
|
||||||
POST /api/auth/refresh - Token refresh
|
|
||||||
GET /api/auth/me - Get user profile
|
|
||||||
```
|
|
||||||
|
|
||||||
## User Flow
|
|
||||||
|
|
||||||
### 1. Registration Flow
|
|
||||||
1. User visits `/signup`
|
|
||||||
2. Fills out registration form
|
|
||||||
3. Submits form → `POST /api/auth/register`
|
|
||||||
4. Backend creates user account and sends verification email
|
|
||||||
5. Frontend shows success message and redirects to `/signin` after 3 seconds
|
|
||||||
6. User receives email with verification link
|
|
||||||
|
|
||||||
### 2. Email Verification Flow
|
|
||||||
1. User clicks verification link in email
|
|
||||||
2. Link points to: `http://localhost:8011/api/auth/verify-email?token=<token>`
|
|
||||||
3. Backend verifies token and redirects to: `http://localhost:3001/signin?verified=true`
|
|
||||||
4. Frontend displays success message: "Email verified successfully! You can now sign in to your account."
|
|
||||||
|
|
||||||
### 3. Login Flow
|
|
||||||
1. User visits `/signin`
|
|
||||||
2. Fills out login form
|
|
||||||
3. Submits form → `POST /api/auth/login`
|
|
||||||
4. Backend validates credentials and returns JWT tokens
|
|
||||||
5. Frontend stores tokens and redirects to dashboard (`/`)
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### Backend Error Responses
|
|
||||||
All API endpoints return consistent error format:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Error type",
|
|
||||||
"message": "Detailed error message"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend Error Handling
|
|
||||||
- **Network Errors**: Display user-friendly messages
|
|
||||||
- **API Errors**: Show specific error messages from backend
|
|
||||||
- **Validation Errors**: Client-side validation with immediate feedback
|
|
||||||
- **Authentication Errors**: Clear messaging for login/registration issues
|
|
||||||
|
|
||||||
### Common Error Scenarios
|
|
||||||
|
|
||||||
#### Registration Errors
|
|
||||||
- **Email already exists**: "An account with this email already exists"
|
|
||||||
- **Username taken**: "Username is already taken"
|
|
||||||
- **Invalid email format**: "Please enter a valid email address"
|
|
||||||
- **Weak password**: "Password must be at least 8 characters long"
|
|
||||||
- **Missing fields**: "Please fill in all required fields"
|
|
||||||
|
|
||||||
#### Login Errors
|
|
||||||
- **Invalid credentials**: "Invalid email or password"
|
|
||||||
- **Email not verified**: "Please verify your email before signing in"
|
|
||||||
- **Account locked**: "Account is temporarily locked due to multiple failed attempts"
|
|
||||||
|
|
||||||
#### Email Verification Errors
|
|
||||||
- **Invalid token**: "Verification link is invalid or has expired"
|
|
||||||
- **Already verified**: "Email is already verified"
|
|
||||||
- **Token expired**: "Verification link has expired. Please request a new one"
|
|
||||||
|
|
||||||
## Components Structure
|
|
||||||
|
|
||||||
### Signup Flow
|
|
||||||
```
|
|
||||||
/signup
|
|
||||||
├── SignUpPage (main container)
|
|
||||||
├── SignUpForm (form component)
|
|
||||||
└── Success State (after registration)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Signin Flow
|
|
||||||
```
|
|
||||||
/signin
|
|
||||||
├── SignInPage (main container)
|
|
||||||
├── SignInForm (form component)
|
|
||||||
└── Verification Messages (from URL params)
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Integration
|
|
||||||
|
|
||||||
### Authentication Handler (`authenticationHandler.tsx`)
|
|
||||||
- Handles API calls to backend
|
|
||||||
- Proper error propagation
|
|
||||||
- TypeScript interfaces for type safety
|
|
||||||
|
|
||||||
### Key Functions
|
|
||||||
```typescript
|
|
||||||
registerUser(data: RegisterData): Promise<ApiResponse>
|
|
||||||
loginUser(email: string, password: string): Promise<ApiResponse>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Features
|
|
||||||
|
|
||||||
### Backend Security
|
|
||||||
- JWT token authentication
|
|
||||||
- Password hashing (bcrypt)
|
|
||||||
- Rate limiting on auth endpoints
|
|
||||||
- CORS configuration
|
|
||||||
- Helmet security headers
|
|
||||||
- Session management
|
|
||||||
|
|
||||||
### Frontend Security
|
|
||||||
- Token storage in localStorage
|
|
||||||
- Automatic token refresh
|
|
||||||
- Secure API communication
|
|
||||||
- Input validation and sanitization
|
|
||||||
|
|
||||||
## Environment Configuration
|
|
||||||
|
|
||||||
### Backend (.env)
|
|
||||||
```env
|
|
||||||
PORT=8011
|
|
||||||
FRONTEND_URL=http://localhost:3001
|
|
||||||
POSTGRES_HOST=localhost
|
|
||||||
POSTGRES_DB=user_auth
|
|
||||||
JWT_SECRET=your-secret-key
|
|
||||||
SMTP_HOST=smtp.gmail.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_USER=your-email@gmail.com
|
|
||||||
SMTP_PASS=your-app-password
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend (.env.local)
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_API_URL=http://localhost:8011
|
|
||||||
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3001
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing the Implementation
|
|
||||||
|
|
||||||
### 1. Start Services
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd automated-dev-pipeline
|
|
||||||
docker-compose up user-auth
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
cd codenuk-frontend-dark-theme
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Registration
|
|
||||||
1. Visit `http://localhost:3001/signup`
|
|
||||||
2. Fill out registration form
|
|
||||||
3. Submit and check for success message
|
|
||||||
4. Check email for verification link
|
|
||||||
|
|
||||||
### 3. Test Email Verification
|
|
||||||
1. Click verification link in email
|
|
||||||
2. Should redirect to `http://localhost:3001/signin?verified=true`
|
|
||||||
3. Verify success message appears
|
|
||||||
|
|
||||||
### 4. Test Login
|
|
||||||
1. Visit `http://localhost:3001/signin`
|
|
||||||
2. Enter credentials
|
|
||||||
3. Should redirect to dashboard on success
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **CORS Errors**
|
|
||||||
- Check backend CORS configuration
|
|
||||||
- Verify frontend URL in allowed origins
|
|
||||||
|
|
||||||
2. **Email Not Sending**
|
|
||||||
- Check SMTP configuration in backend
|
|
||||||
- Verify email credentials
|
|
||||||
|
|
||||||
3. **Verification Link Not Working**
|
|
||||||
- Check frontend URL in backend configuration
|
|
||||||
- Verify token expiration settings
|
|
||||||
|
|
||||||
4. **Login Fails After Verification**
|
|
||||||
- Check if user is properly verified in database
|
|
||||||
- Verify JWT token generation
|
|
||||||
|
|
||||||
### Debug Steps
|
|
||||||
1. Check browser network tab for API calls
|
|
||||||
2. Check backend logs for errors
|
|
||||||
3. Verify database connections
|
|
||||||
4. Test API endpoints directly with Postman/curl
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
1. **Password Reset Flow**
|
|
||||||
2. **Two-Factor Authentication**
|
|
||||||
3. **Social Login Integration**
|
|
||||||
4. **Account Lockout Protection**
|
|
||||||
5. **Session Management Dashboard**
|
|
||||||
6. **Audit Logging**
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
# Dynamic Templates Implementation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
The frontend now fetches templates dynamically from the database instead of using static templates. This allows for real-time template management and custom template creation.
|
|
||||||
|
|
||||||
## Changes Made
|
|
||||||
|
|
||||||
### 1. Template Service (`src/lib/template-service.ts`)
|
|
||||||
- Created service to communicate with the template-manager API
|
|
||||||
- Handles fetching templates by category, individual templates, and creating new templates
|
|
||||||
- Includes TypeScript interfaces for type safety
|
|
||||||
|
|
||||||
### 2. Custom Hook (`src/hooks/useTemplates.ts`)
|
|
||||||
- Manages template data fetching and state
|
|
||||||
- Converts database templates to UI format
|
|
||||||
- Handles loading states and error handling
|
|
||||||
- Provides template feature fetching
|
|
||||||
|
|
||||||
### 3. Custom Template Form (`src/components/custom-template-form.tsx`)
|
|
||||||
- Form component for creating new templates
|
|
||||||
- Includes all required fields: type, title, description, category
|
|
||||||
- Optional fields: icon, gradient, border, text, subtext
|
|
||||||
- Validates required fields before submission
|
|
||||||
|
|
||||||
### 4. Updated Main Dashboard (`src/components/main-dashboard.tsx`)
|
|
||||||
- Replaced static templates with dynamic database templates
|
|
||||||
- Added loading and error states
|
|
||||||
- Dynamic category generation based on available templates
|
|
||||||
- Custom template creation functionality
|
|
||||||
- Fallback to static templates if database is unavailable
|
|
||||||
|
|
||||||
### 5. UI Components
|
|
||||||
- Added `textarea.tsx` component for form inputs
|
|
||||||
- Enhanced existing components with proper styling
|
|
||||||
|
|
||||||
## API Integration
|
|
||||||
|
|
||||||
### Template Manager Service
|
|
||||||
- **Base URL**: `http://localhost:8009`
|
|
||||||
- **Endpoints**:
|
|
||||||
- `GET /api/templates` - Get all templates grouped by category
|
|
||||||
- `GET /api/templates/:id` - Get specific template with features
|
|
||||||
- `GET /api/templates/type/:type` - Get template by type
|
|
||||||
- `POST /api/templates` - Create new template
|
|
||||||
|
|
||||||
### Database Schema
|
|
||||||
Templates are stored in PostgreSQL with the following structure:
|
|
||||||
```sql
|
|
||||||
CREATE TABLE templates (
|
|
||||||
id UUID PRIMARY KEY,
|
|
||||||
type VARCHAR(100) UNIQUE,
|
|
||||||
title VARCHAR(200),
|
|
||||||
description TEXT,
|
|
||||||
category VARCHAR(100),
|
|
||||||
icon VARCHAR(50),
|
|
||||||
gradient VARCHAR(100),
|
|
||||||
border VARCHAR(100),
|
|
||||||
text VARCHAR(100),
|
|
||||||
subtext VARCHAR(100),
|
|
||||||
is_active BOOLEAN,
|
|
||||||
created_at TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Viewing Templates
|
|
||||||
1. Templates are automatically loaded from the database on page load
|
|
||||||
2. If database is unavailable, fallback static templates are shown
|
|
||||||
3. Templates are grouped by category dynamically
|
|
||||||
|
|
||||||
### Creating Custom Templates
|
|
||||||
1. Click "Create Custom Template" button
|
|
||||||
2. Fill in required fields (type, title, description, category)
|
|
||||||
3. Optionally add styling fields (icon, gradient, border, text, subtext)
|
|
||||||
4. Submit to create the template in the database
|
|
||||||
5. Template will appear in the list after creation
|
|
||||||
|
|
||||||
### Template Features
|
|
||||||
- Each template can have associated features stored in `template_features` table
|
|
||||||
- Features are fetched when a template is selected
|
|
||||||
- Features include complexity, type, and usage statistics
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
- Network errors show retry button
|
|
||||||
- Loading states with spinner
|
|
||||||
- Graceful fallback to static templates
|
|
||||||
- Form validation for required fields
|
|
||||||
|
|
||||||
## ✅ Implemented Features
|
|
||||||
|
|
||||||
### Template Management
|
|
||||||
- ✅ **Dynamic Template Display** - Templates fetched from database
|
|
||||||
- ✅ **Custom Template Creation** - Create new templates via form
|
|
||||||
- ✅ **Template Editing** - Edit existing templates
|
|
||||||
- ✅ **Template Deletion** - Delete templates with confirmation
|
|
||||||
- ✅ **Real-time Updates** - Changes reflect immediately in UI
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
- ✅ `GET /api/templates` - Get all templates grouped by category
|
|
||||||
- ✅ `GET /api/templates/:id` - Get specific template with features
|
|
||||||
- ✅ `POST /api/templates` - Create new template
|
|
||||||
- ✅ `PUT /api/templates/:id` - Update existing template
|
|
||||||
- ✅ `DELETE /api/templates/:id` - Delete template (soft delete)
|
|
||||||
|
|
||||||
### Database Operations
|
|
||||||
- ✅ **Soft Delete** - Templates are marked as inactive rather than physically deleted
|
|
||||||
- ✅ **Data Integrity** - All operations maintain referential integrity
|
|
||||||
- ✅ **Error Handling** - Comprehensive error handling for all operations
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
- Feature management for templates
|
|
||||||
- Bulk template operations
|
|
||||||
- Template versioning
|
|
||||||
- Template sharing between users
|
|
||||||
- Template import/export functionality
|
|
||||||
- Template analytics and usage tracking
|
|
||||||
129
package-lock.json
generated
129
package-lock.json
generated
@ -34,8 +34,9 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
|
"framer-motion": "^12.23.22",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"next": "15.4.6",
|
"next": "^15.5.4",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^9.9.0",
|
"react-day-picker": "^9.9.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@ -932,9 +933,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz",
|
||||||
"integrity": "sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ==",
|
"integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
@ -948,9 +949,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz",
|
||||||
"integrity": "sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ==",
|
"integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -964,9 +965,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz",
|
||||||
"integrity": "sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg==",
|
"integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -980,9 +981,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz",
|
||||||
"integrity": "sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw==",
|
"integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -996,9 +997,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz",
|
||||||
"integrity": "sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw==",
|
"integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -1012,9 +1013,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz",
|
||||||
"integrity": "sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA==",
|
"integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -1028,9 +1029,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz",
|
||||||
"integrity": "sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ==",
|
"integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -1044,9 +1045,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz",
|
||||||
"integrity": "sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg==",
|
"integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -1060,9 +1061,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz",
|
||||||
"integrity": "sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng==",
|
"integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4605,9 +4606,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.11.0",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@ -6124,6 +6125,33 @@
|
|||||||
"integrity": "sha512-0tLU0FOedVY7lrvN4LK0DVj6FTuYM0pWDpN97/8UTZE2lx1+OwX8+2uL7IOWc2PmktYTHQjMT6FvZZ3SGCdZdg==",
|
"integrity": "sha512-0tLU0FOedVY7lrvN4LK0DVj6FTuYM0pWDpN97/8UTZE2lx1+OwX8+2uL7IOWc2PmktYTHQjMT6FvZZ3SGCdZdg==",
|
||||||
"license": "CC0-1.0"
|
"license": "CC0-1.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.22.tgz",
|
||||||
|
"integrity": "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.21",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@ -7542,6 +7570,21 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.21.tgz",
|
||||||
|
"integrity": "sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -7590,12 +7633,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.4.6",
|
"version": "15.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
|
||||||
"integrity": "sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ==",
|
"integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.4.6",
|
"@next/env": "15.5.4",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@ -7608,14 +7651,14 @@
|
|||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.4.6",
|
"@next/swc-darwin-arm64": "15.5.4",
|
||||||
"@next/swc-darwin-x64": "15.4.6",
|
"@next/swc-darwin-x64": "15.5.4",
|
||||||
"@next/swc-linux-arm64-gnu": "15.4.6",
|
"@next/swc-linux-arm64-gnu": "15.5.4",
|
||||||
"@next/swc-linux-arm64-musl": "15.4.6",
|
"@next/swc-linux-arm64-musl": "15.5.4",
|
||||||
"@next/swc-linux-x64-gnu": "15.4.6",
|
"@next/swc-linux-x64-gnu": "15.5.4",
|
||||||
"@next/swc-linux-x64-musl": "15.4.6",
|
"@next/swc-linux-x64-musl": "15.5.4",
|
||||||
"@next/swc-win32-arm64-msvc": "15.4.6",
|
"@next/swc-win32-arm64-msvc": "15.5.4",
|
||||||
"@next/swc-win32-x64-msvc": "15.4.6",
|
"@next/swc-win32-x64-msvc": "15.5.4",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@ -35,8 +35,9 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
|
"framer-motion": "^12.23.22",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"next": "15.4.6",
|
"next": "^15.5.4",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-day-picker": "^9.9.0",
|
"react-day-picker": "^9.9.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
|
|||||||
116
src/app/api/ai/tech-recommendations/route.ts
Normal file
116
src/app/api/ai/tech-recommendations/route.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
console.log('🚀 Tech recommendations API called - redirecting to unified service');
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
const body = await request.json();
|
||||||
|
console.log('📊 Request body:', {
|
||||||
|
template: body.template?.title,
|
||||||
|
featuresCount: body.features?.length,
|
||||||
|
businessQuestionsCount: body.businessContext?.questions?.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!body.template || !body.features || !body.businessContext) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Missing required fields: template, features, or businessContext',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate template structure
|
||||||
|
if (!body.template.title || !body.template.category) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Template must have title and category',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate features array
|
||||||
|
if (!Array.isArray(body.features) || body.features.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Features must be a non-empty array',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate business context
|
||||||
|
if (!body.businessContext.questions || !Array.isArray(body.businessContext.questions)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Business context must have questions array',
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to unified service through API Gateway
|
||||||
|
const apiGatewayUrl = process.env.BACKEND_URL || 'https://backend.codenuk.com';
|
||||||
|
|
||||||
|
const response = await fetch(`${apiGatewayUrl}/api/unified/comprehensive-recommendations`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...body,
|
||||||
|
// Add optional parameters for template-based and domain-based recommendations
|
||||||
|
templateId: body.template.id,
|
||||||
|
budget: 15000, // Default budget - could be made configurable
|
||||||
|
domain: body.template.category?.toLowerCase() || 'general',
|
||||||
|
includeClaude: true,
|
||||||
|
includeTemplateBased: true,
|
||||||
|
includeDomainBased: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Unified service error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendations = await response.json();
|
||||||
|
console.log('✅ Comprehensive recommendations received from unified service:', {
|
||||||
|
success: recommendations.success,
|
||||||
|
hasClaudeRecommendations: !!recommendations.data?.claude?.success,
|
||||||
|
hasTemplateRecommendations: !!recommendations.data?.templateBased?.success,
|
||||||
|
hasDomainRecommendations: !!recommendations.data?.domainBased?.success,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the recommendations
|
||||||
|
return NextResponse.json(recommendations);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in tech recommendations API:', error);
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Internal server error',
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle OPTIONS request for CORS
|
||||||
|
export async function OPTIONS(request: NextRequest) {
|
||||||
|
return new NextResponse(null, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -87,6 +87,7 @@ export const logout = async () => {
|
|||||||
export const authApiClient = axios.create({
|
export const authApiClient = axios.create({
|
||||||
baseURL: BACKEND_URL,
|
baseURL: BACKEND_URL,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
timeout: 10000, // 10 second timeout
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add auth token to requests
|
// Add auth token to requests
|
||||||
@ -127,8 +128,34 @@ const addTokenRefreshInterceptor = (client: typeof authApiClient) => {
|
|||||||
try {
|
try {
|
||||||
const status = error?.response?.status
|
const status = error?.response?.status
|
||||||
const data = error?.response?.data
|
const data = error?.response?.data
|
||||||
console.error('🛑 API error:', { url: error?.config?.url, method: error?.config?.method, status, data })
|
const url = error?.config?.url
|
||||||
} catch (_) {}
|
const method = error?.config?.method
|
||||||
|
const message = error?.message || 'Unknown error'
|
||||||
|
const code = error?.code
|
||||||
|
|
||||||
|
// Check if it's a network connectivity issue
|
||||||
|
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') {
|
||||||
|
console.error('🛑 Network connectivity issue:', {
|
||||||
|
url: url || 'Unknown URL',
|
||||||
|
method: method || 'Unknown method',
|
||||||
|
code: code,
|
||||||
|
message: message
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error('🛑 API error:', {
|
||||||
|
url: url || 'Unknown URL',
|
||||||
|
method: method || 'Unknown method',
|
||||||
|
status: status || 'No status',
|
||||||
|
data: data || 'No response data',
|
||||||
|
message: message,
|
||||||
|
errorType: error?.name || 'Unknown error type',
|
||||||
|
code: code
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (debugError) {
|
||||||
|
console.error('🛑 Error logging failed:', debugError)
|
||||||
|
console.error('🛑 Original error:', error)
|
||||||
|
}
|
||||||
|
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
const isRefreshEndpoint = originalRequest?.url?.includes('/api/auth/refresh');
|
const isRefreshEndpoint = originalRequest?.url?.includes('/api/auth/refresh');
|
||||||
|
|||||||
332
src/components/business-context/typeform-survey.tsx
Normal file
332
src/components/business-context/typeform-survey.tsx
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
import { ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react'
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
id: number
|
||||||
|
question: string
|
||||||
|
answer: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeformSurveyProps {
|
||||||
|
questions: string[]
|
||||||
|
onComplete: (answers: Question[]) => void
|
||||||
|
onProgress?: (answers: Question[]) => void
|
||||||
|
onBack: () => void
|
||||||
|
projectName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TypeformSurvey({
|
||||||
|
questions,
|
||||||
|
onComplete,
|
||||||
|
onProgress,
|
||||||
|
onBack,
|
||||||
|
projectName = 'your project'
|
||||||
|
}: TypeformSurveyProps) {
|
||||||
|
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
|
||||||
|
const [answers, setAnswers] = useState<Record<number, string>>({})
|
||||||
|
const [isTransitioning, setIsTransitioning] = useState(false)
|
||||||
|
|
||||||
|
const totalQuestions = questions.length
|
||||||
|
const progress = ((currentQuestionIndex + 1) / totalQuestions) * 100
|
||||||
|
const answeredCount = Object.values(answers).filter(answer => answer.trim()).length
|
||||||
|
|
||||||
|
// Initialize answers
|
||||||
|
useEffect(() => {
|
||||||
|
const initialAnswers: Record<number, string> = {}
|
||||||
|
questions.forEach((_, index) => {
|
||||||
|
initialAnswers[index] = ''
|
||||||
|
})
|
||||||
|
setAnswers(initialAnswers)
|
||||||
|
}, [questions])
|
||||||
|
|
||||||
|
const handleAnswerChange = (value: string) => {
|
||||||
|
const newAnswers = {
|
||||||
|
...answers,
|
||||||
|
[currentQuestionIndex]: value
|
||||||
|
}
|
||||||
|
setAnswers(newAnswers)
|
||||||
|
|
||||||
|
// Call onProgress callback if provided
|
||||||
|
if (onProgress) {
|
||||||
|
const questionAnswers: Question[] = questions.map((question, index) => ({
|
||||||
|
id: index,
|
||||||
|
question,
|
||||||
|
answer: newAnswers[index] || ''
|
||||||
|
}))
|
||||||
|
onProgress(questionAnswers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToNext = useCallback(() => {
|
||||||
|
if (currentQuestionIndex < totalQuestions - 1) {
|
||||||
|
setIsTransitioning(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrentQuestionIndex(prev => prev + 1)
|
||||||
|
setIsTransitioning(false)
|
||||||
|
}, 150)
|
||||||
|
} else {
|
||||||
|
// Submit all answers
|
||||||
|
const questionAnswers: Question[] = questions.map((question, index) => ({
|
||||||
|
id: index,
|
||||||
|
question,
|
||||||
|
answer: answers[index] || ''
|
||||||
|
}))
|
||||||
|
onComplete(questionAnswers)
|
||||||
|
}
|
||||||
|
}, [currentQuestionIndex, totalQuestions, answers, questions, onComplete])
|
||||||
|
|
||||||
|
const goToPrevious = useCallback(() => {
|
||||||
|
if (currentQuestionIndex > 0) {
|
||||||
|
setIsTransitioning(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrentQuestionIndex(prev => prev - 1)
|
||||||
|
setIsTransitioning(false)
|
||||||
|
}, 150)
|
||||||
|
} else {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
}, [currentQuestionIndex, onBack])
|
||||||
|
|
||||||
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
goToNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentAnswer = answers[currentQuestionIndex] || ''
|
||||||
|
const canProceed = currentAnswer.trim().length > 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black text-white relative overflow-hidden">
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="bg-black/80 backdrop-blur-sm border-b border-white/10">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-4">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div className="text-sm text-white/60">
|
||||||
|
Question {currentQuestionIndex + 1} of {totalQuestions}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-white/60">
|
||||||
|
{Math.round(progress)}% complete
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-white/10 rounded-full h-2">
|
||||||
|
<motion.div
|
||||||
|
className="bg-orange-500 h-2 rounded-full"
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
animate={{ width: `${progress}%` }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="pt-4 pb-16 px-6">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={currentQuestionIndex}
|
||||||
|
initial={{ opacity: 0, x: 50 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -50 }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||||
|
className="flex flex-col pt-8"
|
||||||
|
>
|
||||||
|
{/* Question */}
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.1, duration: 0.4 }}
|
||||||
|
className="text-xl font-semibold leading-tight mb-4"
|
||||||
|
>
|
||||||
|
{questions[currentQuestionIndex]}
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.4 }}
|
||||||
|
className="text-base text-white/60"
|
||||||
|
>
|
||||||
|
Help us understand your {projectName} better
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Answer Input */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.3, duration: 0.4 }}
|
||||||
|
className="w-full mx-auto mb-6"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
value={currentAnswer}
|
||||||
|
onChange={(e) => handleAnswerChange(e.target.value)}
|
||||||
|
onKeyDown={handleKeyPress}
|
||||||
|
placeholder="Type your answer here..."
|
||||||
|
className="w-full bg-white/5 border border-white/20 rounded-lg p-3 text-white placeholder:text-white/40 focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 resize-none h-24 text-base leading-relaxed"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.4 }}
|
||||||
|
className="flex items-center justify-between mt-6 max-w-6xl mx-auto w-full"
|
||||||
|
>
|
||||||
|
{/* Back Button */}
|
||||||
|
<button
|
||||||
|
onClick={goToPrevious}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg transition-all duration-200 group"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||||
|
<span>{currentQuestionIndex === 0 ? 'Back to Features' : 'Previous'}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Next Button */}
|
||||||
|
<button
|
||||||
|
onClick={goToNext}
|
||||||
|
disabled={!canProceed || isTransitioning}
|
||||||
|
className={`flex items-center gap-2 px-6 py-2 rounded-lg transition-all duration-200 group ${
|
||||||
|
canProceed
|
||||||
|
? 'bg-orange-500 hover:bg-orange-600 text-white'
|
||||||
|
: 'bg-white/10 text-white/40 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{currentQuestionIndex === totalQuestions - 1 ? 'Submit' : 'Next'}</span>
|
||||||
|
{currentQuestionIndex === totalQuestions - 1 ? (
|
||||||
|
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Progress Indicator */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.5, duration: 0.4 }}
|
||||||
|
className="text-center mt-4"
|
||||||
|
>
|
||||||
|
<div className="text-sm text-white/40">
|
||||||
|
{answeredCount} of {totalQuestions} questions answered
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Background Elements */}
|
||||||
|
<div className="fixed inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-orange-500/5 rounded-full blur-3xl" />
|
||||||
|
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-orange-500/3 rounded-full blur-3xl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary Component for final review
|
||||||
|
interface SummaryProps {
|
||||||
|
questions: Question[]
|
||||||
|
onBack: () => void
|
||||||
|
onSubmit: () => void
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SurveySummary({ questions, onBack, onSubmit, loading = false }: SummaryProps) {
|
||||||
|
const answeredQuestions = questions.filter(q => q.answer.trim())
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black text-white">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="border-b border-white/10 bg-black/80 backdrop-blur-sm">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-4">
|
||||||
|
<h1 className="text-xl font-semibold text-center">Review Your Answers</h1>
|
||||||
|
<p className="text-center text-white/60 mt-1 text-sm">
|
||||||
|
Please review your responses before submitting
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Content */}
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-6">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
{answeredQuestions.map((question, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={question.id}
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: index * 0.05 }}
|
||||||
|
className="bg-white/5 border border-white/10 rounded-lg p-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="bg-orange-500/20 text-orange-300 rounded-full w-6 h-6 flex items-center justify-center text-xs font-semibold flex-shrink-0 mt-0.5">
|
||||||
|
{question.id + 1}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-sm font-medium text-white mb-2">
|
||||||
|
{question.question}
|
||||||
|
</h3>
|
||||||
|
<p className="text-white/80 leading-relaxed text-sm">
|
||||||
|
{question.answer}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.3 }}
|
||||||
|
className="flex items-center justify-between mt-6"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={onBack}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg transition-all duration-200"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
<span>Edit Answers</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onSubmit}
|
||||||
|
disabled={loading}
|
||||||
|
className={`flex items-center gap-2 px-6 py-2 rounded-lg transition-all duration-200 group ${
|
||||||
|
loading
|
||||||
|
? 'bg-orange-500/50 text-white cursor-not-allowed'
|
||||||
|
: 'bg-orange-500 hover:bg-orange-600 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||||
|
<span>Generating Recommendations...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span>Submit & Generate Recommendations</span>
|
||||||
|
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -18,12 +18,14 @@ import AICustomFeatureCreator from "@/components/ai/AICustomFeatureCreator"
|
|||||||
import { BACKEND_URL } from "@/config/backend"
|
import { BACKEND_URL } from "@/config/backend"
|
||||||
// Removed Tooltip import as we are no longer using tooltips for title/description
|
// Removed Tooltip import as we are no longer using tooltips for title/description
|
||||||
import WireframeCanvas from "@/components/wireframe-canvas"
|
import WireframeCanvas from "@/components/wireframe-canvas"
|
||||||
|
import TypeformSurvey, { SurveySummary } from "@/components/business-context/typeform-survey"
|
||||||
import PromptSidePanel from "@/components/prompt-side-panel"
|
import PromptSidePanel from "@/components/prompt-side-panel"
|
||||||
import { DualCanvasEditor } from "@/components/dual-canvas-editor"
|
import { DualCanvasEditor } from "@/components/dual-canvas-editor"
|
||||||
import { getAccessToken } from "@/components/apis/authApiClients"
|
import { getAccessToken } from "@/components/apis/authApiClients"
|
||||||
import TechStackSummary from "@/components/tech-stack-summary"
|
import TechStackSummary from "@/components/tech-stack-summary"
|
||||||
import { attachRepository, getGitHubAuthStatus } from "@/lib/api/github"
|
import { attachRepository, getGitHubAuthStatus } from "@/lib/api/github"
|
||||||
import ViewUserReposButton from "@/components/github/ViewUserReposButton"
|
import ViewUserReposButton from "@/components/github/ViewUserReposButton"
|
||||||
|
import { ErrorBanner } from "@/components/ui/error-banner"
|
||||||
|
|
||||||
interface Template {
|
interface Template {
|
||||||
id: string
|
id: string
|
||||||
@ -70,13 +72,24 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
|||||||
const [authUrl, setAuthUrl] = useState('')
|
const [authUrl, setAuthUrl] = useState('')
|
||||||
const [isGeneratingAuth, setIsGeneratingAuth] = useState(false)
|
const [isGeneratingAuth, setIsGeneratingAuth] = useState(false)
|
||||||
const [isGithubConnected, setIsGithubConnected] = useState<boolean | null>(null)
|
const [isGithubConnected, setIsGithubConnected] = useState<boolean | null>(null)
|
||||||
|
const [connectionError, setConnectionError] = useState<string | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
setConnectionError(null)
|
||||||
const status = await getGitHubAuthStatus()
|
const status = await getGitHubAuthStatus()
|
||||||
setIsGithubConnected(!!status?.data?.connected)
|
setIsGithubConnected(!!status?.data?.connected)
|
||||||
} catch {
|
} catch (error: any) {
|
||||||
|
console.warn('Failed to check GitHub auth status:', error)
|
||||||
setIsGithubConnected(false)
|
setIsGithubConnected(false)
|
||||||
|
|
||||||
|
// Check if it's a connectivity issue
|
||||||
|
if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') {
|
||||||
|
setConnectionError('Unable to connect to the server. Please ensure the backend is running.')
|
||||||
|
} else if (error?.response?.status >= 500) {
|
||||||
|
setConnectionError('Server error. Please try again later.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
@ -758,6 +771,30 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
|||||||
<p className="text-xl text-white/60 max-w-3xl mx-auto">
|
<p className="text-xl text-white/60 max-w-3xl mx-auto">
|
||||||
Select from our comprehensive library of professionally designed templates
|
Select from our comprehensive library of professionally designed templates
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Connection Error Banner */}
|
||||||
|
{connectionError && (
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<ErrorBanner
|
||||||
|
title="Connection Issue"
|
||||||
|
message={connectionError}
|
||||||
|
onRetry={async () => {
|
||||||
|
setConnectionError(null)
|
||||||
|
// Retry the GitHub auth status check
|
||||||
|
try {
|
||||||
|
const status = await getGitHubAuthStatus()
|
||||||
|
setIsGithubConnected(!!status?.data?.connected)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn('Retry failed:', error)
|
||||||
|
setIsGithubConnected(false)
|
||||||
|
if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') {
|
||||||
|
setConnectionError('Unable to connect to the server. Please ensure the backend is running.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!user?.id && (
|
{!user?.id && (
|
||||||
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 max-w-2xl mx-auto">
|
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 max-w-2xl mx-auto">
|
||||||
<p className="text-orange-300 text-sm">
|
<p className="text-orange-300 text-sm">
|
||||||
@ -1699,10 +1736,12 @@ function BusinessQuestionsStep({
|
|||||||
onDone,
|
onDone,
|
||||||
}: { template: Template; selected: TemplateFeature[]; onBack: () => void; onDone: (completeData: any, recommendations: any) => void }) {
|
}: { template: Template; selected: TemplateFeature[]; onBack: () => void; onDone: (completeData: any, recommendations: any) => void }) {
|
||||||
const [businessQuestions, setBusinessQuestions] = useState<string[]>([])
|
const [businessQuestions, setBusinessQuestions] = useState<string[]>([])
|
||||||
const [businessAnswers, setBusinessAnswers] = useState<Record<number, string>>({})
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [submitting, setSubmitting] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [showSummary, setShowSummary] = useState(false)
|
||||||
|
const [answers, setAnswers] = useState<any[]>([])
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
const [answeredQuestionsCount, setAnsweredQuestionsCount] = useState(0)
|
||||||
const selectedKey = selected.map(s => s.id).join(',')
|
const selectedKey = selected.map(s => s.id).join(',')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1729,9 +1768,6 @@ function BusinessQuestionsStep({
|
|||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
const qs: string[] = data?.data?.businessQuestions || []
|
const qs: string[] = data?.data?.businessQuestions || []
|
||||||
setBusinessQuestions(qs)
|
setBusinessQuestions(qs)
|
||||||
const init: Record<number, string> = {}
|
|
||||||
qs.forEach((_, i) => (init[i] = ''))
|
|
||||||
setBusinessAnswers(init)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e?.message || 'Failed to load questions')
|
setError(e?.message || 'Failed to load questions')
|
||||||
} finally {
|
} finally {
|
||||||
@ -1741,7 +1777,91 @@ function BusinessQuestionsStep({
|
|||||||
load()
|
load()
|
||||||
}, [template.id, selectedKey])
|
}, [template.id, selectedKey])
|
||||||
|
|
||||||
const answeredCount = Object.values(businessAnswers).filter((a) => a && a.trim()).length
|
const handleSurveyComplete = (surveyAnswers: any[]) => {
|
||||||
|
setAnswers(surveyAnswers)
|
||||||
|
setAnsweredQuestionsCount(surveyAnswers.length)
|
||||||
|
setShowSummary(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSurveyProgress = (currentAnswers: any[]) => {
|
||||||
|
const answeredCount = currentAnswers.filter(answer => answer.answer && answer.answer.trim().length > 0).length
|
||||||
|
setAnsweredQuestionsCount(answeredCount)
|
||||||
|
// Store the current answers so they're available for submission
|
||||||
|
setAnswers(currentAnswers)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
setSubmitting(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
const token = getAccessToken()
|
||||||
|
// Filter to only include answered questions
|
||||||
|
const answeredQuestions = answers.filter(answer => answer.answer && answer.answer.trim().length > 0)
|
||||||
|
const questionsWithAnswers = answeredQuestions.map(answer => ({
|
||||||
|
question: answer.question,
|
||||||
|
answer: answer.answer,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const completeData = {
|
||||||
|
template,
|
||||||
|
features: selected,
|
||||||
|
businessContext: {
|
||||||
|
questions: questionsWithAnswers,
|
||||||
|
},
|
||||||
|
projectName: template.title,
|
||||||
|
projectType: template.type || template.category,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 Submitting comprehensive recommendations request...')
|
||||||
|
console.log('📊 Total answers received:', answers.length)
|
||||||
|
console.log('📊 Answered questions:', answeredQuestions.length)
|
||||||
|
console.log('📊 Questions with answers:', questionsWithAnswers)
|
||||||
|
console.log('📊 Complete data:', completeData)
|
||||||
|
|
||||||
|
const response = await fetch(`${BACKEND_URL}/api/unified/comprehensive-recommendations`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
template,
|
||||||
|
features: selected,
|
||||||
|
businessContext: {
|
||||||
|
questions: questionsWithAnswers,
|
||||||
|
},
|
||||||
|
projectName: template.title,
|
||||||
|
projectType: template.type || template.category,
|
||||||
|
templateId: template.id,
|
||||||
|
budget: 15000, // Default budget - could be made configurable
|
||||||
|
domain: template.category?.toLowerCase() || 'general',
|
||||||
|
includeClaude: true,
|
||||||
|
includeTemplateBased: true,
|
||||||
|
includeDomainBased: true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendations = await response.json()
|
||||||
|
console.log('✅ Comprehensive recommendations received:', recommendations)
|
||||||
|
|
||||||
|
// Extract the tech recommendations from the response for the frontend component
|
||||||
|
const techRecommendations = recommendations?.data?.claude?.data?.claude_recommendations || null
|
||||||
|
console.log('🎯 Extracted tech recommendations:', techRecommendations)
|
||||||
|
|
||||||
|
// Proceed to next step with complete data and recommendations
|
||||||
|
onDone(completeData, techRecommendations)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ Error submitting business context:', error)
|
||||||
|
setError(error?.message || 'Failed to submit business context')
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -1770,89 +1890,68 @@ function BusinessQuestionsStep({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
if (showSummary) {
|
||||||
try {
|
return (
|
||||||
setSubmitting(true)
|
<SurveySummary
|
||||||
const answeredCount = Object.values(businessAnswers).filter((a) => a && a.trim()).length
|
questions={answers}
|
||||||
if (answeredCount === 0) return
|
onBack={() => setShowSummary(false)}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
const completeData = {
|
loading={submitting}
|
||||||
projectName: template.title,
|
/>
|
||||||
projectType: template.type || template.category,
|
)
|
||||||
allFeatures: selected,
|
|
||||||
businessQuestions,
|
|
||||||
businessAnswers,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
featureName: `${template.title} - Integrated System`,
|
|
||||||
description: `Complete ${template.type || template.category} system with ${selected.length} integrated features`,
|
|
||||||
requirements: (selected as any[]).flatMap((f: any) => f.requirements || []),
|
|
||||||
complexity:
|
|
||||||
(selected as any[]).some((f: any) => f.complexity === 'high')
|
|
||||||
? 'high'
|
|
||||||
: (selected as any[]).some((f: any) => f.complexity === 'medium')
|
|
||||||
? 'medium'
|
|
||||||
: 'low',
|
|
||||||
logicRules: (selected as any[]).flatMap((f: any) => f.logicRules || []),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Store business context Q&A responses in backend (temporarily disabled)
|
|
||||||
console.log('📝 Business context answers:', businessAnswers)
|
|
||||||
|
|
||||||
// Proceed directly to next step with complete data
|
|
||||||
onDone(completeData, null)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Tech stack selection failed', e)
|
|
||||||
} finally {
|
|
||||||
setSubmitting(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="max-w-6xl mx-auto space-y-4">
|
||||||
<div className="text-center space-y-1">
|
<div className="text-center space-y-2">
|
||||||
<h2 className="text-2xl font-bold text-white">Business Context Questions</h2>
|
<h1 className="text-3xl font-bold text-white">Business Context Questions</h1>
|
||||||
<p className="text-white/60">Help us refine recommendations by answering these questions.</p>
|
<p className="text-lg text-white/60 max-w-2xl mx-auto">
|
||||||
<p className="text-sm text-orange-400">Analyzing {selected.length} integrated features</p>
|
Help us understand your {template.title} project better with these AI-generated questions
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<TypeformSurvey
|
||||||
{businessQuestions.map((q, i) => (
|
questions={businessQuestions}
|
||||||
<div key={i} className="bg-white/5 border border-white/10 rounded-lg p-4">
|
onComplete={handleSurveyComplete}
|
||||||
<label className="block text-sm font-medium text-white mb-2">
|
onProgress={handleSurveyProgress}
|
||||||
<span className="inline-flex items-center gap-2">
|
onBack={onBack}
|
||||||
<span className="bg-orange-500/20 text-orange-300 rounded-full w-6 h-6 flex items-center justify-center text-xs font-semibold">{i + 1}</span>
|
projectName={template.title}
|
||||||
<span>{q}</span>
|
/>
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
rows={3}
|
|
||||||
value={businessAnswers[i] || ''}
|
|
||||||
onChange={(e) => setBusinessAnswers((prev) => ({ ...prev, [i]: e.target.value }))}
|
|
||||||
className="w-full bg-white/10 border border-white/20 text-white rounded-md p-3 placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-orange-400/50"
|
|
||||||
placeholder="Your answer..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white/5 border border-white/10 rounded-lg p-4 text-white/80">
|
{/* Navigation buttons */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
<div className="text-center py-4">
|
||||||
<div>Questions answered: {answeredCount} of {businessQuestions.length}</div>
|
|
||||||
<div>Completion: {businessQuestions.length ? Math.round((answeredCount / businessQuestions.length) * 100) : 0}%</div>
|
|
||||||
<div>Features analyzing: {selected.length}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center pt-2">
|
|
||||||
<div className="space-x-4">
|
<div className="space-x-4">
|
||||||
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button>
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
disabled={submitting || answeredCount === 0}
|
onClick={onBack}
|
||||||
onClick={handleSubmit}
|
className="border-white/20 text-white hover:bg-white/10 cursor-pointer"
|
||||||
className={`bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow ${submitting || answeredCount === 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
||||||
>
|
>
|
||||||
{submitting ? 'Getting Recommendations...' : 'Generate Technology Recommendations'}
|
← Back to Features
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={submitting || answeredQuestionsCount < 3}
|
||||||
|
className={`bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow cursor-pointer ${
|
||||||
|
submitting || answeredQuestionsCount < 3 ? 'opacity-50 cursor-not-allowed' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{submitting ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-black"></div>
|
||||||
|
Generating Tech Stack...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Generate Tech Stack →
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="text-white/60 text-sm mt-2">
|
||||||
|
{answeredQuestionsCount < 3
|
||||||
|
? `Answer at least 3 questions to generate your tech stack recommendations (${answeredQuestionsCount}/3 answered)`
|
||||||
|
: `Ready to generate tech stack for ${template.title} (${answeredQuestionsCount} questions answered)`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -121,7 +121,7 @@ function DialogDescription({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Description
|
<div
|
||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
42
src/components/ui/error-banner.tsx
Normal file
42
src/components/ui/error-banner.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { AlertCircle, RefreshCw } from "lucide-react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
|
|
||||||
|
interface ErrorBannerProps {
|
||||||
|
title?: string
|
||||||
|
message?: string
|
||||||
|
onRetry?: () => void
|
||||||
|
showRetry?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorBanner({
|
||||||
|
title = "Connection Issue",
|
||||||
|
message = "Unable to connect to the server. Please check your internet connection and try again.",
|
||||||
|
onRetry,
|
||||||
|
showRetry = true
|
||||||
|
}: ErrorBannerProps) {
|
||||||
|
return (
|
||||||
|
<Card className="border-red-200 bg-red-50">
|
||||||
|
<CardContent className="flex items-center gap-3 p-4">
|
||||||
|
<AlertCircle className="h-5 w-5 text-red-600" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium text-red-800">{title}</h3>
|
||||||
|
<p className="text-sm text-red-700">{message}</p>
|
||||||
|
</div>
|
||||||
|
{showRetry && onRetry && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onRetry}
|
||||||
|
className="border-red-300 text-red-700 hover:bg-red-100"
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
|
Retry
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
// export const BACKEND_URL = 'https://backend.codenuk.com';
|
export const BACKEND_URL = 'https://backend.codenuk.com';
|
||||||
|
|
||||||
export const BACKEND_URL = 'http://localhost:8000';
|
|
||||||
|
|
||||||
export const SOCKET_URL = BACKEND_URL;
|
export const SOCKET_URL = BACKEND_URL;
|
||||||
|
|
||||||
|
|||||||
@ -156,11 +156,24 @@ export interface GitHubAuthStatusData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getGitHubAuthStatus(): Promise<AttachRepositoryResponse<GitHubAuthStatusData>> {
|
export async function getGitHubAuthStatus(): Promise<AttachRepositoryResponse<GitHubAuthStatusData>> {
|
||||||
const rawUser = (typeof window !== 'undefined') ? localStorage.getItem('codenuk_user') : null
|
try {
|
||||||
const userId = rawUser ? (JSON.parse(rawUser)?.id || null) : null
|
const rawUser = (typeof window !== 'undefined') ? localStorage.getItem('codenuk_user') : null
|
||||||
const url = userId ? `/api/github/auth/github/status?user_id=${encodeURIComponent(userId)}` : '/api/github/auth/github/status'
|
const userId = rawUser ? (JSON.parse(rawUser)?.id || null) : null
|
||||||
const response = await authApiClient.get(url)
|
const url = userId ? `/api/github/auth/github/status?user_id=${encodeURIComponent(userId)}` : '/api/github/auth/github/status'
|
||||||
return response.data as AttachRepositoryResponse<GitHubAuthStatusData>
|
const response = await authApiClient.get(url)
|
||||||
|
return response.data as AttachRepositoryResponse<GitHubAuthStatusData>
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('GitHub auth status check failed:', error)
|
||||||
|
// Return a default response indicating no connection
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to check GitHub auth status',
|
||||||
|
data: {
|
||||||
|
connected: false,
|
||||||
|
requires_auth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
430
src/services/claudeTechRecommendations.ts
Normal file
430
src/services/claudeTechRecommendations.ts
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
import Anthropic from '@anthropic-ai/sdk';
|
||||||
|
|
||||||
|
// Initialize Claude API client
|
||||||
|
const anthropic = new Anthropic({
|
||||||
|
apiKey: process.env.NEXT_PUBLIC_CLAUDE_API_KEY || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface TemplateData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FeatureData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
feature_type: 'essential' | 'suggested' | 'custom';
|
||||||
|
complexity: 'low' | 'medium' | 'high';
|
||||||
|
business_rules?: any;
|
||||||
|
technical_requirements?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BusinessContextData {
|
||||||
|
questions: Array<{
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TechRecommendationRequest {
|
||||||
|
template: TemplateData;
|
||||||
|
features: FeatureData[];
|
||||||
|
businessContext: BusinessContextData;
|
||||||
|
projectName?: string;
|
||||||
|
projectType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TechnologyRecommendations {
|
||||||
|
frontend: {
|
||||||
|
framework: string;
|
||||||
|
libraries: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
backend: {
|
||||||
|
language: string;
|
||||||
|
framework: string;
|
||||||
|
libraries: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
database: {
|
||||||
|
primary: string;
|
||||||
|
secondary: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
mobile: {
|
||||||
|
framework: string;
|
||||||
|
libraries: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
devops: {
|
||||||
|
tools: string[];
|
||||||
|
platforms: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
tools: {
|
||||||
|
development: string[];
|
||||||
|
monitoring: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
ai_ml: {
|
||||||
|
frameworks: string[];
|
||||||
|
libraries: string[];
|
||||||
|
reasoning: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClaudeRecommendations {
|
||||||
|
technology_recommendations: TechnologyRecommendations;
|
||||||
|
implementation_strategy: {
|
||||||
|
architecture_pattern: string;
|
||||||
|
deployment_strategy: string;
|
||||||
|
scalability_approach: string;
|
||||||
|
};
|
||||||
|
business_alignment: {
|
||||||
|
scalability: string;
|
||||||
|
maintainability: string;
|
||||||
|
cost_effectiveness: string;
|
||||||
|
time_to_market: string;
|
||||||
|
};
|
||||||
|
risk_assessment: {
|
||||||
|
technical_risks: string[];
|
||||||
|
mitigation_strategies: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TechRecommendationResponse {
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
claude_recommendations: ClaudeRecommendations;
|
||||||
|
functional_requirements: {
|
||||||
|
feature_name: string;
|
||||||
|
description: string;
|
||||||
|
complexity_level: string;
|
||||||
|
technical_requirements: string[];
|
||||||
|
business_logic_rules: string[];
|
||||||
|
all_features: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate technology recommendations using Claude AI
|
||||||
|
*/
|
||||||
|
export async function generateTechRecommendations(
|
||||||
|
request: TechRecommendationRequest
|
||||||
|
): Promise<TechRecommendationResponse> {
|
||||||
|
try {
|
||||||
|
console.log('🤖 Generating tech recommendations with Claude...');
|
||||||
|
console.log('📊 Request data:', {
|
||||||
|
template: request.template.title,
|
||||||
|
featuresCount: request.features.length,
|
||||||
|
businessQuestionsCount: request.businessContext.questions.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build comprehensive prompt for Claude
|
||||||
|
const prompt = buildTechRecommendationPrompt(request);
|
||||||
|
|
||||||
|
// Call Claude API
|
||||||
|
const response = await anthropic.messages.create({
|
||||||
|
model: 'claude-3-5-sonnet-20241022',
|
||||||
|
max_tokens: 4000,
|
||||||
|
temperature: 0.7,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse Claude's response
|
||||||
|
const claudeResponse = response.content[0];
|
||||||
|
if (claudeResponse.type !== 'text') {
|
||||||
|
throw new Error('Unexpected response type from Claude');
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendations = parseClaudeResponse(claudeResponse.text, request);
|
||||||
|
|
||||||
|
console.log('✅ Tech recommendations generated successfully');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: recommendations,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error generating tech recommendations:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
data: {
|
||||||
|
claude_recommendations: getFallbackRecommendations(),
|
||||||
|
functional_requirements: getFallbackFunctionalRequirements(request),
|
||||||
|
},
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build comprehensive prompt for Claude AI
|
||||||
|
*/
|
||||||
|
function buildTechRecommendationPrompt(request: TechRecommendationRequest): string {
|
||||||
|
const { template, features, businessContext, projectName, projectType } = request;
|
||||||
|
|
||||||
|
// Extract feature information
|
||||||
|
const featureNames = features.map(f => f.name).join(', ');
|
||||||
|
const featureDescriptions = features.map(f => `- ${f.name}: ${f.description}`).join('\n');
|
||||||
|
const complexityLevels = features.map(f => f.complexity);
|
||||||
|
const hasHighComplexity = complexityLevels.includes('high');
|
||||||
|
const hasMediumComplexity = complexityLevels.includes('medium');
|
||||||
|
|
||||||
|
// Extract business context
|
||||||
|
const businessAnswers = businessContext.questions
|
||||||
|
.map(qa => `Q: ${qa.question}\nA: ${qa.answer}`)
|
||||||
|
.join('\n\n');
|
||||||
|
|
||||||
|
return `You are an expert technology architect and consultant. Analyze the following project requirements and provide comprehensive technology recommendations.
|
||||||
|
|
||||||
|
PROJECT OVERVIEW:
|
||||||
|
- Project Name: ${projectName || template.title}
|
||||||
|
- Project Type: ${projectType || template.category}
|
||||||
|
- Template: ${template.title} - ${template.description}
|
||||||
|
|
||||||
|
SELECTED FEATURES (${features.length} total):
|
||||||
|
${featureDescriptions}
|
||||||
|
|
||||||
|
COMPLEXITY ANALYSIS:
|
||||||
|
- High complexity features: ${hasHighComplexity ? 'Yes' : 'No'}
|
||||||
|
- Medium complexity features: ${hasMediumComplexity ? 'Yes' : 'No'}
|
||||||
|
- Overall complexity: ${hasHighComplexity ? 'High' : hasMediumComplexity ? 'Medium' : 'Low'}
|
||||||
|
|
||||||
|
BUSINESS CONTEXT:
|
||||||
|
${businessAnswers}
|
||||||
|
|
||||||
|
Please provide a comprehensive technology recommendation in the following JSON format:
|
||||||
|
|
||||||
|
{
|
||||||
|
"technology_recommendations": {
|
||||||
|
"frontend": {
|
||||||
|
"framework": "Recommended frontend framework",
|
||||||
|
"libraries": ["library1", "library2"],
|
||||||
|
"reasoning": "Detailed explanation for frontend choice"
|
||||||
|
},
|
||||||
|
"backend": {
|
||||||
|
"language": "Recommended backend language",
|
||||||
|
"framework": "Recommended backend framework",
|
||||||
|
"libraries": ["library1", "library2"],
|
||||||
|
"reasoning": "Detailed explanation for backend choice"
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"primary": "Primary database recommendation",
|
||||||
|
"secondary": ["secondary1", "secondary2"],
|
||||||
|
"reasoning": "Detailed explanation for database choice"
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"framework": "Recommended mobile framework (if applicable)",
|
||||||
|
"libraries": ["library1", "library2"],
|
||||||
|
"reasoning": "Detailed explanation for mobile choice"
|
||||||
|
},
|
||||||
|
"devops": {
|
||||||
|
"tools": ["tool1", "tool2"],
|
||||||
|
"platforms": ["platform1", "platform2"],
|
||||||
|
"reasoning": "Detailed explanation for DevOps choices"
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"development": ["dev_tool1", "dev_tool2"],
|
||||||
|
"monitoring": ["monitoring_tool1", "monitoring_tool2"],
|
||||||
|
"reasoning": "Detailed explanation for tool choices"
|
||||||
|
},
|
||||||
|
"ai_ml": {
|
||||||
|
"frameworks": ["framework1", "framework2"],
|
||||||
|
"libraries": ["library1", "library2"],
|
||||||
|
"reasoning": "Detailed explanation for AI/ML choices"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"implementation_strategy": {
|
||||||
|
"architecture_pattern": "Recommended architecture pattern (e.g., MVC, Microservices, etc.)",
|
||||||
|
"deployment_strategy": "Recommended deployment approach",
|
||||||
|
"scalability_approach": "How to handle scaling"
|
||||||
|
},
|
||||||
|
"business_alignment": {
|
||||||
|
"scalability": "How the tech stack supports scalability",
|
||||||
|
"maintainability": "How the tech stack supports maintainability",
|
||||||
|
"cost_effectiveness": "Cost considerations and optimization",
|
||||||
|
"time_to_market": "How the tech stack affects development speed"
|
||||||
|
},
|
||||||
|
"risk_assessment": {
|
||||||
|
"technical_risks": ["risk1", "risk2"],
|
||||||
|
"mitigation_strategies": ["strategy1", "strategy2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSIDERATIONS:
|
||||||
|
1. Choose technologies that work well together
|
||||||
|
2. Consider the complexity level of features
|
||||||
|
3. Factor in business requirements from the context
|
||||||
|
4. Prioritize scalability and maintainability
|
||||||
|
5. Consider developer experience and community support
|
||||||
|
6. Balance performance with development speed
|
||||||
|
7. Include modern, actively maintained technologies
|
||||||
|
|
||||||
|
Provide only the JSON response, no additional text.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Claude's response and structure it properly
|
||||||
|
*/
|
||||||
|
function parseClaudeResponse(responseText: string, request: TechRecommendationRequest): any {
|
||||||
|
try {
|
||||||
|
// Extract JSON from response (handle cases where Claude adds extra text)
|
||||||
|
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
||||||
|
if (!jsonMatch) {
|
||||||
|
throw new Error('No JSON found in Claude response');
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = JSON.parse(jsonMatch[0]);
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!parsed.technology_recommendations) {
|
||||||
|
throw new Error('Missing technology_recommendations in response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build functional requirements from the request
|
||||||
|
const functionalRequirements = {
|
||||||
|
feature_name: `${request.template.title} - Integrated System`,
|
||||||
|
description: `Complete ${request.template.category} system with ${request.features.length} integrated features`,
|
||||||
|
complexity_level: request.features.some(f => f.complexity === 'high') ? 'high' :
|
||||||
|
request.features.some(f => f.complexity === 'medium') ? 'medium' : 'low',
|
||||||
|
technical_requirements: request.features.flatMap(f => f.technical_requirements || []),
|
||||||
|
business_logic_rules: request.features.flatMap(f => f.business_rules || []),
|
||||||
|
all_features: request.features.map(f => f.name),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
claude_recommendations: parsed,
|
||||||
|
functional_requirements: functionalRequirements,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing Claude response:', error);
|
||||||
|
throw new Error('Failed to parse Claude response');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback recommendations when Claude API fails
|
||||||
|
*/
|
||||||
|
function getFallbackRecommendations(): ClaudeRecommendations {
|
||||||
|
return {
|
||||||
|
technology_recommendations: {
|
||||||
|
frontend: {
|
||||||
|
framework: 'React',
|
||||||
|
libraries: ['TypeScript', 'Tailwind CSS', 'React Router'],
|
||||||
|
reasoning: 'React provides excellent component reusability and ecosystem support for modern web applications.',
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
language: 'Node.js',
|
||||||
|
framework: 'Express.js',
|
||||||
|
libraries: ['TypeScript', 'Prisma', 'JWT'],
|
||||||
|
reasoning: 'Node.js offers great performance and JavaScript ecosystem consistency between frontend and backend.',
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
primary: 'PostgreSQL',
|
||||||
|
secondary: ['Redis'],
|
||||||
|
reasoning: 'PostgreSQL provides robust ACID compliance and excellent performance for complex applications.',
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
framework: 'React Native',
|
||||||
|
libraries: ['Expo', 'React Navigation', 'AsyncStorage'],
|
||||||
|
reasoning: 'React Native enables cross-platform mobile development with shared codebase and native performance.',
|
||||||
|
},
|
||||||
|
devops: {
|
||||||
|
tools: ['Docker', 'GitHub Actions', 'Kubernetes'],
|
||||||
|
platforms: ['AWS', 'Vercel'],
|
||||||
|
reasoning: 'Modern DevOps stack for containerization, CI/CD, and cloud deployment with excellent scalability.',
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
development: ['VS Code', 'Git', 'ESLint', 'Prettier'],
|
||||||
|
monitoring: ['Sentry', 'LogRocket', 'New Relic'],
|
||||||
|
reasoning: 'Essential development tools for code quality and comprehensive monitoring for production applications.',
|
||||||
|
},
|
||||||
|
ai_ml: {
|
||||||
|
frameworks: ['TensorFlow.js', 'OpenAI API'],
|
||||||
|
libraries: ['NumPy', 'Pandas', 'Scikit-learn'],
|
||||||
|
reasoning: 'AI/ML capabilities for data analysis, machine learning, and integration with modern AI services.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
implementation_strategy: {
|
||||||
|
architecture_pattern: 'MVC (Model-View-Controller)',
|
||||||
|
deployment_strategy: 'Containerized deployment with Docker',
|
||||||
|
scalability_approach: 'Horizontal scaling with load balancing',
|
||||||
|
},
|
||||||
|
business_alignment: {
|
||||||
|
scalability: 'Designed for horizontal scaling with microservices architecture',
|
||||||
|
maintainability: 'Modular architecture with clear separation of concerns',
|
||||||
|
cost_effectiveness: 'Open-source technologies reduce licensing costs',
|
||||||
|
time_to_market: 'Rapid development with modern frameworks and tools',
|
||||||
|
},
|
||||||
|
risk_assessment: {
|
||||||
|
technical_risks: ['Learning curve for new technologies', 'Integration complexity'],
|
||||||
|
mitigation_strategies: ['Comprehensive documentation', 'Phased implementation approach'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback functional requirements when Claude API fails
|
||||||
|
*/
|
||||||
|
function getFallbackFunctionalRequirements(request: TechRecommendationRequest): any {
|
||||||
|
return {
|
||||||
|
feature_name: `${request.template.title} - Integrated System`,
|
||||||
|
description: `Complete ${request.template.category} system with ${request.features.length} integrated features`,
|
||||||
|
complexity_level: request.features.some(f => f.complexity === 'high') ? 'high' :
|
||||||
|
request.features.some(f => f.complexity === 'medium') ? 'medium' : 'low',
|
||||||
|
technical_requirements: request.features.flatMap(f => f.technical_requirements || []),
|
||||||
|
business_logic_rules: request.features.flatMap(f => f.business_rules || []),
|
||||||
|
all_features: request.features.map(f => f.name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test function to validate Claude API integration
|
||||||
|
*/
|
||||||
|
export async function testClaudeIntegration(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const testRequest: TechRecommendationRequest = {
|
||||||
|
template: {
|
||||||
|
id: 'test',
|
||||||
|
title: 'Test Project',
|
||||||
|
description: 'A test project for validation',
|
||||||
|
category: 'Web Application',
|
||||||
|
type: 'web-app',
|
||||||
|
},
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
id: 'test-feature',
|
||||||
|
name: 'User Authentication',
|
||||||
|
description: 'Basic user login and registration',
|
||||||
|
feature_type: 'essential',
|
||||||
|
complexity: 'medium',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
businessContext: {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: 'What is your target audience?',
|
||||||
|
answer: 'Small to medium businesses',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await generateTechRecommendations(testRequest);
|
||||||
|
return result.success;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Claude integration test failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user