feat: improve public user identity fallbacks
This commit is contained in:
parent
c622d0b1d2
commit
5f1b59be70
@ -59,14 +59,20 @@ export default function Header({
|
|||||||
const isLoading = useUserStore((state) => state.isLoading);
|
const isLoading = useUserStore((state) => state.isLoading);
|
||||||
const role: UserRole = user?.role ?? "guest";
|
const role: UserRole = user?.role ?? "guest";
|
||||||
const badge = ROLE_BADGES[role];
|
const badge = ROLE_BADGES[role];
|
||||||
|
const shouldRenderPublicEmail = hasPublicUserEmail({
|
||||||
|
email: user?.email,
|
||||||
|
role,
|
||||||
|
});
|
||||||
const accountLabel =
|
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 accountInitial = resolveAccountInitial(accountLabel);
|
||||||
const statusBadge = isLoading ? "Syncing" : badge.label;
|
const statusBadge = isLoading ? "Syncing" : badge.label;
|
||||||
const badgeClasses = isLoading
|
const badgeClasses = isLoading
|
||||||
? "bg-[var(--color-surface-muted)] text-[var(--color-text-subtle)] opacity-70"
|
? "bg-[var(--color-surface-muted)] text-[var(--color-text-subtle)] opacity-70"
|
||||||
: badge.className;
|
: badge.className;
|
||||||
const shouldRenderPublicEmail = hasPublicUserEmail(user?.email);
|
|
||||||
|
|
||||||
return (
|
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">
|
<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">
|
||||||
|
|||||||
@ -58,11 +58,14 @@ export default function Navbar() {
|
|||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const nav = translations[language].nav;
|
const nav = translations[language].nav;
|
||||||
const accountCopy = nav.account;
|
const accountCopy = nav.account;
|
||||||
|
const shouldRenderPublicEmail = hasPublicUserEmail({
|
||||||
|
email: user?.email,
|
||||||
|
role: user?.role,
|
||||||
|
});
|
||||||
const accountInitial =
|
const accountInitial =
|
||||||
user?.username?.charAt(0)?.toUpperCase() ??
|
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 [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
||||||
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
|||||||
@ -41,14 +41,17 @@ export default function UnifiedNavigation() {
|
|||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const { toggleOpen } = useMoltbotStore();
|
const { toggleOpen } = useMoltbotStore();
|
||||||
const nav = translations[language].nav;
|
const nav = translations[language].nav;
|
||||||
|
const shouldRenderPublicEmail = hasPublicUserEmail({
|
||||||
|
email: user?.email,
|
||||||
|
role: user?.role,
|
||||||
|
});
|
||||||
const accountInitial =
|
const accountInitial =
|
||||||
user?.username?.charAt(0)?.toUpperCase() ??
|
user?.username?.charAt(0)?.toUpperCase() ??
|
||||||
user?.email?.charAt(0)?.toUpperCase() ??
|
(shouldRenderPublicEmail ? user?.email?.charAt(0)?.toUpperCase() : null) ??
|
||||||
"?";
|
"?";
|
||||||
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
||||||
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
const accountMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
const isChinese = language === "zh";
|
const isChinese = language === "zh";
|
||||||
const shouldRenderPublicEmail = hasPublicUserEmail(user?.email);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
|||||||
@ -25,7 +25,18 @@ describe("publicUserIdentity", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("detects whether a public email should be rendered", () => {
|
it("detects whether a public email should be rendered", () => {
|
||||||
expect(hasPublicUserEmail("")).toBe(false);
|
expect(hasPublicUserEmail({ email: "" })).toBe(false);
|
||||||
expect(hasPublicUserEmail("guest@svc.plus")).toBe(true);
|
expect(
|
||||||
|
hasPublicUserEmail({
|
||||||
|
email: "sandbox@svc.plus",
|
||||||
|
role: "guest",
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
hasPublicUserEmail({
|
||||||
|
email: "admin@svc.plus",
|
||||||
|
role: "admin",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,11 +2,15 @@ function normalizeText(value?: string | null): string {
|
|||||||
return typeof value === "string" ? value.trim() : "";
|
return typeof value === "string" ? value.trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRole(value?: string | null): string {
|
||||||
|
return normalizeText(value).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
export function resolvePublicUserEmail(input: {
|
export function resolvePublicUserEmail(input: {
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
role?: string | null;
|
role?: string | null;
|
||||||
}): string {
|
}): string {
|
||||||
const normalizedRole = normalizeText(input.role).toLowerCase();
|
const normalizedRole = normalizeRole(input.role);
|
||||||
if (normalizedRole === "guest") {
|
if (normalizedRole === "guest") {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -14,6 +18,13 @@ export function resolvePublicUserEmail(input: {
|
|||||||
return normalizeText(input.email);
|
return normalizeText(input.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasPublicUserEmail(email?: string | null): boolean {
|
export function hasPublicUserEmail(input: {
|
||||||
return normalizeText(email).length > 0;
|
email?: string | null;
|
||||||
|
role?: string | null;
|
||||||
|
}): boolean {
|
||||||
|
if (normalizeRole(input.role) === "guest") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeText(input.email).length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,12 +51,16 @@ export default function UserOverview({ hideMfaMainPrompt = false }: UserOverview
|
|||||||
const logout = useUserStore((state) => state.logout)
|
const logout = useUserStore((state) => state.logout)
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [guestBoundNodeAddress, setGuestBoundNodeAddress] = useState<string | null>(null)
|
const [guestBoundNodeAddress, setGuestBoundNodeAddress] = useState<string | null>(null)
|
||||||
|
const shouldRenderPublicEmail = hasPublicUserEmail({
|
||||||
|
email: user?.email,
|
||||||
|
role: user?.role,
|
||||||
|
})
|
||||||
|
|
||||||
const displayName = useMemo(() => resolveDisplayName(user), [user])
|
const displayName = useMemo(() => resolveDisplayName(user), [user])
|
||||||
const uuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? '—'
|
const uuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? '—'
|
||||||
const vlessUuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? null
|
const vlessUuid = user?.proxyUuid ?? user?.uuid ?? user?.id ?? null
|
||||||
const username = user?.username ?? '—'
|
const username = user?.username ?? '—'
|
||||||
const email = hasPublicUserEmail(user?.email) ? user?.email : '—'
|
const email = shouldRenderPublicEmail ? user?.email : '—'
|
||||||
const docsUrl = mfaCopy.actions.docsUrl
|
const docsUrl = mfaCopy.actions.docsUrl
|
||||||
const isGuestSandboxReadOnly = Boolean(user?.isGuest && user?.isReadOnly)
|
const isGuestSandboxReadOnly = Boolean(user?.isGuest && user?.isReadOnly)
|
||||||
const guestUuidExpiresAtText = useMemo(() => {
|
const guestUuidExpiresAtText = useMemo(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user