first working

This commit is contained in:
Claude 2025-07-09 02:15:35 +05:30
parent 3fc5f01309
commit 67ba2de335
71 changed files with 3071 additions and 2528 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,119 @@
│ Simplified Hardcoded Agent Pages Plan │
│ │
│ Overview │
│ │
│ Create individual hardcoded pages for each agent with specific integrations to n8n workflows. Each agent │
│ gets its own custom page with tailored UI and webhook connections. │
│ │
│ Implementation Approach │
│ │
│ 1. Route Structure │
│ │
│ - /agent/pdf-processor - PDF processing agent page │
│ - /agent/weather-report - Weather report agent page │
│ - /agent/content-writer - Content writing agent page │
│ - /agent/data-analyzer - Data analysis agent page │
│ - /agent/email-automation - Email automation agent page │
│ - /agent/task-automation - Task automation agent page │
│ │
│ 2. Update Current Flow │
│ │
│ Modify useAgent function in page.tsx: │
│ - Instead of showing modal with mock results │
│ - Redirect to specific agent page: router.push(/agent/${agent.slug}) │
│ - Pass agent info and user data via URL params or state │
│ │
│ 3. Example Implementation: PDF Processor Agent │
│ │
│ Page: /app/agent/pdf-processor/page.tsx │
│ - File upload component for PDF │
│ - Form fields for processing options │
│ - Connect to specific n8n webhook │
│ - Display results from Google Sheets API │
│ - Deduct credits on successful processing │
│ │
│ Workflow: │
│ 1. User uploads PDF file │
│ 2. Send file + options to n8n webhook │
│ 3. n8n processes PDF and saves results to Google Sheet │
│ 4. Page fetches results from Google Sheets │
│ 5. Display formatted results to user │
│ 6. Deduct credits from user account │
│ │
│ 4. Agent Page Components │
│ │
│ Each agent page will have: │
│ - Custom input form (file upload, text fields, dropdowns) │
│ - Processing status indicator │
│ - Results display section │
│ - Credit cost display │
│ - Back to agents list button │
│ │
│ 5. N8N Integration Pattern │
│ │
│ For each agent: │
│ - Unique n8n webhook URL │
│ - Hardcoded request format │
│ - Specific response handling │
│ - Google Sheets integration for data storage/retrieval │
│ │
│ 6. Example Agent Pages Structure │
│ │
│ PDF Processor: │
│ - Input: PDF file, processing type (extract text/data/images) │
│ - N8N: PDF processing workflow │
│ - Output: Extracted data displayed in tables/lists │
│ │
│ Weather Report: │
│ - Input: Location, date range │
│ - N8N: Weather API workflow │
│ - Output: Weather forecast with charts │
│ │
│ Content Writer: │
│ - Input: Topic, content type, length │
│ - N8N: OpenAI integration workflow │
│ - Output: Generated content with formatting │
│ │
│ Data Analyzer: │
│ - Input: CSV/Excel file upload │
│ - N8N: Data processing workflow │
│ - Output: Analysis results, charts, insights │
│ │
│ 7. File Structure │
│ │
│ src/app/agent/ │
│ ├── pdf-processor/ │
│ │ └── page.tsx │
│ ├── weather-report/ │
│ │ └── page.tsx │
│ ├── content-writer/ │
│ │ └── page.tsx │
│ ├── data-analyzer/ │
│ │ └── page.tsx │
│ ├── email-automation/ │
│ │ └── page.tsx │
│ └── task-automation/ │
│ └── page.tsx │
│ │
│ 8. Shared Components │
│ │
│ src/components/agent-shared/ │
│ ├── AgentLayout.tsx # Common layout for all agent pages │
│ ├── FileUpload.tsx # File upload component │
│ ├── ProcessingStatus.tsx # Status indicator │
│ ├── ResultsDisplay.tsx # Results formatting │
│ └── CreditCounter.tsx # Credit cost display │
│ │
│ 9. Implementation Steps │
│ │
│ 1. Create agent slug mapping in database │
│ 2. Update main page to redirect instead of showing modal │
│ 3. Create first agent page (PDF processor) as template │
│ 4. Add n8n webhook integration │
│ 5. Implement Google Sheets result fetching │
│ 6. Add credit deduction on success │
│ 7. Replicate pattern for other agents │
│ │
│ This approach keeps each agent simple and hardcoded while providing real functionality through n8n │
│ workflows and external integrations. │
╰──────────────────────────────────────

26
agents/admin.py Normal file
View File

@ -0,0 +1,26 @@
from django.contrib import admin
from .models import Agent
@admin.register(Agent)
class AgentAdmin(admin.ModelAdmin):
list_display = ('name', 'slug', 'category', 'price', 'is_active', 'rating', 'review_count')
list_filter = ('category', 'is_active', 'created_at')
search_fields = ('name', 'slug', 'description')
prepopulated_fields = {'slug': ('name',)}
list_editable = ('price', 'is_active')
ordering = ('-created_at',)
fieldsets = (
('Basic Information', {
'fields': ('name', 'slug', 'description', 'category', 'icon')
}),
('Pricing & Rating', {
'fields': ('price', 'rating', 'review_count')
}),
('Configuration', {
'fields': ('is_active', 'n8n_webhook_url')
}),
)
readonly_fields = ('created_at',)

View File

@ -5,6 +5,7 @@ from django.core.files.base import ContentFile
import json
import os
class AgentProcessor:
def __init__(self, agent_slug):
self.agent_slug = agent_slug

View File

@ -0,0 +1,91 @@
from django.core.management.base import BaseCommand
from agents.models import Agent
from decimal import Decimal
class Command(BaseCommand):
help = 'Populate the database with sample agent data'
def handle(self, *args, **options):
# Clear existing agents
Agent.objects.all().delete()
# Create sample agents
agents_data = [
{
'name': 'Data Analyzer',
'slug': 'data-analyzer',
'description': 'Upload your data files and get comprehensive analysis with insights, trends, and visualizations.',
'category': 'analytics',
'price': Decimal('15.00'),
'icon': '📊',
'rating': Decimal('4.7'),
'review_count': 324,
'n8n_webhook_url': 'https://n8n.example.com/webhook/data-analyzer',
},
{
'name': 'Weather Reporter',
'slug': 'weather-reporter',
'description': 'Get current weather conditions, forecasts, and detailed meteorological data for any location.',
'category': 'utilities',
'price': Decimal('5.00'),
'icon': '🌤️',
'rating': Decimal('4.5'),
'review_count': 892,
'n8n_webhook_url': '', # Uses OpenWeather API directly
},
{
'name': '5 Whys Analysis',
'slug': 'five-whys',
'description': 'Perform root cause analysis using the 5 Whys technique to identify the underlying cause of problems.',
'category': 'analytics',
'price': Decimal('12.00'),
'icon': '',
'rating': Decimal('4.6'),
'review_count': 156,
'n8n_webhook_url': 'https://n8n.example.com/webhook/five-whys',
},
{
'name': 'FAQ Generator',
'slug': 'faq-generator',
'description': 'Generate comprehensive FAQ sections from your content, documentation, or product information.',
'category': 'content',
'price': Decimal('10.00'),
'icon': '',
'rating': Decimal('4.4'),
'review_count': 287,
'n8n_webhook_url': 'https://n8n.example.com/webhook/faq-generator',
},
{
'name': 'Social Ads Generator',
'slug': 'social-ads-generator',
'description': 'Create compelling social media advertisements with copy, targeting suggestions, and campaign ideas.',
'category': 'marketing',
'price': Decimal('20.00'),
'icon': '📢',
'rating': Decimal('4.8'),
'review_count': 543,
'n8n_webhook_url': 'https://n8n.example.com/webhook/social-ads',
},
{
'name': 'Job Posting Generator',
'slug': 'job-posting-generator',
'description': 'Generate professional job postings with requirements, responsibilities, and compelling descriptions.',
'category': 'content',
'price': Decimal('8.00'),
'icon': '💼',
'rating': Decimal('4.3'),
'review_count': 198,
'n8n_webhook_url': 'https://n8n.example.com/webhook/job-posting',
},
]
for agent_data in agents_data:
agent = Agent.objects.create(**agent_data)
self.stdout.write(
self.style.SUCCESS(f'Successfully created agent: {agent.name}')
)
self.stdout.write(
self.style.SUCCESS(f'Successfully populated {len(agents_data)} agents')
)

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.4 on 2025-07-08 08:17
# Generated by Django 5.2.4 on 2025-07-08 15:00
from decimal import Decimal
from django.db import migrations, models

View File

@ -1,8 +1,7 @@
# Create your models here.
from django.db import models
from decimal import Decimal
class Agent(models.Model):
CATEGORIES = [
('analytics', 'Analytics'),

View File

@ -1,80 +0,0 @@
from django.core.management.base import BaseCommand
from agents.models import Agent
from decimal import Decimal
class Command(BaseCommand):
help = 'Populate database with default agents'
def handle(self, *args, **options):
agents = [
{
'name': '5 Whys Analysis Agent',
'slug': 'five-whys',
'description': 'Systematic root cause analysis using the proven 5 Whys methodology to identify and solve business problems effectively.',
'category': 'analytics',
'price': Decimal('8.00'),
'icon': '🔍',
'rating': Decimal('4.8'),
'review_count': 850,
},
{
'name': 'Data Analysis Agent',
'slug': 'data-analyzer',
'description': 'Processes complex datasets and generates actionable insights with automated reporting and visualization capabilities.',
'category': 'analytics',
'price': Decimal('5.00'),
'icon': '📊',
'rating': Decimal('4.8'),
'review_count': 1800,
},
{
'name': 'Weather Reporter Agent',
'slug': 'weather-reporter',
'description': 'Get detailed weather reports for any location worldwide with current conditions, forecasts, and weather alerts.',
'category': 'utilities',
'price': Decimal('2.00'),
'icon': '🌤️',
'rating': Decimal('4.9'),
'review_count': 1650,
},
{
'name': 'Job Posting Generator Agent',
'slug': 'job-posting-generator',
'description': 'Create compelling, professional job postings with AI-powered content generation.',
'category': 'content',
'price': Decimal('3.00'),
'icon': '📝',
'rating': Decimal('4.7'),
'review_count': 1200,
},
{
'name': 'Social Ads Generator Agent',
'slug': 'social-ads-generator',
'description': 'Create engaging social media advertisements optimized for different platforms.',
'category': 'marketing',
'price': Decimal('4.00'),
'icon': '📱',
'rating': Decimal('4.8'),
'review_count': 950,
},
{
'name': 'FAQ Generator Agent',
'slug': 'faq-generator',
'description': 'Generate comprehensive FAQs from uploaded files or website URLs.',
'category': 'content',
'price': Decimal('3.00'),
'icon': '',
'rating': Decimal('4.7'),
'review_count': 750,
},
]
for agent_data in agents:
agent, created = Agent.objects.get_or_create(
slug=agent_data['slug'],
defaults=agent_data
)
if created:
self.stdout.write(f'Created agent: {agent.name}')
else:
self.stdout.write(f'Agent already exists: {agent.name}')

View File

@ -1,7 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
# Agent management URLs would go here
# For now, agents are handled by core app
]

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,24 +0,0 @@
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='auth/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='homepage'), name='logout'),
path('register/', views.register, name='register'),
path('password-reset/', auth_views.PasswordResetView.as_view(
template_name='auth/password_reset.html',
email_template_name='auth/password_reset_email.html',
success_url='/auth/password-reset/done/'
), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(
template_name='auth/password_reset_done.html'
), name='password_reset_done'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(
template_name='auth/password_reset_confirm.html',
success_url='/auth/reset/done/'
), name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
template_name='auth/password_reset_complete.html'
), name='password_reset_complete'),
]

