removed suspicious comments
This commit is contained in:
parent
08cda349f3
commit
7ae9133b98
195
src/App.tsx
195
src/App.tsx
@ -412,201 +412,6 @@ function AppRoutes({ onLogout }: AppProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the old code below for backward compatibility (local storage fallback)
|
|
||||||
// This can be removed once API integration is fully tested
|
|
||||||
/*
|
|
||||||
// Generate unique ID for the new claim request
|
|
||||||
const requestId = `RE-REQ-2024-CM-${String(dynamicRequests.length + 2).padStart(3, '0')}`;
|
|
||||||
|
|
||||||
// Create full request object
|
|
||||||
const newRequest = {
|
|
||||||
id: requestId,
|
|
||||||
title: `${claimData.activityName} - Claim Request`,
|
|
||||||
description: claimData.requestDescription,
|
|
||||||
category: 'Dealer Operations',
|
|
||||||
subcategory: 'Claim Management',
|
|
||||||
status: 'pending',
|
|
||||||
priority: 'standard',
|
|
||||||
amount: 'TBD',
|
|
||||||
slaProgress: 0,
|
|
||||||
slaRemaining: '7 days',
|
|
||||||
slaEndDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
||||||
currentStep: 1,
|
|
||||||
totalSteps: 8,
|
|
||||||
templateType: 'claim-management',
|
|
||||||
templateName: 'Claim Management',
|
|
||||||
initiator: {
|
|
||||||
name: 'Current User',
|
|
||||||
role: 'Regional Marketing Coordinator',
|
|
||||||
department: 'Marketing',
|
|
||||||
email: 'current.user@royalenfield.com',
|
|
||||||
phone: '+91 98765 43290',
|
|
||||||
avatar: 'CU'
|
|
||||||
},
|
|
||||||
department: 'Marketing',
|
|
||||||
createdAt: new Date().toLocaleString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true
|
|
||||||
}),
|
|
||||||
updatedAt: new Date().toLocaleString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true
|
|
||||||
}),
|
|
||||||
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
||||||
conclusionRemark: '',
|
|
||||||
claimDetails: {
|
|
||||||
activityName: claimData.activityName,
|
|
||||||
activityType: claimData.activityType,
|
|
||||||
activityDate: claimData.activityDate ? new Date(claimData.activityDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '',
|
|
||||||
location: claimData.location,
|
|
||||||
dealerCode: claimData.dealerCode,
|
|
||||||
dealerName: claimData.dealerName,
|
|
||||||
dealerEmail: claimData.dealerEmail || 'N/A',
|
|
||||||
dealerPhone: claimData.dealerPhone || 'N/A',
|
|
||||||
dealerAddress: claimData.dealerAddress || 'N/A',
|
|
||||||
requestDescription: claimData.requestDescription,
|
|
||||||
estimatedBudget: claimData.estimatedBudget || 'TBD',
|
|
||||||
periodStart: claimData.periodStartDate ? new Date(claimData.periodStartDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '',
|
|
||||||
periodEnd: claimData.periodEndDate ? new Date(claimData.periodEndDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : ''
|
|
||||||
},
|
|
||||||
approvalFlow: claimData.workflowSteps || [
|
|
||||||
{
|
|
||||||
step: 1,
|
|
||||||
approver: `${claimData.dealerName} (Dealer)`,
|
|
||||||
role: 'Dealer - Document Upload',
|
|
||||||
status: 'pending',
|
|
||||||
tatHours: 72,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: new Date().toISOString(),
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Dealer uploads proposal document, cost breakup, timeline for closure, and other supporting documents'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 2,
|
|
||||||
approver: 'Current User (Initiator)',
|
|
||||||
role: 'Initiator Evaluation',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 48,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Initiator reviews dealer documents and approves or requests modifications'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 3,
|
|
||||||
approver: 'System Auto-Process',
|
|
||||||
role: 'IO Confirmation',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 1,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Automatic IO (Internal Order) confirmation generated upon initiator approval'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 4,
|
|
||||||
approver: 'Rajesh Kumar',
|
|
||||||
role: 'Department Lead Approval',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 72,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Department head approves and blocks budget in IO for this activity'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 5,
|
|
||||||
approver: `${claimData.dealerName} (Dealer)`,
|
|
||||||
role: 'Dealer - Completion Documents',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 120,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Dealer submits activity completion documents and description'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 6,
|
|
||||||
approver: 'Current User (Initiator)',
|
|
||||||
role: 'Initiator Verification',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 48,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Initiator verifies completion documents and can modify approved amount'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 7,
|
|
||||||
approver: 'System Auto-Process',
|
|
||||||
role: 'E-Invoice Generation',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 1,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Auto-generate e-invoice based on final approved amount'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
step: 8,
|
|
||||||
approver: 'Finance Team',
|
|
||||||
role: 'Credit Note Issuance',
|
|
||||||
status: 'waiting',
|
|
||||||
tatHours: 48,
|
|
||||||
elapsedHours: 0,
|
|
||||||
assignedAt: null,
|
|
||||||
comment: null,
|
|
||||||
timestamp: null,
|
|
||||||
description: 'Finance team issues credit note to dealer'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
documents: [],
|
|
||||||
spectators: [],
|
|
||||||
auditTrail: [
|
|
||||||
{
|
|
||||||
type: 'created',
|
|
||||||
action: 'Request Created',
|
|
||||||
details: `Claim request for ${claimData.activityName} created`,
|
|
||||||
user: 'Current User',
|
|
||||||
timestamp: new Date().toLocaleString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
],
|
|
||||||
tags: ['claim-management', 'new-request', claimData.activityType?.toLowerCase().replace(/\s+/g, '-')]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add to dynamic requests
|
|
||||||
setDynamicRequests(prev => [...prev, newRequest]);
|
|
||||||
|
|
||||||
// Also add to REQUEST_DATABASE for immediate viewing
|
|
||||||
(REQUEST_DATABASE as any)[requestId] = newRequest;
|
|
||||||
|
|
||||||
toast.success('Claim Request Submitted', {
|
|
||||||
description: 'Your claim management request has been created successfully.',
|
|
||||||
});
|
|
||||||
navigate('/my-requests');
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function AnalyticsConfig() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// TODO: Implement API call to save configuration
|
|
||||||
toast.success('Analytics configuration saved successfully');
|
toast.success('Analytics configuration saved successfully');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
export type Role = 'Initiator' | 'Approver' | 'Spectator';
|
export type Role = 'Initiator' | 'Approver' | 'Spectator';
|
||||||
|
|
||||||
export type KPICard =
|
export type KPICard =
|
||||||
| 'Total Requests'
|
| 'Total Requests'
|
||||||
| 'Open Requests'
|
| 'Open Requests'
|
||||||
| 'Approved Requests'
|
| 'Approved Requests'
|
||||||
@ -59,7 +59,7 @@ export function DashboardConfig() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// TODO: Implement API call to save dashboard configuration
|
|
||||||
toast.success('Dashboard layout saved successfully');
|
toast.success('Dashboard layout saved successfully');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export function NotificationConfig() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// TODO: Implement API call to save notification configuration
|
|
||||||
toast.success('Notification configuration saved successfully');
|
toast.success('Notification configuration saved successfully');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export function SharingConfig() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// TODO: Implement API call to save sharing configuration
|
|
||||||
toast.success('Sharing policy saved successfully');
|
toast.success('Sharing policy saved successfully');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,18 +2,18 @@ import { useState, useCallback, useEffect, useRef } from 'react';
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
Users,
|
Users,
|
||||||
Shield,
|
Shield,
|
||||||
Loader2,
|
Loader2,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
@ -75,7 +75,7 @@ export function UserManagement() {
|
|||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
const [loadingUsers, setLoadingUsers] = useState(false);
|
const [loadingUsers, setLoadingUsers] = useState(false);
|
||||||
const [roleStats, setRoleStats] = useState({ admins: 0, management: 0, users: 0, total: 0, active: 0, inactive: 0 });
|
const [roleStats, setRoleStats] = useState({ admins: 0, management: 0, users: 0, total: 0, active: 0, inactive: 0 });
|
||||||
|
|
||||||
// Pagination and filtering
|
// Pagination and filtering
|
||||||
const [roleFilter, setRoleFilter] = useState<'ELEVATED' | 'ALL' | 'ADMIN' | 'MANAGEMENT' | 'USER'>('ELEVATED');
|
const [roleFilter, setRoleFilter] = useState<'ELEVATED' | 'ALL' | 'ADMIN' | 'MANAGEMENT' | 'USER'>('ELEVATED');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -135,14 +135,14 @@ export function UserManagement() {
|
|||||||
// We'll search with a broader filter to find the user
|
// We'll search with a broader filter to find the user
|
||||||
const response = await userApi.getUsersByRole('ALL', 1, 1000);
|
const response = await userApi.getUsersByRole('ALL', 1, 1000);
|
||||||
const allUsers = response.data?.data?.users || [];
|
const allUsers = response.data?.data?.users || [];
|
||||||
const foundUser = allUsers.find((u: any) =>
|
const foundUser = allUsers.find((u: any) =>
|
||||||
u.email?.toLowerCase() === email.toLowerCase()
|
u.email?.toLowerCase() === email.toLowerCase()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundUser && foundUser.role) {
|
if (foundUser && foundUser.role) {
|
||||||
return foundUser.role as 'USER' | 'MANAGEMENT' | 'ADMIN';
|
return foundUser.role as 'USER' | 'MANAGEMENT' | 'ADMIN';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // User not found in system, no role assigned
|
return null; // User not found in system, no role assigned
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch user role:', error);
|
console.error('Failed to fetch user role:', error);
|
||||||
@ -156,7 +156,7 @@ export function UserManagement() {
|
|||||||
setSearchQuery(user.email);
|
setSearchQuery(user.email);
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
setFetchingRole(true);
|
setFetchingRole(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch and set the user's current role if they have one
|
// Fetch and set the user's current role if they have one
|
||||||
const currentRole = await fetchUserRole(user.email);
|
const currentRole = await fetchUserRole(user.email);
|
||||||
@ -186,7 +186,7 @@ export function UserManagement() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await userApi.assignRole(selectedUser.email, selectedRole);
|
await userApi.assignRole(selectedUser.email, selectedRole);
|
||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `Successfully assigned ${selectedRole} role to ${selectedUser.displayName || selectedUser.email}`
|
text: `Successfully assigned ${selectedRole} role to ${selectedUser.displayName || selectedUser.email}`
|
||||||
@ -200,7 +200,7 @@ export function UserManagement() {
|
|||||||
// Refresh the users list
|
// Refresh the users list
|
||||||
await fetchUsers();
|
await fetchUsers();
|
||||||
await fetchRoleStatistics();
|
await fetchRoleStatistics();
|
||||||
|
|
||||||
toast.success(`Role assigned successfully`);
|
toast.success(`Role assigned successfully`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Role assignment failed:', error);
|
console.error('Role assignment failed:', error);
|
||||||
@ -220,7 +220,7 @@ export function UserManagement() {
|
|||||||
setLoadingUsers(true);
|
setLoadingUsers(true);
|
||||||
try {
|
try {
|
||||||
const response = await userApi.getUsersByRole(roleFilter, page, limit);
|
const response = await userApi.getUsersByRole(roleFilter, page, limit);
|
||||||
|
|
||||||
const usersData = response.data?.data?.users || [];
|
const usersData = response.data?.data?.users || [];
|
||||||
const paginationData = response.data?.data?.pagination;
|
const paginationData = response.data?.data?.pagination;
|
||||||
const summaryData = response.data?.data?.summary;
|
const summaryData = response.data?.data?.summary;
|
||||||
@ -234,13 +234,13 @@ export function UserManagement() {
|
|||||||
designation: u.designation,
|
designation: u.designation,
|
||||||
isActive: u.isActive !== false // Default to true if not specified
|
isActive: u.isActive !== false // Default to true if not specified
|
||||||
})));
|
})));
|
||||||
|
|
||||||
if (paginationData) {
|
if (paginationData) {
|
||||||
setCurrentPage(paginationData.currentPage);
|
setCurrentPage(paginationData.currentPage);
|
||||||
setTotalPages(paginationData.totalPages);
|
setTotalPages(paginationData.totalPages);
|
||||||
setTotalUsers(paginationData.totalUsers);
|
setTotalUsers(paginationData.totalUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update summary stats if available
|
// Update summary stats if available
|
||||||
if (summaryData) {
|
if (summaryData) {
|
||||||
setRoleStats(prev => ({
|
setRoleStats(prev => ({
|
||||||
@ -264,13 +264,13 @@ export function UserManagement() {
|
|||||||
try {
|
try {
|
||||||
const response = await userApi.getRoleStatistics();
|
const response = await userApi.getRoleStatistics();
|
||||||
const statsData = response.data?.data?.statistics || response.data?.statistics || [];
|
const statsData = response.data?.data?.statistics || response.data?.statistics || [];
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
admins: parseInt(statsData.find((s: any) => s.role === 'ADMIN')?.count || '0'),
|
admins: parseInt(statsData.find((s: any) => s.role === 'ADMIN')?.count || '0'),
|
||||||
management: parseInt(statsData.find((s: any) => s.role === 'MANAGEMENT')?.count || '0'),
|
management: parseInt(statsData.find((s: any) => s.role === 'MANAGEMENT')?.count || '0'),
|
||||||
users: parseInt(statsData.find((s: any) => s.role === 'USER')?.count || '0')
|
users: parseInt(statsData.find((s: any) => s.role === 'USER')?.count || '0')
|
||||||
};
|
};
|
||||||
|
|
||||||
setRoleStats(prev => ({
|
setRoleStats(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...stats,
|
...stats,
|
||||||
@ -317,8 +317,8 @@ export function UserManagement() {
|
|||||||
const handleToggleUserStatus = async (userId: string) => {
|
const handleToggleUserStatus = async (userId: string) => {
|
||||||
const user = users.find(u => u.userId === userId);
|
const user = users.find(u => u.userId === userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
// TODO: Implement backend API for toggling user status
|
|
||||||
toast.info('User status toggle functionality coming soon');
|
toast.info('User status toggle functionality coming soon');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -326,13 +326,12 @@ export function UserManagement() {
|
|||||||
const handleDeleteUser = async (userId: string) => {
|
const handleDeleteUser = async (userId: string) => {
|
||||||
const user = users.find(u => u.userId === userId);
|
const user = users.find(u => u.userId === userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
if (user.role === 'ADMIN') {
|
if (user.role === 'ADMIN') {
|
||||||
toast.error('Cannot delete admin user');
|
toast.error('Cannot delete admin user');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement backend API for deleting users
|
|
||||||
toast.info('User deletion functionality coming soon');
|
toast.info('User deletion functionality coming soon');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -515,11 +514,10 @@ export function UserManagement() {
|
|||||||
|
|
||||||
{/* Message */}
|
{/* Message */}
|
||||||
{message && (
|
{message && (
|
||||||
<div className={`border-2 rounded-lg p-4 ${
|
<div className={`border-2 rounded-lg p-4 ${message.type === 'success'
|
||||||
message.type === 'success'
|
? 'border-green-200 bg-green-50'
|
||||||
? 'border-green-200 bg-green-50'
|
: 'border-red-200 bg-red-50'
|
||||||
: 'border-red-200 bg-red-50'
|
}`}>
|
||||||
}`}>
|
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{message.type === 'success' ? (
|
{message.type === 'success' ? (
|
||||||
<CheckCircle className="w-5 h-5 text-green-600 shrink-0 mt-0.5" />
|
<CheckCircle className="w-5 h-5 text-green-600 shrink-0 mt-0.5" />
|
||||||
@ -602,7 +600,7 @@ export function UserManagement() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="font-medium text-gray-700">No users found</p>
|
<p className="font-medium text-gray-700">No users found</p>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
{roleFilter === 'ELEVATED'
|
{roleFilter === 'ELEVATED'
|
||||||
? 'Assign ADMIN or MANAGEMENT roles to see users here'
|
? 'Assign ADMIN or MANAGEMENT roles to see users here'
|
||||||
: 'No users match the selected filter'
|
: 'No users match the selected filter'
|
||||||
}
|
}
|
||||||
@ -664,11 +662,10 @@ export function UserManagement() {
|
|||||||
variant={currentPage === pageNum ? "default" : "outline"}
|
variant={currentPage === pageNum ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handlePageChange(pageNum)}
|
onClick={() => handlePageChange(pageNum)}
|
||||||
className={`w-9 h-9 p-0 ${
|
className={`w-9 h-9 p-0 ${currentPage === pageNum
|
||||||
currentPage === pageNum
|
? 'bg-re-green hover:bg-re-green/90'
|
||||||
? 'bg-re-green hover:bg-re-green/90'
|
: ''
|
||||||
: ''
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{pageNum}
|
{pageNum}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ let configLoaded = false;
|
|||||||
// Lazy initialization of configuration
|
// Lazy initialization of configuration
|
||||||
async function ensureConfigLoaded() {
|
async function ensureConfigLoaded() {
|
||||||
if (configLoaded) return;
|
if (configLoaded) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = await configService.getConfig();
|
const config = await configService.getConfig();
|
||||||
WORK_START_HOUR = config.workingHours.START_HOUR;
|
WORK_START_HOUR = config.workingHours.START_HOUR;
|
||||||
@ -30,7 +30,7 @@ async function ensureConfigLoaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize config on first import (non-blocking)
|
// Initialize config on first import (non-blocking)
|
||||||
ensureConfigLoaded().catch(() => {});
|
ensureConfigLoaded().catch(() => { });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if current time is within working hours
|
* Check if current time is within working hours
|
||||||
@ -40,7 +40,7 @@ ensureConfigLoaded().catch(() => {});
|
|||||||
export function isWorkingTime(date: Date = new Date(), priority: string = 'standard'): boolean {
|
export function isWorkingTime(date: Date = new Date(), priority: string = 'standard'): boolean {
|
||||||
const day = date.getDay(); // 0 = Sunday, 6 = Saturday
|
const day = date.getDay(); // 0 = Sunday, 6 = Saturday
|
||||||
const hour = date.getHours();
|
const hour = date.getHours();
|
||||||
|
|
||||||
// For standard priority: exclude weekends
|
// For standard priority: exclude weekends
|
||||||
// For express priority: include weekends (calendar days)
|
// For express priority: include weekends (calendar days)
|
||||||
if (priority === 'standard') {
|
if (priority === 'standard') {
|
||||||
@ -48,14 +48,13 @@ export function isWorkingTime(date: Date = new Date(), priority: string = 'stand
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Working hours check (applies to both priorities)
|
// Working hours check (applies to both priorities)
|
||||||
if (hour < WORK_START_HOUR || hour >= WORK_END_HOUR) {
|
if (hour < WORK_START_HOUR || hour >= WORK_END_HOUR) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add holiday check if holiday API is available
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +65,12 @@ export function isWorkingTime(date: Date = new Date(), priority: string = 'stand
|
|||||||
*/
|
*/
|
||||||
export function getNextWorkingTime(date: Date = new Date(), priority: string = 'standard'): Date {
|
export function getNextWorkingTime(date: Date = new Date(), priority: string = 'standard'): Date {
|
||||||
const result = new Date(date);
|
const result = new Date(date);
|
||||||
|
|
||||||
// If already in working time, return as is
|
// If already in working time, return as is
|
||||||
if (isWorkingTime(result, priority)) {
|
if (isWorkingTime(result, priority)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For standard priority: skip weekends
|
// For standard priority: skip weekends
|
||||||
if (priority === 'standard') {
|
if (priority === 'standard') {
|
||||||
const day = result.getDay();
|
const day = result.getDay();
|
||||||
@ -86,13 +85,13 @@ export function getNextWorkingTime(date: Date = new Date(), priority: string = '
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If before work hours, move to work start
|
// If before work hours, move to work start
|
||||||
if (result.getHours() < WORK_START_HOUR) {
|
if (result.getHours() < WORK_START_HOUR) {
|
||||||
result.setHours(WORK_START_HOUR, 0, 0, 0);
|
result.setHours(WORK_START_HOUR, 0, 0, 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If after work hours, move to next day work start
|
// If after work hours, move to next day work start
|
||||||
if (result.getHours() >= WORK_END_HOUR) {
|
if (result.getHours() >= WORK_END_HOUR) {
|
||||||
result.setDate(result.getDate() + 1);
|
result.setDate(result.getDate() + 1);
|
||||||
@ -100,7 +99,7 @@ export function getNextWorkingTime(date: Date = new Date(), priority: string = '
|
|||||||
// Check if next day is weekend (only for standard priority)
|
// Check if next day is weekend (only for standard priority)
|
||||||
return getNextWorkingTime(result, priority);
|
return getNextWorkingTime(result, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,19 +113,19 @@ export function calculateElapsedWorkingHours(startDate: Date, endDate: Date = ne
|
|||||||
let current = new Date(startDate);
|
let current = new Date(startDate);
|
||||||
const end = new Date(endDate);
|
const end = new Date(endDate);
|
||||||
let elapsedMinutes = 0;
|
let elapsedMinutes = 0;
|
||||||
|
|
||||||
// Move minute by minute and count only working minutes
|
// Move minute by minute and count only working minutes
|
||||||
while (current < end) {
|
while (current < end) {
|
||||||
if (isWorkingTime(current, priority)) {
|
if (isWorkingTime(current, priority)) {
|
||||||
elapsedMinutes++;
|
elapsedMinutes++;
|
||||||
}
|
}
|
||||||
current.setMinutes(current.getMinutes() + 1);
|
current.setMinutes(current.getMinutes() + 1);
|
||||||
|
|
||||||
// Safety: stop if calculating more than 1 year
|
// Safety: stop if calculating more than 1 year
|
||||||
const hoursSoFar = elapsedMinutes / 60;
|
const hoursSoFar = elapsedMinutes / 60;
|
||||||
if (hoursSoFar > 8760) break;
|
if (hoursSoFar > 8760) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert minutes to hours (with decimal precision)
|
// Convert minutes to hours (with decimal precision)
|
||||||
return elapsedMinutes / 60;
|
return elapsedMinutes / 60;
|
||||||
}
|
}
|
||||||
@ -140,12 +139,12 @@ export function calculateElapsedWorkingHours(startDate: Date, endDate: Date = ne
|
|||||||
export function calculateRemainingWorkingHours(deadline: Date, fromDate: Date = new Date(), priority: string = 'standard'): number {
|
export function calculateRemainingWorkingHours(deadline: Date, fromDate: Date = new Date(), priority: string = 'standard'): number {
|
||||||
const deadlineTime = new Date(deadline).getTime();
|
const deadlineTime = new Date(deadline).getTime();
|
||||||
const currentTime = new Date(fromDate).getTime();
|
const currentTime = new Date(fromDate).getTime();
|
||||||
|
|
||||||
// If deadline has passed
|
// If deadline has passed
|
||||||
if (deadlineTime <= currentTime) {
|
if (deadlineTime <= currentTime) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate remaining working hours
|
// Calculate remaining working hours
|
||||||
return calculateElapsedWorkingHours(fromDate, deadline, priority);
|
return calculateElapsedWorkingHours(fromDate, deadline, priority);
|
||||||
}
|
}
|
||||||
@ -160,9 +159,9 @@ export function calculateRemainingWorkingHours(deadline: Date, fromDate: Date =
|
|||||||
export function calculateSLAProgress(startDate: Date, deadline: Date, currentDate: Date = new Date(), priority: string = 'standard'): number {
|
export function calculateSLAProgress(startDate: Date, deadline: Date, currentDate: Date = new Date(), priority: string = 'standard'): number {
|
||||||
const totalHours = calculateElapsedWorkingHours(startDate, deadline, priority);
|
const totalHours = calculateElapsedWorkingHours(startDate, deadline, priority);
|
||||||
const elapsedHours = calculateElapsedWorkingHours(startDate, currentDate, priority);
|
const elapsedHours = calculateElapsedWorkingHours(startDate, currentDate, priority);
|
||||||
|
|
||||||
if (totalHours === 0) return 0;
|
if (totalHours === 0) return 0;
|
||||||
|
|
||||||
const progress = (elapsedHours / totalHours) * 100;
|
const progress = (elapsedHours / totalHours) * 100;
|
||||||
return Math.min(Math.max(progress, 0), 100); // Clamp between 0 and 100
|
return Math.min(Math.max(progress, 0), 100); // Clamp between 0 and 100
|
||||||
}
|
}
|
||||||
@ -185,17 +184,17 @@ export function getSLAStatus(startDate: string | Date, deadline: string | Date,
|
|||||||
const start = new Date(startDate);
|
const start = new Date(startDate);
|
||||||
const end = new Date(deadline);
|
const end = new Date(deadline);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const isWorking = isWorkingTime(now, priority);
|
const isWorking = isWorkingTime(now, priority);
|
||||||
const elapsedHours = calculateElapsedWorkingHours(start, now, priority);
|
const elapsedHours = calculateElapsedWorkingHours(start, now, priority);
|
||||||
const totalHours = calculateElapsedWorkingHours(start, end, priority);
|
const totalHours = calculateElapsedWorkingHours(start, end, priority);
|
||||||
const remainingHours = Math.max(0, totalHours - elapsedHours);
|
const remainingHours = Math.max(0, totalHours - elapsedHours);
|
||||||
const progress = calculateSLAProgress(start, end, now, priority);
|
const progress = calculateSLAProgress(start, end, now, priority);
|
||||||
|
|
||||||
let statusText = '';
|
let statusText = '';
|
||||||
if (!isWorking) {
|
if (!isWorking) {
|
||||||
statusText = priority === 'express'
|
statusText = priority === 'express'
|
||||||
? 'SLA tracking paused (outside working hours)'
|
? 'SLA tracking paused (outside working hours)'
|
||||||
: 'SLA tracking paused (outside working hours/days)';
|
: 'SLA tracking paused (outside working hours/days)';
|
||||||
} else if (remainingHours === 0) {
|
} else if (remainingHours === 0) {
|
||||||
statusText = 'SLA deadline reached';
|
statusText = 'SLA deadline reached';
|
||||||
@ -208,7 +207,7 @@ export function getSLAStatus(startDate: string | Date, deadline: string | Date,
|
|||||||
} else {
|
} else {
|
||||||
statusText = 'On track';
|
statusText = 'On track';
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isWorkingTime: isWorking,
|
isWorkingTime: isWorking,
|
||||||
progress,
|
progress,
|
||||||
@ -231,38 +230,38 @@ export function getSLAStatus(startDate: string | Date, deadline: string | Date,
|
|||||||
export function formatHoursMinutes(hours: number | null | undefined): string {
|
export function formatHoursMinutes(hours: number | null | undefined): string {
|
||||||
if (hours === null || hours === undefined || hours < 0) return '0 hours';
|
if (hours === null || hours === undefined || hours < 0) return '0 hours';
|
||||||
if (hours === 0) return '0 hours';
|
if (hours === 0) return '0 hours';
|
||||||
|
|
||||||
const WORKING_HOURS_PER_DAY = 8;
|
const WORKING_HOURS_PER_DAY = 8;
|
||||||
|
|
||||||
// If less than 1 hour, show minutes only
|
// If less than 1 hour, show minutes only
|
||||||
if (hours < 1) {
|
if (hours < 1) {
|
||||||
const m = Math.round(hours * 60);
|
const m = Math.round(hours * 60);
|
||||||
return m > 0 ? `${m}m` : '0 hours';
|
return m > 0 ? `${m}m` : '0 hours';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate days and remaining hours (8 hours = 1 day)
|
// Calculate days and remaining hours (8 hours = 1 day)
|
||||||
// Match backend formatTime logic from Re_Backend/src/utils/tatTimeUtils.ts
|
// Match backend formatTime logic from Re_Backend/src/utils/tatTimeUtils.ts
|
||||||
const days = Math.floor(hours / WORKING_HOURS_PER_DAY);
|
const days = Math.floor(hours / WORKING_HOURS_PER_DAY);
|
||||||
const remainingHrs = Math.floor(hours % WORKING_HOURS_PER_DAY);
|
const remainingHrs = Math.floor(hours % WORKING_HOURS_PER_DAY);
|
||||||
const minutes = Math.round((hours % 1) * 60);
|
const minutes = Math.round((hours % 1) * 60);
|
||||||
|
|
||||||
// If we have days, format with days (matching backend format)
|
// If we have days, format with days (matching backend format)
|
||||||
if (days > 0) {
|
if (days > 0) {
|
||||||
const dayLabel = days === 1 ? 'day' : 'days';
|
const dayLabel = days === 1 ? 'day' : 'days';
|
||||||
const hourLabel = remainingHrs === 1 ? 'hour' : 'hours';
|
const hourLabel = remainingHrs === 1 ? 'hour' : 'hours';
|
||||||
const minuteLabel = minutes === 1 ? 'min' : 'm';
|
const minuteLabel = minutes === 1 ? 'min' : 'm';
|
||||||
|
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
return `${days} ${dayLabel} ${remainingHrs} ${hourLabel} ${minutes}${minuteLabel}`;
|
return `${days} ${dayLabel} ${remainingHrs} ${hourLabel} ${minutes}${minuteLabel}`;
|
||||||
} else {
|
} else {
|
||||||
return `${days} ${dayLabel} ${remainingHrs} ${hourLabel}`;
|
return `${days} ${dayLabel} ${remainingHrs} ${hourLabel}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No days, just hours and minutes
|
// No days, just hours and minutes
|
||||||
const hourLabel = remainingHrs === 1 ? 'hour' : 'hours';
|
const hourLabel = remainingHrs === 1 ? 'hour' : 'hours';
|
||||||
const minuteLabel = minutes === 1 ? 'min' : 'm';
|
const minuteLabel = minutes === 1 ? 'min' : 'm';
|
||||||
|
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
return `${remainingHrs} ${hourLabel} ${minutes}${minuteLabel}`;
|
return `${remainingHrs} ${hourLabel} ${minutes}${minuteLabel}`;
|
||||||
} else {
|
} else {
|
||||||
@ -276,13 +275,13 @@ export function formatHoursMinutes(hours: number | null | undefined): string {
|
|||||||
export function formatWorkingHours(hours: number): string {
|
export function formatWorkingHours(hours: number): string {
|
||||||
if (hours === 0) return '0h';
|
if (hours === 0) return '0h';
|
||||||
if (hours < 0) return '0h';
|
if (hours < 0) return '0h';
|
||||||
|
|
||||||
const totalMinutes = Math.round(hours * 60);
|
const totalMinutes = Math.round(hours * 60);
|
||||||
const days = Math.floor(totalMinutes / (8 * 60)); // 8 working hours per day
|
const days = Math.floor(totalMinutes / (8 * 60)); // 8 working hours per day
|
||||||
const remainingMinutes = totalMinutes % (8 * 60);
|
const remainingMinutes = totalMinutes % (8 * 60);
|
||||||
const remainingHours = Math.floor(remainingMinutes / 60);
|
const remainingHours = Math.floor(remainingMinutes / 60);
|
||||||
const minutes = remainingMinutes % 60;
|
const minutes = remainingMinutes % 60;
|
||||||
|
|
||||||
if (days > 0 && remainingHours > 0 && minutes > 0) {
|
if (days > 0 && remainingHours > 0 && minutes > 0) {
|
||||||
return `${days}d ${remainingHours}h ${minutes}m`;
|
return `${days}d ${remainingHours}h ${minutes}m`;
|
||||||
} else if (days > 0 && remainingHours > 0) {
|
} else if (days > 0 && remainingHours > 0) {
|
||||||
@ -306,14 +305,14 @@ export function getTimeUntilNextWorking(priority: string = 'standard'): string {
|
|||||||
if (isWorkingTime(new Date(), priority)) {
|
if (isWorkingTime(new Date(), priority)) {
|
||||||
return 'In working hours';
|
return 'In working hours';
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const next = getNextWorkingTime(now, priority);
|
const next = getNextWorkingTime(now, priority);
|
||||||
const diff = next.getTime() - now.getTime();
|
const diff = next.getTime() - now.getTime();
|
||||||
|
|
||||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
|
||||||
if (hours > 24) {
|
if (hours > 24) {
|
||||||
const days = Math.floor(hours / 24);
|
const days = Math.floor(hours / 24);
|
||||||
return `Resumes in ${days}d ${hours % 24}h`;
|
return `Resumes in ${days}d ${hours % 24}h`;
|
||||||
|
|||||||
@ -57,14 +57,14 @@ export const cookieUtils = {
|
|||||||
*/
|
*/
|
||||||
clearAll(): void {
|
clearAll(): void {
|
||||||
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, ID_TOKEN_KEY, USER_DATA_KEY];
|
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, ID_TOKEN_KEY, USER_DATA_KEY];
|
||||||
|
|
||||||
cookieNames.forEach(name => {
|
cookieNames.forEach(name => {
|
||||||
// Remove with default path
|
// Remove with default path
|
||||||
this.remove(name);
|
this.remove(name);
|
||||||
|
|
||||||
// Remove with root path explicitly
|
// Remove with root path explicitly
|
||||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||||
|
|
||||||
// Remove with domain (if applicable)
|
// Remove with domain (if applicable)
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
|
if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
|
||||||
@ -75,82 +75,60 @@ export const cookieUtils = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Token Manager - Handles token storage and retrieval
|
|
||||||
*
|
|
||||||
* SECURITY MODES:
|
|
||||||
* - Production: Tokens stored in httpOnly cookies by backend only
|
|
||||||
* Frontend does NOT store access/refresh tokens anywhere
|
|
||||||
* All API requests rely on cookies being sent automatically
|
|
||||||
*
|
|
||||||
* - Development: Tokens stored in localStorage for debugging
|
|
||||||
* Needed because frontend/backend run on different ports
|
|
||||||
*/
|
|
||||||
export class TokenManager {
|
export class TokenManager {
|
||||||
/**
|
/**
|
||||||
* Store access token
|
* Store access token
|
||||||
* In production: No-op (backend handles via httpOnly cookies)
|
|
||||||
* In development: Store in localStorage for Authorization header
|
|
||||||
*/
|
*/
|
||||||
static setAccessToken(token: string): void {
|
static setAccessToken(token: string): void {
|
||||||
// SECURITY: In production, don't store tokens client-side
|
|
||||||
// Backend sets httpOnly cookies that are sent automatically
|
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
return; // No-op - rely on httpOnly cookies
|
return; // No-op - rely on httpOnly cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
// Development only: Store for debugging and cross-port requests
|
// Development only: Store for debugging and cross-port requests
|
||||||
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get access token
|
* Get access token
|
||||||
* In production: Returns null (cookies are sent automatically)
|
*
|
||||||
* In development: Returns from localStorage
|
|
||||||
*/
|
*/
|
||||||
static getAccessToken(): string | null {
|
static getAccessToken(): string | null {
|
||||||
// SECURITY: In production, return null - cookies are used instead
|
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
return null; // API calls use cookies via withCredentials: true
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Development: Return from localStorage
|
// Development: Return from localStorage
|
||||||
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store refresh token
|
* Store refresh token
|
||||||
* In production: No-op (backend handles via httpOnly cookies)
|
|
||||||
* In development: Store in localStorage
|
|
||||||
*/
|
*/
|
||||||
static setRefreshToken(token: string): void {
|
static setRefreshToken(token: string): void {
|
||||||
// SECURITY: In production, don't store tokens client-side
|
// SECURITY: In production, don't store tokens client-side
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
return; // No-op - rely on httpOnly cookies
|
return; // No-op - rely on httpOnly cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
// Development only
|
// Development only
|
||||||
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get refresh token
|
* Get refresh token
|
||||||
* In production: Returns null (cookies are used)
|
|
||||||
* In development: Returns from localStorage
|
|
||||||
*/
|
*/
|
||||||
static getRefreshToken(): string | null {
|
static getRefreshToken(): string | null {
|
||||||
// SECURITY: In production, return null - backend reads from cookie
|
// SECURITY: In production, return null - backend reads from cookie
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store ID token (from Okta) - needed for logout
|
|
||||||
* Stored in sessionStorage (cleared when tab closes)
|
|
||||||
*/
|
|
||||||
static setIdToken(token: string): void {
|
static setIdToken(token: string): void {
|
||||||
// ID token is needed for Okta logout, use sessionStorage (more secure than localStorage)
|
// ID token is needed for Okta logout, use sessionStorage (more secure than localStorage)
|
||||||
sessionStorage.setItem(ID_TOKEN_KEY, token);
|
sessionStorage.setItem(ID_TOKEN_KEY, token);
|
||||||
@ -183,18 +161,7 @@ export class TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all tokens and user data
|
|
||||||
*
|
|
||||||
* PRODUCTION MODE:
|
|
||||||
* - Clears user data from localStorage
|
|
||||||
* - Clears ID token from sessionStorage
|
|
||||||
* - Backend logout endpoint clears httpOnly cookies
|
|
||||||
*
|
|
||||||
* DEVELOPMENT MODE:
|
|
||||||
* - Clears all localStorage and sessionStorage
|
|
||||||
* - Clears client-side cookies
|
|
||||||
*/
|
|
||||||
static clearAll(): void {
|
static clearAll(): void {
|
||||||
// CRITICAL: Set logout flag in sessionStorage FIRST (before clearing)
|
// CRITICAL: Set logout flag in sessionStorage FIRST (before clearing)
|
||||||
// This flag survives the redirect and prevents auto-authentication
|
// This flag survives the redirect and prevents auto-authentication
|
||||||
@ -204,7 +171,7 @@ export class TokenManager {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Could not set logout flags:', e);
|
console.warn('Could not set logout flags:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear user data (stored in both modes)
|
// Clear user data (stored in both modes)
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(USER_DATA_KEY);
|
localStorage.removeItem(USER_DATA_KEY);
|
||||||
@ -212,7 +179,7 @@ export class TokenManager {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error clearing user data:', e);
|
console.warn('Error clearing user data:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In production, httpOnly cookies are cleared by backend
|
// In production, httpOnly cookies are cleared by backend
|
||||||
// Only need to clear user data above
|
// Only need to clear user data above
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
@ -225,7 +192,7 @@ export class TokenManager {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEVELOPMENT MODE: Clear everything
|
// DEVELOPMENT MODE: Clear everything
|
||||||
const authKeys = [
|
const authKeys = [
|
||||||
ACCESS_TOKEN_KEY,
|
ACCESS_TOKEN_KEY,
|
||||||
@ -246,7 +213,7 @@ export class TokenManager {
|
|||||||
'persist:auth',
|
'persist:auth',
|
||||||
'redux-persist',
|
'redux-persist',
|
||||||
];
|
];
|
||||||
|
|
||||||
authKeys.forEach(key => {
|
authKeys.forEach(key => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
@ -255,14 +222,14 @@ export class TokenManager {
|
|||||||
console.warn(`Error removing ${key}:`, e);
|
console.warn(`Error removing ${key}:`, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear ALL localStorage
|
// Clear ALL localStorage
|
||||||
try {
|
try {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error clearing localStorage:', e);
|
console.error('Error clearing localStorage:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear ALL sessionStorage except logout flags
|
// Clear ALL sessionStorage except logout flags
|
||||||
try {
|
try {
|
||||||
const keysToKeep = ['__logout_in_progress__', '__force_logout__'];
|
const keysToKeep = ['__logout_in_progress__', '__force_logout__'];
|
||||||
@ -277,7 +244,7 @@ export class TokenManager {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error clearing sessionStorage:', e);
|
console.error('Error clearing sessionStorage:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear client-side cookies (development only)
|
// Clear client-side cookies (development only)
|
||||||
cookieUtils.clearAll();
|
cookieUtils.clearAll();
|
||||||
}
|
}
|
||||||
@ -296,11 +263,7 @@ export class TokenManager {
|
|||||||
return !!this.getAccessToken();
|
return !!this.getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if refresh token exists
|
|
||||||
* In production: Always returns true if user data exists
|
|
||||||
* In development: Checks localStorage
|
|
||||||
*/
|
|
||||||
static hasRefreshToken(): boolean {
|
static hasRefreshToken(): boolean {
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
return !!this.getUserData();
|
return !!this.getUserData();
|
||||||
@ -318,7 +281,7 @@ export class TokenManager {
|
|||||||
window.location.hostname === ''
|
window.location.hostname === ''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if we're in production mode
|
* Check if we're in production mode
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user