mirror of
https://github.com/thecyberlearn/quantum-ai-v2.git
synced 2026-08-18 12:12:58 +00:00
🔧 Fix payment message persistence and add expired session popup
- Fix wallet payment messages showing on login page by clearing messages before redirects - Remove misleading "Payment processed\!" message for free agents - Implement minimalist expired session popup for 5 Whys agent - Add auto-detection of expired sessions on page load with payment confirmation - Streamline authentication flow messages for better UX - Remove logout confirmation message to prevent persistence 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b9c6264b0e
commit
ae303c2621
@ -424,6 +424,78 @@
|
|||||||
padding: 20px !important;
|
padding: 20px !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Expired Session Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 30px;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
border: 1px solid var(--outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: var(--on-surface);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content p {
|
||||||
|
margin: 0 0 25px 0;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions .btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions .btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions .btn-primary:hover {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions .btn-secondary {
|
||||||
|
background: var(--surface-variant);
|
||||||
|
color: var(--on-surface);
|
||||||
|
border: 1px solid var(--outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions .btn-secondary:hover {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -584,6 +656,22 @@ document.body.setAttribute('data-session-expires', '{{ chat_session.expires_at.i
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Expired Session Modal -->
|
||||||
|
<div id="expiredSessionModal" class="modal-overlay" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>Session Expired</h3>
|
||||||
|
<p>Start new chat session?<br>This will deduct {{ agent.price }} AED from your wallet.</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button onclick="startNewSessionFromExpired()" class="btn btn-primary">
|
||||||
|
Continue ({{ agent.price }} AED)
|
||||||
|
</button>
|
||||||
|
<button onclick="cancelNewSession()" class="btn btn-secondary">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="agent-sidebar">
|
<div class="agent-sidebar">
|
||||||
<!-- How It Works Widget -->
|
<!-- How It Works Widget -->
|
||||||
@ -795,6 +883,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
// Clear old session start time cache
|
// Clear old session start time cache
|
||||||
localStorage.removeItem(`session_start_${sessionId}`);
|
localStorage.removeItem(`session_start_${sessionId}`);
|
||||||
|
|
||||||
|
// Check if session is expired on page load
|
||||||
|
checkExpiredSessionOnLoad(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize session indicators updates
|
// Initialize session indicators updates
|
||||||
@ -1035,5 +1126,91 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log('Insufficient balance for chat session');
|
console.log('Insufficient balance for chat session');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Expired Session Detection and Modal Functions
|
||||||
|
function checkExpiredSessionOnLoad(sessionId) {
|
||||||
|
// Only check if user is authenticated
|
||||||
|
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
|
||||||
|
if (!isAuthenticated) return;
|
||||||
|
|
||||||
|
// Check session status
|
||||||
|
fetch(`/agents/api/chat/session/${sessionId}/status/`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCSRFToken(),
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.is_expired) {
|
||||||
|
// Session is expired, show modal
|
||||||
|
showExpiredSessionModal();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking session status:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExpiredSessionModal() {
|
||||||
|
const modal = document.getElementById('expiredSessionModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideExpiredSessionModal() {
|
||||||
|
const modal = document.getElementById('expiredSessionModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startNewSessionFromExpired() {
|
||||||
|
const agentSlug = document.body.getAttribute('data-agent-slug');
|
||||||
|
const userBalance = parseFloat(document.body.getAttribute('data-user-balance') || '0');
|
||||||
|
const agentPrice = parseFloat(document.body.getAttribute('data-agent-price') || '0');
|
||||||
|
|
||||||
|
// Check balance first
|
||||||
|
if (userBalance < agentPrice) {
|
||||||
|
alert('Insufficient balance. Please top up your wallet.');
|
||||||
|
window.location.href = '/wallet/';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Start new session (this will deduct payment automatically)
|
||||||
|
const response = await fetch('/agents/api/chat/start/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCSRFToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
agent_slug: agentSlug
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Success - hide modal and reload page to show new session
|
||||||
|
hideExpiredSessionModal();
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Failed to start new session');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting new session:', error);
|
||||||
|
alert('Failed to start new session');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelNewSession() {
|
||||||
|
hideExpiredSessionModal();
|
||||||
|
// Optionally redirect to marketplace or disable chat interface
|
||||||
|
window.location.href = '/agents/';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -241,7 +241,10 @@ def career_navigator_access(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success message and redirect to form
|
# Success message and redirect to form
|
||||||
messages.success(request, f'✅ Payment processed! Welcome to your {agent.name} consultation.')
|
if agent.price > 0:
|
||||||
|
messages.success(request, f'Welcome to your {agent.name} consultation.')
|
||||||
|
else:
|
||||||
|
messages.success(request, f'Welcome to your {agent.name} consultation.')
|
||||||
return redirect('agents:career_navigator')
|
return redirect('agents:career_navigator')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ def validate_password_strength(password):
|
|||||||
is_common = password.lower() in common_passwords
|
is_common = password.lower() in common_passwords
|
||||||
|
|
||||||
if not (has_length and has_lower and has_upper and has_digit and has_special) or is_common:
|
if not (has_length and has_lower and has_upper and has_digit and has_special) or is_common:
|
||||||
return ["Password must be at least 8 characters with uppercase, lowercase, number, and special character"]
|
return ["Password must have 8+ characters, uppercase, lowercase, number, and special character"]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -162,9 +162,9 @@ def register_view(request):
|
|||||||
# Don't automatically login - require email verification first
|
# Don't automatically login - require email verification first
|
||||||
# Send verification email
|
# Send verification email
|
||||||
if send_verification_email(user):
|
if send_verification_email(user):
|
||||||
messages.success(request, 'Account created successfully! Please check your email to verify your account.')
|
messages.success(request, 'Account created. Check your email to verify.')
|
||||||
else:
|
else:
|
||||||
messages.warning(request, 'Account created but verification email could not be sent. You can request a new one after logging in.')
|
messages.warning(request, 'Account created. Email verification failed - try again later.')
|
||||||
|
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
else:
|
else:
|
||||||
@ -173,7 +173,7 @@ def register_view(request):
|
|||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
login(request, user)
|
login(request, user)
|
||||||
messages.success(request, f'Welcome {user.username}! Your account has been created successfully.')
|
messages.success(request, f'Welcome {user.username}!')
|
||||||
return redirect('core:homepage')
|
return redirect('core:homepage')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating account for {email}: {str(e)}")
|
logger.error(f"Error creating account for {email}: {str(e)}")
|
||||||
@ -185,7 +185,6 @@ def register_view(request):
|
|||||||
def logout_view(request):
|
def logout_view(request):
|
||||||
"""User logout view"""
|
"""User logout view"""
|
||||||
logout(request)
|
logout(request)
|
||||||
messages.success(request, 'You have been logged out successfully')
|
|
||||||
return redirect('core:homepage')
|
return redirect('core:homepage')
|
||||||
|
|
||||||
|
|
||||||
@ -337,7 +336,7 @@ def reset_password_view(request, token):
|
|||||||
# Mark token as used
|
# Mark token as used
|
||||||
reset_token.mark_as_used()
|
reset_token.mark_as_used()
|
||||||
|
|
||||||
messages.success(request, 'Your password has been reset successfully. You can now log in.')
|
messages.success(request, 'Password reset. You can now log in.')
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
|
|
||||||
return render(request, 'authentication/reset_password.html', {'token': token})
|
return render(request, 'authentication/reset_password.html', {'token': token})
|
||||||
@ -359,7 +358,7 @@ def verify_email_view(request, token):
|
|||||||
# Mark token as used
|
# Mark token as used
|
||||||
verification_token.mark_as_used()
|
verification_token.mark_as_used()
|
||||||
|
|
||||||
messages.success(request, 'Email verified successfully! You can now log in.')
|
messages.success(request, 'Email verified. You can now log in.')
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
|
|
||||||
|
|
||||||
@ -384,7 +383,7 @@ def resend_verification_view(request):
|
|||||||
|
|
||||||
# Send new verification email
|
# Send new verification email
|
||||||
if send_verification_email(user):
|
if send_verification_email(user):
|
||||||
messages.success(request, 'Verification email sent. Please check your inbox.')
|
messages.success(request, 'Verification email sent.')
|
||||||
else:
|
else:
|
||||||
messages.error(request, 'Unable to send verification email at this time.')
|
messages.error(request, 'Unable to send verification email at this time.')
|
||||||
|
|
||||||
|
|||||||
@ -79,16 +79,22 @@ def wallet_topup_view(request):
|
|||||||
return redirect('wallet:wallet_topup')
|
return redirect('wallet:wallet_topup')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Checkout session creation failed for user {request.user.id}: {e}")
|
logger.error(f"Checkout session creation failed for user {request.user.id}: {e}")
|
||||||
messages.error(request, 'Unable to process payment at this time. Please try again.')
|
messages.error(request, 'Payment failed. Try again.')
|
||||||
return redirect('wallet:wallet_topup')
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
return render(request, 'wallet/wallet_topup.html')
|
return render(request, 'wallet/wallet_topup.html')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@ratelimit(key='user', rate='10/m', method='GET', block=False)
|
@ratelimit(key='user', rate='10/m', method='GET', block=False)
|
||||||
def wallet_topup_success_view(request):
|
def wallet_topup_success_view(request):
|
||||||
"""Payment success page with automatic payment verification"""
|
"""Payment success page with automatic payment verification"""
|
||||||
|
# Check authentication first and clear any messages if redirecting to login
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
# Clear any existing messages to prevent them from showing on login page
|
||||||
|
storage = messages.get_messages(request)
|
||||||
|
storage.used = True
|
||||||
|
return redirect('authentication:login')
|
||||||
|
|
||||||
# Check if rate limited
|
# Check if rate limited
|
||||||
if getattr(request, 'limited', False):
|
if getattr(request, 'limited', False):
|
||||||
logger.warning(f"Payment success page rate limit exceeded for user {request.user.id}")
|
logger.warning(f"Payment success page rate limit exceeded for user {request.user.id}")
|
||||||
@ -99,7 +105,7 @@ def wallet_topup_success_view(request):
|
|||||||
|
|
||||||
if not session_id:
|
if not session_id:
|
||||||
logger.warning(f"No session ID provided for user {request.user.id}")
|
logger.warning(f"No session ID provided for user {request.user.id}")
|
||||||
messages.error(request, 'No payment session found. Please contact support if you completed a payment.')
|
messages.error(request, 'Payment session not found.')
|
||||||
return redirect('wallet:wallet')
|
return redirect('wallet:wallet')
|
||||||
|
|
||||||
# Verify payment directly with Stripe API
|
# Verify payment directly with Stripe API
|
||||||
@ -111,10 +117,10 @@ def wallet_topup_success_view(request):
|
|||||||
|
|
||||||
if result['success']:
|
if result['success']:
|
||||||
if result['processed']:
|
if result['processed']:
|
||||||
messages.success(request, f'Payment successful! {result["amount"]} AED has been added to your wallet.')
|
messages.success(request, f'{result["amount"]} AED added to wallet.')
|
||||||
logger.info(f"Payment verified and wallet updated for user {request.user.id}")
|
logger.info(f"Payment verified and wallet updated for user {request.user.id}")
|
||||||
else:
|
else:
|
||||||
messages.info(request, 'Payment already processed. Your wallet balance is up to date.')
|
messages.info(request, 'Payment already processed.')
|
||||||
logger.info(f"Payment already processed for session {session_id}")
|
logger.info(f"Payment already processed for session {session_id}")
|
||||||
else:
|
else:
|
||||||
messages.warning(request, 'Payment verification failed. Please contact support.')
|
messages.warning(request, 'Payment verification failed. Please contact support.')
|
||||||
@ -122,15 +128,21 @@ def wallet_topup_success_view(request):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error verifying payment for user {request.user.id}: {e}")
|
logger.error(f"Error verifying payment for user {request.user.id}: {e}")
|
||||||
messages.error(request, 'Unable to verify payment. Please contact support if you completed a payment.')
|
messages.error(request, 'Payment verification failed.')
|
||||||
|
|
||||||
return redirect('wallet:wallet')
|
return redirect('wallet:wallet')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def wallet_topup_cancel_view(request):
|
def wallet_topup_cancel_view(request):
|
||||||
"""Payment cancel page"""
|
"""Payment cancel page"""
|
||||||
messages.info(request, 'Payment was cancelled. No charges were made.')
|
# Check authentication first and clear any messages if redirecting to login
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
# Clear any existing messages to prevent them from showing on login page
|
||||||
|
storage = messages.get_messages(request)
|
||||||
|
storage.used = True
|
||||||
|
return redirect('authentication:login')
|
||||||
|
|
||||||
|
messages.info(request, 'Payment cancelled.')
|
||||||
return redirect('wallet:wallet_topup')
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user