ソースを参照

update js and css structure

Daniel Bohry 3 週間 前
コミット
24b4602c9a

+ 0 - 3
src/main/java/com/danielbohry/authservice/api/AuthController.java

@@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.*;
 
 import static org.springframework.http.HttpStatus.CREATED;
 
-@Slf4j
 @RestController
 @AllArgsConstructor
 @CrossOrigin
@@ -26,14 +25,12 @@ public class AuthController {
 
     @PostMapping("register")
     public ResponseEntity<AuthenticationResponse> register(@RequestBody AuthenticationRequest request) {
-        log.info("Registering new username [{}]", request.getUsername());
         var response = service.register(request);
         return ResponseEntity.status(CREATED).body(response);
     }
 
     @PostMapping("authenticate")
     public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest request) {
-        log.info("Authenticating username [{}]", request.getUsername());
         var response = service.authenticate(request);
         return ResponseEntity.ok(response);
     }

+ 4 - 0
src/main/java/com/danielbohry/authservice/service/auth/AuthService.java

@@ -6,6 +6,7 @@ import com.danielbohry.authservice.client.MailClient;
 import com.danielbohry.authservice.domain.ApplicationUser;
 import com.danielbohry.authservice.service.user.UserService;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -19,6 +20,7 @@ import org.springframework.stereotype.Service;
 
 import static com.danielbohry.authservice.service.auth.UserConverter.convert;
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class AuthService implements UserDetailsService {
@@ -44,6 +46,7 @@ public class AuthService implements UserDetailsService {
         UserDetails user = buildUserDetails(request);
         ApplicationUser saved = service.create(convert(user, request.getEmail()));
         Authentication authentication = jwtService.generateToken(saved);
+        log.info("Username [{}] registered", request.getUsername());
         return buildResponse(saved, authentication);
     }
 
@@ -53,6 +56,7 @@ public class AuthService implements UserDetailsService {
         );
         ApplicationUser user = service.findByUsername(request.getUsername());
         Authentication authentication = jwtService.generateToken(user);
+        log.info("Username [{}] authenticated", request.getUsername());
         return buildResponse(user, authentication);
     }
 

+ 266 - 0
src/main/resources/static/css/main.css

@@ -0,0 +1,266 @@
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body {
+    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    min-height: 100vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.container {
+    background: white;
+    border-radius: 15px;
+    box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
+    overflow: hidden;
+    width: 100%;
+    max-width: 400px;
+    margin: 20px;
+}
+
+.header {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    text-align: center;
+    padding: 30px 20px;
+}
+
+.header h1 {
+    font-size: 1.8rem;
+    margin-bottom: 10px;
+}
+
+.header p {
+    opacity: 0.9;
+    font-size: 0.9rem;
+}
+
+.tab-container {
+    display: flex;
+    background: #f8f9fa;
+}
+
+.tab {
+    flex: 1;
+    padding: 15px;
+    text-align: center;
+    cursor: pointer;
+    font-weight: 500;
+    color: #666;
+    border-bottom: 3px solid transparent;
+    transition: all 0.3s ease;
+}
+
+.tab.active {
+    color: #667eea;
+    border-bottom-color: #667eea;
+    background: white;
+}
+
+.form-container {
+    padding: 40px 30px;
+}
+
+.form {
+    display: none;
+}
+
+.form.active {
+    display: block;
+}
+
+.form-group {
+    margin-bottom: 20px;
+}
+
+.form-group label {
+    display: block;
+    margin-bottom: 8px;
+    color: #333;
+    font-weight: 500;
+}
+
+.form-group input {
+    width: 100%;
+    padding: 12px 15px;
+    border: 2px solid #e1e5e9;
+    border-radius: 8px;
+    font-size: 1rem;
+    transition: all 0.3s ease;
+}
+
+.form-group input:focus {
+    outline: none;
+    border-color: #667eea;
+    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+.submit-btn {
+    width: 100%;
+    padding: 12px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 1rem;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+}
+
+.submit-btn:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
+}
+
+.submit-btn:disabled {
+    background: #ccc;
+    cursor: not-allowed;
+    transform: none;
+    box-shadow: none;
+}
+
+.message {
+    margin-top: 15px;
+    padding: 10px;
+    border-radius: 5px;
+    text-align: center;
+    font-weight: 500;
+}
+
+.message.success {
+    background: #d4edda;
+    color: #155724;
+    border: 1px solid #c3e6cb;
+}
+
+.message.error {
+    background: #f8d7da;
+    color: #721c24;
+    border: 1px solid #f5c6cb;
+}
+
+.user-info {
+    display: none;
+    padding: 20px;
+    background: #f8f9fa;
+    border-top: 1px solid #e1e5e9;
+}
+
+.user-info.active {
+    display: block;
+}
+
+.user-details {
+    background: white;
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 15px;
+}
+
+.user-details h3 {
+    color: #333;
+    margin-bottom: 10px;
+}
+
+.user-details p {
+    margin: 5px 0;
+    color: #666;
+}
+
+.change-password-section {
+    background: white;
+    padding: 20px;
+    border-radius: 8px;
+    margin-bottom: 15px;
+}
+
+.change-password-section h3 {
+    color: #333;
+    margin-bottom: 15px;
+    font-size: 1.1rem;
+}
+
+.action-buttons {
+    margin-bottom: 15px;
+}
+
+.action-btn {
+    width: 100%;
+    padding: 12px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 0.95rem;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+}
+
+.action-btn:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+}
+
+.form-buttons {
+    display: flex;
+    gap: 10px;
+    margin-top: 15px;
+}
+
+.form-buttons .submit-btn {
+    flex: 1;
+    margin: 0;
+}
+
+.logout-btn {
+    width: 100%;
+    padding: 10px;
+    background: #dc3545;
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 0.9rem;
+    cursor: pointer;
+    transition: all 0.3s ease;
+}
+
+.logout-btn:hover {
+    background: #c82333;
+}
+
+.loading {
+    display: inline-block;
+    width: 16px;
+    height: 16px;
+    border: 2px solid #ffffff;
+    border-radius: 50%;
+    border-top-color: transparent;
+    animation: spin 1s ease-in-out infinite;
+    margin-right: 8px;
+}
+
+@keyframes spin {
+    to { transform: rotate(360deg); }
+}
+
+.profile-section, .security-section {
+    margin-bottom: 25px;
+    padding: 15px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    border-left: 3px solid #667eea;
+}
+
+.profile-section h4, .security-section h4 {
+    margin: 0 0 15px 0;
+    color: #333;
+    font-size: 1rem;
+    font-weight: 600;
+}

