update
This commit is contained in:
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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)}')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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}')
|
||||
|
||||
|
||||
@@ -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)}')
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user