confirm email
This commit is contained in:
243
public/js/app.js
243
public/js/app.js
@@ -94,6 +94,19 @@ class AccountManager {
|
||||
return this.getCurrentUserRole() === 'admin';
|
||||
}
|
||||
|
||||
getAuthHeaders(includeJson = false) {
|
||||
const headers = {
|
||||
'x-user-id': String(this.getUserId()),
|
||||
'x-user-role': this.getCurrentUserRole()
|
||||
};
|
||||
|
||||
if (includeJson) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.fetchApplications();
|
||||
await this.fetchAccounts();
|
||||
@@ -282,6 +295,11 @@ class AccountManager {
|
||||
logoutBtn.addEventListener('click', () => this.handleLogout());
|
||||
}
|
||||
|
||||
const profileBtn = document.getElementById('profileBtn');
|
||||
if (profileBtn) {
|
||||
profileBtn.addEventListener('click', () => this.openProfileModal());
|
||||
}
|
||||
|
||||
// Update account display
|
||||
this.updateAccountDisplay();
|
||||
|
||||
@@ -1247,6 +1265,218 @@ class AccountManager {
|
||||
});
|
||||
}
|
||||
|
||||
async openProfileModal() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/users/me`, {
|
||||
headers: this.getAuthHeaders(false)
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success || !data.data) {
|
||||
this.notifyFailure(data.message || 'Cannot load profile');
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderProfileModal(data.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.notifyFailure('Cannot load profile');
|
||||
}
|
||||
}
|
||||
|
||||
renderProfileModal(profile) {
|
||||
const isVerified = profile?.EmailVerified === true || profile?.EmailVerified === 1;
|
||||
const html = `
|
||||
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 modal-backdrop open" id="profileModal" style="display:flex;align-items:center;justify-content:center;padding:16px;">
|
||||
<div class="bg-white dark:bg-slate-900 rounded-lg p-6 modal-content" style="width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;">
|
||||
<h2 class="text-xl font-bold text-slate-900 dark:text-slate-50">My Profile</h2>
|
||||
<p class="text-xs text-slate-500 mt-1 mb-4">Update personal info and password in one place.</p>
|
||||
<form id="profileForm" class="space-y-5">
|
||||
<div class="rounded-xl border border-outline-variant/20 bg-surface-container-low/40 dark:bg-slate-800/40 p-4 space-y-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Username</label>
|
||||
<input type="text" value="${profile?.Username || ''}" readonly class="w-full px-3 py-2 border border-outline-variant/30 rounded-lg bg-surface-container-low dark:bg-slate-800 opacity-80">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Full Name</label>
|
||||
<input type="text" id="profileFullName" value="${profile?.FullName || ''}" class="w-full px-3 py-2 border border-outline-variant/30 rounded-lg bg-white dark:bg-slate-900" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Email</label>
|
||||
<input type="email" id="profileEmail" value="${profile?.Email || ''}" class="w-full px-3 py-2 border border-outline-variant/30 rounded-lg bg-white dark:bg-slate-900" required>
|
||||
<p class="text-xs mt-1 ${isVerified ? 'text-green-600' : 'text-amber-600'}">
|
||||
${isVerified ? 'Email is confirmed' : 'Email is not confirmed yet'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-outline-variant/20 bg-surface-container-low/40 dark:bg-slate-800/40 p-4">
|
||||
<label class="block text-sm font-semibold mb-3">Change Password</label>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 dark:text-slate-300 mb-1">Current password</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="password" id="profileCurrentPassword" placeholder="Enter current password" class="flex-1 px-3 py-2 border border-outline-variant/30 rounded-lg bg-white dark:bg-slate-900">
|
||||
<button type="button" data-password-toggle="profileCurrentPassword" class="shrink-0 px-2.5 py-2 border border-outline-variant/30 rounded-lg text-slate-500 hover:text-slate-700 hover:bg-slate-100 dark:hover:text-slate-200 dark:hover:bg-slate-800" aria-label="Show current password">
|
||||
<span class="material-symbols-outlined text-base" id="profileCurrentPasswordIcon">visibility</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 dark:text-slate-300 mb-1">New password</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="password" id="profileNewPassword" placeholder="Enter new password" class="flex-1 px-3 py-2 border border-outline-variant/30 rounded-lg bg-white dark:bg-slate-900">
|
||||
<button type="button" data-password-toggle="profileNewPassword" class="shrink-0 px-2.5 py-2 border border-outline-variant/30 rounded-lg text-slate-500 hover:text-slate-700 hover:bg-slate-100 dark:hover:text-slate-200 dark:hover:bg-slate-800" aria-label="Show new password">
|
||||
<span class="material-symbols-outlined text-base" id="profileNewPasswordIcon">visibility</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 dark:text-slate-300 mb-1">Confirm new password</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="password" id="profileConfirmPassword" placeholder="Confirm new password" class="flex-1 px-3 py-2 border border-outline-variant/30 rounded-lg bg-white dark:bg-slate-900">
|
||||
<button type="button" data-password-toggle="profileConfirmPassword" class="shrink-0 px-2.5 py-2 border border-outline-variant/30 rounded-lg text-slate-500 hover:text-slate-700 hover:bg-slate-100 dark:hover:text-slate-200 dark:hover:bg-slate-800" aria-label="Show confirm password">
|
||||
<span class="material-symbols-outlined text-base" id="profileConfirmPasswordIcon">visibility</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-slate-500">Leave password fields empty if you only want to update profile info.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="button" onclick="closeProfileModal()" class="flex-1 px-4 py-2 bg-outline/10 hover:bg-outline/20 rounded-lg transition-colors">Cancel</button>
|
||||
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary font-bold rounded-lg transition-colors">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const containerId = 'profileModalContainer';
|
||||
let container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = containerId;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
container.innerHTML = html;
|
||||
|
||||
const form = document.getElementById('profileForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => this.saveProfile(e));
|
||||
}
|
||||
|
||||
this.setupProfilePasswordToggles();
|
||||
|
||||
const modal = document.getElementById('profileModal');
|
||||
if (modal) {
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
closeProfileModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupProfilePasswordToggles() {
|
||||
document.querySelectorAll('[data-password-toggle]').forEach((toggleBtn) => {
|
||||
if (toggleBtn.dataset.bound === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
const inputId = toggleBtn.dataset.passwordToggle;
|
||||
if (!inputId) return;
|
||||
|
||||
const input = document.getElementById(inputId);
|
||||
const icon = document.getElementById(`${inputId}Icon`);
|
||||
if (!input) return;
|
||||
|
||||
const isHidden = input.type === 'password';
|
||||
input.type = isHidden ? 'text' : 'password';
|
||||
if (icon) {
|
||||
icon.textContent = isHidden ? 'visibility_off' : 'visibility';
|
||||
}
|
||||
});
|
||||
|
||||
toggleBtn.dataset.bound = 'true';
|
||||
});
|
||||
}
|
||||
|
||||
async saveProfile(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const fullname = document.getElementById('profileFullName')?.value.trim() || '';
|
||||
const email = document.getElementById('profileEmail')?.value.trim() || '';
|
||||
const currentPassword = document.getElementById('profileCurrentPassword')?.value || '';
|
||||
const newPassword = document.getElementById('profileNewPassword')?.value || '';
|
||||
const confirmPassword = document.getElementById('profileConfirmPassword')?.value || '';
|
||||
|
||||
if (!fullname || !email) {
|
||||
this.notifyFailure('Full name and email are required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword && newPassword !== confirmPassword) {
|
||||
this.notifyFailure('New password and confirm password do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword && !currentPassword) {
|
||||
this.notifyFailure('Current password is required to change password');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/users/me`, {
|
||||
method: 'PUT',
|
||||
headers: this.getAuthHeaders(true),
|
||||
body: JSON.stringify({
|
||||
fullname,
|
||||
email,
|
||||
currentPassword,
|
||||
newPassword
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
this.notifyFailure(data.message || 'Update profile failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.user) {
|
||||
this.currentUser = {
|
||||
...this.currentUser,
|
||||
...data.user,
|
||||
role: data.user.role || data.user.Role || this.currentUser.role || this.currentUser.Role
|
||||
};
|
||||
this.saveToStorage('currentUser', this.currentUser);
|
||||
this.updateAccountDisplay();
|
||||
}
|
||||
|
||||
closeProfileModal();
|
||||
this.notifySuccess(data.message || 'Profile updated');
|
||||
|
||||
if (data.verificationRequired && data.emailSent === false) {
|
||||
if (data.verificationPreviewUrl) {
|
||||
this.notifyWarning(`Email confirmation link (dev): ${data.verificationPreviewUrl}`);
|
||||
} else {
|
||||
this.notifyWarning('Email changed but verification email could not be sent.');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.notifyFailure('Update profile failed');
|
||||
}
|
||||
}
|
||||
|
||||
loadFromStorage(key) {
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
@@ -1618,7 +1848,7 @@ class AccountManager {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json', 'x-user-role': this.getCurrentUserRole() },
|
||||
headers: this.getAuthHeaders(true),
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
@@ -1643,7 +1873,7 @@ class AccountManager {
|
||||
async viewUserDetails(userId) {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/users/${userId}`, {
|
||||
headers: { 'x-user-role': this.getCurrentUserRole() }
|
||||
headers: this.getAuthHeaders(false)
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
@@ -1804,7 +2034,7 @@ class AccountManager {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/users/${userId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-user-role': this.getCurrentUserRole() }
|
||||
headers: this.getAuthHeaders(false)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
@@ -1873,6 +2103,13 @@ function closeUserDetailsModal() {
|
||||
}
|
||||
}
|
||||
|
||||
function closeProfileModal() {
|
||||
const profileContainer = document.getElementById('profileModalContainer');
|
||||
if (profileContainer) {
|
||||
profileContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize app when DOM is ready
|
||||
let app;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
@@ -88,13 +88,13 @@
|
||||
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
|
||||
<button id="profileBtn" type="button" class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" title="Edit profile">
|
||||
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
|
||||
<div class="flex flex-col">
|
||||
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
|
||||
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
|
||||
<span class="material-symbols-outlined">logout</span>
|
||||
</button>
|
||||
|
||||
@@ -107,6 +107,14 @@
|
||||
|
||||
<!-- Error Message -->
|
||||
<div id="errorMessage" class="hidden bg-error-container/20 text-error/80 border border-error/30 rounded-lg px-4 py-3 text-xs font-medium"></div>
|
||||
|
||||
<div id="verifyNotice" class="hidden bg-amber-50 text-amber-800 border border-amber-200 rounded-lg px-4 py-3 text-xs font-medium space-y-2">
|
||||
<p id="verifyNoticeText">Please confirm your email before signing in.</p>
|
||||
<button id="resendVerifyBtn" type="button" class="inline-flex items-center gap-1 px-3 py-1.5 rounded-md bg-amber-100 hover:bg-amber-200 text-amber-900 font-semibold transition-colors">
|
||||
<span class="material-symbols-outlined text-sm">forward_to_inbox</span>
|
||||
<span>Resend confirmation email</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Register Form -->
|
||||
@@ -186,6 +194,7 @@
|
||||
</button>
|
||||
|
||||
<div id="registerErrorMessage" class="hidden bg-error-container/20 text-error/80 border border-error/30 rounded-lg px-4 py-3 text-xs font-medium"></div>
|
||||
<div id="registerSuccessMessage" class="hidden bg-green-50 text-green-800 border border-green-200 rounded-lg px-4 py-3 text-xs font-medium"></div>
|
||||
</form>
|
||||
|
||||
<!-- Footer -->
|
||||
@@ -210,12 +219,17 @@
|
||||
const rememberCheckbox = document.getElementById('remember');
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const registerErrorMessage = document.getElementById('registerErrorMessage');
|
||||
const registerSuccessMessage = document.getElementById('registerSuccessMessage');
|
||||
const verifyNotice = document.getElementById('verifyNotice');
|
||||
const verifyNoticeText = document.getElementById('verifyNoticeText');
|
||||
const resendVerifyBtn = document.getElementById('resendVerifyBtn');
|
||||
const regFullnameInput = document.getElementById('regFullname');
|
||||
const regEmailInput = document.getElementById('regEmail');
|
||||
const regUsernameInput = document.getElementById('regUsername');
|
||||
const regPasswordInput = document.getElementById('regPassword');
|
||||
const loginTab = document.getElementById('loginTab');
|
||||
const registerTab = document.getElementById('registerTab');
|
||||
let pendingVerificationIdentifier = '';
|
||||
let currentMode = 'login';
|
||||
|
||||
const setMode = (mode) => {
|
||||
@@ -226,6 +240,8 @@
|
||||
registerForm.classList.toggle('hidden', isLogin);
|
||||
errorMessage.classList.add('hidden');
|
||||
registerErrorMessage.classList.add('hidden');
|
||||
registerSuccessMessage.classList.add('hidden');
|
||||
verifyNotice.classList.add('hidden');
|
||||
|
||||
const activate = (btn, active) => {
|
||||
btn.classList.toggle('bg-primary', active);
|
||||
@@ -262,6 +278,7 @@
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
errorMessage.classList.add('hidden');
|
||||
verifyNotice.classList.add('hidden');
|
||||
|
||||
const username = usernameInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
@@ -291,6 +308,10 @@
|
||||
|
||||
// Redirect to dashboard
|
||||
window.location.href = './index.html';
|
||||
} else if (data.requiresEmailVerification) {
|
||||
pendingVerificationIdentifier = data.username || data.email || username;
|
||||
verifyNoticeText.textContent = data.message || 'Please confirm your email before signing in';
|
||||
verifyNotice.classList.remove('hidden');
|
||||
} else {
|
||||
// Show error
|
||||
errorMessage.textContent = data.message || 'Invalid username or password';
|
||||
@@ -308,6 +329,7 @@
|
||||
registerForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
registerErrorMessage.classList.add('hidden');
|
||||
registerSuccessMessage.classList.add('hidden');
|
||||
|
||||
const payload = {
|
||||
fullname: regFullnameInput.value.trim(),
|
||||
@@ -316,8 +338,8 @@
|
||||
password: regPasswordInput.value
|
||||
};
|
||||
|
||||
if (!payload.username || !payload.password) {
|
||||
registerErrorMessage.textContent = 'Username and password are required';
|
||||
if (!payload.username || !payload.password || !payload.email) {
|
||||
registerErrorMessage.textContent = 'Username, password and email are required';
|
||||
registerErrorMessage.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
@@ -333,10 +355,19 @@
|
||||
const isJson = response.headers.get('content-type')?.includes('application/json');
|
||||
const data = isJson ? await response.json() : null;
|
||||
|
||||
if (response.ok && data?.success && data.user) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(data.user));
|
||||
localStorage.setItem('rememberedUsername', payload.username);
|
||||
window.location.href = './index.html';
|
||||
if (response.ok && data?.success) {
|
||||
const lines = [data.message || 'Registration successful. Please check your email to confirm account.'];
|
||||
if (data.verificationPreviewUrl) {
|
||||
lines.push(`Development verification link: ${data.verificationPreviewUrl}`);
|
||||
}
|
||||
|
||||
regPasswordInput.value = '';
|
||||
setMode('login');
|
||||
usernameInput.value = payload.username;
|
||||
passwordInput.value = '';
|
||||
pendingVerificationIdentifier = payload.email || payload.username;
|
||||
verifyNoticeText.textContent = lines.join(' ');
|
||||
verifyNotice.classList.remove('hidden');
|
||||
} else {
|
||||
const fallback = isJson ? (data?.message || 'Registration failed') : 'Server error (HTML response)';
|
||||
registerErrorMessage.textContent = fallback;
|
||||
@@ -348,6 +379,35 @@
|
||||
console.error('Register error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
resendVerifyBtn.addEventListener('click', async () => {
|
||||
const identifier = pendingVerificationIdentifier || usernameInput.value.trim();
|
||||
if (!identifier) {
|
||||
verifyNoticeText.textContent = 'Enter username/email first, then try resending verification.';
|
||||
verifyNotice.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/resend-verification', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ identifier })
|
||||
});
|
||||
const data = await response.json();
|
||||
verifyNoticeText.textContent = data.message || 'Verification email processed.';
|
||||
verifyNotice.classList.remove('hidden');
|
||||
if (data.verificationPreviewUrl) {
|
||||
verifyNoticeText.textContent += ` Development link: ${data.verificationPreviewUrl}`;
|
||||
}
|
||||
} catch (error) {
|
||||
verifyNoticeText.textContent = 'Cannot resend verification email right now.';
|
||||
verifyNotice.classList.remove('hidden');
|
||||
console.error('Resend verification error:', error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
70
public/pages/verify-email.html
Normal file
70
public/pages/verify-email.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="light">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Confirm Email - AccManager</title>
|
||||
<link rel="stylesheet" href="../css/main.css" />
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; min-height: 100vh; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-slate-100 via-white to-blue-100 text-slate-900 antialiased">
|
||||
<main class="min-h-screen flex items-center justify-center px-4">
|
||||
<section class="w-full max-w-md bg-white rounded-2xl border border-slate-200 shadow-lg p-8">
|
||||
<h1 class="text-2xl font-black tracking-tight mb-2">Email Confirmation</h1>
|
||||
<p class="text-sm text-slate-600 mb-6">We are confirming your account.</p>
|
||||
|
||||
<div id="statusBox" class="rounded-lg border px-4 py-3 text-sm bg-slate-50 border-slate-200 text-slate-700">
|
||||
Confirming your email, please wait...
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex gap-3">
|
||||
<a href="./login.html" class="flex-1 text-center px-4 py-2 rounded-lg bg-slate-100 hover:bg-slate-200 text-slate-700 font-semibold transition-colors">Back to Login</a>
|
||||
<a id="signInBtn" href="./login.html" class="hidden flex-1 text-center px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors">Sign In</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(async () => {
|
||||
const statusBox = document.getElementById('statusBox');
|
||||
const signInBtn = document.getElementById('signInBtn');
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const token = params.get('token');
|
||||
|
||||
if (!token) {
|
||||
statusBox.className = 'rounded-lg border px-4 py-3 text-sm bg-red-50 border-red-200 text-red-700';
|
||||
statusBox.textContent = 'Missing verification token.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/auth/verify-email?token=${encodeURIComponent(token)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
statusBox.className = 'rounded-lg border px-4 py-3 text-sm bg-green-50 border-green-200 text-green-700';
|
||||
if (data.autoLogin && data.user) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(data.user));
|
||||
statusBox.textContent = data.message || 'Email confirmed. Redirecting to dashboard...';
|
||||
setTimeout(() => {
|
||||
window.location.href = './index.html';
|
||||
}, 1000);
|
||||
} else {
|
||||
statusBox.textContent = data.message || 'Email confirmed successfully.';
|
||||
signInBtn.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
statusBox.className = 'rounded-lg border px-4 py-3 text-sm bg-red-50 border-red-200 text-red-700';
|
||||
statusBox.textContent = data.message || 'Email confirmation failed.';
|
||||
}
|
||||
} catch (error) {
|
||||
statusBox.className = 'rounded-lg border px-4 py-3 text-sm bg-red-50 border-red-200 text-red-700';
|
||||
statusBox.textContent = 'Cannot verify email right now. Please try again later.';
|
||||
console.error('Verify email page error:', error);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user