+ 2 - 670
src/main/resources/static/index.html

@@ -4,274 +4,7 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Auth Service</title>
-    <style>
-        * {
-            margin: 0;
-            padding: 0;
-            box-sizing: border-box;
-        }
-
-        body {
-            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            min-height: 100vh;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-        }
-
-        .container {
-            background: white;
-            border-radius: 15px;
-            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
-            overflow: hidden;
-            width: 100%;
-            max-width: 400px;
-            margin: 20px;
-        }
-
-        .header {
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            text-align: center;
-            padding: 30px 20px;
-        }
-
-        .header h1 {
-            font-size: 1.8rem;
-            margin-bottom: 10px;
-        }
-
-        .header p {
-            opacity: 0.9;
-            font-size: 0.9rem;
-        }
-
-        .tab-container {
-            display: flex;
-            background: #f8f9fa;
-        }
-
-        .tab {
-            flex: 1;
-            padding: 15px;
-            text-align: center;
-            cursor: pointer;
-            font-weight: 500;
-            color: #666;
-            border-bottom: 3px solid transparent;
-            transition: all 0.3s ease;
-        }
-
-        .tab.active {
-            color: #667eea;
-            border-bottom-color: #667eea;
-            background: white;
-        }
-
-        .form-container {
-            padding: 40px 30px;
-        }
-
-        .form {
-            display: none;
-        }
-
-        .form.active {
-            display: block;
-        }
-
-        .form-group {
-            margin-bottom: 20px;
-        }
-
-        .form-group label {
-            display: block;
-            margin-bottom: 8px;
-            color: #333;
-            font-weight: 500;
-        }
-
-        .form-group input {
-            width: 100%;
-            padding: 12px 15px;
-            border: 2px solid #e1e5e9;
-            border-radius: 8px;
-            font-size: 1rem;
-            transition: all 0.3s ease;
-        }
-
-        .form-group input:focus {
-            outline: none;
-            border-color: #667eea;
-            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
-        }
-
-        .submit-btn {
-            width: 100%;
-            padding: 12px;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            border: none;
-            border-radius: 8px;
-            font-size: 1rem;
-            font-weight: 600;
-            cursor: pointer;
-            transition: all 0.3s ease;
-        }
-
-        .submit-btn:hover {
-            transform: translateY(-2px);
-            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
-        }
-
-        .submit-btn:disabled {
-            background: #ccc;
-            cursor: not-allowed;
-            transform: none;
-            box-shadow: none;
-        }
-
-        .message {
-            margin-top: 15px;
-            padding: 10px;
-            border-radius: 5px;
-            text-align: center;
-            font-weight: 500;
-        }
-
-        .message.success {
-            background: #d4edda;
-            color: #155724;
-            border: 1px solid #c3e6cb;
-        }
-
-        .message.error {
-            background: #f8d7da;
-            color: #721c24;
-            border: 1px solid #f5c6cb;
-        }
-
-        .user-info {
-            display: none;
-            padding: 20px;
-            background: #f8f9fa;
-            border-top: 1px solid #e1e5e9;
-        }
-
-        .user-info.active {
-            display: block;
-        }
-
-        .user-details {
-            background: white;
-            padding: 15px;
-            border-radius: 8px;
-            margin-bottom: 15px;
-        }
-
-        .user-details h3 {
-            color: #333;
-            margin-bottom: 10px;
-        }
-
-        .user-details p {
-            margin: 5px 0;
-            color: #666;
-        }
-
-        .change-password-section {
-            background: white;
-            padding: 20px;
-            border-radius: 8px;
-            margin-bottom: 15px;
-        }
-
-        .change-password-section h3 {
-            color: #333;
-            margin-bottom: 15px;
-            font-size: 1.1rem;
-        }
-
-        .action-buttons {
-            margin-bottom: 15px;
-        }
-
-        .action-btn {
-            width: 100%;
-            padding: 12px;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            border: none;
-            border-radius: 8px;
-            font-size: 0.95rem;
-            font-weight: 500;
-            cursor: pointer;
-            transition: all 0.3s ease;
-        }
-
-        .action-btn:hover {
-            transform: translateY(-1px);
-            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
-        }
-
-        .form-buttons {
-            display: flex;
-            gap: 10px;
-            margin-top: 15px;
-        }
-
-        .form-buttons .submit-btn {
-            flex: 1;
-            margin: 0;
-        }
-
-        .logout-btn {
-            width: 100%;
-            padding: 10px;
-            background: #dc3545;
-            color: white;
-            border: none;
-            border-radius: 8px;
-            font-size: 0.9rem;
-            cursor: pointer;
-            transition: all 0.3s ease;
-        }
-
-        .logout-btn:hover {
-            background: #c82333;
-        }
-
-        .loading {
-            display: inline-block;
-            width: 16px;
-            height: 16px;
-            border: 2px solid #ffffff;
-            border-radius: 50%;
-            border-top-color: transparent;
-            animation: spin 1s ease-in-out infinite;
-            margin-right: 8px;
-        }
-
-        @keyframes spin {
-            to { transform: rotate(360deg); }
-        }
-
-        .profile-section, .security-section {
-            margin-bottom: 25px;
-            padding: 15px;
-            background: #f8f9fa;
-            border-radius: 8px;
-            border-left: 3px solid #667eea;
-        }
-
-        .profile-section h4, .security-section h4 {
-            margin: 0 0 15px 0;
-            color: #333;
-            font-size: 1rem;
-            font-weight: 600;
-        }
-    </style>
+    <link rel="stylesheet" href="css/main.css">
 </head>
 <body>
     <div class="container">
