feat(identity): store verified email on profile + show in account modal

Email verification wrote to the `email` column but account status read
from `profile_email` — now setUserEmail writes both. Account modal email
section displays the verified address when collapsed. Tour finale step
triggers identity setup on completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-22 15:49:09 -07:00
parent 4c1cd21b8c
commit 29c4f48634
2 changed files with 7 additions and 4 deletions

View File

@ -711,7 +711,7 @@ export class RStackIdentity extends HTMLElement {
let openSection: string | null = null;
// Account completion status
let acctStatus: { email: boolean; multiDevice: boolean; socialRecovery: boolean; guardianCount: number; credentialCount: number } | null = null;
let acctStatus: { email: boolean; emailAddress?: string | null; multiDevice: boolean; socialRecovery: boolean; guardianCount: number; credentialCount: number } | null = null;
// Lazy-loaded data
let guardians: { id: string; name: string; email?: string; status: string }[] = [];
@ -819,10 +819,13 @@ export class RStackIdentity extends HTMLElement {
</div>`;
}
}
const emailDisplay = !isOpen && done && acctStatus?.emailAddress
? `<span style="color:var(--rs-text-secondary);font-size:0.85rem;margin-left:8px">${acctStatus.emailAddress}</span>`
: "";
return `
<div class="account-section${isOpen ? " open" : ""}${done === false ? " section--warning" : ""}">
<div class="account-section-header" data-section="email">
<span>${statusDot(done)} Email</span>
<span>${statusDot(done)} Email${emailDisplay}</span>
<span class="section-arrow">${isOpen ? "▾" : "▸"}</span>
</div>
${body}
@ -1087,7 +1090,7 @@ export class RStackIdentity extends HTMLElement {
body: JSON.stringify({ email: emailAddr, code }),
});
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || "Verification failed");
if (acctStatus) acctStatus.email = true;
if (acctStatus) { acctStatus.email = true; acctStatus.emailAddress = emailAddr; }
openSection = null; render();
this.dispatchEvent(new CustomEvent("identity-action", { bubbles: true, composed: true, detail: { action: "email-added", email: emailAddr } }));
} catch (e: any) {

View File

@ -224,7 +224,7 @@ export async function cleanExpiredChallenges(): Promise<number> {
// ============================================================================
export async function setUserEmail(userId: string, email: string): Promise<void> {
await sql`UPDATE users SET email = ${email} WHERE id = ${userId}`;
await sql`UPDATE users SET email = ${email}, profile_email = ${email}, updated_at = NOW() WHERE id = ${userId}`;
}
export async function getUserByEmail(email: string) {