View File

@ -1,16 +0,0 @@
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, 'Registration successful!')
return redirect('homepage')
else:
form = UserCreationForm()
return render(request, 'auth/register.html', {'form': form})

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,17 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.homepage, name='homepage'),
path('marketplace/', views.marketplace, name='marketplace'),
path('pricing/', views.pricing, name='pricing'),
path('debug/', views.debug_page, name='debug'),
path('reset-password/', views.reset_password, name='reset_password'),
path('agent/<slug:slug>/', views.agent_detail, name='agent_detail'),
path('agent/<slug:slug>/process/', views.process_agent, name='process_agent'),
path('profile/', views.profile, name='profile'),
# API endpoints
path('api/wallet/balance/', views.check_wallet_balance, name='api_wallet_balance'),
path('api/chat/<slug:slug>/', views.chat_message, name='api_chat_message'),
]

View File

@ -1,452 +0,0 @@
# Create your views here.
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils import timezone
from agents.models import Agent
from agents.agent_processors import AgentProcessor
import json
import os
User = get_user_model()
def homepage(request):
"""Enhanced homepage with all features from Next.js version"""
# Handle contact form submission
if request.method == 'POST':
name = request.POST.get('name')
email = request.POST.get('email')
company = request.POST.get('company', '')
message = request.POST.get('message')
if name and email and message:
# Here you can save to database or send email
# For now, just show success message
messages.success(request, f'Thank you {name}! Your message has been sent. We will get back to you soon.')
return redirect('homepage')
else:
messages.error(request, 'Please fill in all required fields.')
# Get featured agents for preview
featured_agents = Agent.objects.filter(is_active=True)[:3]
context = {
'featured_agents': featured_agents,
'total_agents': Agent.objects.filter(is_active=True).count(),
'total_users': User.objects.count(),
}
return render(request, 'homepage.html', context)
def marketplace(request):
"""Display all available agents with enhanced filtering"""
category_filter = request.GET.get('category')
search_query = request.GET.get('search')
agents = Agent.objects.filter(is_active=True)
if category_filter:
agents = agents.filter(category=category_filter)
if search_query:
from django.db import models
agents = agents.filter(
models.Q(name__icontains=search_query) |
models.Q(description__icontains=search_query)
)
# Get unique categories for filter dropdown
categories = Agent.objects.filter(is_active=True).values_list('category', flat=True).distinct()
context = {
'agents': agents.order_by('category', 'name'),
'categories': categories,
'current_category': category_filter,
'search_query': search_query,
}
return render(request, 'marketplace.html', context)
def pricing(request):
"""Enhanced pricing page with payment status handling"""
packages = [
{
'id': 'basic',
'amount': 10,
'price': 9.99,
'label': 'Basic',
'description': 'Perfect for trying out AI agents',
'features': ['2-4 agent uses', 'Basic support', 'Email notifications'],
'icon': '💰',
'gradient': 'from-blue-500 to-purple-600'
},
{
'id': 'popular',
'amount': 50,
'price': 49.99,
'label': 'Popular',
'description': 'Most popular choice for regular users',
'features': ['10-25 agent uses', 'Priority support', 'Advanced analytics', 'Export options'],
'icon': '',
'gradient': 'from-purple-500 to-pink-600',
'popular': True
},
{
'id': 'premium',
'amount': 100,
'price': 99.99,
'label': 'Premium',
'description': 'For power users and small teams',
'features': ['50+ agent uses', '24/7 support', 'Custom integrations', 'Team collaboration'],
'icon': '🚀',
'gradient': 'from-green-500 to-teal-600'
},
{
'id': 'enterprise',
'amount': 500,
'price': 499.99,
'label': 'Enterprise',
'description': 'For large teams and businesses',
'features': ['Unlimited uses', 'Dedicated support', 'Custom development', 'SLA guarantee'],
'icon': '👑',
'gradient': 'from-yellow-500 to-red-600'
},
]
# Handle payment status messages (prevent duplicate messages)
payment_status = request.GET.get('payment')
session_id = request.GET.get('session_id')
# Create session key to prevent duplicate messages
if payment_status:
session_key = f"payment_message_{payment_status}_{session_id or 'cancelled'}"
if not request.session.get(session_key):
request.session[session_key] = True
if payment_status == 'success':
messages.success(request, '✅ Payment successful! Your wallet has been topped up.')
elif payment_status == 'cancelled':
messages.error(request, '❌ Payment was cancelled. No charges were made.')
# FAQ data
faqs = [
{
'question': 'How does the pay-per-use pricing work?',
'answer': 'You add money to your wallet and pay for each AI agent use. Prices range from 2.00 to 8.00 AED per use.'
},
{
'question': 'Do wallet funds expire?',
'answer': 'No, your wallet balance never expires. Use it whenever you need AI assistance.'
},
{
'question': 'Can I get a refund?',
'answer': 'Yes, unused wallet balance can be refunded within 30 days of purchase.'
},
{
'question': 'Is my payment information secure?',
'answer': 'Absolutely. We use Stripe for secure payment processing and never store your payment details.'
}
]
context = {
'packages': packages,
'faqs': faqs,
}
return render(request, 'pricing.html', context)
def debug_page(request):
"""Debug page for development environment checking"""
if not settings.DEBUG:
context = {'debug_mode': False}
return render(request, 'debug.html', context)
# Environment status check
env_status = {
'DATABASE_URL': bool(os.getenv('DATABASE_URL')),
'STRIPE_SECRET_KEY': bool(settings.STRIPE_SECRET_KEY),
'N8N_WEBHOOK_DATA_ANALYZER': bool(settings.N8N_WEBHOOK_DATA_ANALYZER),
'N8N_WEBHOOK_FIVE_WHYS': bool(settings.N8N_WEBHOOK_FIVE_WHYS),
'OPENWEATHER_API_KEY': bool(settings.OPENWEATHER_API_KEY),
'DEBUG': settings.DEBUG,
'ALLOWED_HOSTS': settings.ALLOWED_HOSTS,
}
# Database connection test
try:
user_count = User.objects.count()
agent_count = Agent.objects.count()
db_status = {'status': 'Connected', 'color': 'green'}
except Exception as e:
user_count = 0
agent_count = 0
db_status = {'status': f'Error: {str(e)}', 'color': 'red'}
context = {
'debug_mode': True,
'env_status': json.dumps(env_status, indent=2),
'db_status': db_status,
'user_count': user_count,
'agent_count': agent_count,
}
return render(request, 'debug.html', context)
def reset_password(request):
"""Password reset functionality"""
if request.method == 'POST':
password = request.POST.get('password')
confirm_password = request.POST.get('confirm_password')
if not password or not confirm_password:
context = {'error': 'Both password fields are required'}
return render(request, 'reset_password.html', context)
if password != confirm_password:
context = {'error': 'Passwords do not match'}
return render(request, 'reset_password.html', context)
if len(password) < 8:
context = {'error': 'Password must be at least 8 characters long'}
return render(request, 'reset_password.html', context)
# In a real implementation, you would:
# 1. Verify the reset token from the URL
# 2. Update the user's password
# 3. Redirect to login with success message
messages.success(request, 'Password updated successfully! Please log in with your new password.')
return redirect('homepage')
# Check if we have a valid reset token (simplified version)
token = request.GET.get('token')
if not token:
context = {'error': 'Invalid or expired reset link. Please request a new password reset.'}
return render(request, 'reset_password.html', context)
return render(request, 'reset_password.html')
@login_required
def agent_detail(request, slug):
"""Enhanced agent detail page with wallet balance checking"""
agent = get_object_or_404(Agent, slug=slug, is_active=True)
# Calculate wallet status
user_balance = request.user.wallet_balance
has_sufficient_balance = user_balance >= agent.price
# Calculate usage count
if has_sufficient_balance:
possible_uses = int(user_balance / agent.price)
else:
possible_uses = 0
context = {
'agent': agent,
'user_balance': user_balance,
'has_sufficient_balance': has_sufficient_balance,
'possible_uses': possible_uses,
'balance_after_use': user_balance - agent.price if has_sufficient_balance else user_balance,
}
return render(request, 'agent_detail.html', context)
@login_required
@require_http_methods(["POST"])
def process_agent(request, slug):
"""Enhanced agent processing with comprehensive error handling"""
agent = get_object_or_404(Agent, slug=slug, is_active=True)
# Check wallet balance
if not request.user.has_sufficient_balance(agent.price):
return JsonResponse({
'success': False,
'error': f'Insufficient balance. Required: {agent.price_display}, Available: {request.user.wallet_balance:.2f} AED'
}, status=400)
try:
# Process based on agent type
processor = AgentProcessor(agent.slug)
if agent.slug == 'data-analyzer':
file_obj = request.FILES.get('file')
if not file_obj:
return JsonResponse({'success': False, 'error': 'File is required'}, status=400)
# Validate file size (10MB limit)
if file_obj.size > 10 * 1024 * 1024:
return JsonResponse({'success': False, 'error': 'File size must be less than 10MB'}, status=400)
# Validate file type
allowed_extensions = ['.csv', '.xlsx', '.xls', '.json']
file_extension = os.path.splitext(file_obj.name)[1].lower()
if file_extension not in allowed_extensions:
return JsonResponse({'success': False, 'error': 'Invalid file type. Allowed: CSV, Excel, JSON'}, status=400)
result = processor.process_agent(file_obj=file_obj, user_id=str(request.user.id))
elif agent.slug == 'five-whys':
problem = request.POST.get('problem')
if not problem or len(problem.strip()) < 10:
return JsonResponse({'success': False, 'error': 'Problem description must be at least 10 characters'}, status=400)
result = processor.process_agent(problem_description=problem, user_id=str(request.user.id))
elif agent.slug == 'weather-reporter':
location = request.POST.get('location')
if not location or len(location.strip()) < 2:
return JsonResponse({'success': False, 'error': 'Location must be at least 2 characters'}, status=400)
result = processor.process_agent(location=location)
elif agent.slug == 'job-posting-generator':
required_fields = ['title', 'company', 'description', 'requirements']
job_details = {}
for field in required_fields:
value = request.POST.get(field, '').strip()
if not value:
return JsonResponse({'success': False, 'error': f'{field.title()} is required'}, status=400)
if len(value) < 5:
return JsonResponse({'success': False, 'error': f'{field.title()} must be at least 5 characters'}, status=400)
job_details[field] = value
result = processor.process_agent(job_details=job_details, user_id=str(request.user.id))
elif agent.slug == 'social-ads-generator':
required_fields = ['product', 'platform', 'target_audience', 'tone']
ad_requirements = {}
for field in required_fields:
value = request.POST.get(field, '').strip()
if not value:
return JsonResponse({'success': False, 'error': f'{field.replace("_", " ").title()} is required'}, status=400)
ad_requirements[field] = value
# Validate platform
valid_platforms = ['facebook', 'instagram', 'twitter', 'linkedin']
if ad_requirements['platform'] not in valid_platforms:
return JsonResponse({'success': False, 'error': 'Invalid platform selected'}, status=400)
# Validate tone
valid_tones = ['professional', 'casual', 'humorous', 'urgent']
if ad_requirements['tone'] not in valid_tones:
return JsonResponse({'success': False, 'error': 'Invalid tone selected'}, status=400)
result = processor.process_agent(ad_requirements=ad_requirements, user_id=str(request.user.id))
elif agent.slug == 'faq-generator':
content_source = request.POST.get('content_source', '').strip()
if not content_source:
return JsonResponse({'success': False, 'error': 'Content source is required'}, status=400)
if len(content_source) < 50:
return JsonResponse({'success': False, 'error': 'Content source must be at least 50 characters'}, status=400)
result = processor.process_agent(content_source=content_source, user_id=str(request.user.id))
else:
return JsonResponse({'success': False, 'error': 'Agent not supported'}, status=400)
# Deduct balance on successful processing
if request.user.deduct_balance(
agent.price,
f"Used {agent.name}",
agent.slug
):
return JsonResponse({
'success': True,
'result': result,
'new_balance': float(request.user.wallet_balance),
'agent_used': agent.name,
'cost': float(agent.price)
})
else:
return JsonResponse({
'success': False,
'error': 'Failed to process payment. Please try again.'
}, status=400)
except Exception as e:
# Log the error in production
if not settings.DEBUG:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Agent processing error: {str(e)}", exc_info=True)
return JsonResponse({
'success': False,
'error': 'An error occurred while processing your request. Please try again later.'
}, status=500)
@login_required
def profile(request):
"""Enhanced user profile with transaction history and wallet management"""
# Get recent transactions
transactions = request.user.wallet_transactions.all()[:50] # Last 50 transactions
# Calculate usage statistics
total_spent = sum(abs(t.amount) for t in transactions if t.type == 'agent_usage')
total_topped_up = sum(t.amount for t in transactions if t.type == 'top_up')
total_agents_used = transactions.filter(type='agent_usage').count()
# Get most used agents
from django.db.models import Count
popular_agents = (transactions.filter(type='agent_usage')
.values('agent_slug')
.annotate(count=Count('agent_slug'))
.order_by('-count')[:5])
# Wallet status
balance = request.user.wallet_balance
if balance < 5:
wallet_status = {'status': 'low', 'color': 'red', 'message': 'Low balance - Add money to continue using agents'}
elif balance < 20:
wallet_status = {'status': 'medium', 'color': 'orange', 'message': 'Consider adding more funds'}
else:
wallet_status = {'status': 'high', 'color': 'green', 'message': 'Good balance'}
context = {
'transactions': transactions,
'total_spent': total_spent,
'total_topped_up': total_topped_up,
'total_agents_used': total_agents_used,
'popular_agents': popular_agents,
'wallet_status': wallet_status,
}
return render(request, 'profile.html', context)
# API endpoints for AJAX functionality
@login_required
@require_http_methods(["GET"])
def check_wallet_balance(request):
"""API endpoint to check current wallet balance"""
return JsonResponse({
'balance': float(request.user.wallet_balance),
'formatted_balance': f"{request.user.wallet_balance:.2f} AED"
})
@login_required
@require_http_methods(["POST"])
def chat_message(request, slug):
"""Handle chat messages for interactive agents like 5 Whys"""
if slug != 'five-whys':
return JsonResponse({'success': False, 'error': 'Chat not available for this agent'}, status=400)
message = request.POST.get('message', '').strip()
if not message:
return JsonResponse({'success': False, 'error': 'Message is required'}, status=400)
# Here you would integrate with your 5 Whys processing logic
# For now, return a simple response
response_message = f"Thank you for: {message}. Let me ask you the next Why question..."
return JsonResponse({
'success': True,
'response': response_message,
'timestamp': timezone.now().isoformat()
})

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,72 +0,0 @@
import stripe
from django.conf import settings
from django.contrib.auth import get_user_model
User = get_user_model()
stripe.api_key = settings.STRIPE_SECRET_KEY
class StripeHandler:
"""Handle all Stripe-related operations"""
def __init__(self):
self.packages = {
'basic': {'amount': 999, 'currency': 'aed', 'name': 'Basic Package - 10 AED', 'wallet_amount': 10},
'popular': {'amount': 4999, 'currency': 'aed', 'name': 'Popular Package - 50 AED', 'wallet_amount': 50},
'premium': {'amount': 9999, 'currency': 'aed', 'name': 'Premium Package - 100 AED', 'wallet_amount': 100},
'enterprise': {'amount': 49999, 'currency': 'aed', 'name': 'Enterprise Package - 500 AED', 'wallet_amount': 500},
}
def create_checkout_session(self, amount, user_id, package_id):
"""Create Stripe checkout session"""
package = self.packages.get(package_id)
if not package:
raise ValueError('Invalid package')
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price_data': {
'currency': package['currency'],
'product_data': {
'name': package['name'],
},
'unit_amount': package['amount'],
},
'quantity': 1,
}],
mode='payment',
success_url='https://yoursite.com/wallet/success/?session_id={CHECKOUT_SESSION_ID}',
cancel_url='https://yoursite.com/wallet/cancel/',
client_reference_id=str(user_id),
metadata={
'package_id': package_id,
'user_id': str(user_id),
}
)
return session
def handle_webhook_event(self, event):
"""Handle Stripe webhook events"""
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
# Get user and package info
user_id = session['client_reference_id']
package_id = session['metadata']['package_id']
try:
user = User.objects.get(id=user_id)
package = self.packages.get(package_id)
if package:
amount = package['wallet_amount']
user.add_balance(
amount,
f"Wallet top-up: {amount} AED",
session['id']
)
except User.DoesNotExist:
pass

