resolved ui glitch and fixed logout issue

This commit is contained in:
laxmanhalaki 2025-10-29 20:47:21 +05:30
parent 96d8bedf6d
commit 02b009194c
6 changed files with 292 additions and 127 deletions

View File

@ -16,6 +16,7 @@ import { Toaster } from '@/components/ui/sonner';
import { toast } from 'sonner';
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
import { AuthCallback } from '@/pages/Auth/AuthCallback';
// Combined Request Database for backward compatibility
// This combines both custom and claim management requests
@ -476,6 +477,12 @@ function AppRoutes({ onLogout }: AppProps) {
return (
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
<Routes>
{/* Auth Callback - Must be before other routes */}
<Route
path="/login/callback"
element={<AuthCallback />}
/>
{/* Dashboard */}
<Route
path="/"

View File

@ -102,16 +102,24 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
// PRIORITY 2: Check if URL has logout parameter (from redirect)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('logout')) {
console.log('🔴 Logout parameter in URL - clearing everything');
if (urlParams.has('logout') || urlParams.has('okta_logged_out')) {
console.log('🔴 Logout parameter in URL - clearing everything', {
hasLogout: urlParams.has('logout'),
hasOktaLoggedOut: urlParams.has('okta_logged_out'),
});
TokenManager.clearAll();
localStorage.clear();
sessionStorage.clear();
setIsAuthenticated(false);
setUser(null);
setIsLoading(false);
// Clean URL
window.history.replaceState({}, document.title, '/');
// Clean URL but preserve okta_logged_out flag if it exists (for prompt=login)
const cleanParams = new URLSearchParams();
if (urlParams.has('okta_logged_out')) {
cleanParams.set('okta_logged_out', 'true');
}
const newUrl = cleanParams.toString() ? `/?${cleanParams.toString()}` : '/';
window.history.replaceState({}, document.title, newUrl);
return;
}
@ -336,13 +344,24 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
const scope = 'openid profile email';
const state = Math.random().toString(36).substring(7);
const authUrl = `${oktaDomain}/oauth2/default/v1/authorize?` +
// Check if we're coming from a logout - if so, add prompt=login to force re-authentication
const urlParams = new URLSearchParams(window.location.search);
const isAfterLogout = urlParams.has('logout') || urlParams.has('okta_logged_out');
let authUrl = `${oktaDomain}/oauth2/default/v1/authorize?` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`response_type=${responseType}&` +
`scope=${encodeURIComponent(scope)}&` +
`state=${state}`;
// Add prompt=login if coming from logout to force re-authentication
// This ensures Okta requires login even if a session still exists
if (isAfterLogout) {
authUrl += `&prompt=login`;
console.log('🔐 Adding prompt=login to force re-authentication after logout');
}
window.location.href = authUrl;
} catch (err: any) {
setError(err);
@ -355,8 +374,14 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
console.log('🚪 Current auth state:', { isAuthenticated, hasUser: !!user, isLoading });
try {
// CRITICAL: Get id_token from TokenManager before clearing anything
// Okta logout endpoint works better with id_token_hint to properly end the session
const idToken = TokenManager.getIdToken();
// Set logout flag to prevent auto-authentication after redirect
// This must be set BEFORE clearing storage so it survives
sessionStorage.setItem('__logout_in_progress__', 'true');
sessionStorage.setItem('__force_logout__', 'true');
setIsLoggingOut(true);
console.log('🚪 Step 1: Resetting auth state...');
@ -379,106 +404,62 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
// Continue with logout even if API call fails
}
// Clear all authentication data
// Clear all authentication data EXCEPT the logout flags and id_token (we need it for Okta logout)
console.log('========================================');
console.log('LOGOUT - Clearing all authentication data');
console.log('========================================');
// Use TokenManager.clearAll() which does comprehensive cleanup
// Store id_token temporarily if we have it
let tempIdToken: string | null = null;
if (idToken) {
tempIdToken = idToken;
console.log('🚪 Preserving id_token for Okta logout:', tempIdToken.substring(0, 20) + '...');
}
// Clear tokens but preserve logout flags
const logoutInProgress = sessionStorage.getItem('__logout_in_progress__');
const forceLogout = sessionStorage.getItem('__force_logout__');
// Use TokenManager.clearAll() but then restore logout flags
TokenManager.clearAll();
console.log('tryng to clrear all(localStorage and sessionStorage)')
// Double-check: Clear everything one more time as backup
try {
// Get all localStorage keys and remove them
const localStorageKeys: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) localStorageKeys.push(key);
}
localStorageKeys.forEach(key => {
try {
localStorage.removeItem(key);
} catch (e) {
console.warn(`Failed to remove localStorage key ${key}:`, e);
}
});
localStorage.clear();
// Get all sessionStorage keys and remove them
const sessionStorageKeys: string[] = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key) sessionStorageKeys.push(key);
}
sessionStorageKeys.forEach(key => {
try {
sessionStorage.removeItem(key);
} catch (e) {
console.warn(`Failed to remove sessionStorage key ${key}:`, e);
}
});
sessionStorage.clear();
// Restore logout flags immediately after clearAll
if (logoutInProgress) sessionStorage.setItem('__logout_in_progress__', logoutInProgress);
if (forceLogout) sessionStorage.setItem('__force_logout__', forceLogout);
console.log('Final verification - Storage cleared:');
console.log(`localStorage.length: ${localStorage.length}`);
console.log(`sessionStorage.length: ${sessionStorage.length}`);
if (localStorage.length > 0) {
console.error('ERROR: localStorage still has items:', Object.keys(localStorage));
}
if (sessionStorage.length > 0) {
console.error('ERROR: sessionStorage still has items:', Object.keys(sessionStorage));
}
} catch (e) {
console.error('Error in final cleanup:', e);
}
console.log('🚪 Local storage cleared (logout flags preserved)');
// Final verification BEFORE redirect
console.log('All authentication data cleared. Final verification:');
console.log(`localStorage.length: ${localStorage.length}`);
console.log(`sessionStorage.length: ${sessionStorage.length}`);
console.log('🚪 Final verification - logout flags preserved:', {
logoutInProgress: sessionStorage.getItem('__logout_in_progress__'),
forceLogout: sessionStorage.getItem('__force_logout__'),
});
if (localStorage.length > 0) {
console.error('CRITICAL: localStorage still has items before redirect!', Object.keys(localStorage));
// Force clear one more time
const remainingKeys = Object.keys(localStorage);
remainingKeys.forEach(key => localStorage.removeItem(key));
localStorage.clear();
}
if (sessionStorage.length > 0) {
console.error('CRITICAL: sessionStorage still has items before redirect!', Object.keys(sessionStorage));
const remainingKeys = Object.keys(sessionStorage);
remainingKeys.forEach(key => sessionStorage.removeItem(key));
sessionStorage.clear();
}
console.log('Redirecting to login...');
// Ensure logout flag is set before redirect
sessionStorage.setItem('__logout_in_progress__', 'true');
console.log('🚪 Clearing local session and redirecting to login...');
console.log('🚪 Using prompt=login on next auth to force re-authentication');
console.log('🚪 This will prevent auto-authentication even if Okta session exists');
// Small delay to ensure sessionStorage is written before redirect
await new Promise(resolve => setTimeout(resolve, 50));
await new Promise(resolve => setTimeout(resolve, 100));
// Use replace instead of href to prevent browser history issues
// DON'T use setTimeout - redirect immediately after clearing
if (isLocalhost()) {
// Direct redirect to login page for localhost - force full page reload
// Add timestamp to force fresh page load
window.location.replace('/?logout=' + Date.now());
} else {
// For production, redirect to Okta logout then to login
const oktaDomain = 'https://dev-830839.oktapreview.com';
const loginUrl = `${window.location.origin}/?logout=${Date.now()}`;
const logoutUrl = `${oktaDomain}/oauth2/default/v1/logout?post_logout_redirect_uri=${encodeURIComponent(loginUrl)}`;
window.location.replace(logoutUrl);
}
// Redirect directly to login page with flags
// The okta_logged_out flag will trigger prompt=login in the login() function
// This forces re-authentication even if Okta session still exists
const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
window.location.replace(loginUrl);
} catch (error) {
console.error('Logout error:', error);
// Force redirect even on error
console.error('🚪 Logout error:', error);
// Force redirect even on error - clear everything and redirect to login
try {
localStorage.clear();
sessionStorage.clear();
window.location.replace('/');
sessionStorage.setItem('__logout_in_progress__', 'true');
const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
window.location.replace(loginUrl);
} catch {
// Last resort - redirect to home
window.location.replace('/?logout=' + Date.now());
}
}
};

