fix: handle expired JWT/token by forcing logout and redirecting to login view with expired message
This commit is contained in:
@@ -108,12 +108,18 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = (message = '') => {
|
||||||
setToken('');
|
setToken('');
|
||||||
setUsername('');
|
setUsername('');
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem('username');
|
localStorage.removeItem('username');
|
||||||
|
if (message) {
|
||||||
|
setLoginError(message);
|
||||||
|
setActiveView('login');
|
||||||
|
} else {
|
||||||
|
setLoginError('');
|
||||||
setActiveView('directory');
|
setActiveView('directory');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Client-side search and filtering
|
// Client-side search and filtering
|
||||||
@@ -199,7 +205,7 @@ const App = () => {
|
|||||||
Справочник
|
Справочник
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className="btn btn-secondary" onClick={() => setActiveView('login')} style={{ display: 'flex', gap: '0.4rem' }}>
|
<button className="btn btn-secondary" onClick={() => { setLoginError(''); setActiveView('login'); }} style={{ display: 'flex', gap: '0.4rem' }}>
|
||||||
<LoginIcon style={{ width: '1.1rem', height: '1.1rem' }} /> Войти
|
<LoginIcon style={{ width: '1.1rem', height: '1.1rem' }} /> Войти
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -290,6 +296,7 @@ const App = () => {
|
|||||||
departments={departments}
|
departments={departments}
|
||||||
companies={companies}
|
companies={companies}
|
||||||
onRefreshData={fetchData}
|
onRefreshData={fetchData}
|
||||||
|
onLogout={handleLogout}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import { PlusIcon, EditIcon, DeleteIcon, CloseIcon, PhoneIcon, EmailIcon } from '../utils/svgs';
|
import { PlusIcon, EditIcon, DeleteIcon, CloseIcon, PhoneIcon, EmailIcon } from '../utils/svgs';
|
||||||
import ImportExport from './ImportExport';
|
import ImportExport from './ImportExport';
|
||||||
|
|
||||||
const AdminPanel = ({ token, employees, departments, companies, onRefreshData }) => {
|
const AdminPanel = ({ token, employees, departments, companies, onRefreshData, onLogout }) => {
|
||||||
const [activeTab, setActiveTab] = useState('employees'); // 'employees', 'departments', 'companies', 'import-export'
|
const [activeTab, setActiveTab] = useState('employees'); // 'employees', 'departments', 'companies', 'import-export'
|
||||||
const [showEmpModal, setShowEmpModal] = useState(false);
|
const [showEmpModal, setShowEmpModal] = useState(false);
|
||||||
const [editingEmp, setEditingEmp] = useState(null);
|
const [editingEmp, setEditingEmp] = useState(null);
|
||||||
@@ -57,6 +57,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ name: editingCompanyName.trim() })
|
body: JSON.stringify({ name: editingCompanyName.trim() })
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -84,6 +88,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ name: editingDeptName.trim() })
|
body: JSON.stringify({ name: editingDeptName.trim() })
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -115,6 +123,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ name: newDeptName.trim() })
|
body: JSON.stringify({ name: newDeptName.trim() })
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -139,7 +151,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
onRefreshData();
|
onRefreshData();
|
||||||
showAlert('success', 'Подразделение удалено!');
|
showAlert('success', 'Подразделение удалено!');
|
||||||
@@ -167,6 +182,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ name: newCompanyName.trim() })
|
body: JSON.stringify({ name: newCompanyName.trim() })
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -191,7 +210,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
onRefreshData();
|
onRefreshData();
|
||||||
showAlert('success', 'Компания удалена!');
|
showAlert('success', 'Компания удалена!');
|
||||||
@@ -268,6 +290,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(employeePayload)
|
body: JSON.stringify(employeePayload)
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -292,6 +318,10 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
onRefreshData();
|
onRefreshData();
|
||||||
@@ -705,7 +735,7 @@ const AdminPanel = ({ token, employees, departments, companies, onRefreshData })
|
|||||||
{/* Tab: Import/Export */}
|
{/* Tab: Import/Export */}
|
||||||
{/* ---------------------------------------------------- */}
|
{/* ---------------------------------------------------- */}
|
||||||
{activeTab === 'import-export' && (
|
{activeTab === 'import-export' && (
|
||||||
<ImportExport token={token} onRefreshData={onRefreshData} />
|
<ImportExport token={token} onRefreshData={onRefreshData} onLogout={onLogout} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ---------------------------------------------------- */}
|
{/* ---------------------------------------------------- */}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { DownloadIcon, UploadIcon } from '../utils/svgs';
|
import { DownloadIcon, UploadIcon } from '../utils/svgs';
|
||||||
|
|
||||||
const ImportExport = ({ token, onRefreshData }) => {
|
const ImportExport = ({ token, onRefreshData, onLogout }) => {
|
||||||
const [dragActive, setDragActive] = useState(false);
|
const [dragActive, setDragActive] = useState(false);
|
||||||
const [status, setStatus] = useState({ type: '', message: '' });
|
const [status, setStatus] = useState({ type: '', message: '' });
|
||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
@@ -130,6 +130,11 @@ const ImportExport = ({ token, onRefreshData }) => {
|
|||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setStatus({ type: 'success', message: `Импорт завершен! Загружено компаний: ${payload.companies.length}, отделов: ${payload.departments.length}, сотрудников: ${payload.employees.length}` });
|
setStatus({ type: 'success', message: `Импорт завершен! Загружено компаний: ${payload.companies.length}, отделов: ${payload.departments.length}, сотрудников: ${payload.employees.length}` });
|
||||||
@@ -151,6 +156,10 @@ const ImportExport = ({ token, onRefreshData }) => {
|
|||||||
const res = await fetch('/api/admin/export', {
|
const res = await fetch('/api/admin/export', {
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!res.ok) throw new Error('Ошибка экспорта данных с сервера');
|
if (!res.ok) throw new Error('Ошибка экспорта данных с сервера');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
@@ -173,6 +182,10 @@ const ImportExport = ({ token, onRefreshData }) => {
|
|||||||
const res = await fetch('/api/admin/export', {
|
const res = await fetch('/api/admin/export', {
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
if (res.status === 401 || res.status === 403) {
|
||||||
|
onLogout('Сессия истекла, войдите заново');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!res.ok) throw new Error('Ошибка экспорта данных с сервера');
|
if (!res.ok) throw new Error('Ошибка экспорта данных с сервера');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user