@@ -397,407 +130,6 @@
         </div>
     </div>
 
-    <script>
-        const API_BASE_URL = '/api';
-        let currentUser = null;
-        let resetToken = null;
-
-        function parseURLParams() {
-            const urlParams = new URLSearchParams(window.location.search);
-            const hasResetFlag = urlParams.has('reset-password');
-            const token = urlParams.get('token');
-
-            return {
-                isResetMode: hasResetFlag && token,
-                resetToken: token
-            };
-        }
-
-        document.addEventListener('DOMContentLoaded', function() {
-            const urlInfo = parseURLParams();
-
-            if (urlInfo.isResetMode) {
-                resetToken = urlInfo.resetToken;
-                showResetPasswordSection();
-                return;
-            }
-
-            const token = localStorage.getItem('authToken');
-            const userData = localStorage.getItem('userData');
-
-            if (token && userData) {
-                try {
-                    currentUser = JSON.parse(userData);
-                    showUserSection();
-                } catch (e) {
-                    console.error('Invalid user data in localStorage', e);
-                    localStorage.removeItem('authToken');
-                    localStorage.removeItem('userData');
-                }
-            }
-        });
-
-        function switchTab(tab) {
-            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelector(`.tab:nth-child(${tab === 'login' ? '1' : '2'})`).classList.add('active');
-
-            document.querySelectorAll('.form').forEach(f => f.classList.remove('active'));
-            document.getElementById(tab + 'Form').classList.add('active');
-
-            clearMessages();
-        }
-
-        function clearMessages() {
-            document.querySelectorAll('.message').forEach(msg => {
-                msg.style.display = 'none';
-                msg.textContent = '';
-            });
-        }
-
-        function showMessage(elementId, message, type) {
-            const element = document.getElementById(elementId);
-            element.textContent = message;
-            element.className = `message ${type}`;
-            element.style.display = 'block';
-        }
-
-        function setButtonLoading(buttonId, loading) {
-            const button = document.getElementById(buttonId);
-            if (loading) {
-                button.innerHTML = '<span class="loading"></span>Processing...';
-                button.disabled = true;
-            } else {
-                if (buttonId.includes('login')) {
-                    button.innerHTML = 'Sign In';
-                } else if (buttonId.includes('register')) {
-                    button.innerHTML = 'Create Account';
-                } else if (buttonId.includes('editProfile')) {
-                    button.innerHTML = 'Update Profile';
-                } else if (buttonId.includes('resetPassword')) {
-                    button.innerHTML = 'Reset Password';
-                }
-                button.disabled = false;
-            }
-        }
-
-        document.getElementById('loginForm').addEventListener('submit', async function(e) {
-            e.preventDefault();
-
-            const username = document.getElementById('loginUsername').value;
-            const password = document.getElementById('loginPassword').value;
-
-            setButtonLoading('loginBtn', true);
-            clearMessages();
-
-            try {
-                const response = await fetch(`${API_BASE_URL}/authenticate`, {
-                    method: 'POST',
-                    headers: {
-                        'Content-Type': 'application/json'
-                    },
-                    body: JSON.stringify({ username, password })
-                });
-
-                if (response.ok) {
-                    const data = await response.json();
-
-                    localStorage.setItem('authToken', data.token);
-                    localStorage.setItem('userData', JSON.stringify(data));
-
-                    currentUser = data;
-                    setButtonLoading('loginBtn', false);
-                    showUserSection();
-                } else {
-                    const errorText = await response.text();
-                    showMessage('loginMessage', errorText || 'Login failed. Please check your credentials.', 'error');
-                }
-            } catch (error) {
-                console.error('Login error:', error);
-                showMessage('loginMessage', 'Network error. Please check if the auth service is running.', 'error');
-            } finally {
-                setButtonLoading('loginBtn', false);
-            }
-        });
-
-        document.getElementById('registerForm').addEventListener('submit', async function(e) {
-            e.preventDefault();
-
-            const username = document.getElementById('registerUsername').value;
-            const password = document.getElementById('registerPassword').value;
-            const email = document.getElementById('registerEmail').value;
-
-            setButtonLoading('registerBtn', true);
-            clearMessages();
-
-            try {
-                const response = await fetch(`${API_BASE_URL}/register`, {
-                    method: 'POST',
-                    headers: {
-                        'Content-Type': 'application/json'
-                    },
-                    body: JSON.stringify({ username, email, password })
-                });
-
-                if (response.ok) {
-                    const data = await response.json();
-
-                    localStorage.setItem('authToken', data.token);
-                    localStorage.setItem('userData', JSON.stringify(data));
-
-                    currentUser = data;
-                    setButtonLoading('registerBtn', false);
-                    showUserSection();
-                } else {
-                    const errorText = await response.text();
-                    showMessage('registerMessage', errorText || 'Registration failed. Please try again.', 'error');
-                }
-            } catch (error) {
-                console.error('Registration error:', error);
-                showMessage('registerMessage', 'Network error. Please check if the auth service is running.', 'error');
-            } finally {
-                setButtonLoading('registerBtn', false);
-            }
-        });
-
-        document.getElementById('resetPasswordForm').addEventListener('submit', async function(e) {
-            e.preventDefault();
-
-            const newPassword = document.getElementById('resetNewPassword').value;
-            const confirmPassword = document.getElementById('resetConfirmPassword').value;
-
-            if (newPassword !== confirmPassword) {
-                showMessage('resetPasswordMessage', 'New passwords do not match.', 'error');
-                return;
-            }
-
-            if (newPassword.length < 4) {
-                showMessage('resetPasswordMessage', 'New password must be at least 4 characters long.', 'error');
-                return;
-            }
-
-            setButtonLoading('resetPasswordBtn', true);
-            clearResetPasswordMessages();
-
-            try {
-                const response = await fetch(`${API_BASE_URL}/users/reset-password`, {
-                    method: 'POST',
-                    headers: {
-                        'Content-Type': 'application/json',
-                        'Authorization': 'Bearer ' + resetToken
-                    },
-                    body: JSON.stringify({
-                        newPassword: newPassword
-                    })
-                });
-
-                if (response.ok) {
-                    showMessage('resetPasswordMessage', 'Password reset successfully!', 'success');
-
-                    document.getElementById('resetPasswordForm').reset();
-                    setTimeout(() => {
-                        window.location.href = window.location.pathname;
-                    }, 1000);
-                } else {
-                    const errorText = await response.text();
-                    showMessage('resetPasswordMessage', errorText || 'Failed to reset password. Please try again.', 'error');
-                }
-            } catch (error) {
-                console.error('Reset password error:', error);
-                showMessage('resetPasswordMessage', 'Network error. Please check if the auth service is running.', 'error');
-            } finally {
-                setButtonLoading('resetPasswordBtn', false);
-            }
-        });
-
-        document.getElementById('editProfileForm').addEventListener('submit', async function(e) {
-            e.preventDefault();
-
-            const currentPassword = document.getElementById('currentPassword').value;
-            const newPassword = document.getElementById('newPassword').value;
-            const confirmPassword = document.getElementById('confirmPassword').value;
-            const email = document.getElementById('editEmail').value;
-
-            // Validate that at least one field is being updated
-            const isPasswordChange = newPassword.trim() !== '';
-            const currentEmail = currentUser.email || '';
-            const newEmail = email.trim();
-            const isEmailChange = newEmail !== currentEmail;
-
-            if (!isPasswordChange && !isEmailChange) {
-                showMessage('editProfileMessage', 'Please make at least one change: update your email address or change your password.', 'error');
-                return;
-            }
-
-            if (isPasswordChange && newPassword !== confirmPassword) {
-                showMessage('editProfileMessage', 'New passwords do not match.', 'error');
-                return;
-            }
-
-            if (isPasswordChange && newPassword.length < 4) {
-                showMessage('editProfileMessage', 'New password must be at least 4 characters long.', 'error');
-                return;
-            }
-
-            setButtonLoading('editProfileBtn', true);
-            clearEditProfileMessages();
-
-            try {
-                const requestBody = { currentPassword };
-
-                if (isPasswordChange) {
-                    requestBody.newPassword = newPassword;
-                }
-
-                if (isEmailChange) {
-                    requestBody.email = newEmail;
-                }
-
-                const response = await fetch(`${API_BASE_URL}/users/update-profile`, {
-                    method: 'POST',
-                    headers: {
-                        'Content-Type': 'application/json',
-                        'Authorization': `Bearer ${localStorage.getItem('authToken')}`
-                    },
-                    body: JSON.stringify(requestBody)
-                });
-
-                if (response.ok) {
-                    const data = await response.json();
-
-                    let message = '';
-                    if (isPasswordChange && isEmailChange) {
-                        message = 'Email and password updated successfully! Please log in with your new password.';
-                        document.getElementById('editProfileForm').reset();
-                        setTimeout(() => logout(), 1500);
-                    } else if (isPasswordChange) {
-                        message = 'Password updated successfully! Please log in with your new password.';
-                        document.getElementById('editProfileForm').reset();
-                        setTimeout(() => logout(), 1500);
-                    } else if (isEmailChange) {
-                        message = 'Email updated successfully!';
-                        // Update the stored user data with new email
-                        currentUser.email = newEmail;
-                        localStorage.setItem('userData', JSON.stringify(currentUser));
-                        updateUserDisplay(); // Refresh the displayed email
-                        document.getElementById('editProfileForm').reset();
-                        setTimeout(() => hideEditProfileForm(), 1000);
-                    }
-
-                    showMessage('editProfileMessage', message, 'success');
-                } else {
-                    const errorText = await response.text();
-                    showMessage('editProfileMessage', errorText || 'Failed to update profile. Please try again.', 'error');
-                }
-            } catch (error) {
-                console.error('Update profile error:', error);
-                showMessage('editProfileMessage', 'Network error. Please check if the auth service is running.', 'error');
-            } finally {
-                setButtonLoading('editProfileBtn', false);
-            }
-        });
-
-        function clearEditProfileMessages() {
-            const messageElement = document.getElementById('editProfileMessage');
-            messageElement.style.display = 'none';
-            messageElement.textContent = '';
-        }
-
-        function clearResetPasswordMessages() {
-            const messageElement = document.getElementById('resetPasswordMessage');
-            messageElement.style.display = 'none';
-            messageElement.textContent = '';
-        }
-
-        function updateUserDisplay() {
-            document.getElementById('currentUsername').textContent = currentUser.username;
-            document.getElementById('currentEmail').textContent = currentUser.email || '-';
-            document.getElementById('currentUserId').textContent = maskUserId(currentUser.id);
-            document.getElementById('currentRoles').textContent = currentUser.roles.join(', ');
-
-            const expirationDate = new Date(currentUser.expirationDate);
-            document.getElementById('tokenExpiration').textContent = expirationDate.toLocaleString();
-        }
-
-        function showUserSection() {
-            document.getElementById('authSection').style.display = 'none';
-            document.getElementById('userSection').classList.add('active');
-            updateUserDisplay();
-        }
-
-        function showResetPasswordSection() {
-            document.getElementById('authSection').style.display = 'none';
-            document.getElementById('userSection').classList.remove('active');
-            document.getElementById('resetPasswordSection').style.display = 'block';
-
-            // Focus on the first input field
-            document.getElementById('resetNewPassword').focus();
-        }
-
-        function maskUserId(id) {
-            const parts = id.split("-");
-            return parts[0] + "-****-" + parts[4];
-        }
-
-        function logout() {
-            localStorage.removeItem('authToken');
-            localStorage.removeItem('userData');
-            currentUser = null;
-
-            document.getElementById('userSection').classList.remove('active');
-            document.getElementById('authSection').style.display = 'block';
-
-            document.getElementById('loginForm').reset();
-            document.getElementById('registerForm').reset();
-
-            // Reset edit profile form and hide it
-            hideEditProfileForm();
-
-            clearMessages();
-
-            switchTab('login');
-        }
-
-        function toggleEditProfileForm() {
-            const formSection = document.getElementById('editProfileSection');
-            const isVisible = formSection.style.display === 'block';
-
-            if (isVisible) {
-                hideEditProfileForm();
-            } else {
-                showEditProfileForm();
-            }
-        }
-
-        function showEditProfileForm() {
-            const formSection = document.getElementById('editProfileSection');
-            const actionBtn = document.getElementById('showEditProfileBtn');
-
-            formSection.style.display = 'block';
-            actionBtn.textContent = 'Hide Edit Profile';
-
-            // Clear any previous messages
-            clearEditProfileMessages();
-
-            // Pre-populate email field with current user's email
-            if (currentUser) {
-                document.getElementById('editEmail').value = currentUser.email || '';
-            }
-
-            // Focus on email input first since that's the most common change
-            setTimeout(() => document.getElementById('editEmail').focus(), 100);
-        }
-
-        function hideEditProfileForm() {
-            const formSection = document.getElementById('editProfileSection');
-            const actionBtn = document.getElementById('showEditProfileBtn');
-
-            formSection.style.display = 'none';
-            actionBtn.textContent = 'Edit Profile';
-
-            // Clear form and messages
-            document.getElementById('editProfileForm').reset();
-            clearEditProfileMessages();
-        }
-    </script>
+    <script src="js/main.js"></script>
 </body>
 </html>

