This commit is contained in:
Iliyan Angelov
2025-11-24 08:18:18 +02:00
parent 366f28677a
commit 136f75a859
133 changed files with 14977 additions and 3350 deletions

View File

@@ -1,193 +0,0 @@
# Support Center Module
Enterprise Support Center for handling customer support tickets, knowledge base articles, and support settings.
## Features
- **Ticket Management**
- Create and track support tickets
- Multiple ticket types (technical, billing, feature requests, etc.)
- Priority and status management
- Ticket categories and tags
- SLA deadline tracking
- Message and activity history
- **Knowledge Base**
- Categorized articles
- Search functionality
- Featured articles
- Article feedback (helpful/not helpful)
- View count tracking
- Rich content support
- **Public API**
- Create tickets without authentication
- Check ticket status by ticket number
- Browse knowledge base articles
- Search articles
## Setup
### 1. Run Migrations
```bash
python manage.py migrate
```
### 2. Populate Initial Data
```bash
python manage.py populate_support_data
```
This will create:
- 5 ticket statuses (Open, In Progress, Pending Response, Resolved, Closed)
- 4 ticket priorities (Low, Medium, High, Critical)
- 6 ticket categories
- 6 knowledge base categories
- 6 sample knowledge base articles
### 3. Admin Access
Access the Django admin panel to manage:
- Support tickets
- Ticket categories, statuses, and priorities
- Knowledge base categories and articles
- Support settings
## API Endpoints
### Tickets
- `GET /api/support/tickets/` - List all tickets
- `POST /api/support/tickets/` - Create a new ticket
- `GET /api/support/tickets/{id}/` - Get ticket details
- `POST /api/support/tickets/check-status/` - Check ticket status by ticket number
- `POST /api/support/tickets/{id}/add-message/` - Add a message to a ticket
### Categories
- `GET /api/support/categories/` - List all ticket categories
- `GET /api/support/statuses/` - List all ticket statuses
- `GET /api/support/priorities/` - List all ticket priorities
### Knowledge Base
- `GET /api/support/knowledge-base/` - List all published articles
- `GET /api/support/knowledge-base/{slug}/` - Get article details
- `GET /api/support/knowledge-base/featured/` - Get featured articles
- `GET /api/support/knowledge-base/by-category/{category_slug}/` - Get articles by category
- `POST /api/support/knowledge-base/{slug}/mark-helpful/` - Mark article as helpful/not helpful
- `GET /api/support/knowledge-base-categories/` - List all KB categories
### Settings
- `GET /api/support/settings/` - List all active support settings
- `GET /api/support/settings/{setting_name}/` - Get specific setting
## Models
### SupportTicket
Main model for support tickets with full tracking capabilities.
### TicketStatus
Ticket status options (Open, In Progress, Resolved, etc.)
### TicketPriority
Priority levels with SLA hours (Low, Medium, High, Critical)
### TicketCategory
Categorize tickets for better organization
### TicketMessage
Messages and updates on tickets
### TicketActivity
Audit trail of all ticket changes
### KnowledgeBaseCategory
Categories for knowledge base articles
### KnowledgeBaseArticle
Knowledge base articles with rich content
### SupportSettings
Configurable support center settings
## Usage Examples
### Create a Ticket
```python
import requests
data = {
"title": "Cannot login to my account",
"description": "I've been trying to login but getting error 500",
"ticket_type": "technical",
"user_name": "John Doe",
"user_email": "john@example.com",
"user_phone": "+1234567890",
"company": "Acme Corp",
"category": 1 # Technical Support category ID
}
response = requests.post('http://localhost:8000/api/support/tickets/', json=data)
ticket = response.json()
print(f"Ticket created: {ticket['ticket_number']}")
```
### Check Ticket Status
```python
import requests
data = {
"ticket_number": "TKT-20231015-ABCDE"
}
response = requests.post('http://localhost:8000/api/support/tickets/check-status/', json=data)
ticket = response.json()
print(f"Status: {ticket['status_name']}")
```
### Search Knowledge Base
```python
import requests
response = requests.get('http://localhost:8000/api/support/knowledge-base/', params={'search': 'login'})
articles = response.json()
for article in articles:
print(f"- {article['title']}")
```
## Frontend Integration
The support center is integrated with the Next.js frontend at `/support-center` with:
- Ticket submission form
- Knowledge base browser with search
- Ticket status checker
- Modern, responsive UI
## Email Notifications
To enable email notifications for tickets, configure email settings in `settings.py` and implement email templates in `support/templates/support/`.
## Security
- All endpoints are public (AllowAny permission)
- Ticket numbers are randomly generated and hard to guess
- Internal notes and messages are hidden from public API
- Rate limiting recommended for production
## Future Enhancements
- [ ] Live chat integration
- [ ] File attachments for tickets
- [ ] Email notifications
- [ ] Ticket assignment and routing
- [ ] SLA breach alerts
- [ ] Advanced analytics dashboard
- [ ] Webhook notifications

View File

