Email confirmation, change email/password, show/hide passwords
CI / check (push) Canceled after 0s

- Sign-up emails a confirmation link (24h); unconfirmed accounts see a
  banner on the web with "send it again", and status in Settings.
- Change email (needs the password): the new address gets a confirmation
  link and only replaces the old one once clicked; the old address gets a
  heads-up with a reset-password link. Pending changes can be cancelled.
- Change password (needs the current one): signs out other sessions.
- Password fields have Show/Hide on web and mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
2026-10-06 13:05:26 -04:00
co-authored by Claude Opus 5.5
parent 45a7bd190a
commit 85e1643c29
25 changed files with 2077 additions and 19 deletions
+24 -1
View File
@@ -2,7 +2,8 @@
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { api, useMe } from '@/lib/api';
import { useState } from 'react';
import { api, client, useMe } from '@/lib/api';
import { SupportLink } from './SupportLink';
export function Nav() {
@@ -14,6 +15,7 @@ export function Nav() {
router.push('/');
};
return (
<>
<nav className="nav">
<Link href="/" className="brand">
<img className="brand-logo" src="/logo.png" alt="" width={30} height={30} />
@@ -41,5 +43,26 @@ export function Nav() {
)}
</div>
</nav>
{me && !me.emailVerified && <VerifyBanner email={me.pendingEmail ?? me.email} />}
</>
);
}
/** Until they confirm, a forgotten password can't be reset; nudge them. */
function VerifyBanner({ email }: { email: string }) {
const [sent, setSent] = useState(false);
const resend = async () => {
try {
await client.resendVerification();
setSent(true);
} catch (err) {
alert((err as Error).message);
}
};
return (
<div className="banner">
Confirm your email: we sent a link to <strong>{email}</strong>.{' '}
{sent ? <span className="muted">Sent again.</span> : <button className="link-button" onClick={resend}>Send it again</button>}
</div>
);
}