🔧 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:
Claude 2025-08-03 20:57:27 +05:30
parent b9c6264b0e
commit ae303c2621
4 changed files with 208 additions and 17 deletions

View File

@ -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 %}

View File

@ -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')

View File

@ -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.')

View File

@ -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')