mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 12:12:57 +00:00
🎨 Add external service wrappers and social media previews
- External service wrapper system for JotForm, Zapier integrations - Simple template-based approach (iframe, landing, redirect) - Rich social media previews with Open Graph and Twitter Card tags - Professional social preview image for branded sharing - Updated documentation with usage examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
838927f316
commit
cae8d4a8fe
53
CLAUDE.md
53
CLAUDE.md
@ -212,6 +212,57 @@ The platform supports 2 agent types:
|
|||||||
- **Webhook Agents** - N8N integration with dynamic forms
|
- **Webhook Agents** - N8N integration with dynamic forms
|
||||||
- **Direct Access Agents** - External forms (JotForm, etc.) with embedded interfaces
|
- **Direct Access Agents** - External forms (JotForm, etc.) with embedded interfaces
|
||||||
|
|
||||||
|
## External Service Wrappers
|
||||||
|
|
||||||
|
**Simple template-based system for event invitations, demos, and external forms:**
|
||||||
|
|
||||||
|
**Configuration:** Edit `EXTERNAL_PAGES` dict in `core/views.py`:
|
||||||
|
```python
|
||||||
|
EXTERNAL_PAGES = {
|
||||||
|
'event-invitation': {
|
||||||
|
'title': 'Event Registration',
|
||||||
|
'external_url': 'https://form.jotform.com/252214924850455',
|
||||||
|
'template': 'iframe', # iframe, landing, or redirect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Templates Available:**
|
||||||
|
- `templates/wrapper/iframe.html` - Full-screen iframe embed
|
||||||
|
- `templates/wrapper/landing.html` - Branded landing page with embed
|
||||||
|
- `templates/wrapper/redirect.html` - Auto-redirect with countdown
|
||||||
|
|
||||||
|
**Access:** `/{page-name}/` (e.g., `/event-invitation/`)
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Rate limiting protection
|
||||||
|
- Consistent branding
|
||||||
|
- Mobile responsive
|
||||||
|
- No database needed
|
||||||
|
|
||||||
|
## Social Media Integration
|
||||||
|
|
||||||
|
**Rich social media previews implemented in `templates/base.html`:**
|
||||||
|
|
||||||
|
**Open Graph Tags:**
|
||||||
|
- `og:title` - Page title for social sharing
|
||||||
|
- `og:description` - Page description
|
||||||
|
- `og:image` - Preview image (`static/img/social-preview.png`)
|
||||||
|
- `og:url` - Canonical page URL
|
||||||
|
- `og:site_name` - "Quantum Tasks AI"
|
||||||
|
|
||||||
|
**Twitter Card Tags:**
|
||||||
|
- `twitter:card` - Large image format
|
||||||
|
- `twitter:title/description/image` - Twitter-specific metadata
|
||||||
|
|
||||||
|
**Custom Per-Page:** Override blocks in templates:
|
||||||
|
```django
|
||||||
|
{% block og_title %}Custom Page Title{% endblock %}
|
||||||
|
{% block meta_description %}Custom description{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Rich previews on WhatsApp, Discord, Twitter, LinkedIn with branded image and professional descriptions.
|
||||||
|
|
||||||
## Production Deployment
|
## Production Deployment
|
||||||
|
|
||||||
**Railway Configuration:**
|
**Railway Configuration:**
|
||||||
@ -269,6 +320,8 @@ The platform supports 2 agent types:
|
|||||||
- **Direct Access Agents (4)**: CyberSec Career Navigator, AI Brand Strategist, Lean Six Sigma Expert, SWOT Analysis Expert
|
- **Direct Access Agents (4)**: CyberSec Career Navigator, AI Brand Strategist, Lean Six Sigma Expert, SWOT Analysis Expert
|
||||||
|
|
||||||
**Latest Changes:**
|
**Latest Changes:**
|
||||||
|
- **External service wrapper system** - Simple template-based system for JotForm, Zapier, event invitations
|
||||||
|
- **Social media meta tags** - Rich previews with Open Graph and Twitter Card tags
|
||||||
- **View separation completed** - Split large views.py into focused modules (api, chat, web, direct access)
|
- **View separation completed** - Split large views.py into focused modules (api, chat, web, direct access)
|
||||||
- **Restored digital-branding.css** from git history with proper design system integration
|
- **Restored digital-branding.css** from git history with proper design system integration
|
||||||
- **Added SWOT Analysis Expert** with proper category assignment (analysis)
|
- **Added SWOT Analysis Expert** with proper category assignment (analysis)
|
||||||
|
|||||||
16
agents/configs/agents/demo-assistant.json
Normal file
16
agents/configs/agents/demo-assistant.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"slug": "demo-assistant",
|
||||||
|
"name": "Demo AI Assistant",
|
||||||
|
"short_description": "Demo AI assistant for testing direct access agent functionality",
|
||||||
|
"description": "A simple demo AI assistant that showcases the direct access agent system. Perfect for testing and demonstration purposes.",
|
||||||
|
"category": "analysis",
|
||||||
|
"price": 0.0,
|
||||||
|
"agent_type": "form",
|
||||||
|
"system_type": "direct_access",
|
||||||
|
"form_schema": {
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"webhook_url": "https://agent.jotform.com/019865a942ab7fa5b5b743a5fd2abe09e345",
|
||||||
|
"access_url_name": "agents:direct_access_handler",
|
||||||
|
"display_url_name": "agents:direct_access_display"
|
||||||
|
}
|
||||||
@ -9,4 +9,7 @@ urlpatterns = [
|
|||||||
path('pricing/', views.pricing_view, name='pricing'),
|
path('pricing/', views.pricing_view, name='pricing'),
|
||||||
path('contact/', views.contact_form_view, name='contact_form'),
|
path('contact/', views.contact_form_view, name='contact_form'),
|
||||||
path('health/', views.health_check_view, name='health_check'),
|
path('health/', views.health_check_view, name='health_check'),
|
||||||
|
|
||||||
|
# External service wrapper pages
|
||||||
|
path('<str:page_name>/', views.external_page_view, name='external_page'),
|
||||||
]
|
]
|
||||||
@ -1,7 +1,7 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse, Http404
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django_ratelimit.decorators import ratelimit
|
from django_ratelimit.decorators import ratelimit
|
||||||
@ -282,3 +282,61 @@ def health_check_view(request):
|
|||||||
|
|
||||||
# Always return 200 - we're healthy if Django is running
|
# Always return 200 - we're healthy if Django is running
|
||||||
return JsonResponse(health_data, status=200)
|
return JsonResponse(health_data, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
# Simple external service wrapper configurations
|
||||||
|
EXTERNAL_PAGES = {
|
||||||
|
'event-invitation': {
|
||||||
|
'title': 'Event Registration',
|
||||||
|
'description': 'Register for our upcoming event',
|
||||||
|
'external_url': 'https://form.jotform.com/252214924850455',
|
||||||
|
'template': 'iframe', # iframe, landing, or redirect
|
||||||
|
},
|
||||||
|
'demo-form': {
|
||||||
|
'title': 'Product Demo Request',
|
||||||
|
'description': 'Schedule a personalized demo of our platform',
|
||||||
|
'external_url': 'https://calendly.com/your-demo-link',
|
||||||
|
'template': 'landing',
|
||||||
|
},
|
||||||
|
'consultation': {
|
||||||
|
'title': 'Free Consultation',
|
||||||
|
'external_url': 'https://www.jotform.com/consultation-form',
|
||||||
|
'template': 'redirect',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ratelimit(key='ip', rate='30/m', method='GET', block=False)
|
||||||
|
def external_page_view(request, page_name):
|
||||||
|
"""
|
||||||
|
Simple external service wrapper view.
|
||||||
|
Just renders templates with external URLs - no database needed.
|
||||||
|
"""
|
||||||
|
# Check rate limiting
|
||||||
|
if getattr(request, 'limited', False):
|
||||||
|
logger.warning(f"External page rate limit exceeded for IP {request.META.get('REMOTE_ADDR')}")
|
||||||
|
messages.warning(request, 'Too many requests. Please wait a moment.')
|
||||||
|
return redirect('core:homepage')
|
||||||
|
|
||||||
|
# Get page config
|
||||||
|
page_config = EXTERNAL_PAGES.get(page_name)
|
||||||
|
if not page_config:
|
||||||
|
raise Http404("Page not found")
|
||||||
|
|
||||||
|
# Choose template
|
||||||
|
template_map = {
|
||||||
|
'iframe': 'wrapper/iframe.html',
|
||||||
|
'landing': 'wrapper/landing.html',
|
||||||
|
'redirect': 'wrapper/redirect.html',
|
||||||
|
}
|
||||||
|
|
||||||
|
template_name = template_map.get(page_config['template'], 'wrapper/iframe.html')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'page_title': page_config['title'],
|
||||||
|
'page_description': page_config.get('description', ''),
|
||||||
|
'external_url': page_config['external_url'],
|
||||||
|
'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, template_name, context)
|
||||||
|
|||||||
@ -156,25 +156,29 @@ git push
|
|||||||
- **Zero URL Configuration**: Dynamic routing based on JSON config properties
|
- **Zero URL Configuration**: Dynamic routing based on JSON config properties
|
||||||
- **Zero Templates**: Single generic template works for all direct access agents
|
- **Zero Templates**: Single generic template works for all direct access agents
|
||||||
- **Zero Database Setup**: Pure file-based loading with intelligent caching
|
- **Zero Database Setup**: Pure file-based loading with intelligent caching
|
||||||
|
- **Zero Error Handling**: Automatic webhook error detection and user feedback
|
||||||
|
|
||||||
### Agent Type Handling
|
### Agent Type Handling
|
||||||
- **Webhook Agents**: Automatically generate dynamic forms from `form_schema`
|
- **Webhook Agents**: Automatically generate dynamic forms from `form_schema`
|
||||||
- **Direct Access Agents**: Automatically handle payment processing + external redirect
|
- **Direct Access Agents**: Automatically handle payment processing + external redirect
|
||||||
- **Both Types**: Work with only JSON configuration, no additional code
|
- **Both Types**: Work with only JSON configuration, no additional code
|
||||||
|
- **Error Handling**: Automatically detects webhook failures (OpenAI quota, N8N issues, timeouts) and shows user-friendly messages
|
||||||
|
|
||||||
### Development Workflow Comparison
|
### Development Workflow Comparison
|
||||||
```bash
|
```bash
|
||||||
# ❌ Old Complex Way (5+ steps)
|
# ❌ Old Complex Way (6+ steps)
|
||||||
1. Create JSON config
|
1. Create JSON config
|
||||||
2. Write Python view functions
|
2. Write Python view functions
|
||||||
3. Add URL routes
|
3. Add URL routes
|
||||||
4. Create HTML templates
|
4. Create HTML templates
|
||||||
5. Update view imports
|
5. Update view imports
|
||||||
6. Test and debug
|
6. Write error handling code
|
||||||
|
7. Test and debug
|
||||||
|
|
||||||
# ✅ New Simple Way (1 step)
|
# ✅ New Simple Way (1 step)
|
||||||
1. Create JSON config
|
1. Create JSON config
|
||||||
# Done! Everything else is automatic 🎉
|
# Done! Everything else is automatic 🎉
|
||||||
|
# - Forms, routing, error handling, user feedback all automated
|
||||||
```
|
```
|
||||||
|
|
||||||
### How Both Agent Types Work Now
|
### How Both Agent Types Work Now
|
||||||
@ -182,6 +186,7 @@ git push
|
|||||||
**Webhook Agents:**
|
**Webhook Agents:**
|
||||||
- JSON config → Dynamic form via `agent_detail_view`
|
- JSON config → Dynamic form via `agent_detail_view`
|
||||||
- Form submission → N8N webhook → Results display
|
- Form submission → N8N webhook → Results display
|
||||||
|
- Automatic error detection and user feedback
|
||||||
- No individual Python code needed
|
- No individual Python code needed
|
||||||
|
|
||||||
**Direct Access Agents:**
|
**Direct Access Agents:**
|
||||||
@ -189,6 +194,45 @@ git push
|
|||||||
- Payment → Generic iframe display
|
- Payment → Generic iframe display
|
||||||
- No individual Python code needed
|
- No individual Python code needed
|
||||||
|
|
||||||
|
## Automatic Error Handling 🛡️
|
||||||
|
|
||||||
|
The platform now includes **completely automated error handling** for all agents:
|
||||||
|
|
||||||
|
### What's Automatically Handled
|
||||||
|
- **N8N Webhook Failures**: Service down, timeouts, configuration errors
|
||||||
|
- **AI Service Limits**: OpenAI quota exceeded, rate limits, API errors
|
||||||
|
- **Network Issues**: Connection failures, DNS problems, timeouts
|
||||||
|
- **Invalid Responses**: Malformed data, unexpected formats
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Clear Error Messages**: "Agent is temporarily unavailable. Please try again later."
|
||||||
|
- **Persistent Display**: Error shown in results area (won't disappear like notifications)
|
||||||
|
- **No Technical Jargon**: Simple, friendly language instead of HTTP status codes
|
||||||
|
|
||||||
|
### Developer Benefits
|
||||||
|
- **Zero Configuration**: No error handling code needed in JSON configs
|
||||||
|
- **Automatic Detection**: System distinguishes between success and various failure types
|
||||||
|
- **Consistent UX**: All agents have identical error handling behavior
|
||||||
|
- **Debug Friendly**: Technical errors still logged to console for troubleshooting
|
||||||
|
|
||||||
|
### Examples of Handled Errors
|
||||||
|
```json
|
||||||
|
// N8N Response (OpenAI quota exceeded)
|
||||||
|
{
|
||||||
|
"errorMessage": "You exceeded your current quota",
|
||||||
|
"errorDetails": {"httpCode": "429"}
|
||||||
|
}
|
||||||
|
// → User sees: "Agent is temporarily unavailable. Please try again later."
|
||||||
|
|
||||||
|
// HTTP 500 from webhook
|
||||||
|
// → User sees: "Agent is temporarily unavailable. Please try again later."
|
||||||
|
|
||||||
|
// Connection timeout
|
||||||
|
// → User sees: "Agent is temporarily unavailable. Please try again later."
|
||||||
|
```
|
||||||
|
|
||||||
|
All technical details are logged for debugging, but users always see the same friendly message.
|
||||||
|
|
||||||
## Custom Integration (Advanced)
|
## Custom Integration (Advanced)
|
||||||
|
|
||||||
For agents needing custom behavior, add views to appropriate modules:
|
For agents needing custom behavior, add views to appropriate modules:
|
||||||
@ -229,4 +273,4 @@ Then add URL routes in `agents/urls.py` and update marketplace template if neede
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last updated: 2025-01-14*
|
*Last updated: 2025-01-15*
|
||||||
1
static/img/social-preview.png
Normal file
1
static/img/social-preview.png
Normal file
File diff suppressed because one or more lines are too long
@ -6,6 +6,25 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Quantum Tasks AI - AI Platform{% endblock %}</title>
|
<title>{% block title %}Quantum Tasks AI - AI Platform{% endblock %}</title>
|
||||||
|
|
||||||
|
<!-- SEO Meta Tags -->
|
||||||
|
<meta name="description" content="{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}">
|
||||||
|
<meta name="keywords" content="AI, artificial intelligence, automation, task management, AI agents, quantum computing">
|
||||||
|
<meta name="author" content="Quantum Tasks AI">
|
||||||
|
|
||||||
|
<!-- Open Graph Meta Tags for Social Media -->
|
||||||
|
<meta property="og:title" content="{% block og_title %}{% block title %}Quantum Tasks AI - AI Platform{% endblock %}{% endblock %}">
|
||||||
|
<meta property="og:description" content="{% block og_description %}{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}{% endblock %}">
|
||||||
|
<meta property="og:image" content="{% block og_image %}{% static 'img/social-preview.png' %}{% endblock %}">
|
||||||
|
<meta property="og:url" content="{% block og_url %}{{ request.build_absolute_uri }}{% endblock %}">
|
||||||
|
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
|
||||||
|
<meta property="og:site_name" content="Quantum Tasks AI">
|
||||||
|
|
||||||
|
<!-- Twitter Card Meta Tags -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="{% block twitter_title %}{% block og_title %}{% block title %}Quantum Tasks AI - AI Platform{% endblock %}{% endblock %}{% endblock %}">
|
||||||
|
<meta name="twitter:description" content="{% block twitter_description %}{% block og_description %}{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}{% endblock %}{% endblock %}">
|
||||||
|
<meta name="twitter:image" content="{% block twitter_image %}{% block og_image %}{% static 'img/social-preview.png' %}{% endblock %}{% endblock %}">
|
||||||
|
|
||||||
<!-- Unified Font Loading - Single Source of Truth -->
|
<!-- Unified Font Loading - Single Source of Truth -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|||||||
42
templates/wrapper/iframe.html
Normal file
42
templates/wrapper/iframe.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
/* Full-width iframe styling */
|
||||||
|
.main-container {
|
||||||
|
max-width: none;
|
||||||
|
padding: 0;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-container iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide footer for full-screen */
|
||||||
|
.footer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe
|
||||||
|
src="{{ external_url }}"
|
||||||
|
frameborder="0"
|
||||||
|
scrolling="auto"
|
||||||
|
title="{{ page_title|default:'External Service' }}">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
68
templates/wrapper/landing.html
Normal file
68
templates/wrapper/landing.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.landing-page {
|
||||||
|
min-height: 80vh;
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-description {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #666;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-service-embed {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-service-embed iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="landing-page">
|
||||||
|
<div class="container">
|
||||||
|
<div class="landing-header">
|
||||||
|
<h1 class="landing-title">{{ page_title|default:"External Service" }}</h1>
|
||||||
|
{% if page_description %}
|
||||||
|
<p class="landing-description">{{ page_description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="external-service-embed">
|
||||||
|
<iframe
|
||||||
|
src="{{ external_url }}"
|
||||||
|
frameborder="0"
|
||||||
|
scrolling="auto"
|
||||||
|
title="{{ page_title|default:'External Service' }}">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
105
templates/wrapper/redirect.html
Normal file
105
templates/wrapper/redirect.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Redirecting to {{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.redirect-page {
|
||||||
|
min-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirect-content {
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirect-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirect-timer {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirect-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirect-button:hover {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="redirect-page">
|
||||||
|
<div class="redirect-content">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<h1 class="redirect-title">Redirecting to {{ page_title|default:"External Service" }}</h1>
|
||||||
|
|
||||||
|
<div class="redirect-timer">
|
||||||
|
Redirecting in <span id="countdown">3</span> seconds...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{{ external_url }}" class="redirect-button">
|
||||||
|
Continue Now
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:homepage' %}" class="redirect-button" style="background: #6c757d;">
|
||||||
|
Go Back Home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let countdown = 3;
|
||||||
|
const countdownElement = document.getElementById('countdown');
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
countdownElement.textContent = countdown;
|
||||||
|
|
||||||
|
if (countdown <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
window.location.href = "{{ external_url }}";
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user