confirm email

This commit is contained in:
2026-04-02 15:23:32 +07:00
parent 6c2e89dc93
commit 5a7bf191d0
11 changed files with 893 additions and 39 deletions

View File

@@ -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>

View File

@@ -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>

View 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>