diff --git a/users_microservice/pom.xml b/users_microservice/pom.xml index e6bbd9f3..87518cd3 100644 --- a/users_microservice/pom.xml +++ b/users_microservice/pom.xml @@ -21,7 +21,7 @@ com.h2database h2 - test + runtime org.springframework.boot diff --git a/users_microservice/src/main/java/com/users/application/controllers/PagesController.java b/users_microservice/src/main/java/com/users/application/controllers/PagesController.java index 5c694b68..9862e245 100644 --- a/users_microservice/src/main/java/com/users/application/controllers/PagesController.java +++ b/users_microservice/src/main/java/com/users/application/controllers/PagesController.java @@ -13,6 +13,9 @@ public String index(){ return "home/index"; } + @GetMapping("/add-privilege") + public String addPrivilege(){return "home/add-privilege";} + @GetMapping("/sign-up") public String signupPage() { return "home/registration"; // looks for signup.html in src/main/resources/templates diff --git a/users_microservice/src/main/java/com/users/application/controllers/UsersAuthController.java b/users_microservice/src/main/java/com/users/application/controllers/UsersAuthController.java index f591260f..4f9e34d2 100644 --- a/users_microservice/src/main/java/com/users/application/controllers/UsersAuthController.java +++ b/users_microservice/src/main/java/com/users/application/controllers/UsersAuthController.java @@ -37,7 +37,7 @@ public ResponseEntity> userRegisters(@RequestBo var uri = uriBuilder.path("/dev/api/findUserByIdentityNumber/{id}").buildAndExpand(request.getUserIdentityNo()).toUri(); return ResponseEntity.created(uri).body(set); } else { - ErrorResponse error = (ErrorResponse) set; + ErrorResponse error = (ErrorResponse) set.getFirst(); logger.warn("Error response : {}", error); return ResponseEntity.badRequest().body(List.of(error)); } @@ -77,7 +77,7 @@ public ResponseEntity> forgotPassword(@RequestB if (response.getFirst() instanceof UsersResponse) { return ResponseEntity.ok(response); } else { - ErrorResponse error = (ErrorResponse) response; + ErrorResponse error = (ErrorResponse) response.getFirst(); logger.warn("Error response : {}", error); return ResponseEntity.badRequest().body(List.of(error)); } diff --git a/users_microservice/src/main/java/com/users/application/controllers/VerifyCustomerController.java b/users_microservice/src/main/java/com/users/application/controllers/VerifyCustomerController.java index beeefa5f..20d7b1d3 100644 --- a/users_microservice/src/main/java/com/users/application/controllers/VerifyCustomerController.java +++ b/users_microservice/src/main/java/com/users/application/controllers/VerifyCustomerController.java @@ -42,7 +42,7 @@ public String verifyPasswordUpdate(@RequestParam String qTokq1) { var response = super.executeUserService(service,"verifyUser",new FindByTokenRequest(qTokq1)).getFirst(); if (response instanceof UsersResponse users) { - return "redirect:/reset?emailAddess="+users.getUsersEmailAddress(); + return "redirect:/reset?token="+qTokq1; } else { return "redirect:/login"; } diff --git a/users_microservice/src/main/java/com/users/application/dtos/UpdatePasswordRequest.java b/users_microservice/src/main/java/com/users/application/dtos/UpdatePasswordRequest.java index 9d448dbd..4be76bbd 100644 --- a/users_microservice/src/main/java/com/users/application/dtos/UpdatePasswordRequest.java +++ b/users_microservice/src/main/java/com/users/application/dtos/UpdatePasswordRequest.java @@ -12,7 +12,7 @@ public class UpdatePasswordRequest implements RequestContract { - private String usersPassword,usersEmailAddress; + private String usersPassword,userToken; private String usersConfirmPassword; } diff --git a/users_microservice/src/main/java/com/users/application/services/UsersService.java b/users_microservice/src/main/java/com/users/application/services/UsersService.java index 0638a127..3ba1d6e2 100644 --- a/users_microservice/src/main/java/com/users/application/services/UsersService.java +++ b/users_microservice/src/main/java/com/users/application/services/UsersService.java @@ -183,7 +183,7 @@ private List resetPassword(RequestContract request) { if (request instanceof UpdatePasswordRequest castedRequest) { try { - var dbEntity = userRepository.findByUserEmailAddress(castedRequest.getUsersEmailAddress()); + var dbEntity = userRepository.findByToken(castedRequest.getUserToken()); if (dbEntity.isPresent()) { var user = dbEntity.get(); if (getInstance().checkPasswordValidity(castedRequest.getUsersPassword()).equals(getInstance().checkPasswordValidity(castedRequest.getUsersConfirmPassword()))) { diff --git a/users_microservice/src/main/resources/application-dev.properties b/users_microservice/src/main/resources/application-dev.properties index b3d4d953..9d3f6f92 100644 --- a/users_microservice/src/main/resources/application-dev.properties +++ b/users_microservice/src/main/resources/application-dev.properties @@ -4,10 +4,17 @@ spring.sql.unit.mode= always # MySQL Database Configuration -spring.datasource.url=${SUPABASE_URL} - +# In-memory database (data lost on restart - perfect for quick dev/testing) +spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1;MODE=MySQL;INIT=CREATE SCHEMA IF NOT EXISTS devdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +# Enable H2 web console → http://localhost:8080/h2-console (or your port) +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console spring.flyway.clean-disabled=false - +spring.h2.console.settings.web-allow-others=true server.port=${PORT} spring.mail.host=smtp.gmail.com spring.mail.port=587 @@ -16,3 +23,7 @@ spring.mail.password=${MAIL_PASSWORD} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.docker.compose.enabled=true + +# Optional: if you have data.sql / schema.sql in resources, run them automatically +spring.sql.init.mode=always +spring.sql.init.continue-on-error=true diff --git a/users_microservice/src/main/resources/application-prod.properties b/users_microservice/src/main/resources/application-prod.properties index 42b9e716..73ab953b 100644 --- a/users_microservice/src/main/resources/application-prod.properties +++ b/users_microservice/src/main/resources/application-prod.properties @@ -4,6 +4,7 @@ spring.datasource.url=${SUPABASE_URL} spring.flyway.clean-disabled=true spring.flyway.enabled=true +spring.datasource.driver-class-name=org.postgresql.Driver logging.level.root=WARN logging.level.org.springframework=INFO diff --git a/users_microservice/src/main/resources/application.properties b/users_microservice/src/main/resources/application.properties index 1148abac..ce1377d9 100644 --- a/users_microservice/src/main/resources/application.properties +++ b/users_microservice/src/main/resources/application.properties @@ -5,7 +5,6 @@ logging.level.org.springframework.boot.docker.compose=DEBUG spring.docker.compose.lifecycle-management=start_only spring.threads.virtual.enabled= true -spring.datasource.driver-class-name=org.postgresql.Driver diff --git a/users_microservice/src/main/resources/static/css/landing-page.css b/users_microservice/src/main/resources/static/css/landing-page.css index 51854bf1..0263a1c3 100644 --- a/users_microservice/src/main/resources/static/css/landing-page.css +++ b/users_microservice/src/main/resources/static/css/landing-page.css @@ -40,4 +40,22 @@ box-shadow: 0 16px 32px rgba(255, 105, 180, 0.18); } +/* Optional: make it even more visible or add italic style */ +#contactForm .form-control::placeholder { + /* color: rgba(255, 255, 255, 0.9); */ /* alternative - almost fully white */ + /* font-style: italic; */ +} + +/* If your background is dark → ensure inputs have dark background so white text shows */ +#contactForm .form-control { + background-color: #2c2c2c; /* dark grey/black - adjust to your theme */ + color: #ffffff; /* white text when typing */ + border: 1px solid #555; /* subtle border */ +} + +/* Optional: placeholder changes color slightly on focus */ +#contactForm .form-control:focus::placeholder { + color: rgba(255, 255, 255, 0.4); /* fades a bit when focused */ +} + /* ... add your other landing sections: CTA, testimonials, etc. ... */ \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/css/loader.css b/users_microservice/src/main/resources/static/css/loader.css new file mode 100644 index 00000000..f65b00c4 --- /dev/null +++ b/users_microservice/src/main/resources/static/css/loader.css @@ -0,0 +1,93 @@ +/* Make the form a positioning context */ +#contactForm { + position: relative; +} + +/* Loader container – covers the form */ +.loader-container { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + pointer-events: none; + background: transparent; +} + +/* Very subtle overlay (almost invisible – keeps form readable) */ +.loader-overlay { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.10); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + border-radius: inherit; +} + +/* The spinning colorful circle */ +.loader { + position: relative; + width: 90px; + height: 90px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + + background: conic-gradient( + #ff0000 0deg, + #ff7f00 45deg, + #ffff00 90deg, + #00ff00 135deg, + #0000ff 180deg, + #4b0082 225deg, + #8b00ff 270deg, + #ff0000 315deg, + #ff0000 360deg + ); + + box-shadow: + 0 0 25px #ff0000aa, + 0 0 50px #ffff00aa, + 0 0 75px #00ff00aa, + 0 0 100px #0000ffaa, + inset 0 0 20px rgba(255,255,255,0.6); + + animation: rotate 1.6s linear infinite; +} + +/* Text INSIDE the loader circle */ +.loader-text { + color: white; + font-size: 1.05rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + white-space: nowrap; + + /* Black outline around the white text */ + -webkit-text-stroke: 0.5px black; + + /* Extra contrast & shadow */ + text-shadow: 0 1px 4px rgba(0,0,0,0.7); + + /* Gentle pulse animation */ + animation: pulseText 2.2s ease-in-out infinite; +} + +/* Animations */ +@keyframes rotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes pulseText { + 0%, 100% { opacity: 0.85; } + 50% { opacity: 1.00; } +} + +/* Hide class */ +.d-none { + display: none !important; +} \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/Privilege-script.js b/users_microservice/src/main/resources/static/js/Privilege-script.js new file mode 100644 index 00000000..efc12a5b --- /dev/null +++ b/users_microservice/src/main/resources/static/js/Privilege-script.js @@ -0,0 +1,84 @@ +// ──────────────────────────────────────────────────────────────── +// Add Privilege Form Handler +// ──────────────────────────────────────────────────────────────── +document.getElementById('addPrivilegeForm')?.addEventListener('submit', async function(e) { + e.preventDefault(); + + const alertDiv = document.getElementById('privilegeAlert'); + const submitBtn = document.getElementById('submitBtn'); + + // Reset UI + alertDiv.classList.add('d-none'); + alertDiv.classList.remove('alert-success', 'alert-danger'); + alertDiv.textContent = ''; + submitBtn.disabled = true; + submitBtn.textContent = 'Creating...'; + + // Get form values + const name = document.getElementById('privilegeName').value.trim(); + + if (!name) { + showAlert('Privilege name is required.', 'danger'); + resetButton(); + return; + } + + // Prepare payload (adjust fields to match your backend DTO) + const payload = { + privilegeName: name, + }; + + try { + const response = await fetch('http://localhost:8081/dev/privileges/api/addPrivilege', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + showAlert('Privilege created successfully!', 'success'); + document.getElementById('addPrivilegeForm').reset(); // clear form + // Optional: refresh privileges list elsewhere on page + } else { + let errorData = null; + try { + errorData = await response.json(); + } catch {} + + let message = `Failed to create privilege (${response.status})`; + + if (errorData) { + if (Array.isArray(errorData) && errorData[0]?.resolveIssueDetails) { + message = errorData[0].resolveIssueDetails; + } else if (errorData.resolveIssueDetails) { + message = errorData.resolveIssueDetails; + } else if (errorData.message) { + message = errorData.message; + } else if (errorData.detail) { + message = errorData.detail; + } + } + + showAlert(message, 'danger'); + } + } catch (err) { + console.error('Error:', err); + showAlert('Network error – please check if the server is running.', 'danger'); + } finally { + resetButton(); + } + + function showAlert(text, type) { + alertDiv.textContent = text; + alertDiv.classList.add(`alert-${type}`); + alertDiv.classList.remove('d-none'); + } + + function resetButton() { + submitBtn.disabled = false; + submitBtn.textContent = 'Create Privilege'; + } +}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/forgot-password.js b/users_microservice/src/main/resources/static/js/forgot-password.js new file mode 100644 index 00000000..f86e1531 --- /dev/null +++ b/users_microservice/src/main/resources/static/js/forgot-password.js @@ -0,0 +1,95 @@ +// Forgot Password handler +document.getElementById('forgotPasswordForm').addEventListener('submit', async function(e) { + e.preventDefault(); // Prevent page reload + + // Get DOM elements + const errorDiv = document.getElementById('errorAlert'); + const successDiv = document.getElementById('successAlert'); + const emailInput = document.getElementById('resetEmail'); + const submitBtn = e.target.querySelector('button[type="submit"]'); + + // Reset previous messages + errorDiv.classList.add('d-none'); + errorDiv.textContent = ''; + successDiv.classList.add('d-none'); + successDiv.textContent = ''; + + // Disable button + show loading state + submitBtn.disabled = true; + submitBtn.innerHTML = 'Sending...'; + + const email = emailInput.value.trim(); + + if (!email) { + errorDiv.textContent = 'Please enter your email address'; + errorDiv.classList.remove('d-none'); + submitBtn.disabled = false; + submitBtn.innerHTML = 'Send Reset Link'; + return; + } + + // Prepare payload (adjust field name to match your backend DTO) + const payload = { + emailAddress: email // ← most important: match your backend field name + // email: email // ← alternative common name + }; + + try { + const response = await fetch('http://localhost:8081/dev/api/auth/forgot-password', { // ← CHANGE TO YOUR REAL ENDPOINT + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (response.ok) { // 200–299 + let data; + try { + data = await response.json(); + } catch { + // some APIs return 200 + empty body on success + data = {}; + } + + // Customize success message based on your backend response + successDiv.textContent = data.message + || 'If an account exists with this email, you will receive a password reset link shortly.'; + successDiv.classList.remove('d-none'); + + // Optional: disable form / hide button after success + // emailInput.disabled = true; + // submitBtn.style.display = 'none'; + } + else { + // Error response (400, 404, 500, etc.) + let errorData; + try { + errorData = await response.json(); + } catch { + errorData = { resolveIssueDetails: `Server error (${response.status})` }; + } + + // Adjust according to your actual error response structure + const message = errorData?.resolveIssueDetails + || errorData?.message + || errorData?.error + || `Something went wrong (${response.status})`; + + errorDiv.textContent = message; + errorDiv.classList.remove('d-none'); + } + } + catch (err) { + // Network error, CORS, timeout, etc. + console.error('Forgot password error:', err); + errorDiv.textContent = 'Unable to connect. Please check your internet and try again.'; + errorDiv.classList.remove('d-none'); + } + finally { + // Always re-enable button + submitBtn.disabled = false; + submitBtn.innerHTML = 'Send Reset Link'; + } +}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/forgot.js b/users_microservice/src/main/resources/static/js/forgot.js deleted file mode 100644 index 1478b3b1..00000000 --- a/users_microservice/src/main/resources/static/js/forgot.js +++ /dev/null @@ -1,47 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const form = document.getElementById("forgotForm"); - const emailInput = document.getElementById("email"); - const emailError = document.getElementById("emailError"); - - form.addEventListener("submit", async (event) => { - event.preventDefault(); // prevent page reload - - // Reset error state - emailError.textContent = ""; - emailInput.classList.remove("input-error"); - - const email = emailInput.value.trim(); - - if (!email) { - emailError.textContent = "Email is required."; - emailInput.classList.add("input-error"); // add red border class - return; - } - - try { - const response = await fetch("/dev/api/auth/forgot-password", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ emailAddress : email }) - }); -console.log(JSON.stringify({ emailAddress : email })); - if (!response.ok) { - const errorData = await response.json(); - emailError.textContent = errorData.message || "Something went wrong."; - emailInput.classList.add("input-error"); - return; - } - - const data = await response.json(); - console.log(data); - alert(data.message || "Password reset link sent to your email."); - form.reset(); - } catch (err) { - console.log(err); - emailError.textContent = "Network error. Please try again."; - emailInput.classList.add("input-error"); - } - }); -}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/login-script.js b/users_microservice/src/main/resources/static/js/login-script.js index d61d6141..668812ac 100644 --- a/users_microservice/src/main/resources/static/js/login-script.js +++ b/users_microservice/src/main/resources/static/js/login-script.js @@ -1,86 +1,67 @@ -const form = document.getElementById('loginForm'); -const email = document.getElementById('email'); -const password = document.getElementById('password'); -const emailError = document.getElementById('emailError'); -const passwordError = document.getElementById('passwordError'); +document.getElementById('loginForm').addEventListener('submit', async function(e) { + e.preventDefault(); // Prevent page reload -function setError(input, el, message) { - input.classList.remove('valid'); - input.classList.add('error'); - el.textContent = message; -} + // Hide previous error + const errorDiv = document.getElementById('errorAlert'); + errorDiv.classList.add('d-none'); + errorDiv.textContent = ''; -function setValid(input, el) { - input.classList.remove('error'); - input.classList.add('valid'); - el.textContent = ''; -} + // Get form values + const email = document.getElementById('email').value.trim(); + const password = document.getElementById('password').value; -// Email validation -email.addEventListener('input', () => { - if (!email.value.trim()) { - setError(email, emailError, 'Email is required.'); - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) { - setError(email, emailError, 'Please enter a valid email address.'); - } else { - setValid(email, emailError); - } -}); - -// Password validation -password.addEventListener('input', () => { - if (!password.value.trim()) { - setError(password, passwordError, 'Password is required.'); - } else if (password.value.length < 6) { - setError(password, passwordError, 'Use at least 6 characters.'); - } else { - setValid(password, passwordError); - } -}); - -// Handle login -async function handleLogin(event) { - event.preventDefault(); // prevent form reload - - const username = email.value; // use email as username - const pwd = password.value; + // Prepare payload matching your LoginRequest + const payload = { + usersEmailAddress: email, + usersPassword: password + }; - try { - const response = await fetch('/dev/api/auth/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ usersEmailAddress : username, password: pwd }) - }); - console.log(response); - console.log(JSON.stringify({ usersEmailAddress : username, password: pwd })); - if (!response.ok) { - // Try to parse error JSON if server sends one - const errorData = await response.json().catch(() => null); - const message = errorData?.message || 'Login failed'; - throw new Error(message); - } - - const data = await response.json(); - console.log('Login success:', data); - - // Example: store JWT token - localStorage.setItem('token', data.token); + try { + const response = await fetch('http://localhost:8081/dev/api/auth/login', { // ← Replace with real endpoint + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); - // Redirect to home/dashboard - window.location.href = '/home'; + // Important: fetch only rejects on network failure → we must check status manually + if (response.ok) { // 200–299 + /** @type {UsersResponse} */ + const data = await response.json(); - } catch (error) { - console.error('Error:', error); - alert(error.message); // show server error message - } -} + // Success handling + if (data.token) { + localStorage.setItem('authToken', data.token); // Or sessionStorage / cookie + // Optional: store more user info + // localStorage.setItem('user', JSON.stringify(data)); -// Attach submit handler -form.addEventListener('submit', (e) => { - // Run validations first - email.dispatchEvent(new Event('input')); - password.dispatchEvent(new Event('input')); + alert('Login successful!'); // Replace with real redirect + window.location.href = '/dashboard'; // ← your success page + } else { + throw new Error('No token received from server'); + } + } else { + // Non-2xx → try to parse ErrorResponse + let errorData; + try { + errorData = await response.json(); // Should match ErrorResponse + } catch (jsonErr) { + // If not valid JSON → fallback + errorData = { resolveIssueDetails: `Server error (${response.status})` }; + } + + // Show the error message from backend + const message = errorData[0].resolveIssueDetails; - // Then call login - handleLogin(e); -}); \ No newline at end of file + errorDiv.textContent = message; + errorDiv.classList.remove('d-none'); + } + } catch (err) { + // Network error / CORS / JSON parse fail / etc. + console.error(err); + errorDiv.textContent = 'Something went wrong. Please try again later.'; + errorDiv.classList.remove('d-none'); + } +}); diff --git a/users_microservice/src/main/resources/static/js/register.js b/users_microservice/src/main/resources/static/js/register.js new file mode 100644 index 00000000..ecbda25d --- /dev/null +++ b/users_microservice/src/main/resources/static/js/register.js @@ -0,0 +1,180 @@ +// ──────────────────────────────────────────────────────────────── +// Load privileges when the page loads +// ──────────────────────────────────────────────────────────────── +async function loadPrivileges() { + const select = document.getElementById('privileges'); + if (!select) return; + + select.innerHTML = ''; + + try { + const response = await fetch('http://localhost:8081/dev/privileges/api/printPrivilege', { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`Failed to load privileges (${response.status})`); + } + + const data = await response.json(); + + // Clear loading state + select.innerHTML = ''; + + // Normalize to array (in case single object is returned) + const privilegesList = Array.isArray(data) ? data : [data]; + + privilegesList.forEach(item => { + if (item && typeof item === 'object' && 'id' in item && 'privilegeName' in item) { + const option = document.createElement('option'); + option.value = item.id; // value = id (for easy retrieval) + option.textContent = item.privilegeName; // what the user sees + option.dataset.name = item.privilegeName; // store name in data attribute + select.appendChild(option); + } + }); + + // Optional: auto-select first real option + if (select.options.length > 1) { + select.selectedIndex = 1; + } + + if (privilegesList.length === 0) { + select.innerHTML = ''; + } + + } catch (err) { + console.error('Error loading privileges:', err); + select.innerHTML = ''; + } +} + +// ──────────────────────────────────────────────────────────────── +// Form submission handler +// ──────────────────────────────────────────────────────────────── +document.addEventListener('DOMContentLoaded', () => { + loadPrivileges(); // Load privileges immediately + + const form = document.getElementById('registerForm'); + if (!form) return; + + form.addEventListener('submit', async function(e) { + e.preventDefault(); + + const alertDiv = document.getElementById('registerAlert'); + const submitBtn = document.getElementById('registerBtn'); + + // Reset alert + alertDiv.classList.add('d-none'); + alertDiv.classList.remove('alert-success', 'alert-danger'); + alertDiv.textContent = ''; + + // Disable button during request + submitBtn.disabled = true; + submitBtn.textContent = 'Registering...'; + + // Gather form values + const fullName = document.getElementById('fullName')?.value.trim() || ''; + const email = document.getElementById('email')?.value.trim() || ''; + const cellphone = document.getElementById('cellphone')?.value.trim() || ''; + const identityNo = document.getElementById('identityNo')?.value.trim() || ''; + const password = document.getElementById('password')?.value || ''; + const confirmPassword = document.getElementById('confirmPassword')?.value || ''; + const dob = document.getElementById('dob')?.value || ''; + + // Get selected privilege + const select = document.getElementById('privileges'); + const selectedOption = select.options[select.selectedIndex]; + const privilegesId = selectedOption.value ? parseInt(selectedOption.value, 10) : NaN; + const privilegeName = selectedOption.dataset.name || selectedOption.textContent || ''; + + // Basic client-side validation + if (password !== confirmPassword) { + showAlert('Passwords do not match!', 'danger'); + resetButton(); + return; + } + + if (isNaN(privilegesId) || !privilegeName) { + showAlert('Please select a privilege / role!', 'danger'); + resetButton(); + return; + } + + // Prepare payload (now sending object with id + name) + const payload = { + userFullName: fullName, + userEmailAddress: email, + userCellphoneNo: cellphone, + userIdentityNo: identityNo, + userPassword: password, + confirmPassword: confirmPassword, + userStatus: 1, // adjust as needed + privileges: { + id: privilegesId, + privilegeName: privilegeName + }, + // dateOfBirth: dob, // uncomment if needed + }; + + try { + const response = await fetch('http://localhost:8081/dev/api/auth/sign-up', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + showAlert('Registration successful! Redirecting to login...', 'success'); + setTimeout(() => { + window.location.href = '/login'; // ← adjust path + }, 1800); + } else { + let errorData = null; + try { + errorData = await response.json(); + } catch {} + + let message = `Registration failed (${response.status})`; + + if (errorData) { + if (Array.isArray(errorData) && errorData.length > 0) { + message = errorData[0].resolveIssueDetails + || errorData[0].message + || message; + } else if (errorData.resolveIssueDetails) { + message = errorData.resolveIssueDetails; + } else if (errorData.message) { + message = errorData.message; + } else if (errorData.detail) { + message = errorData.detail; + } + } + + showAlert(message, 'danger'); + } + } catch (err) { + console.error('Registration error:', err); + showAlert('Network error – please check your connection and try again.', 'danger'); + } finally { + resetButton(); + } + + function showAlert(text, type) { + alertDiv.textContent = text; + alertDiv.classList.add(`alert-${type}`); + alertDiv.classList.remove('d-none'); + } + + function resetButton() { + submitBtn.disabled = false; + submitBtn.textContent = 'Register'; + } + }); +}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/reset-password.js b/users_microservice/src/main/resources/static/js/reset-password.js new file mode 100644 index 00000000..d61cc100 --- /dev/null +++ b/users_microservice/src/main/resources/static/js/reset-password.js @@ -0,0 +1,126 @@ +// Reset Password handler +document.getElementById('resetPasswordForm').addEventListener('submit', async function(e) { + e.preventDefault(); + + // DOM elements + const errorDiv = document.getElementById('errorAlert'); + const successDiv = document.getElementById('successAlert'); + const newPassInput = document.getElementById('newPassword'); + const confPassInput = document.getElementById('confirmPassword'); + const submitBtn = e.target.querySelector('button[type="submit"]'); + + // Reset messages + errorDiv.classList.add('d-none'); + errorDiv.textContent = ''; + successDiv.classList.add('d-none'); + successDiv.textContent = ''; + + // Loading state + submitBtn.disabled = true; + submitBtn.innerHTML = 'Updating...'; + + const newPassword = newPassInput.value; + const confirmPassword = confPassInput.value; + + // Basic client-side validation + if (!newPassword || !confirmPassword) { + errorDiv.textContent = 'Please fill in both password fields'; + errorDiv.classList.remove('d-none'); + submitBtn.disabled = false; + submitBtn.innerHTML = 'Update Password'; + return; + } + + if (newPassword !== confirmPassword) { + errorDiv.textContent = 'Passwords do not match'; + errorDiv.classList.remove('d-none'); + submitBtn.disabled = false; + submitBtn.innerHTML = 'Update Password'; + return; + } + + // Extract reset token from URL (common patterns) + const urlParams = new URLSearchParams(window.location.search); + let token = urlParams.get('token'); // ?token=xyz + if (!token) { + token = urlParams.get('resetToken'); // ?resetToken=xyz + } + if (!token && window.location.pathname.includes('/reset/')) { + // e.g. /reset/abc123 → take last segment + token = window.location.pathname.split('/').filter(Boolean).pop(); + } + + if (!token) { + errorDiv.textContent = 'Reset token is missing. Please use the link from your email.'; + errorDiv.classList.remove('d-none'); + submitBtn.disabled = false; + submitBtn.innerHTML = 'Update Password'; + return; + } + + // Prepare payload – adjust field names to match your backend DTO exactly + const payload = { + userToken: token, + usersPassword: newPassword, + usersConfirmPassword: newPassword, // ← include only if backend requires it + // usersPassword: newPassword, // ← if your naming convention uses "users..." + }; + + try { + const response = await fetch('http://localhost:8081/dev/api/auth/reset-password', { // ← CHANGE TO YOUR REAL ENDPOINT + method: 'POST', // or PUT — most APIs use POST for reset-with-token + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + let data = {}; + try { + data = await response.json(); + } catch { + // empty 200 OK is also fine + } + + successDiv.textContent = data.message || 'Password updated successfully! Redirecting to login...'; + successDiv.classList.remove('d-none'); + + // Optional: redirect after short delay + setTimeout(() => { + window.location.href = '/login'; + }, 2500); + } + else { + let errorData; + try { + errorData = await response.json(); + } catch { + errorData = { resolveIssueDetails: `Server error (${response.status})` }; + } + + // Flexible error message extraction – match your backend format + const message = + errorData?.resolveIssueDetails || + errorData?.message || + errorData?.error || + errorData?.[0]?.resolveIssueDetails || // like your login code + `Failed to reset password (${response.status})`; + + errorDiv.textContent = message; + errorDiv.classList.remove('d-none'); + } + } + catch (err) { + console.error('Reset password error:', err); + errorDiv.textContent = 'Unable to connect. Please try again or request a new reset link.'; + errorDiv.classList.remove('d-none'); + } + finally { + submitBtn.disabled = false; + submitBtn.innerHTML = 'Update Password'; + } +}); + + diff --git a/users_microservice/src/main/resources/static/js/reset.js b/users_microservice/src/main/resources/static/js/reset.js deleted file mode 100644 index 5e7324d2..00000000 --- a/users_microservice/src/main/resources/static/js/reset.js +++ /dev/null @@ -1,28 +0,0 @@ -const form = document.getElementById('resetForm'); -const passwordField = document.getElementById('password'); -const confirmField = document.getElementById('confirm'); - -form.addEventListener('submit', (e) => { - e.preventDefault(); - - const payload = { - password: passwordField.value, - confirm: confirmField.value - }; - - fetch('/dev/api/auth/reset-password', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }) - .then(response => { - if (!response.ok) throw new Error('Reset failed'); - return response.json(); - }) - .then(data => { - alert('Password updated successfully!'); - }) - .catch(err => { - alert('Error: ' + err.message); - }); -}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/static/js/sendContactUsFormRequest.js b/users_microservice/src/main/resources/static/js/sendContactUsFormRequest.js new file mode 100644 index 00000000..190e0f84 --- /dev/null +++ b/users_microservice/src/main/resources/static/js/sendContactUsFormRequest.js @@ -0,0 +1,79 @@ +document.addEventListener('DOMContentLoaded', () => { + const form = document.getElementById('contactForm'); + const submitBtn = document.getElementById('submitBtn'); + const loaderCont = document.getElementById('loaderContainer'); // ← this exists in your HTML + const status = document.getElementById('formStatus'); + + if (!form || !submitBtn) { + console.warn("Form or submit button missing – handler not attached."); + return; + } + + form.addEventListener('submit', async function(e) { + e.preventDefault(); + + // Loading state + submitBtn.disabled = true; + submitBtn.style.opacity = '0.6'; // dim instead of hide (smoother) + // submitBtn.style.display = 'none'; // ← use this if you prefer full hide + + if (loaderCont) { + loaderCont.classList.remove('d-none'); + } else { + console.warn("#loaderContainer not found in DOM"); + } + + if (status) status.textContent = ''; + + try { + const data = { + name: form.elements.name?.value.trim() || '', + surname: form.elements.surname?.value.trim() || '', + email: form.elements.email?.value.trim() || '', + message: form.elements.message?.value.trim() || '' + }; + + if (data.message.length < 10) { + throw new Error('Message is too short – please tell us more.'); + } + + const response = await fetch('http://localhost:8081/api/contact/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + + if (!response.ok) { + let errorMsg = `Server error (${response.status})`; + try { + const errorBody = await response.json(); + errorMsg = errorBody.message || errorBody.error || errorMsg; + } catch {} + throw new Error(errorMsg); + } + + const result = await response.json(); + if (status) { + status.textContent = result.message || 'Thank you! Your message has been sent.'; + status.style.color = '#28a745'; + } + form.reset(); + + } catch (err) { + console.error(err); + if (status) { + status.textContent = err.message || 'Sorry, we could not send your message. Please try again.'; + status.style.color = '#dc3545'; + } + } finally { + // Cleanup + submitBtn.disabled = false; + submitBtn.style.opacity = '1'; + // submitBtn.style.display = 'block'; + + if (loaderCont) { + loaderCont.classList.add('d-none'); + } + } + }); +}); \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/add-privilege.html b/users_microservice/src/main/resources/templates/home/add-privilege.html new file mode 100644 index 00000000..a728e6db --- /dev/null +++ b/users_microservice/src/main/resources/templates/home/add-privilege.html @@ -0,0 +1,80 @@ + + + + + + Alcohol Agent - Login + + + + + + + + + + + + + + + + + +
+
+

Welcome Back

+

Login to continue your journey with Alcohol Agent.

+
+
+ +
+
+

Add New Privilege

+ + + + +
+
+ + +
+ Unique name for the privilege/role (will be displayed in dropdowns) +
+
+ + + +
+
+
+ +
+

© 2026 Alcohol Agent. All rights reserved.

+
+ + + + + + + \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/forgot-password.html b/users_microservice/src/main/resources/templates/home/forgot-password.html index ca4d3bd1..c2b3259f 100644 --- a/users_microservice/src/main/resources/templates/home/forgot-password.html +++ b/users_microservice/src/main/resources/templates/home/forgot-password.html @@ -24,21 +24,35 @@

Reset Your Password

- +
-
-

Forgot Password

-
-
- - -
- - -
-
+
+

Forgot Password

+ + + + +
+
+ + +
+ + + + +
+
@@ -48,5 +62,8 @@

Forgot Password

+ + + \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/index.html b/users_microservice/src/main/resources/templates/home/index.html index 90b09b48..e40cacf5 100644 --- a/users_microservice/src/main/resources/templates/home/index.html +++ b/users_microservice/src/main/resources/templates/home/index.html @@ -16,6 +16,8 @@ + + @@ -166,38 +168,81 @@

Get in Touch

-
-
- - -
-
- - -
-
+
+
+ + +
+
+ + +
+
-
- - -
+
+ + +
-
- - -
+
+ + +
+ +
+ +
-
- + +
+
+
+ Loading +
- -
+ +
+ + +
- +
@@ -208,6 +253,8 @@

Get in Touch

+ + \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/login.html b/users_microservice/src/main/resources/templates/home/login.html index b5f180eb..f4e2365f 100644 --- a/users_microservice/src/main/resources/templates/home/login.html +++ b/users_microservice/src/main/resources/templates/home/login.html @@ -42,14 +42,20 @@

Welcome Back

Login Form

-
+ + + + +
- - + +
- - + +
@@ -65,6 +71,8 @@

Login Form

+ + \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/registration.html b/users_microservice/src/main/resources/templates/home/registration.html index da7d54d2..2b342be0 100644 --- a/users_microservice/src/main/resources/templates/home/registration.html +++ b/users_microservice/src/main/resources/templates/home/registration.html @@ -37,17 +37,56 @@

Create Your Account

Registration Form

- -
-
-
-
-
- + + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + +
+ + + + +
+

© 2026 Alcohol Agent. All rights reserved.

+ + + \ No newline at end of file diff --git a/users_microservice/src/main/resources/templates/home/reset.html b/users_microservice/src/main/resources/templates/home/reset.html index e9370f85..5d312686 100644 --- a/users_microservice/src/main/resources/templates/home/reset.html +++ b/users_microservice/src/main/resources/templates/home/reset.html @@ -25,25 +25,48 @@

Reset Your Password

- +
-
-

Reset Password

-
-
- - -
-
- - -
- - -
-
+
+

Reset Password

+ + + + +
+
+ + +
+ +
+ + +
+ + + + +
+
@@ -53,5 +76,7 @@

Reset Password

+ + \ No newline at end of file