View File

@ -1,47 +1,186 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { Loader } from '@/components/common/Loader';
import { useNavigate } from 'react-router-dom';
import { CheckCircle2, AlertCircle, Loader2 } from 'lucide-react';
export function AuthCallback() {
const { isAuthenticated, isLoading, error, user } = useAuth();
const navigate = useNavigate();
const [authStep, setAuthStep] = useState<'exchanging' | 'fetching' | 'complete' | 'error'>('exchanging');
useEffect(() => {
console.log('AuthCallback Component Mounted');
console.log('Auth State during callback:', {
isAuthenticated,
isLoading,
error: error?.message,
hasUser: !!user,
timestamp: new Date().toISOString()
});
if (user) {
console.log('User authenticated successfully:', {
userId: user.userId,
employeeId: user.employeeId,
email: user.email,
displayName: user.displayName,
userData: user
});
// Redirect to home after successful authentication
navigate('/');
}
// Determine current authentication step based on state
if (error) {
console.error('Error during authentication callback:', error);
setAuthStep('error');
return;
}
}, [isAuthenticated, isLoading, error, user, navigate]);
if (error) {
console.error('AuthCallback Error:', error);
if (isLoading) {
// If we have code in URL but no user yet, we're exchanging
const urlParams = new URLSearchParams(window.location.search);
const hasCode = urlParams.get('code');
if (hasCode && !user) {
setAuthStep('exchanging');
} else if (user && !isAuthenticated) {
setAuthStep('fetching');
} else {
setAuthStep('exchanging');
}
} else if (user && isAuthenticated) {
setAuthStep('complete');
// Small delay before redirect for better UX
const timer = setTimeout(() => {
// Use window.location instead of navigate since Router context isn't available yet
window.location.href = '/';
}, 1500);
return () => clearTimeout(timer);
}
}, [isAuthenticated, isLoading, error, user]);
// Get dynamic loading message
const getLoadingMessage = () => {
switch (authStep) {
case 'exchanging':
return 'Exchanging authorization code...';
case 'fetching':
return 'Fetching your profile...';
case 'complete':
return 'Authentication successful!';
case 'error':
return 'Authentication failed';
default:
return 'Completing authentication...';
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
<Loader message="Completing authentication..." />
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-20"></div>
<div className="relative z-10 text-center px-4 max-w-md w-full">
{/* Logo/Brand Section */}
<div className="mb-8">
<div className="inline-flex items-center justify-center w-20 h-20 mb-4 rounded-2xl bg-gradient-to-br from-orange-500 to-red-600 shadow-lg shadow-orange-500/20">
<svg
className="w-12 h-12 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</div>
<h1 className="text-2xl font-bold text-white mb-2">Royal Enfield</h1>
<p className="text-slate-400 text-sm">Approval Portal</p>
</div>
{/* Main Loader Card */}
<div className="bg-white/10 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/20">
{/* Status Icon */}
<div className="mb-6 flex justify-center">
{authStep === 'error' ? (
<div className="relative">
<div className="absolute inset-0 animate-ping opacity-75">
<AlertCircle className="w-16 h-16 text-red-500" />
</div>
<AlertCircle className="w-16 h-16 text-red-500 relative" />
</div>
) : authStep === 'complete' ? (
<div className="relative">
<div className="absolute inset-0 animate-ping opacity-75">
<CheckCircle2 className="w-16 h-16 text-green-500" />
</div>
<CheckCircle2 className="w-16 h-16 text-green-500 relative" />
</div>
) : (
<div className="relative">
<Loader2 className="w-16 h-16 text-orange-500 animate-spin" />
{/* Outer rotating ring */}
<div className="absolute inset-0 border-4 border-orange-500/20 rounded-full"></div>
<div className="absolute inset-0 border-4 border-transparent border-t-orange-500 rounded-full animate-spin"></div>
</div>
)}
</div>
{/* Loading Message */}
<div className="mb-6">
<h2 className="text-xl font-semibold text-white mb-2">
{authStep === 'complete' ? 'Welcome Back!' : authStep === 'error' ? 'Authentication Error' : 'Authenticating'}
</h2>
<p className="text-slate-300 text-sm">{getLoadingMessage()}</p>
</div>
{/* Progress Steps */}
{authStep !== 'error' && authStep !== 'complete' && (
<div className="space-y-3 mb-6">
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'exchanging' ? 'text-white' : 'text-slate-400'}`}>
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'exchanging' ? 'bg-orange-500 animate-pulse' : 'bg-slate-600'}`}></div>
<span>Validating credentials</span>
</div>
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'fetching' ? 'text-white' : 'text-slate-400'}`}>
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'fetching' ? 'bg-orange-500 animate-pulse' : 'bg-slate-600'}`}></div>
<span>Loading your profile</span>
</div>
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'complete' ? 'text-white' : 'text-slate-400'}`}>
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'complete' ? 'bg-green-500' : 'bg-slate-600'}`}></div>
<span>Setting up your session</span>
</div>
</div>
)}
{/* Error Message */}
{authStep === 'error' && error && (
<div className="mt-6 p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<p className="text-red-400 text-sm">{error.message || 'An error occurred during authentication'}</p>
<button
onClick={() => {
// Use window.location instead of navigate since Router context isn't available yet
window.location.href = '/';
}}
className="mt-4 text-sm text-red-400 hover:text-red-300 underline"
>
Return to login
</button>
</div>
)}
{/* Animated Progress Bar */}
{authStep !== 'error' && authStep !== 'complete' && (
<div className="mt-6">
<div className="h-1.5 bg-slate-700/50 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-orange-500 to-red-600 rounded-full animate-pulse"
style={{
animation: 'progress 2s ease-in-out infinite',
}}
></div>
</div>
<style>{`
@keyframes progress {
0%, 100% { width: 20%; }
50% { width: 80%; }
}
`}</style>
</div>
)}
</div>
{/* Footer Text */}
<p className="mt-6 text-slate-500 text-xs">
{authStep === 'complete' ? 'Redirecting to dashboard...' : 'Please wait while we secure your session'}
</p>
</div>
{/* Animated Background Elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-orange-500/5 rounded-full blur-3xl animate-pulse"></div>
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-red-500/5 rounded-full blur-3xl animate-pulse delay-1000"></div>
</div>
</div>
);
}

View File

@ -9,6 +9,9 @@ export function AuthenticatedApp() {
const { isAuthenticated, isLoading, error, user, logout } = useAuth();
const [showDebugInfo, setShowDebugInfo] = useState(false);
// Check if we're on callback route (after all hooks are called)
const isCallbackRoute = typeof window !== 'undefined' && window.location.pathname === '/login/callback';
const handleLogout = async () => {
console.log('🔵 ========================================');
console.log('🔵 AuthenticatedApp.handleLogout - CALLED');
@ -70,6 +73,11 @@ export function AuthenticatedApp() {
}
}, [isAuthenticated, isLoading, error, user]);
// Always show callback loader when on callback route (after all hooks)
if (isCallbackRoute) {
return <AuthCallback />;
}
// Show loading state while checking authentication
if (isLoading) {
console.log('Auth0 is still loading...');

View File

@ -86,6 +86,7 @@ export interface TokenExchangeResponse {
};
accessToken: string;
refreshToken: string;
idToken?: string; // ID token from Okta for logout
}
export interface RefreshTokenResponse {
@ -154,6 +155,11 @@ export async function exchangeCodeForTokens(
TokenManager.setAccessToken(result.accessToken);
TokenManager.setRefreshToken(result.refreshToken);
TokenManager.setUserData(result.user);
// Store id_token if available (needed for proper Okta logout)
if (result.idToken) {
TokenManager.setIdToken(result.idToken);
console.log('✅ ID token stored for logout');
}
console.log('✅ Tokens stored successfully');
} else {
console.warn('⚠️ Tokens missing in response', { result });

View File

@ -5,6 +5,7 @@
const ACCESS_TOKEN_KEY = 'accessToken';
const REFRESH_TOKEN_KEY = 'refreshToken';
const ID_TOKEN_KEY = 'idToken';
const USER_DATA_KEY = 'userData';
/**
@ -49,7 +50,7 @@ export const cookieUtils = {
* Attempts to clear cookies with different paths and domains
*/
clearAll(): void {
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, USER_DATA_KEY];
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, ID_TOKEN_KEY, USER_DATA_KEY];
cookieNames.forEach(name => {
// Remove with default path
@ -119,6 +120,27 @@ export class TokenManager {
return localStorage.getItem(REFRESH_TOKEN_KEY);
}
/**
* Store ID token (from Okta)
*/
static setIdToken(token: string): void {
localStorage.setItem(ID_TOKEN_KEY, token);
if (this.isLocalhost()) {
cookieUtils.set(ID_TOKEN_KEY, token, 1); // 1 day
}
}
/**
* Get ID token
*/
static getIdToken(): string | null {
if (this.isLocalhost()) {
// Try cookie first, then localStorage
return cookieUtils.get(ID_TOKEN_KEY) || localStorage.getItem(ID_TOKEN_KEY);
}
return localStorage.getItem(ID_TOKEN_KEY);
}
/**
* Store user data
*/
@ -164,6 +186,7 @@ export class TokenManager {
const authKeys = [
ACCESS_TOKEN_KEY,
REFRESH_TOKEN_KEY,
ID_TOKEN_KEY,
USER_DATA_KEY,
'oktaToken',
'authToken',
@ -172,6 +195,7 @@ export class TokenManager {
'access_token',
'refresh_token',
'id_token',
'idToken',
'token',
'accessToken',
'refreshToken',