use display name function

This commit is contained in:
miloschwartz
2026-01-19 20:49:39 -08:00
parent e09cd6c16c
commit b299f3d6aa
14 changed files with 121 additions and 26 deletions

View File

@@ -80,7 +80,8 @@ async function queryApprovals(
user: {
name: users.name,
userId: users.userId,
username: users.username
username: users.username,
email: users.email
}
})
.from(approvals)

View File

@@ -1,5 +1,6 @@
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { ListUsersResponse } from "@server/routers/user";
import { AxiosResponse } from "axios";
import UsersTable, { UserRow } from "../../../../../components/UsersTable";
@@ -73,7 +74,11 @@ export default async function UsersPage(props: UsersPageProps) {
return {
id: user.id,
username: user.username,
displayUsername: user.email || user.name || user.username,
displayUsername: getUserDisplayName({
email: user.email,
name: user.name,
username: user.username
}),
name: user.name,
email: user.email,
type: user.type,

View File

@@ -40,6 +40,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { useResourceContext } from "@app/hooks/useResourceContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { orgQueries, resourceQueries } from "@app/lib/queries";
import { zodResolver } from "@hookform/resolvers/zod";
import { build } from "@server/build";
@@ -154,7 +155,10 @@ export default function ResourceAuthenticationPage() {
const allUsers = useMemo(() => {
return orgUsers.map((user) => ({
id: user.id.toString(),
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
text: `${getUserDisplayName({
email: user.email,
username: user.username
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
}));
}, [orgUsers]);
@@ -229,7 +233,10 @@ export default function ResourceAuthenticationPage() {
"users",
resourceUsers.map((i) => ({
id: i.userId.toString(),
text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
text: `${getUserDisplayName({
email: i.email,
username: i.username
})}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
}))
);

View File

@@ -2,6 +2,7 @@ import { internal } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { redirect } from "next/navigation";
import { authCookieHeader } from "@app/lib/api/cookies";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { AdminGetUserResponse } from "@server/routers/user/adminGetUser";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { cache } from "react";
@@ -44,7 +45,15 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
return (
<>
<SettingsSectionTitle
title={`${user?.email || user?.name || user?.username}`}
title={
user
? getUserDisplayName({
email: user.email,
name: user.name,
username: user.username
})
: ""
}
description={t("userDescription2")}
/>
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>

View File

@@ -1,6 +1,7 @@
import { verifySession } from "@app/lib/auth/verifySession";
import { redirect } from "next/navigation";
import DeviceLoginForm from "@/components/DeviceLoginForm";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { cache } from "react";
export const dynamic = "force-dynamic";
@@ -24,7 +25,12 @@ export default async function DeviceLoginPage({ searchParams }: Props) {
);
}
const userName = user?.name || user?.username || "";
const userName = user
? getUserDisplayName({
name: user.name,
username: user.username
})
: "";
return (
<DeviceLoginForm

View File

@@ -11,6 +11,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { toast } from "@app/hooks/useToast";
import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from "next-intl";
import {
@@ -321,10 +322,13 @@ export default function UsersTable({ users }: Props) {
<div className="space-y-2">
<p>
{t("userQuestionRemove", {
selectedUser:
selected?.email ||
selected?.name ||
selected?.username
selectedUser: selected
? getUserDisplayName({
email: selected.email,
name: selected.name,
username: selected.username
})
: ""
})}
</p>
@@ -337,9 +341,11 @@ export default function UsersTable({ users }: Props) {
}
buttonText={t("userDeleteConfirm")}
onConfirm={async () => deleteUser(selected!.id)}
string={
selected.email || selected.name || selected.username
}
string={getUserDisplayName({
email: selected.email,
name: selected.name,
username: selected.username
})}
title={t("userDeleteServer")}
/>
)}

View File

@@ -2,6 +2,7 @@
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { cn } from "@app/lib/cn";
import {
approvalFiltersSchema,
@@ -185,7 +186,11 @@ function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {
<LaptopMinimal className="size-4 text-muted-foreground flex-none relative top-2 sm:top-0" />
<span>
<span className="text-primary">
{approval.user.username}
{getUserDisplayName({
email: approval.user.email,
name: approval.user.name,
username: approval.user.username
})}
</span>
&nbsp;
{approval.type === "user_device" && (

View File

@@ -46,6 +46,7 @@ import { Switch } from "@app/components/ui/switch";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { cn } from "@app/lib/cn";
import { orgQueries } from "@app/lib/queries";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -270,7 +271,10 @@ export default function CreateInternalResourceDialog({
const allUsers = usersResponse.map((user) => ({
id: user.id.toString(),
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
text: `${getUserDisplayName({
email: user.email,
username: user.username
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
}));
const allClients = clientsResponse

View File

@@ -36,6 +36,7 @@ import {
import { toast } from "@app/hooks/useToast";
import { useTranslations } from "next-intl";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { Tag, TagInput } from "@app/components/tags/tag-input";
import { UserType } from "@server/types/UserTypes";
@@ -304,7 +305,10 @@ export default function EditInternalResourceDialog({
const allUsers = (usersQuery.data ?? []).map((user) => ({
id: user.id.toString(),
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
text: `${getUserDisplayName({
email: user.email,
username: user.username
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
}));
const machineClients = (clientsQuery.data ?? [])
@@ -330,7 +334,10 @@ export default function EditInternalResourceDialog({
const formUsers = (resourceUsersQuery.data ?? []).map((i) => ({
id: i.userId.toString(),
text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
text: `${getUserDisplayName({
email: i.email,
username: i.username
})}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
}));
return {

View File

@@ -14,6 +14,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { Laptop, LogOut, Moon, Sun, Smartphone } from "lucide-react";
import { useTheme } from "next-themes";
import { useRouter } from "next/navigation";
@@ -49,9 +50,8 @@ export default function ProfileIcon() {
const t = useTranslations();
function getInitials() {
return (user.email || user.name || user.username)
.substring(0, 1)
.toUpperCase();
const displayName = getUserDisplayName({ user });
return displayName.substring(0, 1).toUpperCase();
}
function handleThemeChange(theme: "light" | "dark" | "system") {
@@ -109,7 +109,7 @@ export default function ProfileIcon() {
{t("signingAs")}
</p>
<p className="text-xs leading-none text-muted-foreground">
{user.email || user.name || user.username}
{getUserDisplayName({ user })}
</p>
</div>
{user.serverAdmin ? (

View File

@@ -12,6 +12,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import {
ArrowRight,
ArrowUpDown,
@@ -344,7 +345,10 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
href={`/${r.orgId}/settings/access/users/${r.userId}`}
>
<Button variant="outline">
{r.userEmail || r.username || r.userId}
{getUserDisplayName({
email: r.userEmail,
username: r.username
}) || r.userId}
<ArrowUpRight className="ml-2 h-4 w-4" />
</Button>
</Link>

View File

@@ -19,6 +19,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from "next-intl";
@@ -271,10 +272,13 @@ export default function UsersTable({ users: u }: UsersTableProps) {
buttonText={t("userRemoveOrgConfirm")}
onConfirm={removeUser}
string={
selectedUser?.email ||
selectedUser?.name ||
selectedUser?.username ||
""
selectedUser
? getUserDisplayName({
email: selectedUser.email,
name: selectedUser.name,
username: selectedUser.username
})
: ""
}
title={t("userRemoveOrg")}
/>

View File

@@ -0,0 +1,36 @@
import { GetUserResponse } from "@server/routers/user";
type UserDisplayNameInput =
| {
user: GetUserResponse;
}
| {
email?: string | null;
name?: string | null;
username?: string | null;
};
/**
* Gets the display name for a user.
* Priority: email > name > username
*
* @param input - Either a user object or individual email, name, username properties
* @returns The display name string
*/
export function getUserDisplayName(input: UserDisplayNameInput): string {
let email: string | null | undefined;
let name: string | null | undefined;
let username: string | null | undefined;
if ("user" in input) {
email = input.user.email;
name = input.user.name;
username = input.user.username;
} else {
email = input.email;
name = input.name;
username = input.username;
}
return email || name || username || "";
}

View File

@@ -340,6 +340,7 @@ export type ApprovalItem = {
name: string | null;
userId: string;
username: string;
email: string | null;
};
};