fix(web): UserMenu crashes for phone-only signups (null email + name)
All checks were successful
Deploy to Production / deploy (push) Successful in 56s

Dashboard layout threw TypeError: Cannot read properties of null (reading
'charAt') the moment a phone-only user reached any dashboard page —
user.email and user.name are both null for fresh SMS signups, and
the initial-letter computation didn't tolerate it.

Fallback chain for the visible identifier: name → email → phone →
'Account'. Avatar colour seed falls back to userId. The secondary line
under the name also uses phone when email is null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marco Sadjadi 2026-05-25 22:59:45 +02:00
parent d0f3c202eb
commit c656bd3189

View File

@ -16,8 +16,12 @@ import { useEffect, useRef, useState } from 'react';
interface MeUser {
userId: string;
email: string;
// All three can be null on a fresh phone-only signup: name not collected,
// email not entered, phone is the only stable identifier. Anything that
// dereferences these MUST handle the null case.
email: string | null;
name: string | null;
phone: string | null;
plan?: 'hobby' | 'pro' | 'team' | 'enterprise';
isAdmin?: boolean;
}
@ -81,9 +85,19 @@ export function UserMenu() {
if (!user) return null;
const label = user.name?.trim() || user.email;
const initial = (user.name?.trim() || user.email).charAt(0).toUpperCase();
const shade = avatarShade(user.email);
// Pick the best identifier we have. Phone-only signups have null email
// AND null name — used to crash with .charAt(null) here. Fallback chain:
// name → email → phone → "Account".
const identifier =
user.name?.trim() ||
user.email ||
user.phone ||
'Account';
const label = identifier;
const initial = identifier.charAt(0).toUpperCase() || '?';
// Avatar colour is derived from a stable identifier so it doesn't shift
// across sessions. Fall back to userId so we always have something.
const shade = avatarShade(user.email || user.phone || user.userId);
return (
<div ref={wrapRef} className="relative">
@ -121,7 +135,7 @@ export function UserMenu() {
{label}
</div>
<div className="mono truncate text-[10.5px] text-[--color-fg-subtle]">
{user.email}
{user.email || user.phone || user.userId.slice(0, 8)}
</div>
</div>
</div>