@@ -3,15 +3,53 @@ Email Service for Support Tickets
Handles sending email notifications for ticket creation and updates
"""
from django.core.mail import EmailMultiAlternatives
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.conf import settings
from django.utils.html import strip_tags
import logging
import time
logger = logging.getLogger(__name__)
def _send_email_with_retry(email_message, max_retries: int = 3, delay: float = 1.0) -> bool:
"""
Send email with retry logic for production reliability.
Uses Django settings from .env file.
Args:
email_message: EmailMultiAlternatives instance
max_retries: Maximum number of retry attempts
delay: Delay between retries in seconds
Returns:
bool: True if email was sent successfully, False otherwise
"""
for attempt in range(max_retries + 1):
try:
# Test connection before sending (uses EMAIL_BACKEND from settings)
connection = get_connection()
connection.open()
connection.close()
# Send the email (uses EMAIL_BACKEND and credentials from settings)
email_message.send()
return True
except Exception as e:
logger.warning(f"Email send attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries:
time.sleep(delay * (2 ** attempt)) # Exponential backoff
continue
else:
logger.error(f"Failed to send email after {max_retries + 1} attempts: {str(e)}")
return False
return False
class SupportEmailService:
"""Service for sending support ticket related emails"""
@@ -39,6 +77,8 @@ class SupportEmailService:
'status': ticket.status.name if ticket.status else 'Open',
'created_at': ticket.created_at.strftime('%B %d, %Y at %I:%M %p'),
'support_url': f'{settings.SITE_URL}/support-center',
'logo_url': f'{settings.SITE_URL}/images/logo.png',
'site_url': settings.SITE_URL,
}
# Render HTML email
@@ -53,22 +93,25 @@ class SupportEmailService:
context
)
# Create email
# Create email (uses DEFAULT_FROM_EMAIL from settings)
email = EmailMultiAlternatives(
subject=subject,
body=text_message,
from_email=settings.DEFAULT_FROM_EMAIL,
from_email=settings.DEFAULT_FROM_EMAIL, # From .env file
to=[ticket.user_email],
)
# Attach HTML version
email.attach_alternative(html_message, "text/html")
# Send email
email.send(fail_silently=False)
# Send email with retry logic (uses EMAIL_BACKEND and credentials from settings)
success = _send_email_with_retry(email)
logger.info(f'Ticket confirmation email sent to user: {ticket.user_email} for ticket {ticket.ticket_number}')
return True
if success:
logger.info(f'Ticket confirmation email sent to user: {ticket.user_email} for ticket {ticket.ticket_number}')
else:
logger.error(f'Failed to send ticket confirmation email to user: {ticket.user_email} for ticket {ticket.ticket_number} after retries')
return success
except Exception as e:
logger.error(f'Failed to send ticket confirmation email to user: {str(e)}')
@@ -104,6 +147,8 @@ class SupportEmailService:
'status': ticket.status.name if ticket.status else 'Open',
'created_at': ticket.created_at.strftime('%B %d, %Y at %I:%M %p'),
'admin_url': f'{settings.SITE_URL}/admin/support/supportticket/{ticket.id}/change/',
'logo_url': f'{settings.SITE_URL}/images/logo.png',
'site_url': settings.SITE_URL,
}
# Render HTML email
@@ -118,23 +163,26 @@ class SupportEmailService:
context
)
# Create email
# Create email (uses DEFAULT_FROM_EMAIL and SUPPORT_EMAIL from settings)
email = EmailMultiAlternatives(
subject=subject,
body=text_message,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[company_email],
from_email=settings.DEFAULT_FROM_EMAIL, # From .env file
to=[company_email], # Uses settings.SUPPORT_EMAIL or settings.DEFAULT_FROM_EMAIL
reply_to=[ticket.user_email],
)
# Attach HTML version
email.attach_alternative(html_message, "text/html")
# Send email
email.send(fail_silently=False)
# Send email with retry logic (uses EMAIL_BACKEND and credentials from settings)
success = _send_email_with_retry(email)
logger.info(f'Ticket notification email sent to company: {company_email} for ticket {ticket.ticket_number}')
return True
if success:
logger.info(f'Ticket notification email sent to company: {company_email} for ticket {ticket.ticket_number}')
else:
logger.error(f'Failed to send ticket notification email to company: {company_email} for ticket {ticket.ticket_number} after retries')
return success
except Exception as e:
logger.error(f'Failed to send ticket notification email to company: {str(e)}')

View File

@@ -1,43 +1,87 @@
from django.core.management.base import BaseCommand
from django.utils.text import slugify
from django.db import transaction
from support.models import (
TicketStatus, TicketPriority, TicketCategory,
KnowledgeBaseCategory, KnowledgeBaseArticle
)
from services.models import Service
class Command(BaseCommand):
help = 'Populate support center with initial data'
help = 'Populate support center with enterprise support data based on services'
def add_arguments(self, parser):
parser.add_argument(
'--delete-old',
action='store_true',
help='Delete all existing support data before populating',
)
def handle(self, *args, **kwargs):
self.stdout.write(self.style.SUCCESS('Starting to populate support data...'))
delete_old = kwargs.get('delete_old', False)
self.stdout.write(self.style.SUCCESS('Starting to populate enterprise support data...'))
# Create Ticket Statuses
self.create_ticket_statuses()
# Create Ticket Priorities
self.create_ticket_priorities()
# Create Ticket Categories
self.create_ticket_categories()
# Create Knowledge Base Categories
self.create_kb_categories()
# Create Knowledge Base Articles
self.create_kb_articles()
with transaction.atomic():
# Delete old data if requested
if delete_old:
self.delete_old_data()
# Create Ticket Statuses
self.create_ticket_statuses()
# Create Ticket Priorities
self.create_ticket_priorities()
# Create Ticket Categories based on services
self.create_ticket_categories()
# Create Knowledge Base Categories
self.create_kb_categories()
# Create Knowledge Base Articles
self.create_kb_articles()
self.stdout.write(self.style.SUCCESS('Successfully populated support data!'))
self.stdout.write(self.style.SUCCESS('Successfully populated enterprise support data!'))
def delete_old_data(self):
"""Delete all existing support data"""
self.stdout.write(self.style.WARNING('Deleting old support data...'))
# Delete in reverse order of dependencies
kb_articles_count = KnowledgeBaseArticle.objects.count()
KnowledgeBaseArticle.objects.all().delete()
self.stdout.write(f' ✓ Deleted {kb_articles_count} knowledge base articles')
kb_categories_count = KnowledgeBaseCategory.objects.count()
KnowledgeBaseCategory.objects.all().delete()
self.stdout.write(f' ✓ Deleted {kb_categories_count} knowledge base categories')
ticket_categories_count = TicketCategory.objects.count()
TicketCategory.objects.all().delete()
self.stdout.write(f' ✓ Deleted {ticket_categories_count} ticket categories')
ticket_priorities_count = TicketPriority.objects.count()
TicketPriority.objects.all().delete()
self.stdout.write(f' ✓ Deleted {ticket_priorities_count} ticket priorities')
ticket_statuses_count = TicketStatus.objects.count()
TicketStatus.objects.all().delete()
self.stdout.write(f' ✓ Deleted {ticket_statuses_count} ticket statuses')
self.stdout.write(self.style.SUCCESS('Old data deleted successfully!'))
def create_ticket_statuses(self):
self.stdout.write('Creating ticket statuses...')
statuses = [
{'name': 'Open', 'color': '#3b82f6', 'description': 'Ticket has been opened', 'is_closed': False, 'display_order': 1},
{'name': 'In Progress', 'color': '#f59e0b', 'description': 'Ticket is being worked on', 'is_closed': False, 'display_order': 2},
{'name': 'Pending Response', 'color': '#8b5cf6', 'description': 'Waiting for customer response', 'is_closed': False, 'display_order': 3},
{'name': 'Resolved', 'color': '#10b981', 'description': 'Ticket has been resolved', 'is_closed': True, 'display_order': 4},
{'name': 'Closed', 'color': '#6b7280', 'description': 'Ticket has been closed', 'is_closed': True, 'display_order': 5},
{'name': 'Open', 'color': '#3b82f6', 'description': 'Ticket has been opened and is awaiting assignment', 'is_closed': False, 'display_order': 1},
{'name': 'In Progress', 'color': '#f59e0b', 'description': 'Ticket is being actively worked on by support team', 'is_closed': False, 'display_order': 2},
{'name': 'Pending Response', 'color': '#8b5cf6', 'description': 'Waiting for customer response or additional information', 'is_closed': False, 'display_order': 3},
{'name': 'On Hold', 'color': '#6b7280', 'description': 'Ticket is temporarily on hold', 'is_closed': False, 'display_order': 4},
{'name': 'Resolved', 'color': '#10b981', 'description': 'Ticket has been resolved and is awaiting customer confirmation', 'is_closed': True, 'display_order': 5},
{'name': 'Closed', 'color': '#6b7280', 'description': 'Ticket has been closed', 'is_closed': True, 'display_order': 6},
]
for status_data in statuses:
@@ -54,10 +98,10 @@ class Command(BaseCommand):
self.stdout.write('Creating ticket priorities...')
priorities = [
{'name': 'Low', 'level': 4, 'color': '#6b7280', 'description': 'Low priority issue', 'sla_hours': 72},
{'name': 'Medium', 'level': 3, 'color': '#3b82f6', 'description': 'Medium priority issue', 'sla_hours': 48},
{'name': 'High', 'level': 2, 'color': '#f59e0b', 'description': 'High priority issue', 'sla_hours': 24},
{'name': 'Critical', 'level': 1, 'color': '#ef4444', 'description': 'Critical issue requiring immediate attention', 'sla_hours': 4},
{'name': 'Low', 'level': 4, 'color': '#6b7280', 'description': 'Low priority issue - non-urgent', 'sla_hours': 72},
{'name': 'Medium', 'level': 3, 'color': '#3b82f6', 'description': 'Medium priority issue - standard response', 'sla_hours': 48},
{'name': 'High', 'level': 2, 'color': '#f59e0b', 'description': 'High priority issue - requires prompt attention', 'sla_hours': 24},
{'name': 'Critical', 'level': 1, 'color': '#ef4444', 'description': 'Critical issue requiring immediate attention - production down', 'sla_hours': 4},
]
for priority_data in priorities:
@@ -71,37 +115,108 @@ class Command(BaseCommand):
self.stdout.write(f' - Priority already exists: {priority.name}')
def create_ticket_categories(self):
self.stdout.write('Creating ticket categories...')
self.stdout.write('Creating ticket categories based on services...')
categories = [
{'name': 'Technical Support', 'description': 'Technical issues and troubleshooting', 'color': '#3b82f6', 'icon': 'fa-wrench', 'display_order': 1},
# Get all active services
services = Service.objects.filter(is_active=True).order_by('display_order')
# Service-based categories with icons and colors
service_category_mapping = {
'backend': {'icon': 'fa-server', 'color': '#3b82f6', 'description': 'Backend development, APIs, and server-side issues'},
'frontend': {'icon': 'fa-code', 'color': '#10b981', 'description': 'Frontend development, UI/UX, and client-side issues'},
'data': {'icon': 'fa-database', 'color': '#8b5cf6', 'description': 'Data replication, synchronization, and database issues'},
'infrastructure': {'icon': 'fa-cloud', 'color': '#f59e0b', 'description': 'Infrastructure management, cloud services, and system administration'},
'server': {'icon': 'fa-server', 'color': '#ef4444', 'description': 'Server management, configuration, and administration'},
'devops': {'icon': 'fa-cogs', 'color': '#06b6d4', 'description': 'DevOps, CI/CD, automation, and deployment issues'},
'api': {'icon': 'fa-plug', 'color': '#ec4899', 'description': 'API integration, connectivity, and third-party service issues'},
'ai': {'icon': 'fa-brain', 'color': '#daa520', 'description': 'AI/ML solutions, model training, and machine learning issues'},
}
# Base categories that apply to all services
base_categories = [
{'name': 'Technical Support', 'description': 'General technical issues and troubleshooting', 'color': '#3b82f6', 'icon': 'fa-wrench', 'display_order': 1},
{'name': 'Billing & Payments', 'description': 'Billing questions and payment issues', 'color': '#10b981', 'icon': 'fa-credit-card', 'display_order': 2},
{'name': 'Account Management', 'description': 'Account settings and access issues', 'color': '#8b5cf6', 'icon': 'fa-user-cog', 'display_order': 3},
{'name': 'Product Inquiry', 'description': 'Questions about products and features', 'color': '#f59e0b', 'icon': 'fa-box', 'display_order': 4},
{'name': 'Feature Requests', 'description': 'Request new features or improvements', 'color': '#06b6d4', 'icon': 'fa-lightbulb', 'display_order': 4},
{'name': 'Bug Reports', 'description': 'Report software bugs and issues', 'color': '#ef4444', 'icon': 'fa-bug', 'display_order': 5},
{'name': 'Feature Requests', 'description': 'Request new features or improvements', 'color': '#06b6d4', 'icon': 'fa-lightbulb', 'display_order': 6},
]
for category_data in categories:
# Create base categories
display_order = 1
for category_data in base_categories:
category, created = TicketCategory.objects.get_or_create(
name=category_data['name'],
defaults=category_data
defaults={**category_data, 'display_order': display_order}
)
if created:
self.stdout.write(self.style.SUCCESS(f' ✓ Created category: {category.name}'))
else:
self.stdout.write(f' - Category already exists: {category.name}')
display_order += 1
# Create service-specific categories
for service in services:
service_title_lower = service.title.lower()
category_name = None
category_config = None
# Map service to category
if 'backend' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['backend']
elif 'frontend' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['frontend']
elif 'data replication' in service_title_lower or 'synchronization' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['data']
elif 'infrastructure management' in service_title_lower or 'infrastructure support' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['infrastructure']
elif 'server management' in service_title_lower or 'server administration' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['server']
elif 'devops' in service_title_lower or 'automation' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['devops']
elif 'api integration' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['api']
elif 'artificial intelligence' in service_title_lower or 'machine learning' in service_title_lower or 'ai' in service_title_lower:
category_name = f'{service.title} Support'
category_config = service_category_mapping['ai']
if category_name and category_config:
category, created = TicketCategory.objects.get_or_create(
name=category_name,
defaults={
'description': f'Support for {service.title}. {category_config["description"]}',
'color': category_config['color'],
'icon': category_config['icon'],
'display_order': display_order
}
)
if created:
self.stdout.write(self.style.SUCCESS(f' ✓ Created category: {category.name}'))
else:
self.stdout.write(f' - Category already exists: {category.name}')
display_order += 1
def create_kb_categories(self):
self.stdout.write('Creating knowledge base categories...')
categories = [
{'name': 'Getting Started', 'slug': 'getting-started', 'description': 'Learn the basics and get started quickly', 'icon': 'fa-rocket', 'color': '#3b82f6', 'display_order': 1},
{'name': 'Account & Billing', 'slug': 'account-billing', 'description': 'Manage your account and billing information', 'icon': 'fa-user-circle', 'color': '#10b981', 'display_order': 2},
{'name': 'Technical Documentation', 'slug': 'technical-docs', 'description': 'Technical guides and API documentation', 'icon': 'fa-code', 'color': '#8b5cf6', 'display_order': 3},
{'name': 'Troubleshooting', 'slug': 'troubleshooting', 'description': 'Common issues and how to resolve them', 'icon': 'fa-tools', 'color': '#f59e0b', 'display_order': 4},
{'name': 'Security & Privacy', 'slug': 'security-privacy', 'description': 'Security features and privacy settings', 'icon': 'fa-shield-alt', 'color': '#ef4444', 'display_order': 5},
{'name': 'Best Practices', 'slug': 'best-practices', 'description': 'Tips and best practices for optimal use', 'icon': 'fa-star', 'color': '#daa520', 'display_order': 6},
{'name': 'Getting Started', 'slug': 'getting-started', 'description': 'Learn the basics and get started with our enterprise services', 'icon': 'fa-rocket', 'color': '#3b82f6', 'display_order': 1},
{'name': 'Backend Development', 'slug': 'backend-development', 'description': 'Backend development guides, APIs, and server-side documentation', 'icon': 'fa-server', 'color': '#3b82f6', 'display_order': 2},
{'name': 'Frontend Development', 'slug': 'frontend-development', 'description': 'Frontend development guides, UI/UX, and client-side documentation', 'icon': 'fa-code', 'color': '#10b981', 'display_order': 3},
{'name': 'Infrastructure & DevOps', 'slug': 'infrastructure-devops', 'description': 'Infrastructure management, DevOps, and deployment guides', 'icon': 'fa-cloud', 'color': '#f59e0b', 'display_order': 4},
{'name': 'Data Management', 'slug': 'data-management', 'description': 'Data replication, synchronization, and database management', 'icon': 'fa-database', 'color': '#8b5cf6', 'display_order': 5},
{'name': 'API Integration', 'slug': 'api-integration', 'description': 'API integration guides and third-party service connectivity', 'icon': 'fa-plug', 'color': '#ec4899', 'display_order': 6},
{'name': 'AI & Machine Learning', 'slug': 'ai-machine-learning', 'description': 'AI/ML solutions, model training, and machine learning guides', 'icon': 'fa-brain', 'color': '#daa520', 'display_order': 7},
{'name': 'Troubleshooting', 'slug': 'troubleshooting', 'description': 'Common issues and how to resolve them', 'icon': 'fa-tools', 'color': '#ef4444', 'display_order': 8},
{'name': 'Security & Privacy', 'slug': 'security-privacy', 'description': 'Security features, best practices, and privacy settings', 'icon': 'fa-shield-alt', 'color': '#ef4444', 'display_order': 9},
{'name': 'Account & Billing', 'slug': 'account-billing', 'description': 'Manage your account and billing information', 'icon': 'fa-user-circle', 'color': '#10b981', 'display_order': 10},
{'name': 'Best Practices', 'slug': 'best-practices', 'description': 'Tips and best practices for optimal use of our services', 'icon': 'fa-star', 'color': '#daa520', 'display_order': 11},
]
for category_data in categories:
@@ -119,140 +234,455 @@ class Command(BaseCommand):
# Get categories
getting_started = KnowledgeBaseCategory.objects.filter(slug='getting-started').first()
account_billing = KnowledgeBaseCategory.objects.filter(slug='account-billing').first()
technical = KnowledgeBaseCategory.objects.filter(slug='technical-docs').first()
backend_dev = KnowledgeBaseCategory.objects.filter(slug='backend-development').first()
frontend_dev = KnowledgeBaseCategory.objects.filter(slug='frontend-development').first()
infrastructure = KnowledgeBaseCategory.objects.filter(slug='infrastructure-devops').first()
data_mgmt = KnowledgeBaseCategory.objects.filter(slug='data-management').first()
api_integration = KnowledgeBaseCategory.objects.filter(slug='api-integration').first()
ai_ml = KnowledgeBaseCategory.objects.filter(slug='ai-machine-learning').first()
troubleshooting = KnowledgeBaseCategory.objects.filter(slug='troubleshooting').first()
security = KnowledgeBaseCategory.objects.filter(slug='security-privacy').first()
account_billing = KnowledgeBaseCategory.objects.filter(slug='account-billing').first()
best_practices = KnowledgeBaseCategory.objects.filter(slug='best-practices').first()
articles = [
{
'title': 'How to Get Started with Our Platform',
'slug': 'how-to-get-started',
'title': 'Getting Started with Enterprise Services',
'slug': 'getting-started-enterprise-services',
'category': getting_started,
'summary': 'A comprehensive guide to help you get started with our platform quickly and easily.',
'content': '''<h2>Welcome to Our Platform!</h2>
<p>This guide will help you get started with our platform in just a few simple steps.</p>
'summary': 'A comprehensive guide to help you get started with our enterprise services quickly and efficiently.',
'content': '''<h2>Welcome to GNX Enterprise Services!</h2>
<p>This guide will help you get started with our enterprise services in just a few simple steps.</p>
<h3>Step 1: Create Your Account</h3>
<p>Visit our sign-up page and create your account using your email address or social login.</p>
<h3>Step 1: Understand Your Service Package</h3>
<p>Review your service agreement and understand what services are included in your package. Each service comes with specific deliverables, timelines, and support options.</p>
<h3>Step 2: Complete Your Profile</h3>
<p>Add your company information and customize your profile settings.</p>
<h3>Step 2: Access Your Service Portal</h3>
<p>Log in to your enterprise portal to access service documentation, support tickets, and project management tools.</p>
<h3>Step 3: Explore the Dashboard</h3>
<p>Familiarize yourself with the main dashboard and available features.</p>
<h3>Step 3: Set Up Your Development Environment</h3>
<p>Follow the setup guides for your specific services. We provide detailed documentation for backend, frontend, infrastructure, and integration services.</p>
<h3>Step 4: Start Using Our Services</h3>
<p>Begin using our services and tools to achieve your business goals.</p>
<h3>Step 4: Connect with Your Support Team</h3>
<p>Your dedicated support team is available 24/7. Use the support ticket system for technical issues or contact your account manager for service-related questions.</p>
<p>If you need any help, our support team is always here to assist you!</p>''',
<h3>Step 5: Explore Knowledge Base</h3>
<p>Browse our comprehensive knowledge base for detailed guides, API documentation, troubleshooting tips, and best practices.</p>
<p>If you need any help, our enterprise support team is always here to assist you!</p>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'Understanding Your Billing Cycle',
'slug': 'understanding-billing-cycle',
'category': account_billing,
'summary': 'Learn how our billing cycle works and how to manage your payments.',
'content': '''<h2>Billing Cycle Overview</h2>
<p>Understanding your billing cycle is important for managing your subscription effectively.</p>
'title': 'Enterprise Backend Development Best Practices',
'slug': 'backend-development-best-practices',
'category': backend_dev,
'summary': 'Learn best practices for enterprise backend development including API design, database optimization, and security.',
'content': '''<h2>Backend Development Best Practices</h2>
<p>Follow these best practices to ensure your backend applications are robust, scalable, and secure.</p>
<h3>Monthly Billing</h3>
<p>For monthly subscriptions, you'll be charged on the same date each month.</p>
<h3>API Design Principles</h3>
<ul>
<li>Use RESTful conventions for API endpoints</li>
<li>Implement proper versioning (e.g., /api/v1/)</li>
<li>Use consistent naming conventions</li>
<li>Return appropriate HTTP status codes</li>
<li>Implement pagination for large datasets</li>
</ul>
<h3>Annual Billing</h3>
<p>Annual subscriptions offer a discount and are billed once per year.</p>
<h3>Database Optimization</h3>
<ul>
<li>Use proper indexing for frequently queried fields</li>
<li>Implement connection pooling</li>
<li>Use database transactions appropriately</li>
<li>Optimize queries to avoid N+1 problems</li>
<li>Implement caching strategies</li>
</ul>
<h3>Managing Your Subscription</h3>
<p>You can upgrade, downgrade, or cancel your subscription at any time from your account settings.</p>''',
<h3>Security Best Practices</h3>
<ul>
<li>Implement authentication and authorization</li>
<li>Use HTTPS for all API communications</li>
<li>Validate and sanitize all inputs</li>
<li>Implement rate limiting</li>
<li>Keep dependencies updated</li>
</ul>
<h3>Performance Optimization</h3>
<ul>
<li>Implement caching at multiple levels</li>
<li>Use async processing for long-running tasks</li>
<li>Optimize database queries</li>
<li>Implement proper logging and monitoring</li>
<li>Use CDN for static assets</li>
</ul>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'API Documentation Overview',
'slug': 'api-documentation-overview',
'category': technical,
'summary': 'Complete guide to our API endpoints and authentication.',
'content': '''<h2>API Documentation</h2>
<p>Our API provides programmatic access to all platform features.</p>
'title': 'Frontend Development Guidelines',
'slug': 'frontend-development-guidelines',
'category': frontend_dev,
'summary': 'Comprehensive guidelines for building modern, responsive frontend applications.',
'content': '''<h2>Frontend Development Guidelines</h2>
<p>Build modern, performant, and accessible frontend applications with these guidelines.</p>
<h3>Authentication</h3>
<p>All API requests require authentication using an API key.</p>
<code>Authorization: Bearer YOUR_API_KEY</code>
<h3>Component Architecture</h3>
<ul>
<li>Use component-based architecture (React, Vue, etc.)</li>
<li>Keep components small and focused</li>
<li>Implement proper state management</li>
<li>Use reusable component libraries</li>
<li>Follow consistent naming conventions</li>
</ul>
<h3>Rate Limits</h3>
<p>Standard accounts are limited to 1000 requests per hour.</p>
<h3>Performance Optimization</h3>
<ul>
<li>Implement code splitting and lazy loading</li>
<li>Optimize images and assets</li>
<li>Use efficient rendering techniques</li>
<li>Minimize bundle size</li>
<li>Implement proper caching strategies</li>
</ul>
<h3>Response Format</h3>
<p>All responses are returned in JSON format.</p>''',
<h3>Responsive Design</h3>
<ul>
<li>Use mobile-first approach</li>
<li>Test on multiple devices and browsers</li>
<li>Implement flexible layouts</li>
<li>Optimize touch interactions</li>
<li>Ensure proper viewport settings</li>
</ul>
<h3>Accessibility</h3>
<ul>
<li>Use semantic HTML</li>
<li>Implement proper ARIA labels</li>
<li>Ensure keyboard navigation</li>
<li>Maintain proper color contrast</li>
<li>Test with screen readers</li>
</ul>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'Common Login Issues and Solutions',
'slug': 'common-login-issues',
'title': 'DevOps and Infrastructure Setup Guide',
'slug': 'devops-infrastructure-setup',
'category': infrastructure,
'summary': 'Complete guide to setting up DevOps pipelines and infrastructure automation.',
'content': '''<h2>DevOps and Infrastructure Setup</h2>
<p>Set up robust DevOps pipelines and infrastructure automation for your enterprise applications.</p>
<h3>CI/CD Pipeline Setup</h3>
<ul>
<li>Configure automated testing in your pipeline</li>
<li>Implement code quality checks</li>
<li>Set up automated deployments</li>
<li>Configure environment-specific deployments</li>
<li>Implement rollback strategies</li>
</ul>
<h3>Infrastructure as Code</h3>
<ul>
<li>Use Terraform or CloudFormation for infrastructure</li>
<li>Version control your infrastructure code</li>
<li>Implement infrastructure testing</li>
<li>Use configuration management tools</li>
<li>Document infrastructure changes</li>
</ul>
<h3>Containerization</h3>
<ul>
<li>Containerize your applications with Docker</li>
<li>Use Kubernetes for orchestration</li>
<li>Implement proper health checks</li>
<li>Configure resource limits</li>
<li>Set up container registries</li>
</ul>
<h3>Monitoring and Logging</h3>
<ul>
<li>Implement comprehensive monitoring</li>
<li>Set up centralized logging</li>
<li>Configure alerting systems</li>
<li>Use APM tools for performance monitoring</li>
<li>Implement log aggregation</li>
</ul>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'Data Replication and Synchronization Guide',
'slug': 'data-replication-synchronization',
'category': data_mgmt,
'summary': 'Complete guide to setting up and managing data replication and synchronization.',
'content': '''<h2>Data Replication and Synchronization</h2>
<p>Ensure data consistency and availability with proper replication and synchronization strategies.</p>
<h3>Replication Strategies</h3>
<ul>
<li>Master-Slave replication for read scaling</li>
<li>Master-Master replication for high availability</li>
<li>Multi-region replication for disaster recovery</li>
<li>Real-time vs. batch replication</li>
<li>Conflict resolution strategies</li>
</ul>
<h3>Synchronization Best Practices</h3>
<ul>
<li>Implement change data capture (CDC)</li>
<li>Use transaction logs for consistency</li>
<li>Monitor replication lag</li>
<li>Implement data validation</li>
<li>Set up automated failover</li>
</ul>
<h3>Disaster Recovery</h3>
<ul>
<li>Regular backup schedules</li>
<li>Test recovery procedures</li>
<li>Maintain multiple backup copies</li>
<li>Document recovery processes</li>
<li>Monitor backup health</li>
</ul>
<h3>Troubleshooting Common Issues</h3>
<ul>
<li>Replication lag issues</li>
<li>Data inconsistency problems</li>
<li>Network connectivity issues</li>
<li>Storage capacity management</li>
<li>Performance optimization</li>
</ul>''',
'is_published': True,
'is_featured': False,
},
{
'title': 'API Integration Best Practices',
'slug': 'api-integration-best-practices',
'category': api_integration,
'summary': 'Best practices for integrating with third-party APIs and services.',
'content': '''<h2>API Integration Best Practices</h2>
<p>Integrate seamlessly with third-party APIs using these best practices.</p>
<h3>Authentication and Security</h3>
<ul>
<li>Use OAuth 2.0 for secure authentication</li>
<li>Store API keys securely</li>
<li>Implement token refresh mechanisms</li>
<li>Use HTTPS for all API calls</li>
<li>Validate API responses</li>
</ul>
<h3>Error Handling</h3>
<ul>
<li>Implement retry logic with exponential backoff</li>
<li>Handle rate limiting gracefully</li>
<li>Log all API errors</li>
<li>Implement circuit breakers</li>
<li>Provide meaningful error messages</li>
</ul>
<h3>Performance Optimization</h3>
<ul>
<li>Implement request caching</li>
<li>Use batch requests when possible</li>
<li>Implement connection pooling</li>
<li>Monitor API response times</li>
<li>Optimize payload sizes</li>
</ul>
<h3>Testing and Monitoring</h3>
<ul>
<li>Test with sandbox environments</li>
<li>Monitor API usage and costs</li>
<li>Set up alerts for failures</li>
<li>Document integration patterns</li>
<li>Version your integrations</li>
</ul>''',
'is_published': True,
'is_featured': False,
},
{
'title': 'AI and Machine Learning Implementation Guide',
'slug': 'ai-machine-learning-implementation',
'category': ai_ml,
'summary': 'Guide to implementing AI and machine learning solutions in your applications.',
'content': '''<h2>AI and Machine Learning Implementation</h2>
<p>Integrate AI and machine learning capabilities into your enterprise applications.</p>
<h3>Model Development</h3>
<ul>
<li>Define clear use cases and objectives</li>
<li>Prepare and clean your data</li>
<li>Choose appropriate algorithms</li>
<li>Train and validate models</li>
<li>Evaluate model performance</li>
</ul>
<h3>Model Deployment</h3>
<ul>
<li>Containerize models for deployment</li>
<li>Implement model versioning</li>
<li>Set up model serving infrastructure</li>
<li>Implement A/B testing</li>
<li>Monitor model performance</li>
</ul>
<h3>Integration Best Practices</h3>
<ul>
<li>Use API gateways for model access</li>
<li>Implement proper authentication</li>
<li>Handle model inference errors</li>
<li>Optimize inference latency</li>
<li>Implement caching strategies</li>
</ul>
<h3>Monitoring and Maintenance</h3>
<ul>
<li>Monitor model accuracy over time</li>
<li>Track data drift</li>
<li>Implement model retraining pipelines</li>
<li>Set up performance alerts</li>
<li>Document model behavior</li>
</ul>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'Common Troubleshooting Issues',
'slug': 'common-troubleshooting-issues',
'category': troubleshooting,
'summary': 'Troubleshoot common login problems and learn how to resolve them.',
'content': '''<h2>Login Troubleshooting</h2>
<p>Having trouble logging in? Here are some common issues and solutions.</p>
'summary': 'Common issues and their solutions for enterprise services.',
'content': '''<h2>Common Troubleshooting Issues</h2>
<p>Resolve common issues quickly with these troubleshooting guides.</p>
<h3>Forgot Password</h3>
<p>Click "Forgot Password" on the login page to reset your password via email.</p>
<h3>Connection Issues</h3>
<ul>
<li>Check network connectivity</li>
<li>Verify firewall settings</li>
<li>Test DNS resolution</li>
<li>Check SSL/TLS certificates</li>
<li>Review connection timeouts</li>
</ul>
<h3>Account Locked</h3>
<p>After multiple failed login attempts, your account may be temporarily locked for security.</p>
<h3>Performance Issues</h3>
<ul>
<li>Check server resource usage</li>
<li>Review database query performance</li>
<li>Analyze application logs</li>
<li>Check for memory leaks</li>
<li>Review caching strategies</li>
</ul>
<h3>Browser Issues</h3>
<p>Clear your browser cache and cookies, or try a different browser.</p>
<h3>Authentication Problems</h3>
<ul>
<li>Verify credentials</li>
<li>Check token expiration</li>
<li>Review permission settings</li>
<li>Test with different accounts</li>
<li>Check session management</li>
</ul>
<h3>Still Having Issues?</h3>
<p>Contact our support team for personalized assistance.</p>''',
<h3>Data Issues</h3>
<ul>
<li>Verify data integrity</li>
<li>Check replication status</li>
<li>Review data validation rules</li>
<li>Check for data corruption</li>
<li>Verify backup integrity</li>
</ul>
<p>If you continue to experience issues, please contact our support team.</p>''',
'is_published': True,
'is_featured': False,
},
{
'title': 'How to Update Your Payment Method',
'slug': 'update-payment-method',
'category': account_billing,
'summary': 'Step-by-step guide to updating your payment information.',
'content': '''<h2>Updating Payment Information</h2>
<p>Keep your payment method up to date to avoid service interruptions.</p>
'title': 'Enterprise Security Best Practices',
'slug': 'enterprise-security-best-practices',
'category': security,
'summary': 'Essential security practices for enterprise applications and infrastructure.',
'content': '''<h2>Enterprise Security Best Practices</h2>
<p>Protect your enterprise applications and data with these security best practices.</p>
<h3>Steps to Update</h3>
<ol>
<li>Go to Account Settings</li>
<li>Click on "Billing & Payments"</li>
<li>Select "Update Payment Method"</li>
<li>Enter your new payment details</li>
<li>Click "Save Changes"</li>
</ol>
<h3>Authentication and Authorization</h3>
<ul>
<li>Implement multi-factor authentication (MFA)</li>
<li>Use strong password policies</li>
<li>Implement role-based access control (RBAC)</li>
<li>Regularly review user permissions</li>
<li>Use OAuth 2.0 for API authentication</li>
</ul>
<h3>Supported Payment Methods</h3>
<p>We accept all major credit cards, PayPal, and bank transfers.</p>''',
'is_published': True,
'is_featured': False,
},
{
'title': 'Security Best Practices',
'slug': 'security-best-practices',
'category': KnowledgeBaseCategory.objects.filter(slug='security-privacy').first(),
'summary': 'Essential security practices to keep your account safe.',
'content': '''<h2>Account Security</h2>
<p>Follow these best practices to keep your account secure.</p>
<h3>Data Protection</h3>
<ul>
<li>Encrypt data at rest and in transit</li>
<li>Implement proper key management</li>
<li>Use secure communication protocols</li>
<li>Implement data masking for sensitive information</li>
<li>Regularly backup critical data</li>
</ul>
<h3>Use Strong Passwords</h3>
<p>Create complex passwords with a mix of letters, numbers, and symbols.</p>
<h3>Network Security</h3>
<ul>
<li>Use firewalls and network segmentation</li>
<li>Implement DDoS protection</li>
<li>Use VPNs for remote access</li>
<li>Monitor network traffic</li>
<li>Implement intrusion detection systems</li>
</ul>
<h3>Enable Two-Factor Authentication</h3>
<p>Add an extra layer of security with 2FA.</p>
<h3>Regular Security Audits</h3>
<p>Review your account activity regularly for any suspicious behavior.</p>
<h3>Keep Software Updated</h3>
<p>Always use the latest version of our software for the best security.</p>''',
<h3>Compliance and Auditing</h3>
<ul>
<li>Maintain security audit logs</li>
<li>Regularly review access logs</li>
<li>Conduct security assessments</li>
<li>Stay compliant with regulations</li>
<li>Document security procedures</li>
</ul>''',
'is_published': True,
'is_featured': True,
},
{
'title': 'Understanding Your Enterprise Billing',
'slug': 'enterprise-billing-guide',
'category': account_billing,
'summary': 'Learn how enterprise billing works and how to manage your account.',
'content': '''<h2>Enterprise Billing Guide</h2>
<p>Understand your enterprise billing and manage your account effectively.</p>
<h3>Billing Structure</h3>
<ul>
<li>Service-based pricing models</li>
<li>Monthly vs. annual billing options</li>
<li>Usage-based charges</li>
<li>Support tier pricing</li>
<li>Additional service add-ons</li>
</ul>
<h3>Payment Methods</h3>
<ul>
<li>Credit card payments</li>
<li>Bank transfers</li>
<li>Purchase orders</li>
<li>Invoice-based billing</li>
<li>Payment terms and schedules</li>
</ul>
<h3>Managing Your Account</h3>
<ul>
<li>Update payment information</li>
<li>View billing history</li>
<li>Download invoices</li>
<li>Manage service subscriptions</li>
<li>Contact billing support</li>
</ul>
<h3>Billing Support</h3>
<p>For billing questions or issues, contact our billing team through the support portal or email billing@gnxsoft.com.</p>''',
'is_published': True,
'is_featured': False,
},
]
for article_data in articles:
@@ -265,4 +695,3 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS(f' ✓ Created article: {article.title}'))
else:
self.stdout.write(f' - Article already exists: {article.title}')

View File

@@ -5,16 +5,54 @@ Handles automatic notifications when tickets are updated
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.core.mail import EmailMultiAlternatives
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.conf import settings
import logging
import time
from .models import SupportTicket, TicketMessage, TicketActivity
logger = logging.getLogger(__name__)
def _send_email_with_retry(email_message, max_retries: int = 3, delay: float = 1.0) -> bool:
"""
Send email with retry logic for production reliability.
Uses Django settings from .env file.
Args:
email_message: EmailMultiAlternatives instance
max_retries: Maximum number of retry attempts
delay: Delay between retries in seconds
Returns:
bool: True if email was sent successfully, False otherwise
"""
for attempt in range(max_retries + 1):
try:
# Test connection before sending (uses EMAIL_BACKEND from settings)
connection = get_connection()
connection.open()
connection.close()
# Send the email (uses EMAIL_BACKEND and credentials from settings)
email_message.send()
return True
except Exception as e:
logger.warning(f"Email send attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries:
time.sleep(delay * (2 ** attempt)) # Exponential backoff
continue
else:
logger.error(f"Failed to send email after {max_retries + 1} attempts: {str(e)}")
return False
return False
class TicketUpdateNotifier:
"""Service for sending ticket update notifications"""
@@ -34,6 +72,8 @@ class TicketUpdateNotifier:
'status_color': ticket.status.color if ticket.status else '#3b82f6',
'updated_at': ticket.updated_at.strftime('%B %d, %Y at %I:%M %p'),
'support_url': f'{settings.SITE_URL}/support-center',
'logo_url': f'{settings.SITE_URL}/images/logo.png',
'site_url': settings.SITE_URL,
}
# Render HTML email
@@ -48,19 +88,24 @@ class TicketUpdateNotifier:
context
)
# Create email
# Create email (uses DEFAULT_FROM_EMAIL from settings)
email = EmailMultiAlternatives(
subject=subject,
body=text_message,
from_email=settings.DEFAULT_FROM_EMAIL,
from_email=settings.DEFAULT_FROM_EMAIL, # From .env file
to=[ticket.user_email],
)
email.attach_alternative(html_message, "text/html")
email.send(fail_silently=False)
logger.info(f'Status change notification sent for ticket {ticket.ticket_number}')
return True
# Send email with retry logic (uses EMAIL_BACKEND and credentials from settings)
success = _send_email_with_retry(email)
if success:
logger.info(f'Status change notification sent for ticket {ticket.ticket_number}')
else:
logger.error(f'Failed to send status change notification for ticket {ticket.ticket_number} after retries')
return success
except Exception as e:
logger.error(f'Failed to send status change notification: {str(e)}')
@@ -85,6 +130,8 @@ class TicketUpdateNotifier:
'message_author': message.author_name or 'Support Team',
'created_at': message.created_at.strftime('%B %d, %Y at %I:%M %p'),
'support_url': f'{settings.SITE_URL}/support-center',
'logo_url': f'{settings.SITE_URL}/images/logo.png',
'site_url': settings.SITE_URL,
}
# Render HTML email
@@ -99,19 +146,24 @@ class TicketUpdateNotifier:
context
)
# Create email
# Create email (uses DEFAULT_FROM_EMAIL from settings)
email = EmailMultiAlternatives(
subject=subject,
body=text_message,
from_email=settings.DEFAULT_FROM_EMAIL,
from_email=settings.DEFAULT_FROM_EMAIL, # From .env file
to=[ticket.user_email],
)
email.attach_alternative(html_message, "text/html")
email.send(fail_silently=False)
logger.info(f'Message notification sent for ticket {ticket.ticket_number}')
return True
# Send email with retry logic (uses EMAIL_BACKEND and credentials from settings)
success = _send_email_with_retry(email)
if success:
logger.info(f'Message notification sent for ticket {ticket.ticket_number}')
else:
logger.error(f'Failed to send message notification for ticket {ticket.ticket_number} after retries')
return success
except Exception as e:
logger.error(f'Failed to send message notification: {str(e)}')
@@ -131,6 +183,8 @@ class TicketUpdateNotifier:
'assigned_to': assigned_to.get_full_name() or assigned_to.username,
'updated_at': ticket.updated_at.strftime('%B %d, %Y at %I:%M %p'),
'support_url': f'{settings.SITE_URL}/support-center',
'logo_url': f'{settings.SITE_URL}/images/logo.png',
'site_url': settings.SITE_URL,
}
# Render HTML email
@@ -145,19 +199,24 @@ class TicketUpdateNotifier:
context
)
# Create email
# Create email (uses DEFAULT_FROM_EMAIL from settings)
email = EmailMultiAlternatives(
subject=subject,
body=text_message,
from_email=settings.DEFAULT_FROM_EMAIL,
from_email=settings.DEFAULT_FROM_EMAIL, # From .env file
to=[ticket.user_email],
)
email.attach_alternative(html_message, "text/html")
email.send(fail_silently=False)
logger.info(f'Assignment notification sent for ticket {ticket.ticket_number}')
return True
# Send email with retry logic (uses EMAIL_BACKEND and credentials from settings)
success = _send_email_with_retry(email)
if success:
logger.info(f'Assignment notification sent for ticket {ticket.ticket_number}')
else:
logger.error(f'Failed to send assignment notification for ticket {ticket.ticket_number} after retries')
return success
except Exception as e:
logger.error(f'Failed to send assignment notification: {str(e)}')

View File

@@ -5,111 +5,294 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ticket Assigned</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333333;
background-color: #f4f4f4;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.email-header h1 {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.7;
color: #2c3e50;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin: 0;
font-size: 24px;
padding: 40px 20px;
}
.email-wrapper {
max-width: 650px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.email-header {
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 50%, #6d28d9 100%);
padding: 50px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.email-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
}
.email-header h1 {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin: 0;
position: relative;
z-index: 2;
letter-spacing: -0.5px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.email-body {
padding: 50px 40px;
background: #ffffff;
}
.greeting {
font-size: 18px;
color: #1e293b;
margin-bottom: 30px;
font-weight: 500;
}
.intro-text {
font-size: 16px;
color: #475569;
line-height: 1.8;
margin-bottom: 35px;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.08) 0%, rgba(218, 165, 32, 0.03) 100%);
border: 2px solid rgba(218, 165, 32, 0.2);
border-left: 5px solid #daa520;
padding: 25px;
margin: 35px 0;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(218, 165, 32, 0.1);
}
.ticket-info strong {
color: #475569;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
display: block;
margin-bottom: 8px;
}
.ticket-info span {
color: #1e293b;
font-size: 16px;
font-weight: 600;
}
.email-body {
padding: 40px 30px;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.1) 0%, rgba(218, 165, 32, 0.05) 100%);
border-left: 4px solid #daa520;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
}
.assignment-box {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(139, 92, 246, 0.05) 100%);
border-left: 4px solid #8b5cf6;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
border: 2px solid rgba(139, 92, 246, 0.2);
border-left: 5px solid #8b5cf6;
padding: 35px;
margin: 35px 0;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.15);
}
.assignment-box h3 {
margin: 0 0 20px 0;
color: #1e293b;
font-size: 16px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.assigned-name {
font-size: 28px;
font-weight: 800;
color: #8b5cf6;
margin: 15px 0;
text-shadow: 0 2px 4px rgba(139, 92, 246, 0.2);
}
.assignment-time {
color: #64748b;
font-size: 13px;
margin-top: 15px;
font-style: italic;
}
.cta-container {
text-align: center;
margin: 45px 0;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #daa520, #d4af37);
background: linear-gradient(135deg, #daa520 0%, #d4af37 100%);
color: #ffffff !important;
text-decoration: none;
padding: 14px 32px;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
padding: 18px 40px;
border-radius: 10px;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.5px;
box-shadow: 0 8px 25px rgba(218, 165, 32, 0.4);
transition: all 0.3s ease;
text-transform: uppercase;
}
.footer {
background-color: #f8f8f8;
padding: 25px 30px;
text-align: center;
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(218, 165, 32, 0.5);
}
.info-note {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #8b5cf6;
padding: 20px 25px;
border-radius: 8px;
margin-top: 35px;
color: #475569;
font-size: 14px;
color: #666666;
line-height: 1.7;
}
.email-footer {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.9);
}
.footer-company {
font-size: 18px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
}
.footer-tagline {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px;
}
.footer-links {
margin: 25px 0;
padding-top: 25px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-links a {
color: #daa520;
text-decoration: none;
font-weight: 600;
font-size: 14px;
}
.footer-links a:hover {
text-decoration: underline;
}
.footer-note {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-top: 25px;
line-height: 1.6;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.email-header {
padding: 40px 25px;
}
.email-header h1 {
font-size: 26px;
}
.email-body {
padding: 35px 25px;
}
.assigned-name {
font-size: 24px;
}
.cta-button {
padding: 16px 30px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-wrapper">
<div class="email-header">
<h1>👤 Ticket Assigned</h1>
</div>
<div class="email-body">
<p>Dear {{ user_name }},</p>
<div class="greeting">Dear {{ user_name }},</div>
<p>Your support ticket has been assigned to a team member who will be assisting you.</p>
<div class="intro-text">
Your support ticket has been assigned to a team member who will be assisting you with your request.
</div>
<div class="ticket-info">
<strong>Ticket:</strong> {{ ticket_number }}<br>
<strong>Subject:</strong> {{ title }}
<strong>Ticket Number</strong>
<span>{{ ticket_number }}</span>
<br><br>
<strong>Subject</strong>
<span>{{ title }}</span>
</div>
<div class="assignment-box">
<h3 style="margin-top: 0;">Assigned To</h3>
<div style="font-size: 18px; font-weight: 700; color: #8b5cf6;">{{ assigned_to }}</div>
<p style="margin-top: 10px; color: #666; font-size: 14px;">{{ updated_at }}</p>
<h3>Assigned To</h3>
<div class="assigned-name">{{ assigned_to }}</div>
<div class="assignment-time">{{ updated_at }}</div>
</div>
<div style="text-align: center; margin: 30px 0;">
<div class="cta-container">
<a href="{{ support_url }}" class="cta-button">View Ticket Details</a>
</div>
<p style="color: #666;">
<div class="info-note">
Your assigned support agent will review your ticket and respond as soon as possible.
</p>
</div>
</div>
<div class="footer">
<p style="margin: 0 0 10px 0;">
<strong>GNX Software Solutions</strong><br>
Enterprise Support Center
</p>
<p style="margin: 0; font-size: 12px;">
<div class="email-footer">
<div class="footer-company">GNX Software Solutions</div>
<div class="footer-tagline">Enterprise Support Center</div>
<div class="footer-links">
<a href="{{ support_url }}">Visit Support Center</a>
</div>
<div class="footer-note">
This is an automated notification. For assistance, visit our <a href="{{ support_url }}" style="color: #daa520;">Support Center</a>
</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -5,121 +5,357 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Support Ticket Confirmation</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333333;
background-color: #f4f4f4;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.email-header h1 {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.7;
color: #2c3e50;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin: 0;
font-size: 24px;
font-weight: 600;
padding: 40px 20px;
}
.email-body {
padding: 40px 30px;
.email-wrapper {
max-width: 650px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.ticket-number-box {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.1) 0%, rgba(218, 165, 32, 0.05) 100%);
border-left: 4px solid #daa520;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
.email-header {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
padding: 50px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.ticket-number {
font-size: 24px;
.email-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(218, 165, 32, 0.1) 0%, transparent 70%);
animation: pulse 8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.1); opacity: 0.8; }
}
.email-header h1 {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin: 0;
position: relative;
z-index: 2;
letter-spacing: -0.5px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.email-header .subtitle {
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
margin-top: 12px;
font-weight: 300;
position: relative;
z-index: 2;
}
.email-body {
padding: 50px 40px;
background: #ffffff;
}
.greeting {
font-size: 18px;
color: #1e293b;
margin-bottom: 30px;
font-weight: 500;
}
.intro-text {
font-size: 16px;
color: #475569;
line-height: 1.8;
margin-bottom: 35px;
}
.ticket-number-box {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.08) 0%, rgba(218, 165, 32, 0.03) 100%);
border: 2px solid rgba(218, 165, 32, 0.2);
border-left: 5px solid #daa520;
padding: 30px;
margin: 35px 0;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 20px rgba(218, 165, 32, 0.1);
}
.ticket-label {
font-size: 13px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 600;
margin-bottom: 12px;
}
.ticket-number {
font-size: 36px;
font-weight: 800;
color: #daa520;
font-family: 'Courier New', monospace;
letter-spacing: 1px;
letter-spacing: 2px;
text-shadow: 0 2px 4px rgba(218, 165, 32, 0.2);
}
.ticket-hint {
font-size: 13px;
color: #94a3b8;
margin-top: 12px;
font-style: italic;
}
.info-section {
margin: 25px 0;
margin: 40px 0;
}
.section-title {
font-size: 20px;
font-weight: 700;
color: #0f172a;
margin-bottom: 25px;
padding-bottom: 12px;
border-bottom: 2px solid #e2e8f0;
position: relative;
}
.section-title::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 60px;
height: 2px;
background: linear-gradient(90deg, #daa520, #d4af37);
}
.info-row {
padding: 12px 0;
border-bottom: 1px solid #e5e5e5;
padding: 18px 0;
border-bottom: 1px solid #f1f5f9;
display: flex;
align-items: flex-start;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-weight: 600;
color: #555555;
display: inline-block;
width: 120px;
}
.info-value {
color: #333333;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #daa520, #d4af37);
color: #ffffff !important;
text-decoration: none;
padding: 14px 32px;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
text-align: center;
}
.footer {
background-color: #f8f8f8;
padding: 25px 30px;
text-align: center;
color: #475569;
min-width: 130px;
font-size: 14px;
color: #666666;
}
.info-value {
color: #1e293b;
flex: 1;
font-size: 15px;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
background-color: #3b82f6;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.description-box {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-left: 4px solid #daa520;
padding: 25px;
border-radius: 8px;
white-space: pre-wrap;
font-size: 15px;
line-height: 1.8;
color: #334155;
margin-top: 15px;
}
.cta-container {
text-align: center;
margin: 45px 0;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #daa520 0%, #d4af37 100%);
color: #ffffff !important;
text-decoration: none;
padding: 18px 40px;
border-radius: 10px;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.5px;
box-shadow: 0 8px 25px rgba(218, 165, 32, 0.4);
transition: all 0.3s ease;
text-transform: uppercase;
}
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(218, 165, 32, 0.5);
}
.next-steps {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #3b82f6;
padding: 25px;
border-radius: 8px;
margin-top: 40px;
}
.next-steps strong {
color: #0f172a;
font-size: 16px;
display: block;
margin-bottom: 12px;
}
.next-steps p {
color: #475569;
font-size: 15px;
line-height: 1.7;
margin: 0;
}
.email-footer {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.9);
}
.footer-company {
font-size: 18px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
}
.footer-tagline {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px;
}
.footer-links {
margin: 25px 0;
padding-top: 25px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-links a {
color: #daa520;
text-decoration: none;
font-weight: 600;
font-size: 14px;
}
.footer-links a:hover {
text-decoration: underline;
}
.footer-note {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-top: 25px;
line-height: 1.6;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.email-header {
padding: 40px 25px;
}
.email-header h1 {
font-size: 26px;
}
.email-body {
padding: 35px 25px;
}
.ticket-number {
font-size: 28px;
}
.info-row {
flex-direction: column;
}
.info-label {
min-width: auto;
margin-bottom: 6px;
}
.cta-button {
padding: 16px 30px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-wrapper">
<!-- Header -->
<div class="email-header">
<h1> Support Ticket Created</h1>
<h1>✓ Ticket Created Successfully</h1>
<div class="subtitle">Your support request has been received</div>
</div>
<!-- Body -->
<div class="email-body">
<p>Dear {{ user_name }},</p>
<div class="greeting">Dear {{ user_name }},</div>
<p>Thank you for contacting our support team. We've received your support request and our team will respond as soon as possible.</p>
<div class="intro-text">
Thank you for contacting our support team. We've received your support request and our team will respond as soon as possible. Your ticket has been created and assigned a unique reference number.
</div>
<!-- Ticket Number Box -->
<div class="ticket-number-box">
<div style="font-size: 14px; color: #666; margin-bottom: 8px;">Your Ticket Number</div>
<div class="ticket-label">Your Ticket Number</div>
<div class="ticket-number">{{ ticket_number }}</div>
<div style="font-size: 13px; color: #666; margin-top: 8px;">Please save this number for future reference</div>
<div class="ticket-hint">Please save this number for future reference</div>
</div>
<!-- Ticket Details -->
<div class="info-section">
<h3 style="color: #0f172a; margin-bottom: 15px;">Ticket Details</h3>
<h3 class="section-title">Ticket Details</h3>
<div class="info-row">
<span class="info-label">Subject:</span>
@@ -143,7 +379,7 @@
<div class="info-row">
<span class="info-label">Status:</span>
<span class="status-badge">{{ status }}</span>
<span class="info-value"><span class="status-badge">{{ status }}</span></span>
</div>
<div class="info-row">
@@ -154,33 +390,36 @@
<!-- Description -->
<div class="info-section">
<h3 style="color: #0f172a; margin-bottom: 15px;">Description</h3>
<div style="background-color: #f8f8f8; padding: 15px; border-radius: 4px; white-space: pre-wrap;">{{ description }}</div>
<h3 class="section-title">Description</h3>
<div class="description-box">{{ description }}</div>
</div>
<!-- CTA Button -->
<div style="text-align: center; margin: 30px 0;">
<a href="{{ support_url }}" class="cta-button">Check Ticket Status</a>
<div class="cta-container">
<a href="{{ support_url }}" class="cta-button">View Ticket Status</a>
</div>
<p style="margin-top: 30px; color: #666;">
<strong>What happens next?</strong><br>
Our support team will review your ticket and respond within our standard SLA timeframe. You'll receive an email notification when there's an update.
</p>
<!-- Next Steps -->
<div class="next-steps">
<strong>What happens next?</strong>
<p>Our support team will review your ticket and respond within our standard SLA timeframe. You'll receive an email notification when there's an update on your ticket.</p>
</div>
</div>
<!-- Footer -->
<div class="footer">
<p style="margin: 0 0 10px 0;">
<strong>GNX Software Solutions</strong><br>
Enterprise Support Center
</p>
<p style="margin: 0; font-size: 12px;">
<div class="email-footer">
<div class="footer-company">GNX Software Solutions</div>
<div class="footer-tagline">Enterprise Support Center</div>
<div class="footer-links">
<a href="{{ support_url }}">Visit Support Center</a>
</div>
<div class="footer-note">
This is an automated message. Please do not reply directly to this email.<br>
For assistance, please visit our <a href="{{ support_url }}" style="color: #daa520;">Support Center</a>
</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -5,120 +5,297 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Response on Your Ticket</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333333;
background-color: #f4f4f4;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.email-header h1 {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.7;
color: #2c3e50;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin: 0;
font-size: 24px;
padding: 40px 20px;
}
.email-wrapper {
max-width: 650px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.email-header {
background: linear-gradient(135deg, #10b981 0%, #059669 50%, #047857 100%);
padding: 50px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.email-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
}
.email-header h1 {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin: 0;
position: relative;
z-index: 2;
letter-spacing: -0.5px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.email-body {
padding: 50px 40px;
background: #ffffff;
}
.greeting {
font-size: 18px;
color: #1e293b;
margin-bottom: 30px;
font-weight: 500;
}
.intro-text {
font-size: 16px;
color: #475569;
line-height: 1.8;
margin-bottom: 35px;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.08) 0%, rgba(218, 165, 32, 0.03) 100%);
border: 2px solid rgba(218, 165, 32, 0.2);
border-left: 5px solid #daa520;
padding: 25px;
margin: 35px 0;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(218, 165, 32, 0.1);
}
.ticket-info strong {
color: #475569;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
display: block;
margin-bottom: 8px;
}
.ticket-info span {
color: #1e293b;
font-size: 16px;
font-weight: 600;
}
.email-body {
padding: 40px 30px;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.1) 0%, rgba(218, 165, 32, 0.05) 100%);
border-left: 4px solid #daa520;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
}
.message-box {
background: #f8f9fa;
border-left: 4px solid #10b981;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border: 2px solid rgba(16, 185, 129, 0.2);
border-left: 5px solid #10b981;
padding: 30px;
margin: 35px 0;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.1);
}
.message-author {
font-weight: 700;
color: #10b981;
margin-bottom: 10px;
color: #059669;
margin-bottom: 15px;
font-size: 16px;
display: flex;
align-items: center;
}
.message-author::before {
content: '👤';
margin-right: 10px;
font-size: 20px;
}
.message-content {
color: #333;
color: #1e293b;
white-space: pre-wrap;
line-height: 1.8;
line-height: 1.9;
font-size: 15px;
margin-bottom: 20px;
}
.message-time {
color: #64748b;
font-size: 13px;
font-style: italic;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(16, 185, 129, 0.2);
}
.cta-container {
text-align: center;
margin: 45px 0;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #daa520, #d4af37);
background: linear-gradient(135deg, #daa520 0%, #d4af37 100%);
color: #ffffff !important;
text-decoration: none;
padding: 14px 32px;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
padding: 18px 40px;
border-radius: 10px;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.5px;
box-shadow: 0 8px 25px rgba(218, 165, 32, 0.4);
transition: all 0.3s ease;
text-transform: uppercase;
}
.footer {
background-color: #f8f8f8;
padding: 25px 30px;
text-align: center;
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(218, 165, 32, 0.5);
}
.info-note {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #3b82f6;
padding: 20px 25px;
border-radius: 8px;
margin-top: 35px;
color: #475569;
font-size: 14px;
color: #666666;
line-height: 1.7;
}
.email-footer {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.9);
}
.footer-company {
font-size: 18px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
}
.footer-tagline {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px;
}
.footer-links {
margin: 25px 0;
padding-top: 25px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-links a {
color: #daa520;
text-decoration: none;
font-weight: 600;
font-size: 14px;
}
.footer-links a:hover {
text-decoration: underline;
}
.footer-note {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-top: 25px;
line-height: 1.6;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.email-header {
padding: 40px 25px;
}
.email-header h1 {
font-size: 26px;
}
.email-body {
padding: 35px 25px;
}
.cta-button {
padding: 16px 30px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-wrapper">
<div class="email-header">
<h1>💬 New Response on Your Ticket</h1>
</div>
<div class="email-body">
<p>Dear {{ user_name }},</p>
<div class="greeting">Dear {{ user_name }},</div>
<p>Our support team has responded to your ticket.</p>
<div class="intro-text">
Our support team has responded to your ticket. You can view the complete conversation and reply below.
</div>
<div class="ticket-info">
<strong>Ticket:</strong> {{ ticket_number }}<br>
<strong>Subject:</strong> {{ title }}
<strong>Ticket Number</strong>
<span>{{ ticket_number }}</span>
<br><br>
<strong>Subject</strong>
<span>{{ title }}</span>
</div>
<div class="message-box">
<div class="message-author">{{ message_author }} replied:</div>
<div class="message-content">{{ message }}</div>
<div style="margin-top: 15px; color: #666; font-size: 13px;">{{ created_at }}</div>
<div class="message-time">{{ created_at }}</div>
</div>
<div style="text-align: center; margin: 30px 0;">
<div class="cta-container">
<a href="{{ support_url }}" class="cta-button">View Full Conversation</a>
</div>
<p style="color: #666;">
<div class="info-note">
You can view the complete ticket history and reply to this message in the Support Center.
</p>
</div>
</div>
<div class="footer">
<p style="margin: 0 0 10px 0;">
<strong>GNX Software Solutions</strong><br>
Enterprise Support Center
</p>
<p style="margin: 0; font-size: 12px;">
<div class="email-footer">
<div class="footer-company">GNX Software Solutions</div>
<div class="footer-tagline">Enterprise Support Center</div>
<div class="footer-links">
<a href="{{ support_url }}">Visit Support Center</a>
</div>
<div class="footer-note">
This is an automated notification. For assistance, visit our <a href="{{ support_url }}" style="color: #daa520;">Support Center</a>
</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -5,129 +5,384 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Support Ticket</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333333;
background-color: #f4f4f4;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.email-container {
max-width: 700px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.email-header h1 {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.7;
color: #2c3e50;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin: 0;
font-size: 24px;
font-weight: 600;
padding: 40px 20px;
}
.email-wrapper {
max-width: 700px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.email-header {
background: linear-gradient(135deg, #dc2626 0%, #ef4444 50%, #f87171 100%);
padding: 50px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.email-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
}
.email-header h1 {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin: 0;
position: relative;
z-index: 2;
letter-spacing: -0.5px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.alert-badge {
background-color: rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
padding: 10px 24px;
border-radius: 25px;
display: inline-block;
margin-top: 10px;
margin-top: 15px;
font-size: 14px;
font-weight: 700;
color: #ffffff;
text-transform: uppercase;
letter-spacing: 1px;
position: relative;
z-index: 2;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.email-body {
padding: 40px 30px;
padding: 50px 40px;
background: #ffffff;
}
.intro-alert {
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
border-left: 5px solid #ef4444;
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-weight: 600;
color: #991b1b;
font-size: 16px;
}
.ticket-number-box {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%);
border-left: 4px solid #ef4444;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
border: 2px solid rgba(239, 68, 68, 0.2);
border-left: 5px solid #ef4444;
padding: 30px;
margin: 35px 0;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.15);
}
.ticket-label {
font-size: 13px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 600;
margin-bottom: 12px;
}
.ticket-number {
font-size: 28px;
font-weight: 700;
font-size: 36px;
font-weight: 800;
color: #ef4444;
font-family: 'Courier New', monospace;
letter-spacing: 1px;
letter-spacing: 2px;
}
.customer-info {
background-color: #f8f9fa;
padding: 20px;
border-radius: 6px;
margin: 25px 0;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
padding: 30px;
border-radius: 12px;
margin: 35px 0;
border: 1px solid #e2e8f0;
}
.section-title {
font-size: 20px;
font-weight: 700;
color: #0f172a;
margin-bottom: 25px;
padding-bottom: 12px;
border-bottom: 2px solid #e2e8f0;
position: relative;
}
.section-title::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 60px;
height: 2px;
background: linear-gradient(90deg, #ef4444, #dc2626);
}
.info-grid {
display: grid;
grid-template-columns: 140px 1fr;
gap: 12px;
gap: 18px;
align-items: start;
}
.info-label {
font-weight: 600;
color: #555555;
color: #475569;
font-size: 14px;
}
.info-value {
color: #333333;
color: #1e293b;
font-size: 15px;
}
.info-value a {
color: #3b82f6;
text-decoration: none;
font-weight: 600;
}
.info-value a:hover {
text-decoration: underline;
}
.ticket-details {
margin: 35px 0;
}
.subject-box {
background: #f8fafc;
border-left: 4px solid #daa520;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
.subject-box .subject-label {
font-size: 13px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
margin-bottom: 8px;
}
.subject-box .subject-value {
font-size: 20px;
font-weight: 700;
color: #0f172a;
}
.priority-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.priority-high {
background-color: #f59e0b;
color: white;
}
.priority-medium {
background-color: #3b82f6;
color: white;
}
.priority-low {
background-color: #6b7280;
color: white;
}
.priority-critical {
background-color: #ef4444;
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
}
.priority-high {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: white;
}
.priority-medium {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
}
.priority-low {
background: linear-gradient(135deg, #6b7280, #4b5563);
color: white;
}
.description-box {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-left: 4px solid #daa520;
padding: 25px;
border-radius: 8px;
white-space: pre-wrap;
font-size: 15px;
line-height: 1.8;
color: #334155;
margin-top: 15px;
}
.action-buttons {
display: flex;
gap: 15px;
margin: 45px 0;
flex-wrap: wrap;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #0f172a, #1e293b);
color: #ffffff !important;
text-decoration: none;
padding: 14px 32px;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
}
.footer {
background-color: #f8f8f8;
padding: 25px 30px;
padding: 18px 35px;
border-radius: 10px;
font-weight: 700;
font-size: 15px;
letter-spacing: 0.5px;
text-align: center;
font-size: 14px;
color: #666666;
flex: 1;
min-width: 200px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.description-box {
background-color: #f8f8f8;
padding: 20px;
border-radius: 6px;
border-left: 3px solid #daa520;
margin: 20px 0;
white-space: pre-wrap;
.cta-button-primary {
background: linear-gradient(135deg, #0f172a, #1e293b);
}
.cta-button-secondary {
background: linear-gradient(135deg, #daa520, #d4af37);
}
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.3);
}
.sla-warning {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border-left: 5px solid #f59e0b;
padding: 20px 25px;
border-radius: 8px;
margin-top: 35px;
}
.sla-warning strong {
color: #92400e;
font-size: 16px;
display: block;
margin-bottom: 8px;
}
.sla-warning p {
color: #78350f;
font-size: 14px;
margin: 0;
line-height: 1.6;
}
.email-footer {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.9);
}
.footer-company {
font-size: 18px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
}
.footer-tagline {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px;
}
.footer-links {
margin: 25px 0;
padding-top: 25px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-links a {
color: #daa520;
text-decoration: none;
font-weight: 600;
font-size: 14px;
margin: 0 15px;
}
.footer-links a:hover {
text-decoration: underline;
}
.footer-note {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-top: 25px;
line-height: 1.6;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.email-header {
padding: 40px 25px;
}
.email-header h1 {
font-size: 26px;
}
.email-body {
padding: 35px 25px;
}
.info-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.action-buttons {
flex-direction: column;
}
.cta-button {
width: 100%;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-wrapper">
<!-- Header -->
<div class="email-header">
<h1>🎫 New Support Ticket</h1>
@@ -136,30 +391,32 @@
<!-- Body -->
<div class="email-body">
<p><strong>A new support ticket has been submitted and requires your attention.</strong></p>
<div class="intro-alert">
A new support ticket has been submitted and requires your attention.
</div>
<!-- Ticket Number Box -->
<div class="ticket-number-box">
<div style="font-size: 14px; color: #666; margin-bottom: 8px;">Ticket Number</div>
<div class="ticket-label">Ticket Number</div>
<div class="ticket-number">{{ ticket_number }}</div>
<div style="margin-top: 15px;">
<div style="margin-top: 15px; color: #64748b; font-size: 14px;">
<span class="info-label">Created:</span> {{ created_at }}
</div>
</div>
<!-- Customer Information -->
<div class="customer-info">
<h3 style="margin-top: 0; color: #0f172a;">Customer Information</h3>
<h3 class="section-title">Customer Information</h3>
<div class="info-grid">
<div class="info-label">Name:</div>
<div class="info-value">{{ user_name }}</div>
<div class="info-value"><strong>{{ user_name }}</strong></div>
<div class="info-label">Email:</div>
<div class="info-value"><a href="mailto:{{ user_email }}" style="color: #3b82f6;">{{ user_email }}</a></div>
<div class="info-value"><a href="mailto:{{ user_email }}">{{ user_email }}</a></div>
{% if user_phone %}
<div class="info-label">Phone:</div>
<div class="info-value"><a href="tel:{{ user_phone }}" style="color: #3b82f6;">{{ user_phone }}</a></div>
<div class="info-value"><a href="tel:{{ user_phone }}">{{ user_phone }}</a></div>
{% endif %}
{% if company %}
@@ -170,15 +427,15 @@
</div>
<!-- Ticket Details -->
<div style="margin: 25px 0;">
<h3 style="color: #0f172a;">Ticket Details</h3>
<div class="ticket-details">
<h3 class="section-title">Ticket Details</h3>
<div style="margin: 15px 0;">
<div class="info-label" style="display: block; margin-bottom: 8px;">Subject:</div>
<div style="font-size: 18px; font-weight: 600; color: #0f172a;">{{ title }}</div>
<div class="subject-box">
<div class="subject-label">Subject</div>
<div class="subject-value">{{ title }}</div>
</div>
<div class="info-grid" style="margin-top: 20px;">
<div class="info-grid" style="margin-top: 25px;">
<div class="info-label">Type:</div>
<div class="info-value">{{ ticket_type }}</div>
@@ -205,34 +462,37 @@
<!-- Description -->
<div>
<h3 style="color: #0f172a;">Description</h3>
<h3 class="section-title">Description</h3>
<div class="description-box">{{ description }}</div>
</div>
<!-- Action Buttons -->
<div style="text-align: center; margin: 40px 0 20px;">
<a href="{{ admin_url }}" class="cta-button" style="margin-right: 10px;">View in Admin Panel</a>
<a href="mailto:{{ user_email }}" class="cta-button" style="background: linear-gradient(135deg, #daa520, #d4af37);">Reply to Customer</a>
<div class="action-buttons">
<a href="{{ admin_url }}" class="cta-button cta-button-primary">View in Admin Panel</a>
<a href="mailto:{{ user_email }}" class="cta-button cta-button-secondary">Reply to Customer</a>
</div>
<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; border-radius: 4px; margin-top: 30px;">
<strong>⚠️ Action Required</strong><br>
Please review and respond to this ticket according to the SLA requirements for {{ priority }} priority tickets.
<!-- SLA Warning -->
<div class="sla-warning">
<strong>⚠️ Action Required</strong>
<p>Please review and respond to this ticket according to the SLA requirements for <strong>{{ priority }}</strong> priority tickets.</p>
</div>
</div>
<!-- Footer -->
<div class="footer">
<p style="margin: 0 0 10px 0;">
<strong>GNX Software Solutions</strong><br>
Internal Support Notification
</p>
<p style="margin: 0; font-size: 12px;">
<div class="email-footer">
<div class="footer-company">GNX Software Solutions</div>
<div class="footer-tagline">Internal Support Notification</div>
<div class="footer-links">
<a href="{{ admin_url }}">Admin Panel</a>
</div>
<div class="footer-note">
This is an automated notification for new support tickets.<br>
Manage tickets in the <a href="{{ admin_url }}" style="color: #3b82f6;">Admin Panel</a>
</p>
Manage tickets in the <a href="{{ admin_url }}" style="color: #daa520;">Admin Panel</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -5,128 +5,328 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ticket Status Updated</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333333;
background-color: #f4f4f4;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.email-container {
max-width: 600px;
margin: 20px auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.email-header {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.email-header h1 {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.7;
color: #2c3e50;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
margin: 0;
font-size: 24px;
padding: 40px 20px;
}
.email-wrapper {
max-width: 650px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.email-header {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 50%, #1d4ed8 100%);
padding: 50px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.email-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
}
.email-header h1 {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin: 0;
position: relative;
z-index: 2;
letter-spacing: -0.5px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.email-body {
padding: 50px 40px;
background: #ffffff;
}
.greeting {
font-size: 18px;
color: #1e293b;
margin-bottom: 30px;
font-weight: 500;
}
.intro-text {
font-size: 16px;
color: #475569;
line-height: 1.8;
margin-bottom: 35px;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.08) 0%, rgba(218, 165, 32, 0.03) 100%);
border: 2px solid rgba(218, 165, 32, 0.2);
border-left: 5px solid #daa520;
padding: 25px;
margin: 35px 0;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(218, 165, 32, 0.1);
}
.ticket-info strong {
color: #475569;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
display: block;
margin-bottom: 8px;
}
.ticket-info span {
color: #1e293b;
font-size: 16px;
font-weight: 600;
}
.email-body {
padding: 40px 30px;
}
.status-change-box {
background: #f8f9fa;
border-radius: 8px;
padding: 25px;
margin: 25px 0;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border: 2px solid rgba(59, 130, 246, 0.2);
border-radius: 12px;
padding: 40px 30px;
margin: 35px 0;
text-align: center;
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.1);
}
.status-change-box h3 {
margin: 0 0 30px 0;
color: #1e293b;
font-size: 18px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.status-display {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
}
.status-badge {
display: inline-block;
padding: 8px 20px;
border-radius: 20px;
padding: 12px 28px;
border-radius: 25px;
font-size: 14px;
font-weight: 700;
margin: 0 10px;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}
.status-old {
background: linear-gradient(135deg, #94a3b8, #64748b);
color: white;
}
.status-new {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
}
.arrow {
font-size: 24px;
color: #666;
margin: 0 10px;
font-size: 32px;
color: #64748b;
font-weight: 300;
}
.ticket-info {
background: linear-gradient(135deg, rgba(218, 165, 32, 0.1) 0%, rgba(218, 165, 32, 0.05) 100%);
border-left: 4px solid #daa520;
padding: 20px;
margin: 25px 0;
border-radius: 4px;
.update-time {
color: #64748b;
font-size: 13px;
margin-top: 25px;
font-style: italic;
}
.cta-container {
text-align: center;
margin: 45px 0;
}
.cta-button {
display: inline-block;
background: linear-gradient(135deg, #daa520, #d4af37);
background: linear-gradient(135deg, #daa520 0%, #d4af37 100%);
color: #ffffff !important;
text-decoration: none;
padding: 14px 32px;
border-radius: 6px;
font-weight: 600;
margin: 20px 0;
padding: 18px 40px;
border-radius: 10px;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.5px;
box-shadow: 0 8px 25px rgba(218, 165, 32, 0.4);
transition: all 0.3s ease;
text-transform: uppercase;
}
.footer {
background-color: #f8f8f8;
padding: 25px 30px;
text-align: center;
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(218, 165, 32, 0.5);
}
.info-note {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-left: 4px solid #3b82f6;
padding: 20px 25px;
border-radius: 8px;
margin-top: 35px;
color: #475569;
font-size: 14px;
color: #666666;
line-height: 1.7;
}
.email-footer {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
padding: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.9);
}
.footer-company {
font-size: 18px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
}
.footer-tagline {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px;
}
.footer-links {
margin: 25px 0;
padding-top: 25px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-links a {
color: #daa520;
text-decoration: none;
font-weight: 600;
font-size: 14px;
}
.footer-links a:hover {
text-decoration: underline;
}
.footer-note {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
margin-top: 25px;
line-height: 1.6;
}
@media (max-width: 600px) {
body {
padding: 20px 10px;
}
.email-header {
padding: 40px 25px;
}
.email-header h1 {
font-size: 26px;
}
.email-body {
padding: 35px 25px;
}
.status-display {
flex-direction: column;
}
.arrow {
transform: rotate(90deg);
}
.cta-button {
padding: 16px 30px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="email-wrapper">
<div class="email-header">
<h1>🔔 Ticket Status Updated</h1>
</div>
<div class="email-body">
<p>Dear {{ user_name }},</p>
<div class="greeting">Dear {{ user_name }},</div>
<p>Your support ticket status has been updated.</p>
<div class="intro-text">
Your support ticket status has been updated. You can view the full details below.
</div>
<div class="ticket-info">
<strong>Ticket:</strong> {{ ticket_number }}<br>
<strong>Subject:</strong> {{ title }}
<strong>Ticket Number</strong>
<span>{{ ticket_number }}</span>
<br><br>
<strong>Subject</strong>
<span>{{ title }}</span>
</div>
<div class="status-change-box">
<h3 style="margin-top: 0;">Status Change</h3>
<div style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap;">
<span class="status-badge" style="background-color: #94a3b8;">{{ old_status }}</span>
<h3>Status Change</h3>
<div class="status-display">
<span class="status-badge status-old">{{ old_status }}</span>
<span class="arrow"></span>
<span class="status-badge" style="background-color: {{ status_color }};">{{ new_status }}</span>
<span class="status-badge status-new">{{ new_status }}</span>
</div>
<p style="margin-top: 15px; color: #666; font-size: 14px;">Updated: {{ updated_at }}</p>
<div class="update-time">Updated: {{ updated_at }}</div>
</div>
<div style="text-align: center; margin: 30px 0;">
<div class="cta-container">
<a href="{{ support_url }}" class="cta-button">View Ticket Details</a>
</div>
<p style="color: #666;">
<div class="info-note">
You can check the full details of your ticket and any new messages in the Support Center.
</p>
</div>
</div>
<div class="footer">
<p style="margin: 0 0 10px 0;">
<strong>GNX Software Solutions</strong><br>
Enterprise Support Center
</p>
<p style="margin: 0; font-size: 12px;">
<div class="email-footer">
<div class="footer-company">GNX Software Solutions</div>
<div class="footer-tagline">Enterprise Support Center</div>
<div class="footer-links">
<a href="{{ support_url }}">Visit Support Center</a>
</div>
<div class="footer-note">
This is an automated notification. For assistance, visit our <a href="{{ support_url }}" style="color: #daa520;">Support Center</a>
</p>
</div>
</div>
</div>
</body>
</html>