Skip to content

Commit 1f98d8f

Browse files
committed
feat: add resend verification email button on login error
1 parent b797a6b commit 1f98d8f

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

public/css/style.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,34 @@ cap-widget {
424424
display: block;
425425
}
426426

427+
.auth-error.show:has(.auth-resend-btn) {
428+
display: flex;
429+
flex-direction: column;
430+
gap: 10px;
431+
}
432+
433+
.auth-resend-btn {
434+
background: rgba(239, 68, 68, 0.15);
435+
border: 1px solid rgba(239, 68, 68, 0.4);
436+
color: var(--accent-red);
437+
padding: 6px 14px;
438+
border-radius: var(--radius-sm);
439+
font-size: 0.8rem;
440+
font-weight: 600;
441+
cursor: pointer;
442+
transition: background var(--transition), opacity var(--transition);
443+
align-self: flex-start;
444+
}
445+
446+
.auth-resend-btn:hover:not(:disabled) {
447+
background: rgba(239, 68, 68, 0.25);
448+
}
449+
450+
.auth-resend-btn:disabled {
451+
opacity: 0.5;
452+
cursor: not-allowed;
453+
}
454+
427455
/* ===== DASHBOARD LAYOUT ===== */
428456
.dashboard-layout {
429457
display: flex;

public/js/app.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,33 @@ function showToast(message, type = 'success') {
278278
function showError(form, message) {
279279
const errorEl = form.querySelector('.auth-error');
280280
if (errorEl) {
281-
errorEl.textContent = message;
281+
if (message && message.includes('verify your email')) {
282+
const email = (form.querySelector('#login-email')?.value || '').trim();
283+
errorEl.innerHTML = html`
284+
<span>${escapeHtml(message)}</span>
285+
<button type="button" class="auth-resend-btn" id="auth-resend-btn" ${!email ? 'disabled' : ''}>Resend verification link</button>
286+
`;
287+
const resendBtn = errorEl.querySelector('#auth-resend-btn');
288+
if (resendBtn) {
289+
resendBtn.addEventListener('click', async () => {
290+
if (!email) return;
291+
resendBtn.disabled = true;
292+
resendBtn.textContent = 'Sending...';
293+
try {
294+
await api('/auth/resend-verification', {
295+
method: 'POST',
296+
body: JSON.stringify({ email }),
297+
});
298+
resendBtn.textContent = 'Email sent!';
299+
} catch (err) {
300+
resendBtn.textContent = 'Failed to send';
301+
resendBtn.disabled = false;
302+
}
303+
});
304+
}
305+
} else {
306+
errorEl.textContent = message;
307+
}
282308
errorEl.classList.add('show');
283309
}
284310
}

routes/auth.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,47 @@ router.get('/verify-email', async (req, res) => {
300300
}
301301
});
302302

303+
router.post('/resend-verification', async (req, res) => {
304+
try {
305+
const { email } = req.body;
306+
if (!email || typeof email !== 'string') {
307+
return res.status(400).json({ error: 'Email is required' });
308+
}
309+
310+
const users = await query('SELECT id, email, username, email_verified FROM users WHERE email = ?', [email]);
311+
if (users.length === 0) {
312+
return res.json({ message: 'If an account with that email exists, a verification link has been sent.' });
313+
}
314+
315+
const user = users[0];
316+
if (user.email_verified) {
317+
return res.json({ message: 'Email already verified. You can now sign in.' });
318+
}
319+
320+
const verificationToken = randomBytes(32).toString('hex');
321+
const tokenExpires = new Date(Date.now() + 24 * 60 * 60 * 1000);
322+
323+
await query(
324+
'UPDATE users SET verification_token = ?, verification_token_expires = ? WHERE id = ?',
325+
[verificationToken, tokenExpires, user.id]
326+
);
327+
328+
try {
329+
await sendVerificationEmail(user.email, user.username, verificationToken);
330+
} catch (err) {
331+
console.error('Failed to send verification email:', err.message);
332+
return res.status(500).json({ error: 'Failed to send verification email. Please try again later.' });
333+
}
334+
335+
await logActivity(user.id, 'verification_resent', 'Resent verification email');
336+
337+
res.json({ message: 'Verification email sent. Check your inbox.' });
338+
} catch (err) {
339+
console.error('Resend verification error:', err.message);
340+
res.status(500).json({ error: 'Failed to resend verification email' });
341+
}
342+
});
343+
303344
router.post('/login', async (req, res) => {
304345
try {
305346
const { email, password, capToken } = req.body;

0 commit comments

Comments
 (0)