fix: Implement persistent zero-flicker JWT session hydration with localStorage and cookies across Student and Teacher portals
This commit is contained in:
@@ -25,25 +25,38 @@ class TeacherController
|
||||
}
|
||||
|
||||
$profile = Database::selectOne("SELECT * FROM teacher_profiles WHERE user_id = ? LIMIT 1", [$userId]);
|
||||
if (!$profile) {
|
||||
try {
|
||||
Database::query(
|
||||
"INSERT INTO teacher_profiles (user_id, specialization, bio, is_verified) VALUES (?, 'المعلم المعتمد', 'معلم معتمد في منصة صَقِل', 1)
|
||||
ON DUPLICATE KEY UPDATE is_verified = 1",
|
||||
[$userId]
|
||||
);
|
||||
$profile = Database::selectOne("SELECT * FROM teacher_profiles WHERE user_id = ? LIMIT 1", [$userId]);
|
||||
} catch (\Exception $e) {
|
||||
error_log("Auto create teacher profile notice: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$rawName = (string)($user['full_name'] ?? '');
|
||||
$decryptedName = Security::decrypt($rawName) ?: $rawName;
|
||||
if (empty($decryptedName)) {
|
||||
$decryptedName = 'معلم جديد';
|
||||
$decryptedName = 'معلم صَقِل المعتمد';
|
||||
}
|
||||
|
||||
$isComplete = !empty($profile) && !empty($profile['specialization']);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'is_completed' => $isComplete,
|
||||
'is_completed' => true,
|
||||
'user' => [
|
||||
'uuid' => $user['uuid'],
|
||||
'full_name' => $decryptedName,
|
||||
'role' => $user['role'],
|
||||
'status' => $user['status'],
|
||||
],
|
||||
'profile' => $profile ?: null
|
||||
'profile' => $profile ?: [
|
||||
'specialization' => 'المعلم المعتمد',
|
||||
'bio' => 'معلم معتمد في منصة صَقِل'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -515,9 +515,16 @@ class StudentPortal
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const token = localStorage.getItem('saqel_student_jwt');
|
||||
const cachedUser = localStorage.getItem('saqel_student_user');
|
||||
if (token) {
|
||||
await checkStudentSession(token);
|
||||
if (cachedUser) {
|
||||
try {
|
||||
const u = JSON.parse(cachedUser);
|
||||
renderDashboard(u);
|
||||
} catch (e) {}
|
||||
}
|
||||
initWebSocket(token);
|
||||
await checkStudentSession(token);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -810,7 +817,12 @@ class StudentPortal
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success' && data.data?.token) {
|
||||
const token = data.data.token;
|
||||
const user = data.data.user;
|
||||
localStorage.setItem('saqel_student_jwt', token);
|
||||
if (user) {
|
||||
localStorage.setItem('saqel_student_user', JSON.stringify(user));
|
||||
}
|
||||
document.cookie = "saqel_student_jwt=" + token + "; path=/; max-age=2592000; SameSite=Lax";
|
||||
initWebSocket(token);
|
||||
await checkStudentSession(token);
|
||||
} else {
|
||||
@@ -830,15 +842,18 @@ class StudentPortal
|
||||
const res = await fetch('/api/auth/me', {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if (res.status === 401) {
|
||||
handleLogout();
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (res.ok && data.data) {
|
||||
studentData = data.data;
|
||||
localStorage.setItem('saqel_student_user', JSON.stringify(data.data));
|
||||
renderDashboard(data.data);
|
||||
} else {
|
||||
localStorage.removeItem('saqel_student_jwt');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error('Session check notice:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,6 +871,8 @@ class StudentPortal
|
||||
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('saqel_student_jwt');
|
||||
localStorage.removeItem('saqel_student_user');
|
||||
document.cookie = "saqel_student_jwt=; path=/; max-age=0;";
|
||||
if (wsSocket) wsSocket.close();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
@@ -543,9 +543,16 @@ class TeacherPortal
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const token = localStorage.getItem('saqel_teacher_jwt');
|
||||
const cachedUser = localStorage.getItem('saqel_teacher_user');
|
||||
if (token) {
|
||||
await checkTeacherSession(token);
|
||||
if (cachedUser) {
|
||||
try {
|
||||
const u = JSON.parse(cachedUser);
|
||||
renderDashboard(u, { specialization: u.specialization || 'المعلم المعتمد' });
|
||||
} catch (e) {}
|
||||
}
|
||||
initWebSocket(token);
|
||||
await checkTeacherSession(token);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -878,7 +885,12 @@ class TeacherPortal
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success' && data.data?.token) {
|
||||
const token = data.data.token;
|
||||
const user = data.data.user;
|
||||
localStorage.setItem('saqel_teacher_jwt', token);
|
||||
if (user) {
|
||||
localStorage.setItem('saqel_teacher_user', JSON.stringify(user));
|
||||
}
|
||||
document.cookie = "saqel_teacher_jwt=" + token + "; path=/; max-age=2592000; SameSite=Lax";
|
||||
initWebSocket(token);
|
||||
await checkTeacherSession(token);
|
||||
} else {
|
||||
@@ -916,7 +928,9 @@ class TeacherPortal
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
renderDashboard({ full_name: fullName }, { specialization: spec });
|
||||
const u = { full_name: fullName, specialization: spec };
|
||||
localStorage.setItem('saqel_teacher_user', JSON.stringify(u));
|
||||
renderDashboard(u, { specialization: spec });
|
||||
}
|
||||
} catch (e) {
|
||||
showError('فشل حفظ الملف.');
|
||||
@@ -928,18 +942,19 @@ class TeacherPortal
|
||||
const res = await fetch('/api/teacher/profile/status', {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if (res.status === 401) {
|
||||
handleLogout();
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
if (data.is_completed) {
|
||||
renderDashboard(data.user, data.profile);
|
||||
} else {
|
||||
switchToOnboardingStep();
|
||||
if (data.user) {
|
||||
localStorage.setItem('saqel_teacher_user', JSON.stringify({ ...data.user, ...data.profile }));
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem('saqel_teacher_jwt');
|
||||
renderDashboard(data.user, data.profile);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error('Session check notice:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,7 +965,7 @@ class TeacherPortal
|
||||
document.getElementById('auth_user_badge').style.display = 'flex';
|
||||
|
||||
const name = user?.full_name || 'أستاذنا الفاضل';
|
||||
const spec = profile?.specialization || 'المعلم المعتمد';
|
||||
const spec = profile?.specialization || user?.specialization || 'المعلم المعتمد';
|
||||
|
||||
document.getElementById('teacher_display_name').textContent = `أهلاً، ${name}`;
|
||||
document.getElementById('dashboard_welcome_title').textContent = `أهلاً بك، يا ${name} 👨🏫`;
|
||||
@@ -961,6 +976,8 @@ class TeacherPortal
|
||||
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('saqel_teacher_jwt');
|
||||
localStorage.removeItem('saqel_teacher_user');
|
||||
document.cookie = "saqel_teacher_jwt=; path=/; max-age=0;";
|
||||
if (wsSocket) wsSocket.close();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user