diff --git a/app/lib/owner-map.ts b/app/lib/owner-map.ts index 70e118e..c041424 100644 --- a/app/lib/owner-map.ts +++ b/app/lib/owner-map.ts @@ -1,4 +1,4 @@ -import { findUserByClerkId } from "~/models/user"; +import { findUserByClerkId, getUserDisplayName } from "~/models/user"; /** * Builds a map of teamId -> manager username (or displayName fallback). @@ -15,7 +15,7 @@ export async function buildOwnerMap( const ownerEntries = await Promise.all( uniqueOwnerIds.map(async (ownerId) => { const user = await findUserByClerkId(ownerId); - const name = user?.username || user?.displayName || null; + const name = user ? getUserDisplayName(user) : null; return name ? { ownerId, name } : null; }) ); diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts index 380bbcc..7b38560 100644 --- a/app/models/scoring-calculator.ts +++ b/app/models/scoring-calculator.ts @@ -6,6 +6,7 @@ import { getSeasonResults } from "./participant-season-result"; import { updateProbabilitiesAfterResult } from "~/services/probability-updater"; import { sendStandingsUpdateNotification, type ScoredMatch } from "~/services/discord"; import { BRACKET_TEMPLATES } from "~/lib/bracket-templates"; +import { getUserDisplayName } from "~/models/user"; /** * Core scoring calculation engine @@ -1313,7 +1314,7 @@ export async function recalculateAffectedLeagues( : []; const usernameByClerkId = new Map( teamOwnerUsers - .map((u) => [u.clerkId, u.username ?? u.displayName]) + .map((u) => [u.clerkId, getUserDisplayName(u)]) .filter((entry): entry is [string, string] => entry[1] != null) ); diff --git a/app/models/user.ts b/app/models/user.ts index fea35d2..58ca681 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -5,6 +5,17 @@ import * as schema from "~/database/schema"; export type User = typeof schema.users.$inferSelect; export type NewUser = typeof schema.users.$inferInsert; +/** + * Returns the best available display identifier for a user. + * Prefers Clerk username; falls back to displayName (always set from + * firstName+lastName or email prefix). Returns null only if both are absent. + */ +export function getUserDisplayName( + user: Pick +): string | null { + return user.username ?? user.displayName ?? null; +} + export async function createUser(data: NewUser): Promise { const db = database(); const [user] = await db.insert(schema.users).values(data).returning(); diff --git a/app/routes/api/seasons.$seasonId.draft.ts b/app/routes/api/seasons.$seasonId.draft.ts index fd80c9c..37316c4 100644 --- a/app/routes/api/seasons.$seasonId.draft.ts +++ b/app/routes/api/seasons.$seasonId.draft.ts @@ -2,6 +2,7 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, inArray } from "drizzle-orm"; import { calculatePickInfo } from "~/models/draft-utils"; +import { getUserDisplayName } from "~/models/user"; import type { LoaderFunctionArgs } from "react-router"; @@ -77,7 +78,7 @@ export async function loader({ params }: LoaderFunctionArgs) { .from(schema.users) .where(inArray(schema.users.clerkId, allOwnerIds)); for (const owner of owners) { - usernameByClerkId.set(owner.clerkId, owner.username ?? owner.displayName ?? null); + usernameByClerkId.set(owner.clerkId, getUserDisplayName(owner)); } } diff --git a/app/routes/api/webhooks/clerk.ts b/app/routes/api/webhooks/clerk.ts index c1cd1de..448ac85 100644 --- a/app/routes/api/webhooks/clerk.ts +++ b/app/routes/api/webhooks/clerk.ts @@ -1,6 +1,6 @@ import { Webhook } from "svix"; import type { Route } from "./+types/clerk"; -import { findOrCreateUser } from "~/models/user"; +import { findOrCreateUser, getUserDisplayName } from "~/models/user"; export async function action({ request }: Route.ActionArgs) { // Get the webhook secret from environment @@ -62,7 +62,7 @@ export async function action({ request }: Route.ActionArgs) { imageUrl: image_url, }); console.log( - `User created in database: ${user.id} (${user.username || user.displayName})` + `User created in database: ${user.id} (${getUserDisplayName(user)})` ); } else { // Update existing user (or create if doesn't exist) @@ -78,7 +78,7 @@ export async function action({ request }: Route.ActionArgs) { imageUrl: image_url, }); console.log( - `User updated in database: ${user.id} (${user.username || user.displayName})` + `User updated in database: ${user.id} (${getUserDisplayName(user)})` ); } } catch (error) { diff --git a/app/routes/i.$inviteCode.tsx b/app/routes/i.$inviteCode.tsx index 10ae4d9..308c4e7 100644 --- a/app/routes/i.$inviteCode.tsx +++ b/app/routes/i.$inviteCode.tsx @@ -11,6 +11,7 @@ import { claimTeam, isUserLeagueMember, findUserByClerkId, + getUserDisplayName, } from "~/models"; import { Button } from "~/components/ui/button"; import { @@ -103,7 +104,7 @@ export async function action(args: Route.ActionArgs) { // Claim the first available team, setting owner and name atomically const team = availableTeams[0]; - const teamName = `Team ${user.username || user.displayName || "Member"}`; + const teamName = `Team ${getUserDisplayName(user) ?? "Member"}`; await claimTeam(team.id, userId, teamName); // Redirect to league page diff --git a/app/routes/leagues/$leagueId.server.ts b/app/routes/leagues/$leagueId.server.ts index a8d73d8..b52c56d 100644 --- a/app/routes/leagues/$leagueId.server.ts +++ b/app/routes/leagues/$leagueId.server.ts @@ -8,6 +8,7 @@ import { isUserLeagueMember, isCommissioner, findUserByClerkId, + getUserDisplayName, findDraftSlotsBySeasonId, } from "~/models"; import { findCurrentSeasonWithSports } from "~/models/season"; @@ -83,7 +84,7 @@ export async function loader(args: Route.LoaderArgs) { uniqueOwnerIds.map(async (ownerId) => { const user = await findUserByClerkId(ownerId); return user - ? { clerkId: ownerId, name: user.username || user.displayName } + ? { clerkId: ownerId, name: getUserDisplayName(user) } : null; }) ); @@ -99,7 +100,7 @@ export async function loader(args: Route.LoaderArgs) { commissionerIds.map(async (commissionerId) => { const user = await findUserByClerkId(commissionerId); return user - ? { clerkId: commissionerId, name: user.username || user.displayName } + ? { clerkId: commissionerId, name: getUserDisplayName(user) } : null; }) ); diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index c420029..15eea52 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -15,7 +15,7 @@ import { } from "~/models/commissioner"; import { findCurrentSeasonWithSports, updateSeason } from "~/models/season"; import { findTeamsBySeasonId, deleteTeam, removeTeamOwner, claimTeam } from "~/models/team"; -import { findAllUsers, findUserByClerkId, isUserAdminByClerkId } from "~/models/user"; +import { findAllUsers, findUserByClerkId, getUserDisplayName, isUserAdminByClerkId } from "~/models/user"; import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport"; import { findAllSportsSeasons } from "~/models/sports-season"; import { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot"; @@ -117,7 +117,7 @@ export async function loader(args: Route.LoaderArgs) { const user = await findUserByClerkId(c.userId); return { ...c, - userName: user?.username || user?.displayName || "Unknown User", + userName: user ? (getUserDisplayName(user) ?? "Unknown User") : "Unknown User", }; }) ); @@ -131,7 +131,7 @@ export async function loader(args: Route.LoaderArgs) { uniqueOwnerIds.map(async (ownerId) => { const user = await findUserByClerkId(ownerId); return user - ? { clerkId: ownerId, name: user.username || user.displayName, id: user.id } + ? { clerkId: ownerId, name: getUserDisplayName(user), id: user.id } : null; }) ); @@ -382,7 +382,7 @@ export async function action(args: Route.ActionArgs) { } try { - const teamName = `Team ${assignedUser.username || assignedUser.displayName || "Member"}`; + const teamName = `Team ${getUserDisplayName(assignedUser) ?? "Member"}`; await claimTeam(teamId, userClerkId, teamName); return { success: true, message: "Owner assigned successfully" }; diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts index d647efa..9f9b020 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts @@ -5,6 +5,7 @@ import { isUserLeagueMember, isCommissioner, findTeamsBySeasonId, + getUserDisplayName, } from "~/models"; import { getDraftPicks } from "~/models/draft-pick"; import { getSeasonResults } from "~/models/participant-season-result"; @@ -98,7 +99,7 @@ export async function loader(args: Route.LoaderArgs) { }) : []; const ownerMap = new Map( - ownerUserRows.map((u) => [u.clerkId, u.username || u.displayName || "Unknown"]) + ownerUserRows.map((u) => [u.clerkId, getUserDisplayName(u) ?? "Unknown"]) ); // Map draft picks to ownership