View File

@ -1,9 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('create-checkout/', views.create_checkout_session, name='create_checkout_session'),
path('webhook/', views.stripe_webhook, name='stripe_webhook'),
path('success/', views.payment_success, name='payment_success'),
path('cancel/', views.payment_cancel, name='payment_cancel'),
]

View File

@ -1,76 +0,0 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.http import JsonResponse, HttpResponse
from django.contrib import messages
from django.conf import settings
from django.contrib.auth import get_user_model
from .stripe_handler import StripeHandler
import json
import stripe
User = get_user_model()
@login_required
@require_http_methods(["POST"])
def create_checkout_session(request):
"""Create Stripe checkout session for wallet top-up"""
try:
data = json.loads(request.body)
amount = data.get('amount')
package_id = data.get('package_id')
if not amount or amount <= 0:
return JsonResponse({'error': 'Invalid amount'}, status=400)
# Create Stripe checkout session
stripe_handler = StripeHandler()
session = stripe_handler.create_checkout_session(
amount=amount,
user_id=request.user.id,
package_id=package_id
)
return JsonResponse({'checkout_url': session.url})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@csrf_exempt
@require_http_methods(["POST"])
def stripe_webhook(request):
"""Handle Stripe webhook events"""
payload = request.body
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
stripe_handler = StripeHandler()
stripe_handler.handle_webhook_event(event)
return HttpResponse(status=200)
except ValueError:
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError:
return HttpResponse(status=400)
except Exception as e:
return HttpResponse(status=500)
@login_required
def payment_success(request):
"""Handle successful payment redirect"""
session_id = request.GET.get('session_id')
if session_id:
messages.success(request, '✅ Payment successful! Your wallet has been topped up.')
return redirect('pricing')
@login_required
def payment_cancel(request):
"""Handle cancelled payment redirect"""
messages.error(request, '❌ Payment was cancelled. No charges were made.')
return redirect('pricing')

22
authentication/admin.py Normal file
View File

@ -0,0 +1,22 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
@admin.register(User)
class CustomUserAdmin(UserAdmin):
list_display = ('username', 'email', 'wallet_balance', 'is_staff', 'is_active', 'date_joined')
list_filter = ('is_staff', 'is_active', 'date_joined')
search_fields = ('username', 'email')
ordering = ('-date_joined',)
fieldsets = UserAdmin.fieldsets + (
('Wallet Information', {'fields': ('wallet_balance',)}),
)
readonly_fields = ('date_joined', 'last_login')
def get_readonly_fields(self, request, obj=None):
if obj: # editing an existing object
return self.readonly_fields + ('username',)
return self.readonly_fields

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.4 on 2025-07-08 08:17
# Generated by Django 5.2.4 on 2025-07-08 15:00
import django.contrib.auth.models
import django.contrib.auth.validators

View File

@ -1,8 +1,8 @@
# Create your models here.
from django.contrib.auth.models import AbstractUser
from django.db import models
from decimal import Decimal
class User(AbstractUser):
email = models.EmailField(unique=True)
wallet_balance = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))

