main.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. const API_BASE_URL = '/api';
  2. let currentUser = null;
  3. let resetToken = null;
  4. // Theme management
  5. function initializeTheme() {
  6. const savedTheme = localStorage.getItem('theme') || 'light';
  7. document.documentElement.setAttribute('data-theme', savedTheme);
  8. }
  9. function toggleTheme() {
  10. const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
  11. const newTheme = currentTheme === 'light' ? 'dark' : 'light';
  12. document.documentElement.setAttribute('data-theme', newTheme);
  13. localStorage.setItem('theme', newTheme);
  14. }
  15. function parseURLParams() {
  16. const urlParams = new URLSearchParams(window.location.search);
  17. const hasResetFlag = urlParams.has('reset-password');
  18. const token = urlParams.get('token');
  19. return {
  20. isResetMode: hasResetFlag && token,
  21. resetToken: token
  22. };
  23. }
  24. document.addEventListener('DOMContentLoaded', function() {
  25. // Initialize theme before anything else
  26. initializeTheme();
  27. // Ensure user section is hidden by default
  28. const userSection = document.getElementById('userSection');
  29. if (userSection) {
  30. userSection.classList.remove('active');
  31. userSection.style.display = 'none';
  32. }
  33. const urlInfo = parseURLParams();
  34. if (urlInfo.isResetMode) {
  35. resetToken = urlInfo.resetToken;
  36. showResetPasswordSection();
  37. return;
  38. }
  39. const token = localStorage.getItem('authToken');
  40. const userData = localStorage.getItem('userData');
  41. if (token && userData) {
  42. try {
  43. currentUser = JSON.parse(userData);
  44. // Only show user section if we have valid user data
  45. if (currentUser && currentUser.username) {
  46. showUserSection();
  47. } else {
  48. throw new Error('Invalid user data structure');
  49. }
  50. } catch (e) {
  51. console.error('Invalid user data in localStorage', e);
  52. localStorage.removeItem('authToken');
  53. localStorage.removeItem('userData');
  54. currentUser = null;
  55. // Make sure user section stays hidden
  56. if (userSection) {
  57. userSection.classList.remove('active');
  58. userSection.style.display = 'none';
  59. }
  60. }
  61. } else {
  62. // No auth data, make sure user section is hidden
  63. currentUser = null;
  64. if (userSection) {
  65. userSection.classList.remove('active');
  66. userSection.style.display = 'none';
  67. }
  68. }
  69. const forgotPasswordLink = document.getElementById('forgotPasswordLink');
  70. if (forgotPasswordLink) {
  71. forgotPasswordLink.addEventListener('click', handleForgotPassword);
  72. }
  73. });
  74. function switchTab(tab) {
  75. document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
  76. document.querySelector(`.tab:nth-child(${tab === 'login' ? '1' : '2'})`).classList.add('active');
  77. document.querySelectorAll('.form').forEach(f => f.classList.remove('active'));
  78. document.getElementById(tab + 'Form').classList.add('active');
  79. clearMessages();
  80. }
  81. function clearMessages() {
  82. document.querySelectorAll('.message').forEach(msg => {
  83. msg.style.display = 'none';
  84. msg.textContent = '';
  85. });
  86. }
  87. function showMessage(elementId, message, type) {
  88. const element = document.getElementById(elementId);
  89. element.textContent = message;
  90. element.className = `message ${type}`;
  91. element.style.display = 'block';
  92. }
  93. function setButtonLoading(buttonId, loading) {
  94. const button = document.getElementById(buttonId);
  95. if (loading) {
  96. button.innerHTML = '<span class="loading"></span>Processing...';
  97. button.disabled = true;
  98. } else {
  99. if (buttonId.includes('login')) {
  100. button.innerHTML = 'Sign In';
  101. } else if (buttonId.includes('register')) {
  102. button.innerHTML = 'Create Account';
  103. } else if (buttonId.includes('editProfile')) {
  104. button.innerHTML = 'Update Profile';
  105. } else if (buttonId.includes('resetPassword')) {
  106. button.innerHTML = 'Reset Password';
  107. }
  108. button.disabled = false;
  109. }
  110. }
  111. // Only add event listeners if elements exist (for index.html)
  112. const loginForm = document.getElementById('loginForm');
  113. if (loginForm) {
  114. loginForm.addEventListener('submit', async function(e) {
  115. e.preventDefault();
  116. const username = document.getElementById('loginUsername').value;
  117. const password = document.getElementById('loginPassword').value;
  118. setButtonLoading('loginBtn', true);
  119. clearMessages();
  120. try {
  121. const response = await fetch(`${API_BASE_URL}/authenticate`, {
  122. method: 'POST',
  123. headers: {
  124. 'Content-Type': 'application/json'
  125. },
  126. body: JSON.stringify({ username, password })
  127. });
  128. if (response.ok) {
  129. const data = await response.json();
  130. localStorage.setItem('authToken', data.token);
  131. localStorage.setItem('userData', JSON.stringify(data));
  132. currentUser = data;
  133. setButtonLoading('loginBtn', false);
  134. showUserSection();
  135. } else {
  136. try {
  137. const errorData = await response.json();
  138. const errorMessage = errorData.message || 'Login failed. Please check your credentials.';
  139. showMessage('loginMessage', errorMessage, 'error');
  140. } catch (parseError) {
  141. // If response is not JSON, fall back to text
  142. const errorText = await response.text();
  143. showMessage('loginMessage', errorText || 'Login failed. Please check your credentials.', 'error');
  144. }
  145. }
  146. } catch (error) {
  147. console.error('Login error:', error);
  148. showMessage('loginMessage', 'Network error. Please check if the auth service is running.', 'error');
  149. } finally {
  150. setButtonLoading('loginBtn', false);
  151. }
  152. });
  153. }
  154. const registerForm = document.getElementById('registerForm');
  155. if (registerForm) {
  156. registerForm.addEventListener('submit', async function(e) {
  157. e.preventDefault();
  158. const username = document.getElementById('registerUsername').value;
  159. const password = document.getElementById('registerPassword').value;
  160. const email = document.getElementById('registerEmail').value;
  161. if (username.length < 4) {
  162. showMessage('registerMessage', "Username must be at least 4 characters long.", 'error');
  163. return;
  164. }
  165. if (password.length < 4) {
  166. showMessage('registerMessage', "Password must be at least 4 characters long.", 'error');
  167. return;
  168. }
  169. setButtonLoading('registerBtn', true);
  170. clearMessages();
  171. try {
  172. const response = await fetch(`${API_BASE_URL}/register`, {
  173. method: 'POST',
  174. headers: {
  175. 'Content-Type': 'application/json'
  176. },
  177. body: JSON.stringify({ username, email, password })
  178. });
  179. if (response.ok) {
  180. const data = await response.json();
  181. localStorage.setItem('authToken', data.token);
  182. localStorage.setItem('userData', JSON.stringify(data));
  183. currentUser = data;
  184. setButtonLoading('registerBtn', false);
  185. showUserSection();
  186. } else {
  187. try {
  188. const errorData = await response.json();
  189. const errorMessage = errorData.message || 'Registration failed. Please try again.';
  190. showMessage('registerMessage', errorMessage, 'error');
  191. } catch (parseError) {
  192. const errorText = await response.text();
  193. showMessage('registerMessage', errorText || 'Registration failed. Please try again.', 'error');
  194. }
  195. }
  196. } catch (error) {
  197. console.error('Registration error:', error);
  198. showMessage('registerMessage', 'Network error. Please check if the auth service is running.', 'error');
  199. } finally {
  200. setButtonLoading('registerBtn', false);
  201. }
  202. });
  203. }
  204. const resetPasswordForm = document.getElementById('resetPasswordForm');
  205. if (resetPasswordForm) {
  206. resetPasswordForm.addEventListener('submit', async function(e) {
  207. e.preventDefault();
  208. const newPassword = document.getElementById('resetNewPassword').value;
  209. const confirmPassword = document.getElementById('resetConfirmPassword').value;
  210. if (newPassword !== confirmPassword) {
  211. showMessage('resetPasswordMessage', 'New passwords do not match.', 'error');
  212. return;
  213. }
  214. if (newPassword.length < 4) {
  215. showMessage('resetPasswordMessage', 'New password must be at least 4 characters long.', 'error');
  216. return;
  217. }
  218. setButtonLoading('resetPasswordBtn', true);
  219. clearResetPasswordMessages();
  220. try {
  221. const response = await fetch(`${API_BASE_URL}/users/reset-password`, {
  222. method: 'POST',
  223. headers: {
  224. 'Content-Type': 'application/json',
  225. 'Authorization': 'Bearer ' + resetToken
  226. },
  227. body: JSON.stringify({
  228. newPassword: newPassword
  229. })
  230. });
  231. if (response.ok) {
  232. showMessage('resetPasswordMessage', 'Password reset successfully!', 'success');
  233. document.getElementById('resetPasswordForm').reset();
  234. setTimeout(() => {
  235. window.location.href = window.location.pathname;
  236. }, 1000);
  237. } else {
  238. try {
  239. const errorData = await response.json();
  240. const errorMessage = errorData.message || 'Failed to reset password. Please try again.';
  241. showMessage('resetPasswordMessage', errorMessage, 'error');
  242. } catch (parseError) {
  243. // If response is not JSON, fall back to text
  244. const errorText = await response.text();
  245. showMessage('resetPasswordMessage', errorText || 'Failed to reset password. Please try again.', 'error');
  246. }
  247. }
  248. } catch (error) {
  249. console.error('Reset password error:', error);
  250. showMessage('resetPasswordMessage', 'Network error. Please check if the auth service is running.', 'error');
  251. } finally {
  252. setButtonLoading('resetPasswordBtn', false);
  253. }
  254. });
  255. }
  256. const editProfileForm = document.getElementById('editProfileForm');
  257. if (editProfileForm) {
  258. editProfileForm.addEventListener('submit', async function(e) {
  259. e.preventDefault();
  260. const currentPassword = document.getElementById('currentPassword').value;
  261. const newPassword = document.getElementById('newPassword').value;
  262. const confirmPassword = document.getElementById('confirmPassword').value;
  263. const email = document.getElementById('editEmail').value;
  264. // Validate that at least one field is being updated
  265. const isPasswordChange = newPassword.trim() !== '';
  266. const currentEmail = currentUser.email || '';
  267. const newEmail = email.trim();
  268. const isEmailChange = newEmail !== currentEmail;
  269. if (!isPasswordChange && !isEmailChange) {
  270. showMessage('editProfileMessage', 'Please make at least one change: update your email address or change your password.', 'error');
  271. return;
  272. }
  273. if (isPasswordChange && newPassword !== confirmPassword) {
  274. showMessage('editProfileMessage', 'New passwords do not match.', 'error');
  275. return;
  276. }
  277. if (isPasswordChange && newPassword.length < 4) {
  278. showMessage('editProfileMessage', 'New password must be at least 4 characters long.', 'error');
  279. return;
  280. }
  281. setButtonLoading('editProfileBtn', true);
  282. clearEditProfileMessages();
  283. try {
  284. const requestBody = { currentPassword };
  285. if (isPasswordChange) {
  286. requestBody.newPassword = newPassword;
  287. }
  288. if (isEmailChange) {
  289. requestBody.email = newEmail;
  290. }
  291. const response = await fetch(`${API_BASE_URL}/users/update-profile`, {
  292. method: 'POST',
  293. headers: {
  294. 'Content-Type': 'application/json',
  295. 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
  296. },
  297. body: JSON.stringify(requestBody)
  298. });
  299. if (response.ok) {
  300. const data = await response.json();
  301. let message = '';
  302. if (isPasswordChange && isEmailChange) {
  303. message = 'Email and password updated successfully! Please log in with your new password.';
  304. document.getElementById('editProfileForm').reset();
  305. setTimeout(() => logout(), 1500);
  306. } else if (isPasswordChange) {
  307. message = 'Password updated successfully! Please log in with your new password.';
  308. document.getElementById('editProfileForm').reset();
  309. setTimeout(() => logout(), 1500);
  310. } else if (isEmailChange) {
  311. message = 'Email updated successfully!';
  312. // Update the stored user data with new email
  313. currentUser.email = newEmail;
  314. localStorage.setItem('userData', JSON.stringify(currentUser));
  315. updateUserDisplay(); // Refresh the displayed email
  316. document.getElementById('editProfileForm').reset();
  317. setTimeout(() => hideEditProfileForm(), 1000);
  318. }
  319. showMessage('editProfileMessage', message, 'success');
  320. } else {
  321. try {
  322. const errorData = await response.json();
  323. const errorMessage = errorData.message || 'Failed to update profile. Please try again.';
  324. showMessage('editProfileMessage', errorMessage, 'error');
  325. } catch (parseError) {
  326. // If response is not JSON, fall back to text
  327. const errorText = await response.text();
  328. showMessage('editProfileMessage', errorText || 'Failed to update profile. Please try again.', 'error');
  329. }
  330. }
  331. } catch (error) {
  332. console.error('Update profile error:', error);
  333. showMessage('editProfileMessage', 'Network error. Please check if the auth service is running.', 'error');
  334. } finally {
  335. setButtonLoading('editProfileBtn', false);
  336. }
  337. });
  338. }
  339. function clearEditProfileMessages() {
  340. const messageElement = document.getElementById('editProfileMessage');
  341. messageElement.style.display = 'none';
  342. messageElement.textContent = '';
  343. }
  344. function clearResetPasswordMessages() {
  345. const messageElement = document.getElementById('resetPasswordMessage');
  346. messageElement.style.display = 'none';
  347. messageElement.textContent = '';
  348. }
  349. function updateUserDisplay() {
  350. if (!currentUser) {
  351. console.log('updateUserDisplay called but no currentUser');
  352. return;
  353. }
  354. const usernameEl = document.getElementById('currentUsername');
  355. const emailEl = document.getElementById('currentEmail');
  356. const userIdEl = document.getElementById('currentUserId');
  357. const rolesEl = document.getElementById('currentRoles');
  358. if (usernameEl) usernameEl.textContent = currentUser.username || '';
  359. if (emailEl) emailEl.textContent = maskUserEmail(currentUser.email) || '-';
  360. if (userIdEl) userIdEl.textContent = maskUserId(currentUser.id) || '';
  361. if (rolesEl) rolesEl.textContent = (currentUser.roles || []).join(', ');
  362. // Show/hide admin panel button based on user role
  363. updateAdminPanelVisibility();
  364. }
  365. function showUserSection() {
  366. const authSection = document.getElementById('authSection');
  367. const userSection = document.getElementById('userSection');
  368. const header = document.querySelector('.header');
  369. if (authSection) authSection.style.display = 'none';
  370. if (userSection) {
  371. userSection.classList.add('active');
  372. userSection.style.display = 'block';
  373. }
  374. if (header) header.style.display = 'block';
  375. updateUserDisplay();
  376. }
  377. function showResetPasswordSection() {
  378. document.getElementById('authSection').style.display = 'none';
  379. document.getElementById('userSection').classList.remove('active');
  380. document.getElementById('resetPasswordSection').style.display = 'block';
  381. document.querySelector('.header').style.display = 'none';
  382. document.getElementById('resetNewPassword').focus();
  383. }
  384. function maskUserId(id) {
  385. const parts = id.split("-");
  386. return parts[0] + "-****-" + parts[4];
  387. }
  388. function maskUserEmail(email) {
  389. if (!email.includes("@")) {
  390. return email;
  391. }
  392. const [user, domain] = email.split("@");
  393. if (user.length <= 2) {
  394. return user[0] + "*@" + domain;
  395. }
  396. const first = user[0];
  397. const last = user[user.length - 1];
  398. const maskedPart = "*".repeat(user.length - 2);
  399. return `${first}${maskedPart}${last}@${domain}`;
  400. }
  401. function logout() {
  402. localStorage.removeItem('authToken');
  403. localStorage.removeItem('userData');
  404. currentUser = null;
  405. const userSection = document.getElementById('userSection');
  406. const authSection = document.getElementById('authSection');
  407. const header = document.querySelector('.header');
  408. const loginForm = document.getElementById('loginForm');
  409. const registerForm = document.getElementById('registerForm');
  410. if (userSection) {
  411. userSection.classList.remove('active');
  412. userSection.style.display = 'none';
  413. }
  414. if (authSection) authSection.style.display = 'block';
  415. if (header) header.style.display = 'block';
  416. if (loginForm) loginForm.reset();
  417. if (registerForm) registerForm.reset();
  418. hideEditProfileForm();
  419. clearMessages();
  420. switchTab('login');
  421. }
  422. function toggleEditProfileForm() {
  423. const formSection = document.getElementById('editProfileSection');
  424. const isVisible = formSection.style.display === 'block';
  425. if (isVisible) {
  426. hideEditProfileForm();
  427. } else {
  428. showEditProfileForm();
  429. }
  430. }
  431. function showEditProfileForm() {
  432. const formSection = document.getElementById('editProfileSection');
  433. const actionBtn = document.getElementById('showEditProfileBtn');
  434. if (formSection) formSection.style.display = 'block';
  435. // Add active styling to button
  436. if (actionBtn) actionBtn.classList.add('active');
  437. // Clear any previous messages
  438. clearEditProfileMessages();
  439. // Pre-populate email field with current user's email
  440. if (currentUser) {
  441. const editEmail = document.getElementById('editEmail');
  442. if (editEmail) editEmail.value = currentUser.email || '';
  443. }
  444. // Focus on email input first since that's the most common change
  445. setTimeout(() => {
  446. const editEmail = document.getElementById('editEmail');
  447. if (editEmail) editEmail.focus();
  448. }, 100);
  449. }
  450. function hideEditProfileForm() {
  451. const formSection = document.getElementById('editProfileSection');
  452. const actionBtn = document.getElementById('showEditProfileBtn');
  453. if (formSection) formSection.style.display = 'none';
  454. // Remove active styling from button
  455. if (actionBtn) actionBtn.classList.remove('active');
  456. // Clear form and messages
  457. const editForm = document.getElementById('editProfileForm');
  458. if (editForm) editForm.reset();
  459. clearEditProfileMessages();
  460. }
  461. // Admin panel functions
  462. function hasAdminRole() {
  463. return currentUser && Array.isArray(currentUser.roles) && currentUser.roles.includes('ADMIN');
  464. }
  465. function updateAdminPanelVisibility() {
  466. const adminBtn = document.getElementById('adminPanelBtn');
  467. if (adminBtn) {
  468. adminBtn.style.display = hasAdminRole() ? 'block' : 'none';
  469. }
  470. }
  471. function openAdminPanel() {
  472. if (!hasAdminRole()) {
  473. alert('Access denied. Admin privileges required.');
  474. return;
  475. }
  476. window.location.href = 'admin.html';
  477. }
  478. async function handleForgotPassword(e) {
  479. e.preventDefault();
  480. const username = document.getElementById('loginUsername').value.trim();
  481. if (!username) {
  482. showMessage('loginMessage', 'Please enter your username above and then click "Forgot password" again.', 'error');
  483. document.getElementById('loginUsername').focus();
  484. return;
  485. }
  486. clearMessages();
  487. const link = document.getElementById('forgotPasswordLink');
  488. const originalText = link.textContent;
  489. link.textContent = 'Sending email...';
  490. link.style.pointerEvents = 'none';
  491. try {
  492. const response = await fetch(`${API_BASE_URL}/forgot-password?username=${encodeURIComponent(username)}`, {
  493. method: 'POST',
  494. headers: {
  495. 'Content-Type': 'application/json'
  496. }
  497. });
  498. if (response.ok) {
  499. showMessage('loginMessage', 'Password reset email sent! Please check your inbox.', 'success');
  500. document.getElementById('loginUsername').value = '';
  501. } else {
  502. try {
  503. const errorData = await response.json();
  504. const errorMessage = errorData.message || 'Failed to send reset email. Please try again.';
  505. showMessage('loginMessage', errorMessage, 'error');
  506. } catch (parseError) {
  507. const errorText = await response.text();
  508. showMessage('loginMessage', errorText || 'Failed to send reset email. Please try again.', 'error');
  509. }
  510. }
  511. } catch (error) {
  512. console.error('Forgot password error:', error);
  513. showMessage('loginMessage', 'Network error. Please check if the auth service is running.', 'error');
  514. } finally {
  515. link.textContent = originalText;
  516. link.style.pointerEvents = 'auto';
  517. }
  518. }