feat: improve public user identity fallbacks

This commit is contained in:
Haitao Pan 2026-04-12 18:52:08 +08:00
parent c622d0b1d2
commit 5f1b59be70
6 changed files with 50 additions and 12 deletions

View File

@ -59,14 +59,20 @@ export default function Header({
const isLoading = useUserStore((state) => state.isLoading);
const role: UserRole = user?.role ?? "guest";
const badge = ROLE_BADGES[role];
const shouldRenderPublicEmail = hasPublicUserEmail({
email: user?.email,
role,
});
const accountLabel =
user?.name ?? user?.username ?? user?.email ?? "Guest user";
user?.name ??
user?.username ??
(shouldRenderPublicEmail ? user?.email : undefined) ??
"Guest user";
const accountInitial = resolveAccountInitial(accountLabel);
const statusBadge = isLoading ? "Syncing" : badge.label;
const badgeClasses = isLoading
? "bg-[var(--color-surface-muted)] text-[var(--color-text-subtle)] opacity-70"
: badge.className;
const shouldRenderPublicEmail = hasPublicUserEmail(user?.email);
return (
<header className="sticky top-0 z-30 overflow-hidden border-b border-[color:var(--color-surface-border)] bg-[var(--color-surface-elevated)] text-[var(--color-text)] shadow-[var(--shadow-soft)] backdrop-blur-xl transition-colors">

View File

@ -58,11 +58,14 @@ export default function Navbar() {
const user = useUserStore((state) => state.user);
const nav = translations[language].nav;
const accountCopy = nav.account;
const shouldRenderPublicEmail = hasPublicUserEmail({
email: user?.email,
role: user?.role,
});
const accountInitial =
user?.username?.charAt(0)?.toUpperCase() ??
user?.email?.charAt(0)?.toUpperCase() ??
(shouldRenderPublicEmail ? user?.email?.charAt(0)?.toUpperCase() : null) ??
"?";
const shouldRenderPublicEmail = hasPublicUserEmail(user?.email);
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
const accountMenuRef = useRef<HTMLDivElement | null>(null);

View File

@ -41,14 +41,17 @@ export default function UnifiedNavigation() {
const user = useUserStore((state) => state.user);
const { toggleOpen } = useMoltbotStore();
const nav = translations[language].nav;
const shouldRenderPublicEmail = hasPublicUserEmail({
email: user?.email,
role: user?.role,
});
const accountInitial =
user?.username?.charAt(0)?.toUpperCase() ??
user?.email?.charAt(0)?.toUpperCase() ??
(shouldRenderPublicEmail ? user?.email?.charAt(0)?.toUpperCase() : null) ??
"?";
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
const accountMenuRef = useRef<HTMLDivElement | null>(null);
const isChinese = language === "zh";
const shouldRenderPublicEmail = hasPublicUserEmail(user?.email);
useEffect(() => {
if (typeof window === "undefined") return;

View File

@ -25,7 +25,18 @@ describe("publicUserIdentity", () => {
});
it("detects whether a public email should be rendered", () => {
expect(hasPublicUserEmail("")).toBe(false);
expect(hasPublicUserEmail("guest@svc.plus")).toBe(true);
expect(hasPublicUserEmail({ email: "" })).toBe(false);
expect(
hasPublicUserEmail({
email: "sandbox@svc.plus",
role: "guest",
}),
).toBe(false);
expect(
hasPublicUserEmail({
email: "admin@svc.plus",
role: "admin",
}),
).toBe(true);
});
});

View File

@ -2,11 +2,15 @@ function normalizeText(value?: string | null): string {
return typeof value === "string" ? value.trim() : "";
}
function normalizeRole(value?: string | null): string {
return normalizeText(value).toLowerCase();
}
export function resolvePublicUserEmail(input: {
email?: string | null;
role?: string | null;
}): string {
const normalizedRole = normalizeText(input.role).toLowerCase();
const normalizedRole = normalizeRole(input.role);
if (normalizedRole === "guest") {
return "";
}
@ -14,6 +18,13 @@ export function resolvePublicUserEmail(input: {
return normalizeText(input.email);
}
export function hasPublicUserEmail(email?: string | null): boolean {
return normalizeText(email).length > 0;
export function hasPublicUserEmail(input: {
email?: string | null;
role?: string | null;
}): boolean {
if (normalizeRole(input.role) === "guest") {
return false;
}
return normalizeText(input.email).length > 0;
}

View File

@ -51,12 +51,16 @@ export default function UserOverview({ hideMfaMainPrompt = false }: UserOverview
const logout = useUserStore((state) => state.logout)
const [copied, setCopied] = useState(false)
const [guestBoundNodeAddress, setGuestBoundNodeAddress] = useState<string | null>(null)
const shouldRenderPublicEmail = hasPublicUserEmail({
email: user?.email,
role: user?.role,
})
const displayName = useMemo(() => resolveDisplayName(user), [user])
const uuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? '—'
const vlessUuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? null
const username = user?.username ?? '—'
const email = hasPublicUserEmail(user?.email) ? user?.email : '—'
const email = shouldRenderPublicEmail ? user?.email : '—'
const docsUrl = mfaCopy.actions.docsUrl
const isGuestSandboxReadOnly = Boolean(user?.isGuest && user?.isReadOnly)
const guestUuidExpiresAtText = useMemo(() => {