From b299f3d6aad18a7ee6c2057181ccc374280bdbc1 Mon Sep 17 00:00:00 2001
From: miloschwartz
Date: Mon, 19 Jan 2026 20:49:39 -0800
Subject: [PATCH] use display name function
---
.../routers/approvals/listApprovals.ts | 3 +-
.../[orgId]/settings/access/users/page.tsx | 7 +++-
.../proxy/[niceId]/authentication/page.tsx | 11 ++++--
src/app/admin/users/[userId]/layout.tsx | 11 +++++-
src/app/auth/login/device/page.tsx | 8 ++++-
src/components/AdminUsersTable.tsx | 20 +++++++----
src/components/ApprovalFeed.tsx | 7 +++-
.../CreateInternalResourceDialog.tsx | 6 +++-
src/components/EditInternalResourceDialog.tsx | 11 ++++--
src/components/ProfileIcon.tsx | 8 ++---
src/components/UserDevicesTable.tsx | 6 +++-
src/components/UsersTable.tsx | 12 ++++---
src/lib/getUserDisplayName.ts | 36 +++++++++++++++++++
src/lib/queries.ts | 1 +
14 files changed, 121 insertions(+), 26 deletions(-)
create mode 100644 src/lib/getUserDisplayName.ts
diff --git a/server/private/routers/approvals/listApprovals.ts b/server/private/routers/approvals/listApprovals.ts
index 6006e48b..76a895a6 100644
--- a/server/private/routers/approvals/listApprovals.ts
+++ b/server/private/routers/approvals/listApprovals.ts
@@ -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)
diff --git a/src/app/[orgId]/settings/access/users/page.tsx b/src/app/[orgId]/settings/access/users/page.tsx
index 662ada60..c1036373 100644
--- a/src/app/[orgId]/settings/access/users/page.tsx
+++ b/src/app/[orgId]/settings/access/users/page.tsx
@@ -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,
diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx
index b26d79a9..3dedea05 100644
--- a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx
+++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx
@@ -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})` : ""}`
}))
);
diff --git a/src/app/admin/users/[userId]/layout.tsx b/src/app/admin/users/[userId]/layout.tsx
index 0c8c50e6..e773a2ec 100644
--- a/src/app/admin/users/[userId]/layout.tsx
+++ b/src/app/admin/users/[userId]/layout.tsx
@@ -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 (
<>
{children}
diff --git a/src/app/auth/login/device/page.tsx b/src/app/auth/login/device/page.tsx
index 2fa5fac5..9b6b2bd2 100644
--- a/src/app/auth/login/device/page.tsx
+++ b/src/app/auth/login/device/page.tsx
@@ -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 (
{t("userQuestionRemove", {
- selectedUser:
- selected?.email ||
- selected?.name ||
- selected?.username
+ selectedUser: selected
+ ? getUserDisplayName({
+ email: selected.email,
+ name: selected.name,
+ username: selected.username
+ })
+ : ""
})}
@@ -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")}
/>
)}
diff --git a/src/components/ApprovalFeed.tsx b/src/components/ApprovalFeed.tsx
index b3d446b1..974a02b2 100644
--- a/src/components/ApprovalFeed.tsx
+++ b/src/components/ApprovalFeed.tsx
@@ -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) {
- {approval.user.username}
+ {getUserDisplayName({
+ email: approval.user.email,
+ name: approval.user.name,
+ username: approval.user.username
+ })}
{approval.type === "user_device" && (
diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx
index 2c9efdf5..4c8d6325 100644
--- a/src/components/CreateInternalResourceDialog.tsx
+++ b/src/components/CreateInternalResourceDialog.tsx
@@ -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
diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx
index 64d29909..4b5030be 100644
--- a/src/components/EditInternalResourceDialog.tsx
+++ b/src/components/EditInternalResourceDialog.tsx
@@ -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 {
diff --git a/src/components/ProfileIcon.tsx b/src/components/ProfileIcon.tsx
index b35fae35..d466b707 100644
--- a/src/components/ProfileIcon.tsx
+++ b/src/components/ProfileIcon.tsx
@@ -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")}
- {user.email || user.name || user.username}
+ {getUserDisplayName({ user })}
{user.serverAdmin ? (
diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx
index 102014e3..11e5bead 100644
--- a/src/components/UserDevicesTable.tsx
+++ b/src/components/UserDevicesTable.tsx
@@ -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}`}
>
diff --git a/src/components/UsersTable.tsx b/src/components/UsersTable.tsx
index e8729a9d..d6b6e610 100644
--- a/src/components/UsersTable.tsx
+++ b/src/components/UsersTable.tsx
@@ -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")}
/>
diff --git a/src/lib/getUserDisplayName.ts b/src/lib/getUserDisplayName.ts
new file mode 100644
index 00000000..e95096c1
--- /dev/null
+++ b/src/lib/getUserDisplayName.ts
@@ -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 || "";
+}
diff --git a/src/lib/queries.ts b/src/lib/queries.ts
index de2dc64a..d016ec77 100644
--- a/src/lib/queries.ts
+++ b/src/lib/queries.ts
@@ -340,6 +340,7 @@ export type ApprovalItem = {
name: string | null;
userId: string;
username: string;
+ email: string | null;
};
};