9
authentication/urls.py Normal file
View File

@ -0,0 +1,9 @@
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login_view, name='login'),
path('register/', views.register_view, name='register'),
path('logout/', views.logout_view, name='logout'),
path('profile/', views.profile_view, name='profile'),
]

103
authentication/views.py Normal file
View File

@ -0,0 +1,103 @@
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.auth.forms import UserCreationForm
from django.http import JsonResponse
from .models import User
def login_view(request):
"""User login view"""
if request.method == 'POST':
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(request, username=email, password=password)
if user is not None:
login(request, user)
return redirect('homepage')
else:
messages.error(request, 'Invalid email or password')
return render(request, 'authentication/login.html')
def register_view(request):
"""User registration view"""
if request.method == 'POST':
username = request.POST.get('username')
email = request.POST.get('email')
password1 = request.POST.get('password1')
password2 = request.POST.get('password2')
if password1 != password2:
messages.error(request, 'Passwords do not match')
return render(request, 'authentication/register.html')
if User.objects.filter(email=email).exists():
messages.error(request, 'Email already exists')
return render(request, 'authentication/register.html')
try:
user = User.objects.create_user(
username=username,
email=email,
password=password1
)
login(request, user)
messages.success(request, 'Account created successfully!')
return redirect('homepage')
except Exception as e:
messages.error(request, 'Error creating account')
return render(request, 'authentication/register.html')
def logout_view(request):
"""User logout view"""
logout(request)
messages.success(request, 'You have been logged out successfully')
return redirect('homepage')
@login_required
def profile_view(request):
"""User profile view"""
# Get all transactions first (not sliced)
all_transactions = request.user.wallet_transactions.all()
# Get recent transactions (sliced for display)
transactions = all_transactions[:50]
# Calculate usage statistics using all transactions
total_spent = sum(abs(t.amount) for t in all_transactions if t.type == 'agent_usage')
total_topped_up = sum(t.amount for t in all_transactions if t.type == 'top_up')
total_agents_used = all_transactions.filter(type='agent_usage').count()
# Get most used agents
from django.db.models import Count
popular_agents = (all_transactions.filter(type='agent_usage')
.values('agent_slug')
.annotate(count=Count('agent_slug'))
.order_by('-count')[:5])
# Wallet status
balance = request.user.wallet_balance
if balance < 5:
wallet_status = {'status': 'low', 'color': 'red', 'message': 'Low balance - Add money to continue using agents'}
elif balance < 20:
wallet_status = {'status': 'medium', 'color': 'orange', 'message': 'Consider adding more funds'}
else:
wallet_status = {'status': 'high', 'color': 'green', 'message': 'Good balance'}
context = {
'transactions': transactions,
'total_spent': total_spent,
'total_topped_up': total_topped_up,
'total_agents_used': total_agents_used,
'popular_agents': popular_agents,
'wallet_status': wallet_status,
}
return render(request, 'authentication/profile.html', context)

12
core/urls.py Normal file
View File

@ -0,0 +1,12 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.homepage_view, name='homepage'),
path('agents/<slug:agent_slug>/', views.agent_detail_view, name='agent_detail'),
path('agents/<slug:agent_slug>/use/', views.use_agent_view, name='use_agent'),
path('wallet/', views.wallet_view, name='wallet'),
path('wallet/topup/', views.wallet_topup_view, name='wallet_topup'),
path('stripe/webhook/', views.stripe_webhook_view, name='stripe_webhook'),
path('api/agents/', views.agents_api_view, name='agents_api'),
]

229
core/views.py Normal file
View File

