fix: handle expired JWT/token by forcing logout and redirecting to login view with expired message

This commit is contained in:
2026-05-28 08:13:02 +07:00
parent 29308d756a
commit c2a5857f62
3 changed files with 58 additions and 8 deletions
+9 -2
View File
@@ -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>
+34 -4
View File
@@ -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} />
)} )}
{/* ---------------------------------------------------- */} {/* ---------------------------------------------------- */}
+14 -1
View File
@@ -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();