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