@ -0,0 +1,229 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from django.views import View
from agents.models import Agent
from agents.agent_processors import AgentProcessor
from wallet.stripe_handler import StripePaymentHandler
from wallet.models import WalletTransaction
import json
def homepage_view(request):
"""Homepage view showing all available agents"""
agents = Agent.objects.filter(is_active=True)
# Group agents by category
categories = {}
for agent in agents:
category = agent.get_category_display()
if category not in categories:
categories[category] = []
categories[category].append(agent)
context = {
'agents': agents,
'categories': categories,
'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0,
}
return render(request, 'core/homepage.html', context)
@login_required
def agent_detail_view(request, agent_slug):
"""Individual agent detail page"""
agent = get_object_or_404(Agent, slug=agent_slug, is_active=True)
# Check if user has sufficient balance
can_use_agent = request.user.has_sufficient_balance(agent.price)
# Get recent usage by this user
recent_usage = WalletTransaction.objects.filter(
user=request.user,
agent_slug=agent_slug,
type='agent_usage'
)[:5]
context = {
'agent': agent,
'can_use_agent': can_use_agent,
'recent_usage': recent_usage,
'user_balance': request.user.wallet_balance,
}
return render(request, 'core/agent_detail.html', context)
@login_required
@require_http_methods(["POST"])
def use_agent_view(request, agent_slug):
"""Process agent usage"""
agent = get_object_or_404(Agent, slug=agent_slug, is_active=True)
# Check balance
if not request.user.has_sufficient_balance(agent.price):
return JsonResponse({
'success': False,
'error': 'Insufficient balance'
}, status=400)
try:
# Get input data based on agent type
if agent_slug == 'data-analyzer':
file_obj = request.FILES.get('file')
if not file_obj:
return JsonResponse({'success': False, 'error': 'File required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(file_obj=file_obj, user_id=request.user.id)
elif agent_slug == 'five-whys':
problem = request.POST.get('problem')
if not problem:
return JsonResponse({'success': False, 'error': 'Problem description required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(problem_description=problem, user_id=request.user.id)
elif agent_slug == 'weather-reporter':
location = request.POST.get('location')
if not location:
return JsonResponse({'success': False, 'error': 'Location required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(location=location)
elif agent_slug == 'job-posting-generator':
job_details = request.POST.get('job_details')
if not job_details:
return JsonResponse({'success': False, 'error': 'Job details required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(job_details=job_details, user_id=request.user.id)
elif agent_slug == 'social-ads-generator':
ad_requirements = request.POST.get('ad_requirements')
if not ad_requirements:
return JsonResponse({'success': False, 'error': 'Ad requirements required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(ad_requirements=ad_requirements, user_id=request.user.id)
elif agent_slug == 'faq-generator':
content_source = request.POST.get('content_source')
if not content_source:
return JsonResponse({'success': False, 'error': 'Content source required'}, status=400)
processor = AgentProcessor(agent_slug)
result = processor.process_agent(content_source=content_source, user_id=request.user.id)
else:
return JsonResponse({'success': False, 'error': 'Invalid agent'}, status=400)
# Deduct balance and record transaction
request.user.deduct_balance(
amount=agent.price,
description=f"Used {agent.name}",
agent_slug=agent_slug
)
return JsonResponse({
'success': True,
'result': result,
'remaining_balance': float(request.user.wallet_balance)
})
except Exception as e:
return JsonResponse({
'success': False,
'error': str(e)
}, status=500)
@login_required
def wallet_view(request):
"""Wallet management page"""
transactions = request.user.wallet_transactions.all()[:50]
# Calculate statistics
total_spent = sum(abs(t.amount) for t in transactions if t.type == 'agent_usage')
total_topped_up = sum(t.amount for t in transactions if t.type == 'top_up')
context = {
'transactions': transactions,
'total_spent': total_spent,
'total_topped_up': total_topped_up,
'current_balance': request.user.wallet_balance,
}
return render(request, 'core/wallet.html', context)
@login_required
def wallet_topup_view(request):
"""Wallet top-up page"""
if request.method == 'POST':
amount = request.POST.get('amount')
try:
amount = float(amount)
if amount not in [10, 50, 100, 500]:
messages.error(request, 'Invalid amount selected')
return redirect('wallet_topup')
# Create Stripe checkout session
stripe_handler = StripePaymentHandler()
session_data = stripe_handler.create_checkout_session(request.user, amount)
return redirect(session_data['payment_url'])
except (ValueError, TypeError):
messages.error(request, 'Invalid amount')
return redirect('wallet_topup')
return render(request, 'core/wallet_topup.html')
@csrf_exempt
@require_http_methods(["POST"])
def stripe_webhook_view(request):
"""Handle Stripe webhook events"""
payload = request.body
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
stripe_handler = StripePaymentHandler()
result = stripe_handler.handle_webhook(payload, sig_header)
if result['success']:
return JsonResponse({'status': 'success'})
else:
return JsonResponse({'status': 'error', 'message': result['error']}, status=400)
def agents_api_view(request):
"""API endpoint for agents list"""
agents = Agent.objects.filter(is_active=True)
agents_data = []
for agent in agents:
agents_data.append({
'id': agent.id,
'name': agent.name,
'slug': agent.slug,
'description': agent.description,
'category': agent.category,
'price': float(agent.price),
'icon': agent.icon,
'rating': float(agent.rating),
'review_count': agent.review_count,
})
return JsonResponse({
'agents': agents_data,
'total_count': len(agents_data)
})

48
future agent creation Normal file
View File

@ -0,0 +1,48 @@
● Future Agent Creation - What You Need to Tell Me
🎯 Minimum Required Information
1. Agent Basic Info
- Agent Name: (e.g., "PDF Document Processor")
- Description: (what it does)
- Icon/Emoji: (e.g., 📄)
- Cost: (credits per use)
2. Input Requirements
- What inputs does the user provide?
• File upload? (what file types?)
• Text input? (what kind?)
• Form fields? (which ones?)
• Options/settings? (what choices?)
3. Processing Method
- How should it work?
• API integration? (which service?)
• Mock/simulation? (what response?)
• External webhook? (URL/endpoint?)
💡 Example Request
"Create a PDF Document Processor agent:
- Name: PDF Document Processor
- Description: Extract text and summarize PDF documents
- Icon: 📄
- Cost: 35 credits
- Input: PDF file upload (max 10MB)
- Processing: OpenAI API for text extraction and summarization
- Output: Text summary + key points"
🚀 What I'll Handle Automatically
- ✅ Suspense wrappers
- ✅ File structure (/agent/pdf-processor/page.tsx)
- ✅ Slug mapping in agentUtils.ts
- ✅ Complete component structure
- ✅ Error handling
- ✅ Credit system integration
- ✅ UI consistency with existing agents
Just give me the basics above and I'll build the complete agent for you!

View File

@ -1,18 +1,40 @@
"""
Django settings for netcop_hub project.
Generated by 'django-admin startproject' using Django 5.2.4.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
from decouple import config
import sys
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Add apps directory to Python path
sys.path.insert(0, str(BASE_DIR / 'apps'))
# Security
SECRET_KEY = config('SECRET_KEY', default='your-secret-key-here')
DEBUG = config('DEBUG', default=False, cast=bool)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='django-insecure-thdd^re4==p$4geq^$52w7%egd0xxrj#fpgk1c+$xt-jrr5d7%')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True # Force DEBUG=True for development
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@ -21,10 +43,10 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'core',
'authentication',
'agents',
'wallet',
'core',
]
MIDDLEWARE = [
@ -39,7 +61,6 @@ MIDDLEWARE = [
ROOT_URLCONF = 'netcop_hub.urls'
# Templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -56,7 +77,12 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'netcop_hub.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
@ -64,21 +90,55 @@ DATABASES = {
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Custom user model
AUTH_USER_MODEL = 'authentication.User'
# Static files
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
# STATICFILES_DIRS removed for development - Django will use app static files
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Stripe
STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY')
STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET')
STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY', default='')
STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET', default='')
# N8N Webhooks
N8N_WEBHOOK_DATA_ANALYZER = config('N8N_WEBHOOK_DATA_ANALYZER', default='')
@ -93,3 +153,13 @@ OPENWEATHER_API_KEY = config('OPENWEATHER_API_KEY', default='')
# Security settings
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default='').split(',')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Authentication URLs
LOGIN_URL = '/auth/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

View File

@ -1,3 +1,19 @@
"""
URL configuration for netcop_hub project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
@ -7,9 +23,10 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('', include('core.urls')),
path('auth/', include('authentication.urls')),
path('agents/', include('agents.urls')),
path('wallet/', include('wallet.urls')),
]
# Serve static files during development
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# Also serve media files
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,8 +0,0 @@
Django==4.2.7
djangorestframework==3.14.0
stripe==7.8.0
python-decouple==3.8
requests==2.31.0
Pillow==10.1.0
psycopg2-binary==2.9.9
gunicorn==21.2.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,485 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ agent.name }} - NetCop AI Hub{% endblock %}
{% block content %}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Content -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow-lg p-6">
<div class="flex items-center mb-6">
<div class="w-16 h-16 bg-gradient-to-r {{ agent.get_gradient_class }} rounded-lg flex items-center justify-center text-white text-2xl mr-4">
{{ agent.icon }}
</div>
<div>
<h1 class="text-2xl font-bold text-gray-900">{{ agent.name }}</h1>
<p class="text-gray-600">{{ agent.description }}</p>
</div>
</div>
<!-- Agent-specific interfaces -->
{% if agent.slug == 'five-whys' %}
<!-- Advanced Chat Interface for 5 Whys -->
<div id="chatContainer" class="h-96 overflow-y-auto border border-gray-200 rounded-lg p-4 mb-4" style="background: linear-gradient(to bottom, #f9fafb, #ffffff);">
<div id="chatMessages">
<div class="mb-4 flex">
<div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center text-white text-sm mr-3 flex-shrink-0">
🤖
</div>
<div class="bg-gray-100 rounded-lg p-3 max-w-md">
<div class="markdown-content">
<p>👋 Welcome to the 5 Whys Root Cause Analysis!</p>
<p>I'll help you systematically analyze your problem using the proven 5 Whys methodology.</p>
<p><strong>To get started, please describe the problem you're experiencing.</strong></p>
<p>For example:</p>
<ul>
<li>"Our customer complaints increased by 40% this month"</li>
<li>"Production quality has decreased recently"</li>
<li>"Website performance is slower than usual"</li>
</ul>
<p>What problem would you like to analyze?</p>
</div>
</div>
</div>
</div>
</div>
<div class="flex gap-2">
<input type="text" id="chatInput" placeholder="Describe your problem..." class="flex-1 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<button id="sendButton" class="px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white rounded-lg hover:from-purple-700 hover:to-blue-700 transition-all duration-300">
Send
</button>
</div>
<!-- Report Display -->
<div id="reportSection" class="mt-6 hidden">
<h3 class="text-lg font-semibold mb-4">5 Whys Analysis Report</h3>
<div id="reportContent" class="bg-gray-50 p-4 rounded-lg"></div>
<div class="mt-4 flex gap-2">
<button onclick="copyReport()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Copy Report</button>
<button onclick="downloadReport()" class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">Download PDF</button>
</div>
</div>
{% elif agent.slug == 'data-analyzer' %}
<!-- File Upload Interface -->
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center" id="fileDropZone">
<div class="mb-4">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<p class="text-lg text-gray-600 mb-2">Drop your file here or click to browse</p>
<p class="text-sm text-gray-500">Supports CSV, Excel, JSON files up to 10MB</p>
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls,.json" class="hidden">
<button onclick="document.getElementById('fileInput').click()" class="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Choose File
</button>
</div>
<div id="filePreview" class="mt-4 hidden">
<div class="bg-gray-50 p-4 rounded-lg">
<p class="font-medium">Selected File:</p>
<p id="fileName" class="text-gray-600"></p>
<p id="fileSize" class="text-sm text-gray-500"></p>
</div>
</div>
{% elif agent.slug == 'weather-reporter' %}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Location</label>
<input type="text" name="location" id="locationInput" required class="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-purple-500" placeholder="Enter city name (e.g., Dubai, UAE)">
</div>
{% elif agent.slug == 'job-posting-generator' %}
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Job Title</label>
<input type="text" name="title" required class="w-full p-3 border border-gray-300 rounded-md">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Company</label>
<input type="text" name="company" required class="w-full p-3 border border-gray-300 rounded-md">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Job Description</label>
<textarea name="description" rows="4" required class="w-full p-3 border border-gray-300 rounded-md"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Requirements</label>
<textarea name="requirements" rows="4" required class="w-full p-3 border border-gray-300 rounded-md"></textarea>
</div>
</div>
{% elif agent.slug == 'social-ads-generator' %}
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Product/Service</label>
<input type="text" name="product" required class="w-full p-3 border border-gray-300 rounded-md">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Platform</label>
<select name="platform" required class="w-full p-3 border border-gray-300 rounded-md">
<option value="facebook">Facebook</option>
<option value="instagram">Instagram</option>
<option value="twitter">Twitter</option>
<option value="linkedin">LinkedIn</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Target Audience</label>
<input type="text" name="target_audience" required class="w-full p-3 border border-gray-300 rounded-md">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Tone</label>
<select name="tone" required class="w-full p-3 border border-gray-300 rounded-md">
<option value="professional">Professional</option>
<option value="casual">Casual</option>
<option value="humorous">Humorous</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
{% elif agent.slug == 'faq-generator' %}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Content Source</label>
<textarea name="content_source" rows="6" required class="w-full p-3 border border-gray-300 rounded-md" placeholder="Paste your content or URL here..."></textarea>
</div>
{% endif %}
<!-- Processing Status -->
<div id="processingStatus" class="mt-6 hidden">
<div class="flex items-center justify-center p-6">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mr-3"></div>
<span class="text-gray-600">Processing your request...</span>
</div>
</div>
<!-- Results Display -->
<div id="results" class="mt-6 hidden">
<h3 class="text-lg font-semibold mb-4">Results</h3>
<div id="resultsContent" class="bg-gray-50 p-4 rounded-md">
<!-- Results will be inserted here -->
</div>
<div class="mt-4 flex gap-2">
<button onclick="copyResults()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Copy Results</button>
<button onclick="downloadResults()" class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">Download</button>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="lg:col-span-1">
<!-- Wallet Balance Component -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6">
<h3 class="text-lg font-semibold mb-4">Cost</h3>
<div class="mb-4">
<div class="flex justify-between items-center mb-2">
<span class="text-gray-600">Current Balance:</span>
<span class="font-semibold">{{ user.wallet_balance }} AED</span>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-600">Cost:</span>
<span class="font-semibold text-red-600">-{{ agent.price_display }}</span>
</div>
<div class="border-t pt-2">
<div class="flex justify-between items-center">
<span class="font-semibold">After Processing:</span>
<span class="font-semibold {% if has_sufficient_balance %}text-green-600{% else %}text-red-600{% endif %}">
{% if has_sufficient_balance %}
{{ user.wallet_balance|floatformat:2 }} AED
{% else %}
Insufficient Balance
{% endif %}
</span>
</div>
</div>
</div>
<button id="processBtn" type="button"
class="w-full py-3 px-4 rounded-md font-semibold transition-colors
{% if has_sufficient_balance %}
bg-blue-600 text-white hover:bg-blue-700
{% else %}
bg-gray-300 text-gray-500 cursor-not-allowed
{% endif %}"
{% if not has_sufficient_balance %}disabled{% endif %}>
{% if has_sufficient_balance %}
Process Agent ({{ agent.price_display }})
{% else %}
Insufficient Balance
{% endif %}
</button>
{% if not has_sufficient_balance %}
<p class="text-center mt-4 text-sm">
<a href="{% url 'pricing' %}" class="text-blue-600 hover:text-blue-800 underline">
Top up wallet
</a> to use this agent
</p>
{% endif %}
</div>
<!-- Agent Stats -->
<div class="bg-white rounded-lg shadow-lg p-6">
<h3 class="text-lg font-semibold mb-4">Agent Statistics</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">Rating:</span>
<div class="flex items-center">
<span class="text-yellow-400"></span>
<span class="ml-1">{{ agent.rating }}</span>
</div>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Reviews:</span>
<span>{{ agent.review_count }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Category:</span>
<span class="capitalize">{{ agent.get_category_display }}</span>
</div>
</div>
</div>
</div>
</div>
<script>
// Agent processing logic
document.addEventListener('DOMContentLoaded', function() {
const agentSlug = '{{ agent.slug }}';
const processBtn = document.getElementById('processBtn');
const processingStatus = document.getElementById('processingStatus');
const results = document.getElementById('results');
const resultsContent = document.getElementById('resultsContent');
// File upload for data analyzer
if (agentSlug === 'data-analyzer') {
const fileInput = document.getElementById('fileInput');
const fileDropZone = document.getElementById('fileDropZone');
const filePreview = document.getElementById('filePreview');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
fileName.textContent = file.name;
fileSize.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`;
filePreview.classList.remove('hidden');
}
});
// Drag and drop functionality
fileDropZone.addEventListener('dragover', function(e) {
e.preventDefault();
fileDropZone.classList.add('border-blue-400', 'bg-blue-50');
});
fileDropZone.addEventListener('dragleave', function(e) {
e.preventDefault();
fileDropZone.classList.remove('border-blue-400', 'bg-blue-50');
});
fileDropZone.addEventListener('drop', function(e) {
e.preventDefault();
fileDropZone.classList.remove('border-blue-400', 'bg-blue-50');
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
fileInput.dispatchEvent(new Event('change'));
}
});
}
// Chat functionality for 5 Whys
if (agentSlug === 'five-whys') {
const chatInput = document.getElementById('chatInput');
const sendButton = document.getElementById('sendButton');
const chatMessages = document.getElementById('chatMessages');
const chatContainer = document.getElementById('chatContainer');
function addMessage(content, sender = 'user') {
const messageDiv = document.createElement('div');
messageDiv.className = 'mb-4 flex';
if (sender === 'user') {
messageDiv.innerHTML = `
<div class="ml-auto flex">
<div class="bg-blue-600 text-white rounded-lg p-3 max-w-md">
${content}
</div>
<div class="w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center text-white text-sm ml-3 flex-shrink-0">
👤
</div>
</div>
`;
} else {
messageDiv.innerHTML = `
<div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center text-white text-sm mr-3 flex-shrink-0">
🤖
</div>
<div class="bg-gray-100 rounded-lg p-3 max-w-md">
<div class="markdown-content">${content}</div>
</div>
`;
}
chatMessages.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function sendMessage() {
const message = chatInput.value.trim();
if (!message) return;
addMessage(message, 'user');
chatInput.value = '';
// Here you would send the message to your Django backend
// For now, we'll simulate a response
setTimeout(() => {
addMessage('Thank you for sharing that problem. Let me ask you the first "Why" question...', 'bot');
}, 1000);
}
sendButton.addEventListener('click', sendMessage);
chatInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
}
// Main process button functionality
processBtn.addEventListener('click', async function() {
if (processBtn.disabled) return;
processBtn.disabled = true;
processBtn.textContent = 'Processing...';
processingStatus.classList.remove('hidden');
try {
const formData = new FormData();
// Collect form data based on agent type
if (agentSlug === 'data-analyzer') {
const fileInput = document.getElementById('fileInput');
if (fileInput.files[0]) {
formData.append('file', fileInput.files[0]);
} else {
throw new Error('Please select a file');
}
} else if (agentSlug === 'weather-reporter') {
const location = document.getElementById('locationInput').value;
if (!location) throw new Error('Please enter a location');
formData.append('location', location);
} else {
// For other agents, collect all form inputs
const inputs = document.querySelectorAll('input[name], textarea[name], select[name]');
inputs.forEach(input => {
if (input.value) {
formData.append(input.name, input.value);
}
});
}
const response = await fetch(`{% url 'process_agent' agent.slug %}`, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
});
const data = await response.json();
if (data.success) {
// Display results
resultsContent.innerHTML = `<pre>${JSON.stringify(data.result, null, 2)}</pre>`;
results.classList.remove('hidden');
// Update balance display (you might want to refresh the page or update via AJAX)
location.reload();
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Error: ' + error.message);
} finally {
processBtn.disabled = false;
processBtn.textContent = 'Process Agent ({{ agent.price_display }})';
processingStatus.classList.add('hidden');
}
});
});
// Utility functions
function copyResults() {
const content = document.getElementById('resultsContent').textContent;
navigator.clipboard.writeText(content).then(() => {
alert('Results copied to clipboard!');
});
}
function downloadResults() {
const content = document.getElementById('resultsContent').textContent;
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '{{ agent.slug }}_results.txt';
a.click();
window.URL.revokeObjectURL(url);
}
function copyReport() {
const content = document.getElementById('reportContent').textContent;
navigator.clipboard.writeText(content).then(() => {
alert('Report copied to clipboard!');
});
}
function downloadReport() {
const content = document.getElementById('reportContent').textContent;
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'five_whys_analysis_report.txt';
a.click();
window.URL.revokeObjectURL(url);
}
</script>
<style>
.markdown-content ul {
list-style-type: disc;
margin-left: 20px;
margin-top: 8px;
margin-bottom: 8px;
}
.markdown-content li {
margin: 4px 0;
}
.markdown-content p {
margin: 8px 0;
}
.markdown-content strong {
font-weight: 600;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
</style>
{% endblock %}

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 400px; margin: 50px auto; }
.login-form { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.btn { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; }
.btn:hover { background: #0056b3; }
.messages { margin-bottom: 20px; }
.message { padding: 10px; border-radius: 4px; margin-bottom: 10px; }
.message.error { background: #ffe6e6; color: #cc0000; }
.message.success { background: #e6ffe6; color: #006600; }
.auth-links { text-align: center; margin-top: 20px; }
.auth-links a { color: #007bff; text-decoration: none; }
.auth-links a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<div class="login-form">
<h2>Login to NetCop Hub</h2>
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="message {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">Login</button>
</form>
<div class="auth-links">
<p>Don't have an account? <a href="{% url 'register' %}">Register here</a></p>
<p><a href="{% url 'homepage' %}">Back to Homepage</a></p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profile - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; }
.profile-header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.wallet-status { padding: 15px; border-radius: 8px; margin: 20px 0; }
.wallet-status.high { background: #d4edda; color: #155724; }
.wallet-status.medium { background: #fff3cd; color: #856404; }
.wallet-status.low { background: #f8d7da; color: #721c24; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 20px; }
.stat-card { background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-value { font-size: 1.5em; font-weight: bold; color: #007bff; }
.popular-agents { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
.agent-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; }
.agent-item:last-child { border-bottom: none; }
.transactions { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.transaction-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; }
.transaction-item:last-child { border-bottom: none; }
.transaction-amount { font-weight: bold; }
.transaction-amount.positive { color: #28a745; }
.transaction-amount.negative { color: #dc3545; }
.btn { padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; display: inline-block; margin-right: 10px; }
.btn:hover { background: #0056b3; }
.back-link { display: inline-block; margin-bottom: 20px; color: #007bff; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<a href="{% url 'homepage' %}" class="back-link">← Back to Homepage</a>
<div class="profile-header">
<h1>Profile - {{ user.username }}</h1>
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Balance:</strong> ${{ user.wallet_balance }}</p>
<div class="wallet-status {{ wallet_status.status }}">
<strong>{{ wallet_status.message }}</strong>
</div>
<a href="{% url 'wallet' %}" class="btn">Manage Wallet</a>
<a href="{% url 'wallet_topup' %}" class="btn">Top Up</a>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value">${{ total_spent }}</div>
<div>Total Spent</div>
</div>
<div class="stat-card">
<div class="stat-value">${{ total_topped_up }}</div>
<div>Total Topped Up</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ total_agents_used }}</div>
<div>Agents Used</div>
</div>
</div>
{% if popular_agents %}
<div class="popular-agents">
<h2>Your Most Used Agents</h2>
{% for agent in popular_agents %}
<div class="agent-item">
<span>{{ agent.agent_slug }}</span>
<span><strong>{{ agent.count }}</strong> times</span>
</div>
{% endfor %}
</div>
{% endif %}
<div class="transactions">
<h2>Recent Transactions</h2>
{% if transactions %}
{% for transaction in transactions %}
<div class="transaction-item">
<div>
<strong>{{ transaction.description }}</strong>
<small>{{ transaction.created_at|date:"M d, Y H:i" }}</small>
</div>
<div class="transaction-amount {% if transaction.type == 'top_up' %}positive{% else %}negative{% endif %}">
{% if transaction.type == 'top_up' %}+{% else %}-{% endif %}${{ transaction.amount }}
</div>
</div>
{% endfor %}
{% else %}
<p>No transactions yet.</p>
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 400px; margin: 50px auto; }
.register-form { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.btn { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; }
.btn:hover { background: #0056b3; }
.messages { margin-bottom: 20px; }
.message { padding: 10px; border-radius: 4px; margin-bottom: 10px; }
.message.error { background: #ffe6e6; color: #cc0000; }
.message.success { background: #e6ffe6; color: #006600; }
.auth-links { text-align: center; margin-top: 20px; }
.auth-links a { color: #007bff; text-decoration: none; }
.auth-links a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<div class="register-form">
<h2>Register for NetCop Hub</h2>
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="message {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password1">Password:</label>
<input type="password" id="password1" name="password1" required>
</div>
<div class="form-group">
<label for="password2">Confirm Password:</label>
<input type="password" id="password2" name="password2" required>
</div>
<button type="submit" class="btn">Register</button>
</form>
<div class="auth-links">
<p>Already have an account? <a href="{% url 'login' %}">Login here</a></p>
<p><a href="{% url 'homepage' %}">Back to Homepage</a></p>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,74 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}NetCop AI Hub{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.glass { backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.1); }
.gradient-bg { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
</style>
</head>
<body class="bg-gray-50">
<!-- Navigation -->
<nav class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<a href="{% url 'marketplace' %}" class="text-xl font-bold text-gray-900">
NetCop AI Hub
</a>
</div>
<div class="flex items-center space-x-4">
{% if user.is_authenticated %}
<div class="glass px-3 py-1 rounded-full">
<span class="text-sm font-medium">💰 {{ user.wallet_balance }} AED</span>
</div>
<a href="{% url 'pricing' %}" class="text-blue-600 hover:text-blue-800">Top Up</a>
<a href="{% url 'profile' %}" class="text-gray-600 hover:text-gray-800">Profile</a>
<a href="{% url 'logout' %}" class="text-red-600 hover:text-red-800">Logout</a>
{% else %}
<a href="{% url 'login' %}" class="text-blue-600 hover:text-blue-800">Login</a>
<a href="{% url 'register' %}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">Sign Up</a>
{% endif %}
</div>
</div>
</div>
</nav>
<!-- Messages -->
{% if messages %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-4">
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-error bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{% else %}
<div class="alert alert-success bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{% endif %}
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-gray-800 text-white mt-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="text-center">
<p>&copy; 2024 NetCop AI Hub. All rights reserved.</p>
</div>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ agent.name }} - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; }
.agent-detail { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.agent-header { display: flex; align-items: center; margin-bottom: 20px; }
.agent-icon { font-size: 3em; margin-right: 20px; }
.agent-info h1 { margin: 0; }
.agent-price { color: #007bff; font-size: 1.5em; font-weight: bold; margin: 10px 0; }
.agent-rating { color: #ffa500; font-size: 1.1em; }
.user-balance { background: #e8f5e8; padding: 10px; border-radius: 5px; margin: 20px 0; }
.insufficient-balance { background: #ffe6e6; padding: 10px; border-radius: 5px; margin: 20px 0; color: #cc0000; }
.agent-form { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.form-group textarea { height: 100px; resize: vertical; }
.btn { padding: 12px 24px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; }
.btn:hover { background: #0056b3; }
.btn:disabled { background: #ccc; cursor: not-allowed; }
.back-link { display: inline-block; margin-bottom: 20px; color: #007bff; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
.recent-usage { margin-top: 30px; }
.usage-item { background: #f8f9fa; padding: 10px; margin: 5px 0; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<a href="{% url 'homepage' %}" class="back-link">← Back to Homepage</a>
<div class="agent-detail">
<div class="agent-header">
<div class="agent-icon">{{ agent.icon }}</div>
<div class="agent-info">
<h1>{{ agent.name }}</h1>
<div class="agent-price">${{ agent.price }}</div>
<div class="agent-rating">★ {{ agent.rating }} ({{ agent.review_count }} reviews)</div>
</div>
</div>
<p>{{ agent.description }}</p>
{% if user.is_authenticated %}
<div class="user-balance">
Your Balance: ${{ user_balance }}
</div>
{% if can_use_agent %}
<div class="agent-form">
<h3>Use {{ agent.name }}</h3>
<form method="post" action="{% url 'use_agent' agent.slug %}" enctype="multipart/form-data">
{% csrf_token %}
{% if agent.slug == 'data-analyzer' %}
<div class="form-group">
<label for="file">Upload Data File:</label>
<input type="file" id="file" name="file" accept=".csv,.xlsx,.json" required>
</div>
{% elif agent.slug == 'five-whys' %}
<div class="form-group">
<label for="problem">Problem Description:</label>
<textarea id="problem" name="problem" placeholder="Describe the problem you want to analyze..." required></textarea>
</div>
{% elif agent.slug == 'weather-reporter' %}
<div class="form-group">
<label for="location">Location:</label>
<input type="text" id="location" name="location" placeholder="Enter city name or coordinates" required>
</div>
{% elif agent.slug == 'job-posting-generator' %}
<div class="form-group">
<label for="job_details">Job Details:</label>
<textarea id="job_details" name="job_details" placeholder="Enter job title, requirements, company info..." required></textarea>
</div>
{% elif agent.slug == 'social-ads-generator' %}
<div class="form-group">
<label for="ad_requirements">Ad Requirements:</label>
<textarea id="ad_requirements" name="ad_requirements" placeholder="Describe your product/service, target audience, goals..." required></textarea>
</div>
{% elif agent.slug == 'faq-generator' %}
<div class="form-group">
<label for="content_source">Content Source:</label>
<textarea id="content_source" name="content_source" placeholder="Paste your content, documentation, or product information..." required></textarea>
</div>
{% endif %}
<button type="submit" class="btn">Use Agent (${{ agent.price }})</button>
</form>
</div>
{% else %}
<div class="insufficient-balance">
<strong>Insufficient Balance!</strong> You need ${{ agent.price }} to use this agent.
<a href="{% url 'wallet_topup' %}" style="color: #007bff;">Top up your wallet</a>
</div>
{% endif %}
{% if recent_usage %}
<div class="recent-usage">
<h3>Your Recent Usage</h3>
{% for usage in recent_usage %}
<div class="usage-item">
<strong>${{ usage.amount }}</strong> - {{ usage.description }}
<small>({{ usage.created_at|date:"M d, Y H:i" }})</small>
</div>
{% endfor %}
</div>
{% endif %}
{% else %}
<p><a href="{% url 'login' %}">Login</a> to use this agent.</p>
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NetCop Hub - AI Agent Marketplace</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; }
.header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.user-info { float: right; }
.balance { background: #e8f5e8; padding: 5px 15px; border-radius: 20px; color: #2d5a2d; }
.agents-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.agent-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.agent-header { display: flex; align-items: center; margin-bottom: 15px; }
.agent-icon { font-size: 2em; margin-right: 15px; }
.agent-name { font-size: 1.3em; font-weight: bold; margin: 0; }
.agent-price { color: #007bff; font-weight: bold; }
.agent-rating { color: #ffa500; }
.category-section { margin-bottom: 30px; }
.category-title { font-size: 1.5em; margin-bottom: 15px; padding: 10px; background: #007bff; color: white; border-radius: 5px; }
.btn { padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; display: inline-block; margin-top: 10px; }
.btn:hover { background: #0056b3; }
.auth-links { margin-top: 10px; }
.auth-links a { margin-right: 15px; text-decoration: none; color: #007bff; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>NetCop Hub - AI Agent Marketplace</h1>
<div class="user-info">
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
<div class="balance">Balance: ${{ user_balance }}</div>
<div class="auth-links">
<a href="{% url 'profile' %}">Profile</a>
<a href="{% url 'wallet' %}">Wallet</a>
<a href="{% url 'logout' %}">Logout</a>
</div>
{% else %}
<div class="auth-links">
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'register' %}">Register</a>
</div>
{% endif %}
</div>
<div style="clear: both;"></div>
</div>
{% if categories %}
{% for category_name, category_agents in categories.items %}
<div class="category-section">
<div class="category-title">{{ category_name }}</div>
<div class="agents-grid">
{% for agent in category_agents %}
<div class="agent-card">
<div class="agent-header">
<div class="agent-icon">{{ agent.icon }}</div>
<div>
<div class="agent-name">{{ agent.name }}</div>
<div class="agent-price">${{ agent.price }}</div>
<div class="agent-rating">
★ {{ agent.rating }} ({{ agent.review_count }} reviews)
</div>
</div>
</div>
<p>{{ agent.description }}</p>
<a href="{% url 'agent_detail' agent.slug %}" class="btn">Use Agent</a>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<div class="agents-grid">
<div class="agent-card">
<h3>No agents available</h3>
<p>Please check back later or contact the administrator.</p>
</div>
</div>
{% endif %}
</div>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wallet - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; }
.wallet-header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.balance { font-size: 2em; color: #007bff; font-weight: bold; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 20px; }
.stat-card { background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-value { font-size: 1.5em; font-weight: bold; color: #007bff; }
.transactions { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.transaction-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; }
.transaction-item:last-child { border-bottom: none; }
.transaction-amount { font-weight: bold; }
.transaction-amount.positive { color: #28a745; }
.transaction-amount.negative { color: #dc3545; }
.btn { padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; display: inline-block; margin-bottom: 20px; }
.btn:hover { background: #0056b3; }
.back-link { display: inline-block; margin-bottom: 20px; color: #007bff; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<a href="{% url 'homepage' %}" class="back-link">← Back to Homepage</a>
<div class="wallet-header">
<h1>Your Wallet</h1>
<div class="balance">${{ current_balance }}</div>
<a href="{% url 'wallet_topup' %}" class="btn">Top Up Wallet</a>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value">${{ total_spent }}</div>
<div>Total Spent</div>
</div>
<div class="stat-card">
<div class="stat-value">${{ total_topped_up }}</div>
<div>Total Topped Up</div>
</div>
</div>
<div class="transactions">
<h2>Recent Transactions</h2>
{% if transactions %}
{% for transaction in transactions %}
<div class="transaction-item">
<div>
<strong>{{ transaction.description }}</strong>
<small>{{ transaction.created_at|date:"M d, Y H:i" }}</small>
</div>
<div class="transaction-amount {% if transaction.type == 'top_up' %}positive{% else %}negative{% endif %}">
{% if transaction.type == 'top_up' %}+{% else %}-{% endif %}${{ transaction.amount }}
</div>
</div>
{% endfor %}
{% else %}
<p>No transactions yet.</p>
{% endif %}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Top Up Wallet - NetCop Hub</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; }
.topup-form { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.amount-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin: 20px 0; }
.amount-option { padding: 20px; border: 2px solid #ddd; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.3s; }
.amount-option:hover { border-color: #007bff; background: #f8f9fa; }
.amount-option.selected { border-color: #007bff; background: #e7f3ff; }
.amount-value { font-size: 1.5em; font-weight: bold; color: #007bff; }
.btn { width: 100%; padding: 15px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; margin-top: 20px; }
.btn:hover { background: #0056b3; }
.btn:disabled { background: #ccc; cursor: not-allowed; }
.back-link { display: inline-block; margin-bottom: 20px; color: #007bff; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
.messages { margin-bottom: 20px; }
.message { padding: 10px; border-radius: 4px; margin-bottom: 10px; }
.message.error { background: #ffe6e6; color: #cc0000; }
.message.success { background: #e6ffe6; color: #006600; }
</style>
</head>
<body>
<div class="container">
<a href="{% url 'wallet' %}" class="back-link">← Back to Wallet</a>
<div class="topup-form">
<h1>Top Up Your Wallet</h1>
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="message {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post" id="topup-form">
{% csrf_token %}
<p>Select an amount to add to your wallet:</p>
<div class="amount-options">
<div class="amount-option" data-amount="10">
<div class="amount-value">$10</div>
<div>Basic</div>
</div>
<div class="amount-option" data-amount="50">
<div class="amount-value">$50</div>
<div>Popular</div>
</div>
<div class="amount-option" data-amount="100">
<div class="amount-value">$100</div>
<div>Best Value</div>
</div>
<div class="amount-option" data-amount="500">
<div class="amount-value">$500</div>
<div>Premium</div>
</div>
</div>
<input type="hidden" name="amount" id="selected-amount" value="">
<button type="submit" class="btn" id="topup-btn" disabled>Select an amount</button>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const amountOptions = document.querySelectorAll('.amount-option');
const selectedAmountInput = document.getElementById('selected-amount');
const topupBtn = document.getElementById('topup-btn');
amountOptions.forEach(option => {
option.addEventListener('click', function() {
// Remove selected class from all options
amountOptions.forEach(opt => opt.classList.remove('selected'));
// Add selected class to clicked option
this.classList.add('selected');
// Set the selected amount
const amount = this.getAttribute('data-amount');
selectedAmountInput.value = amount;
// Enable submit button
topupBtn.disabled = false;
topupBtn.textContent = `Top Up $${amount}`;
});
});
});
</script>
</body>
</html>

View File

@ -1,36 +0,0 @@
{% extends 'base.html' %}
{% block title %}Debug - Environment Status{% endblock %}
{% block content %}
<div style="padding: 20px; font-family: monospace; max-width: 800px; margin: 0 auto;">
{% if not debug_mode %}
<h1>🚫 Debug page disabled in production</h1>
<p>This debug page is only available in development mode.</p>
{% else %}
<h1>🔧 Environment Debug Page</h1>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h2>Environment Variables Status:</h2>
<pre>{{ env_status|safe }}</pre>
</div>
<div style="background: #e8f4f8; padding: 15px; border-radius: 8px;">
<h3>💡 Troubleshooting Tips:</h3>
<ul>
<li>Make sure .env file exists in your project root</li>
<li>Restart your Django server after changing environment variables</li>
<li>In production, set environment variables in your hosting platform dashboard</li>
<li>Check that sensitive variables are properly configured</li>
</ul>
</div>
<div style="background: #fff3cd; padding: 15px; border-radius: 8px; margin-top: 20px;">
<h3>🔍 Database Status:</h3>
<p>Database Connection: <strong style="color: {{ db_status.color }};">{{ db_status.status }}</strong></p>
<p>User Count: <strong>{{ user_count }}</strong></p>
<p>Agent Count: <strong>{{ agent_count }}</strong></p>
</div>
{% endif %}
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
{% extends 'base.html' %}
{% block title %}AI Agent Marketplace - NetCop AI Hub{% endblock %}
{% block content %}
<div class="text-center mb-12">
<h1 class="text-4xl font-bold text-gray-900 mb-4">AI Agent Marketplace</h1>
<p class="text-xl text-gray-600">Choose from our collection of powerful AI agents</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for agent in agents %}
<div class="bg-white rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-gradient-to-r {{ agent.get_gradient_class }} rounded-lg flex items-center justify-center text-white font-bold">
{{ agent.icon }}
</div>
<div class="text-right">
<div class="text-lg font-bold text-gray-900">{{ agent.price_display }}</div>
<div class="text-sm text-gray-500">per use</div>
</div>
</div>
<h3 class="text-xl font-semibold text-gray-900 mb-2">{{ agent.name }}</h3>
<p class="text-gray-600 mb-4">{{ agent.description }}</p>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<span class="text-yellow-400"></span>
<span class="ml-1 text-sm text-gray-600">{{ agent.rating }} ({{ agent.review_count }})</span>
</div>
<span class="bg-{{ agent.category == 'analytics' and 'blue' or 'green' }}-100 text-{{ agent.category == 'analytics' and 'blue' or 'green' }}-800 px-2 py-1 rounded-full text-xs">
{{ agent.get_category_display }}
</span>
</div>
<a href="{% url 'agent_detail' agent.slug %}" class="block w-full bg-blue-600 text-white text-center py-2 rounded-md hover:bg-blue-700 transition-colors">
Use Agent
</a>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,58 +0,0 @@
{% extends 'base.html' %}
{% block title %}Reset Password - NetCop AI Hub{% endblock %}
{% block content %}
<div style="min-height: 100vh; background: linear-gradient(135deg, #f6f8ff 0%, #e8f0fe 50%, #f0f7ff 100%); display: flex; align-items: center; justify-content: center; padding: 20px;">
<div style="background: white; border-radius: 20px; padding: 40px; width: 100%; max-width: 400px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);">
<div style="text-align: center; margin-bottom: 32px;">
<h1 style="font-size: 28px; font-weight: bold; color: #1f2937; margin-bottom: 8px;">
Reset Your Password
</h1>
<p style="color: #6b7280;">
Enter your new password below
</p>
</div>
{% if error %}
<div style="background: #fef2f2; border: 1px solid #fecaca; color: #dc2626; padding: 16px; border-radius: 12px; text-align: center; margin-bottom: 20px;">
<div style="font-size: 48px; margin-bottom: 16px;"></div>
<h3 style="font-weight: bold; margin-bottom: 8px;">Error</h3>
<p style="margin-bottom: 16px;">{{ error }}</p>
<a href="{% url 'homepage' %}" style="background: #dc2626; color: white; padding: 8px 16px; border-radius: 8px; text-decoration: none; font-size: 14px;">
Go to Homepage
</a>
</div>
{% else %}
<form method="post" style="display: flex; flex-direction: column; gap: 20px;">
{% csrf_token %}
<div>
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: #374151;">
New Password
</label>
<input type="password" name="password" required style="width: 100%; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 12px; font-size: 16px; transition: border-color 0.2s;" placeholder="Enter new password">
</div>
<div>
<label style="display: block; margin-bottom: 8px; font-weight: 500; color: #374151;">
Confirm Password
</label>
<input type="password" name="confirm_password" required style="width: 100%; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 12px; font-size: 16px; transition: border-color 0.2s;" placeholder="Confirm new password">
</div>
<button type="submit" style="background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); color: white; padding: 14px 24px; border-radius: 12px; font-size: 16px; font-weight: 600; border: none; cursor: pointer; transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
Update Password
</button>
</form>
{% endif %}
<div style="text-align: center; margin-top: 24px; padding-top: 24px; border-top: 1px solid #e5e7eb;">
<a href="{% url 'homepage' %}" style="background: none; border: none; color: #3b82f6; font-weight: 500; cursor: pointer; text-decoration: underline;">
Back to Homepage
</a>
</div>
</div>
</div>
{% endblock %}

34
test_views.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
import os
import sys
import django
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netcop_hub.settings')
django.setup()
from django.test import RequestFactory
from django.contrib.auth import get_user_model
from core.views import homepage_view
# Create a test request
factory = RequestFactory()
request = factory.get('/')
# Create a mock user
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
try:
# Test homepage view
response = homepage_view(request)
print(f"Homepage view status: {response.status_code}")
print("Homepage view working correctly!")
except Exception as e:
print(f"Error in homepage view: {e}")
import traceback
traceback.print_exc()

0
wallet/__init__.py Normal file
View File

31
wallet/admin.py Normal file
View File

@ -0,0 +1,31 @@
from django.contrib import admin
from .models import WalletTransaction
@admin.register(WalletTransaction)
class WalletTransactionAdmin(admin.ModelAdmin):
list_display = ('user', 'type', 'amount', 'agent_slug', 'created_at', 'id')
list_filter = ('type', 'created_at', 'agent_slug')
search_fields = ('user__username', 'user__email', 'description', 'agent_slug')
readonly_fields = ('id', 'created_at')
ordering = ('-created_at',)
fieldsets = (
('Transaction Details', {
'fields': ('user', 'type', 'amount', 'description')
}),
('Agent Information', {
'fields': ('agent_slug',)
}),
('Payment Information', {
'fields': ('stripe_session_id',)
}),
('Metadata', {
'fields': ('id', 'created_at')
}),
)
def get_readonly_fields(self, request, obj=None):
if obj: # editing an existing object
return self.readonly_fields + ('user', 'type', 'amount')
return self.readonly_fields

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.4 on 2025-07-08 08:17
# Generated by Django 5.2.4 on 2025-07-08 15:00
import django.db.models.deletion
import uuid

View File

View File

@ -4,6 +4,7 @@ import uuid
User = get_user_model()
class WalletTransaction(models.Model):
TRANSACTION_TYPES = [
('top_up', 'Top Up'),
@ -25,4 +26,3 @@ class WalletTransaction(models.Model):
def __str__(self):
return f"{self.user.email} - {self.amount} AED ({self.type})"

106
wallet/stripe_handler.py Normal file
View File

@ -0,0 +1,106 @@
import stripe
from django.conf import settings
from django.contrib.auth import get_user_model
from django.http import JsonResponse
from decimal import Decimal
import json
User = get_user_model()
stripe.api_key = settings.STRIPE_SECRET_KEY
class StripePaymentHandler:
def __init__(self):
self.payment_links = {
10: 'https://buy.stripe.com/test_28EbJ16AA7ly3ic7vh2VG0a',
50: 'https://buy.stripe.com/test_4gM00jbUUgW83ic3f12VG0b',
100: 'https://buy.stripe.com/test_aFadR99MM35ibOI6rd2VG0c',
500: 'https://buy.stripe.com/test_14AbJ12kk7lyf0U16T2VG0d'
}
def create_checkout_session(self, user, amount):
"""Create a Stripe checkout session for wallet top-up"""
if amount not in self.payment_links:
raise ValueError(f"Invalid amount: {amount}")
payment_link = self.payment_links[amount]
# Return the payment link URL with user reference
return {
'payment_url': f"{payment_link}?client_reference_id={user.id}&prefilled_email={user.email}",
'session_id': None # Payment links don't have session IDs
}
def verify_payment(self, session_id):
"""Verify payment from Stripe webhook"""
try:
session = stripe.checkout.Session.retrieve(session_id)
if session.payment_status == 'paid':
return {
'success': True,
'amount': session.amount_total / 100, # Convert from cents
'customer_email': session.customer_details.email,
'client_reference_id': session.client_reference_id
}
else:
return {'success': False, 'error': 'Payment not completed'}
except stripe.error.StripeError as e:
return {'success': False, 'error': str(e)}
def handle_webhook(self, payload, signature):
"""Handle Stripe webhook events"""
try:
event = stripe.Webhook.construct_event(
payload, signature, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError:
return {'success': False, 'error': 'Invalid payload'}
except stripe.error.SignatureVerificationError:
return {'success': False, 'error': 'Invalid signature'}
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
# Process successful payment
user_id = session.get('client_reference_id')
amount = session['amount_total'] / 100 # Convert from cents
if user_id:
try:
user = User.objects.get(id=user_id)
user.add_balance(
amount=amount,
description=f"Wallet top-up via Stripe",
stripe_session_id=session['id']
)
return {'success': True, 'message': 'Payment processed successfully'}
except User.DoesNotExist:
return {'success': False, 'error': 'User not found'}
return {'success': True, 'message': 'Event processed'}
def process_refund(self, session_id, amount=None):
"""Process refund for a payment"""
try:
session = stripe.checkout.Session.retrieve(session_id)
payment_intent = session.payment_intent
if amount:
refund = stripe.Refund.create(
payment_intent=payment_intent,
amount=int(amount * 100) # Convert to cents
)
else:
refund = stripe.Refund.create(payment_intent=payment_intent)
return {
'success': True,
'refund_id': refund.id,
'amount': refund.amount / 100,
'status': refund.status
}
except stripe.error.StripeError as e:
return {'success': False, 'error': str(e)}

3
wallet/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.