+ 437 - 0
src/main/resources/static/js/main.js

@@ -0,0 +1,437 @@
+const API_BASE_URL = '/api';
+let currentUser = null;
+let resetToken = null;
+
+function parseURLParams() {
+    const urlParams = new URLSearchParams(window.location.search);
+    const hasResetFlag = urlParams.has('reset-password');
+    const token = urlParams.get('token');
+
+    return {
+        isResetMode: hasResetFlag && token,
+        resetToken: token
+    };
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+    const urlInfo = parseURLParams();
+
+    if (urlInfo.isResetMode) {
+        resetToken = urlInfo.resetToken;
+        showResetPasswordSection();
+        return;
+    }
+
+    const token = localStorage.getItem('authToken');
+    const userData = localStorage.getItem('userData');
+
+    if (token && userData) {
+        try {
+            currentUser = JSON.parse(userData);
+            showUserSection();
+        } catch (e) {
+            console.error('Invalid user data in localStorage', e);
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('userData');
+        }
+    }
+});
+
+function switchTab(tab) {
+    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+    document.querySelector(`.tab:nth-child(${tab === 'login' ? '1' : '2'})`).classList.add('active');
+
+    document.querySelectorAll('.form').forEach(f => f.classList.remove('active'));
+    document.getElementById(tab + 'Form').classList.add('active');
+
+    clearMessages();
+}
+
+function clearMessages() {
+    document.querySelectorAll('.message').forEach(msg => {
+        msg.style.display = 'none';
+        msg.textContent = '';
+    });
+}
+
+function showMessage(elementId, message, type) {
+    const element = document.getElementById(elementId);
+    element.textContent = message;
+    element.className = `message ${type}`;
+    element.style.display = 'block';
+}
+
+function setButtonLoading(buttonId, loading) {
+    const button = document.getElementById(buttonId);
+    if (loading) {
+        button.innerHTML = '<span class="loading"></span>Processing...';
+        button.disabled = true;
+    } else {
+        if (buttonId.includes('login')) {
+            button.innerHTML = 'Sign In';
+        } else if (buttonId.includes('register')) {
+            button.innerHTML = 'Create Account';
+        } else if (buttonId.includes('editProfile')) {
+            button.innerHTML = 'Update Profile';
+        } else if (buttonId.includes('resetPassword')) {
+            button.innerHTML = 'Reset Password';
+        }
+        button.disabled = false;
+    }
+}
+
+document.getElementById('loginForm').addEventListener('submit', async function(e) {
+    e.preventDefault();
+
+    const username = document.getElementById('loginUsername').value;
+    const password = document.getElementById('loginPassword').value;
+
+    setButtonLoading('loginBtn', true);
+    clearMessages();
+
+    try {
+        const response = await fetch(`${API_BASE_URL}/authenticate`, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({ username, password })
+        });
+
+        if (response.ok) {
+            const data = await response.json();
+
+            localStorage.setItem('authToken', data.token);
+            localStorage.setItem('userData', JSON.stringify(data));
+
+            currentUser = data;
+            setButtonLoading('loginBtn', false);
+            showUserSection();
+        } else {
+            try {
+                const errorData = await response.json();
+                const errorMessage = errorData.message || 'Login failed. Please check your credentials.';
+                showMessage('loginMessage', errorMessage, 'error');
+            } catch (parseError) {
+                // If response is not JSON, fall back to text
+                const errorText = await response.text();
+                showMessage('loginMessage', errorText || 'Login failed. Please check your credentials.', 'error');
+            }
+        }
+    } catch (error) {
+        console.error('Login error:', error);
+        showMessage('loginMessage', 'Network error. Please check if the auth service is running.', 'error');
+    } finally {
+        setButtonLoading('loginBtn', false);
+    }
+});
+
+document.getElementById('registerForm').addEventListener('submit', async function(e) {
+    e.preventDefault();
+
+    const username = document.getElementById('registerUsername').value;
+    const password = document.getElementById('registerPassword').value;
+    const email = document.getElementById('registerEmail').value;
+
+    if (username.length < 4) {
+        showMessage('registerMessage', "Username must be at least 4 characters long.", 'error');
+        return;
+    }
+
+    if (password.length < 4) {
+        showMessage('registerMessage', "Password must be at least 4 characters long.", 'error');
+        return;
+    }
+
+    setButtonLoading('registerBtn', true);
+    clearMessages();
+
+    try {
+        const response = await fetch(`${API_BASE_URL}/register`, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({ username, email, password })
+        });
+
+        if (response.ok) {
+            const data = await response.json();
+
+            localStorage.setItem('authToken', data.token);
+            localStorage.setItem('userData', JSON.stringify(data));
+
+            currentUser = data;
+            setButtonLoading('registerBtn', false);
+            showUserSection();
+        } else {
+            try {
+                const errorData = await response.json();
+                const errorMessage = errorData.message || 'Registration failed. Please try again.';
+                showMessage('registerMessage', errorMessage, 'error');
+            } catch (parseError) {
+                const errorText = await response.text();
+                showMessage('registerMessage', errorText || 'Registration failed. Please try again.', 'error');
+            }
+        }
+    } catch (error) {
+        console.error('Registration error:', error);
+        showMessage('registerMessage', 'Network error. Please check if the auth service is running.', 'error');
+    } finally {
+        setButtonLoading('registerBtn', false);
+    }
+});
+
+document.getElementById('resetPasswordForm').addEventListener('submit', async function(e) {
+    e.preventDefault();
+
+    const newPassword = document.getElementById('resetNewPassword').value;
+    const confirmPassword = document.getElementById('resetConfirmPassword').value;
+
+    if (newPassword !== confirmPassword) {
+        showMessage('resetPasswordMessage', 'New passwords do not match.', 'error');
+        return;
+    }
+
+    if (newPassword.length < 4) {
+        showMessage('resetPasswordMessage', 'New password must be at least 4 characters long.', 'error');
+        return;
+    }
+
+    setButtonLoading('resetPasswordBtn', true);
+    clearResetPasswordMessages();
+
+    try {
+        const response = await fetch(`${API_BASE_URL}/users/reset-password`, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                'Authorization': 'Bearer ' + resetToken
+            },
+            body: JSON.stringify({
+                newPassword: newPassword
+            })
+        });
+
+        if (response.ok) {
+            showMessage('resetPasswordMessage', 'Password reset successfully!', 'success');
+
+            document.getElementById('resetPasswordForm').reset();
+            setTimeout(() => {
+                window.location.href = window.location.pathname;
+            }, 1000);
+        } else {
+            try {
+                const errorData = await response.json();
+                const errorMessage = errorData.message || 'Failed to reset password. Please try again.';
+                showMessage('resetPasswordMessage', errorMessage, 'error');
+            } catch (parseError) {
+                // If response is not JSON, fall back to text
+                const errorText = await response.text();
+                showMessage('resetPasswordMessage', errorText || 'Failed to reset password. Please try again.', 'error');
+            }
+        }
+    } catch (error) {
+        console.error('Reset password error:', error);
+        showMessage('resetPasswordMessage', 'Network error. Please check if the auth service is running.', 'error');
+    } finally {
+        setButtonLoading('resetPasswordBtn', false);
+    }
+});
+
+document.getElementById('editProfileForm').addEventListener('submit', async function(e) {
+    e.preventDefault();
+
+    const currentPassword = document.getElementById('currentPassword').value;
+    const newPassword = document.getElementById('newPassword').value;
+    const confirmPassword = document.getElementById('confirmPassword').value;
+    const email = document.getElementById('editEmail').value;
+
+    // Validate that at least one field is being updated
+    const isPasswordChange = newPassword.trim() !== '';
+    const currentEmail = currentUser.email || '';
+    const newEmail = email.trim();
+    const isEmailChange = newEmail !== currentEmail;
+
+    if (!isPasswordChange && !isEmailChange) {
+        showMessage('editProfileMessage', 'Please make at least one change: update your email address or change your password.', 'error');
+        return;
+    }
+
+    if (isPasswordChange && newPassword !== confirmPassword) {
+        showMessage('editProfileMessage', 'New passwords do not match.', 'error');
+        return;
+    }
+
+    if (isPasswordChange && newPassword.length < 4) {
+        showMessage('editProfileMessage', 'New password must be at least 4 characters long.', 'error');
+        return;
+    }
+
+    setButtonLoading('editProfileBtn', true);
+    clearEditProfileMessages();
+
+    try {
+        const requestBody = { currentPassword };
+
+        if (isPasswordChange) {
+            requestBody.newPassword = newPassword;
+        }
+
+        if (isEmailChange) {
+            requestBody.email = newEmail;
+        }
+
+        const response = await fetch(`${API_BASE_URL}/users/update-profile`, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                'Authorization': `Bearer ${localStorage.getItem('authToken')}`
+            },
+            body: JSON.stringify(requestBody)
+        });
+
+        if (response.ok) {
+            const data = await response.json();
+
+            let message = '';
+            if (isPasswordChange && isEmailChange) {
+                message = 'Email and password updated successfully! Please log in with your new password.';
+                document.getElementById('editProfileForm').reset();
+                setTimeout(() => logout(), 1500);
+            } else if (isPasswordChange) {
+                message = 'Password updated successfully! Please log in with your new password.';
+                document.getElementById('editProfileForm').reset();
+                setTimeout(() => logout(), 1500);
+            } else if (isEmailChange) {
+                message = 'Email updated successfully!';
+                // Update the stored user data with new email
+                currentUser.email = newEmail;
+                localStorage.setItem('userData', JSON.stringify(currentUser));
+                updateUserDisplay(); // Refresh the displayed email
+                document.getElementById('editProfileForm').reset();
+                setTimeout(() => hideEditProfileForm(), 1000);
+            }
+
+            showMessage('editProfileMessage', message, 'success');
+        } else {
+            try {
+                const errorData = await response.json();
+                const errorMessage = errorData.message || 'Failed to update profile. Please try again.';
+                showMessage('editProfileMessage', errorMessage, 'error');
+            } catch (parseError) {
+                // If response is not JSON, fall back to text
+                const errorText = await response.text();
+                showMessage('editProfileMessage', errorText || 'Failed to update profile. Please try again.', 'error');
+            }
+        }
+    } catch (error) {
+        console.error('Update profile error:', error);
+        showMessage('editProfileMessage', 'Network error. Please check if the auth service is running.', 'error');
+    } finally {
+        setButtonLoading('editProfileBtn', false);
+    }
+});
+
+function clearEditProfileMessages() {
+    const messageElement = document.getElementById('editProfileMessage');
+    messageElement.style.display = 'none';
+    messageElement.textContent = '';
+}
+
+function clearResetPasswordMessages() {
+    const messageElement = document.getElementById('resetPasswordMessage');
+    messageElement.style.display = 'none';
+    messageElement.textContent = '';
+}
+
+function updateUserDisplay() {
+    document.getElementById('currentUsername').textContent = currentUser.username;
+    document.getElementById('currentEmail').textContent = currentUser.email || '-';
+    document.getElementById('currentUserId').textContent = maskUserId(currentUser.id);
+    document.getElementById('currentRoles').textContent = currentUser.roles.join(', ');
+
+    const expirationDate = new Date(currentUser.expirationDate);
+    document.getElementById('tokenExpiration').textContent = expirationDate.toLocaleString();
+}
+
+function showUserSection() {
+    document.getElementById('authSection').style.display = 'none';
+    document.getElementById('userSection').classList.add('active');
+    updateUserDisplay();
+}
+
+function showResetPasswordSection() {
+    document.getElementById('authSection').style.display = 'none';
+    document.getElementById('userSection').classList.remove('active');
+    document.getElementById('resetPasswordSection').style.display = 'block';
+
+    // Focus on the first input field
+    document.getElementById('resetNewPassword').focus();
+}
+
+function maskUserId(id) {
+    const parts = id.split("-");
+    return parts[0] + "-****-" + parts[4];
+}
+
+function logout() {
+    localStorage.removeItem('authToken');
+    localStorage.removeItem('userData');
+    currentUser = null;
+
+    document.getElementById('userSection').classList.remove('active');
+    document.getElementById('authSection').style.display = 'block';
+
+    document.getElementById('loginForm').reset();
+    document.getElementById('registerForm').reset();
+
+    // Reset edit profile form and hide it
+    hideEditProfileForm();
+
+    clearMessages();
+
+    switchTab('login');
+}
+
+function toggleEditProfileForm() {
+    const formSection = document.getElementById('editProfileSection');
+    const isVisible = formSection.style.display === 'block';
+
+    if (isVisible) {
+        hideEditProfileForm();
+    } else {
+        showEditProfileForm();
+    }
+}
+
+function showEditProfileForm() {
+    const formSection = document.getElementById('editProfileSection');
+    const actionBtn = document.getElementById('showEditProfileBtn');
+
+    formSection.style.display = 'block';
+    actionBtn.textContent = 'Hide Edit Profile';
+
+    // Clear any previous messages
+    clearEditProfileMessages();
+
+    // Pre-populate email field with current user's email
+    if (currentUser) {
+        document.getElementById('editEmail').value = currentUser.email || '';
+    }
+
+    // Focus on email input first since that's the most common change
+    setTimeout(() => document.getElementById('editEmail').focus(), 100);
+}
+
+function hideEditProfileForm() {
+    const formSection = document.getElementById('editProfileSection');
+    const actionBtn = document.getElementById('showEditProfileBtn');
+
+    formSection.style.display = 'none';
+    actionBtn.textContent = 'Edit Profile';
+
+    // Clear form and messages
+    document.getElementById('editProfileForm').reset();
+    clearEditProfileMessages();
+}