Custom name (#1754)

* Migration and edit

* Migrate from discordName

* discordDiscriminator
This commit is contained in:
Kalle 2024-06-03 22:20:21 +03:00 committed by GitHub
parent 764fd8ba7b
commit bfce8d9646
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
97 changed files with 747 additions and 789 deletions

View File

@ -15,7 +15,7 @@ import type {
} from "~/modules/in-game-lists";
import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
import { databaseTimestampToDate } from "~/utils/dates";
import { discordFullName, gearTypeToInitial } from "~/utils/strings";
import { gearTypeToInitial } from "~/utils/strings";
import {
analyzerPage,
gearImageUrl,
@ -58,10 +58,7 @@ interface BuildProps {
maxPower: number | null;
}>;
};
owner?: Pick<
UserWithPlusTier,
"discordId" | "discordName" | "discordDiscriminator" | "plusTier"
>;
owner?: Pick<UserWithPlusTier, "discordId" | "username" | "plusTier">;
canEdit?: boolean;
}
@ -117,7 +114,7 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
to={userBuildsPage(owner)}
className="build__date-author-row__owner"
>
{discordFullName(owner)}
{owner.username}
</Link>
<div></div>
</>

View File

@ -89,7 +89,7 @@ export function UserSearch({
: "Search via name or ID..."
}
onChange={(event) => setQuery(event.target.value)}
displayValue={(user: UserSearchUserItem) => user?.discordName ?? ""}
displayValue={(user: UserSearchUserItem) => user?.username ?? ""}
className={clsx("combobox-input", className)}
data-1p-ignore
data-testid={`${inputName}-combobox-input`}
@ -118,9 +118,7 @@ export function UserSearch({
<Avatar user={user} size="xs" />
<div>
<div className="stack xs horizontal items-center">
<span className="combobox-username">
{user.discordName}
</span>{" "}
<span className="combobox-username">{user.username}</span>{" "}
{user.plusTier ? (
<span className="text-xxs">+{user.plusTier}</span>
) : null}

View File

@ -2,7 +2,6 @@ import { Link } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { usePatrons } from "~/hooks/swr";
import { discordFullName } from "~/utils/strings";
import {
CONTRIBUTIONS_PAGE,
FAQ_PAGE,
@ -105,7 +104,7 @@ function PatronsList() {
to={userPage(patron)}
className="layout__footer__patron-list__patron"
>
{discordFullName(patron)}
{patron.username}
</Link>
</li>
))}

View File

@ -18,7 +18,7 @@ export function UserItem() {
<Avatar
user={user}
alt={t("header.loggedInAs", {
userName: `${user.discordName}`,
userName: `${user.username}`,
})}
className="layout__avatar"
size="sm"

View File

@ -6,6 +6,7 @@ export const DISCORD_MESSAGE_MAX_LENGTH = 2000;
export const USER = {
BIO_MAX_LENGTH: DISCORD_MESSAGE_MAX_LENGTH,
CUSTOM_URL_MAX_LENGTH: 32,
CUSTOM_NAME_MAX_LENGTH: 32,
IN_GAME_NAME_TEXT_MAX_LENGTH: 20,
IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH: 5,
WEAPON_POOL_MAX_SIZE: 5,

View File

@ -223,7 +223,6 @@ function wipeDB() {
async function adminUser() {
await UserRepository.upsert({
discordDiscriminator: "0",
discordId: ADMIN_DISCORD_ID,
discordName: "Sendou",
twitch: "Sendou",
@ -267,7 +266,6 @@ function adminUserWeaponPool() {
function nzapUser() {
return UserRepository.upsert({
discordDiscriminator: "6227",
discordId: NZAP_TEST_DISCORD_ID,
discordName: "N-ZAP",
twitch: null,
@ -450,7 +448,6 @@ async function userQWeaponPool() {
function fakeUser(usedNames: Set<string>) {
return () => ({
discordAvatar: null,
discordDiscriminator: String(faker.string.numeric(4)),
discordId: String(faker.string.numeric(17)),
discordName: uniqueDiscordName(usedNames),
twitch: null,

View File

@ -667,9 +667,11 @@ export interface User {
css: ColumnType<Record<string, string> | null, string | null, string | null>;
customUrl: string | null;
discordAvatar: string | null;
discordDiscriminator: string;
discordId: string;
discordName: string;
customName: string | null;
/** coalesce(customName, discordName) */
username: ColumnType<string, never, never>;
discordUniqueName: string | null;
favoriteBadgeId: number | null;
id: GeneratedAlways<number>;

View File

@ -14,7 +14,7 @@ export interface User {
discordId: string;
/** Discord display name aka global name (non-unique) */
discordName: string;
discordDiscriminator: string;
username: string;
discordAvatar: string | null;
/** Discord username (unique) */
discordUniqueName: string | null;

View File

@ -50,7 +50,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
.innerJoin("User", "User.id", "TournamentTeamMember.userId")
.select([
"User.id as userId",
"User.discordName",
"User.username",
"User.discordId",
"User.discordAvatar",
"TournamentTeamMember.isOwner",
@ -85,7 +85,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
members: team.members.map((member) => {
return {
userId: member.userId,
name: member.discordName,
name: member.username,
discordId: member.discordId,
avatarUrl: member.discordAvatar
? `https://cdn.discordapp.com/avatars/${member.discordId}/${member.discordAvatar}.png`

View File

@ -7,14 +7,12 @@ export interface ListedArt {
tags?: string[];
linkedUsers?: Array<{
discordId: User["discordId"];
discordName: User["discordName"];
discordDiscriminator: User["discordDiscriminator"];
username: User["username"];
customUrl: User["customUrl"];
}>;
author?: {
discordId: User["discordId"];
discordName: User["discordName"];
discordDiscriminator: User["discordDiscriminator"];
username: User["username"];
discordAvatar: User["discordAvatar"];
commissionsOpen?: User["commissionsOpen"];
};

View File

@ -2,7 +2,6 @@ import { Link } from "@remix-run/react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import { Avatar } from "~/components/Avatar";
import { useIsMounted } from "~/hooks/useIsMounted";
import { discordFullName } from "~/utils/strings";
import {
artPage,
conditionalUserSubmittedImage,
@ -116,7 +115,7 @@ function BigImageDialog({ close, art }: { close: () => void; art: ListedArt }) {
key={user.discordId}
className="art__dialog__tag art__dialog__tag__user"
>
{discordFullName(user)}
{user.username}
</Link>
))}
{art.tags?.map((tag) => (
@ -205,7 +204,7 @@ function ImagePreview({
})}
>
<Avatar user={art.author} size="xxs" />
{t("art:madeBy")} {discordFullName(art.author)}
{t("art:madeBy")} {art.author.username}
</Link>
</div>
);
@ -220,7 +219,7 @@ function ImagePreview({
})}
>
<Avatar user={art.author} size="xxs" />
{discordFullName(art.author)}
{art.author.username}
</div>
</Link>
);

View File

@ -9,8 +9,7 @@ const stm = sql.prepare(/* sql */ `
"Art"."description",
"Art"."createdAt",
"User"."discordId",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"User"."discordAvatar",
"UserSubmittedImage"."url"
from
@ -28,8 +27,7 @@ const stm = sql.prepare(/* sql */ `
"Art"."description",
"Art"."createdAt",
null, -- discordId
null, -- discordName
null, -- discordDiscriminator
null, -- username
null, -- discordAvatar
"UserSubmittedImage"."url"
from
@ -55,8 +53,7 @@ const stm = sql.prepare(/* sql */ `
json_group_array(
json_object(
'discordId', "LinkedUser"."discordId",
'discordName', "LinkedUser"."discordName",
'discordDiscriminator', "LinkedUser"."discordDiscriminator",
'username', "LinkedUser"."username",
'customUrl', "LinkedUser"."customUrl"
)
) as "linkedUsers"
@ -83,9 +80,8 @@ export function artsByUserId(userId: number): ListedArt[] {
? {
commissionsOpen: a.commissionsOpen,
discordAvatar: a.discordAvatar,
discordDiscriminator: a.discordDiscriminator,
discordId: a.discordId,
discordName: a.discordName,
username: a.username,
}
: undefined,
};

View File

@ -7,8 +7,7 @@ const showcaseArtsStm = sql.prepare(/* sql */ `
"Art"."id",
"User"."id" as "userId",
"User"."discordId",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"User"."discordAvatar",
"User"."commissionsOpen",
"UserSubmittedImage"."url"
@ -28,9 +27,8 @@ export function showcaseArts(): ListedArt[] {
author: {
commissionsOpen: a.commissionsOpen,
discordAvatar: a.discordAvatar,
discordDiscriminator: a.discordDiscriminator,
discordId: a.discordId,
discordName: a.discordName,
username: a.username,
},
}));
}
@ -40,8 +38,7 @@ const showcaseArtsByTagStm = sql.prepare(/* sql */ `
"Art"."id",
"User"."id" as "userId",
"User"."discordId",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"User"."discordAvatar",
"User"."commissionsOpen",
"UserSubmittedImage"."url"
@ -76,9 +73,8 @@ export function showcaseArtsByTag(tagId: ArtTag["id"]): ListedArt[] {
author: {
commissionsOpen: a.commissionsOpen,
discordAvatar: a.discordAvatar,
discordDiscriminator: a.discordDiscriminator,
discordId: a.discordId,
discordName: a.discordName,
username: a.username,
},
}));
}

View File

@ -76,7 +76,6 @@ export class DiscordStrategy extends OAuth2Strategy<
const userFromDb = await UserRepository.upsert({
discordAvatar: user.avatar ?? null,
discordDiscriminator: user.discriminator,
discordId: user.id,
discordName: user.global_name ?? user.username,
discordUniqueName: user.global_name ? user.username : null,

View File

@ -110,7 +110,6 @@ export const createLogInLinkAction: ActionFunction = async ({ request }) => {
const user = await UserRepository.upsert({
discordAvatar: data.discordAvatar ?? null,
discordDiscriminator: "0",
discordId: data.discordId,
discordName: data.discordName,
discordUniqueName: data.discordUniqueName,

View File

@ -90,7 +90,7 @@ export function findOwnersByBadgeId(badgeId: number) {
fn.count<number>("BadgeOwner.badgeId").as("count"),
"User.id",
"User.discordId",
"User.discordName",
"User.username",
])
.where("BadgeOwner.badgeId", "=", badgeId)
.groupBy("User.id")

View File

@ -126,7 +126,7 @@ function Managers({ data }: { data: BadgeDetailsLoaderData }) {
<ul className="badges-edit__users-list">
{managers.map((manager) => (
<li key={manager.id}>
{manager.discordName}
{manager.username}
<Button
icon={<TrashIcon />}
variant="minimal-destructive"
@ -201,7 +201,7 @@ function Owners({ data }: { data: BadgeDetailsLoaderData }) {
<ul className="badges-edit__users-list">
{owners.map((owner) => (
<li key={owner.id}>
{owner.discordName}
{owner.username}
<input
className="badges-edit__number-input"
id="number"
@ -230,13 +230,13 @@ function Owners({ data }: { data: BadgeDetailsLoaderData }) {
<>
{o.difference}{" "}
<span className="text-success font-semi-bold">added</span> to{" "}
{o.discordFullName}
{o.username}
</>
) : (
<>
{o.difference}{" "}
<span className="text-error font-semi-bold">removed</span>{" "}
from {o.discordFullName}
from {o.username}
</>
)}
</li>
@ -270,7 +270,7 @@ function getOwnerDifferences(
id: User["id"];
type: "added" | "removed";
difference: number;
discordFullName: string;
username: string;
}> = [];
for (const owner of newOwners) {
@ -280,7 +280,7 @@ function getOwnerDifferences(
id: owner.id,
type: "added",
difference: owner.count,
discordFullName: owner.discordName,
username: owner.username,
});
continue;
}
@ -290,7 +290,7 @@ function getOwnerDifferences(
id: owner.id,
type: owner.count > oldOwner.count ? "added" : "removed",
difference: Math.abs(owner.count - oldOwner.count),
discordFullName: owner.discordName,
username: owner.username,
});
}
}

View File

@ -58,7 +58,7 @@ export default function BadgeDetailsPage() {
})}
>
{t("managedBy", {
users: data.managers.map((m) => m.discordName).join(", "),
users: data.managers.map((m) => m.username).join(", "),
})}
</div>
</div>
@ -78,7 +78,7 @@ export default function BadgeDetailsPage() {
>
×{owner.count}
</span>
<span>{owner.discordName}</span>
<span>{owner.username}</span>
</li>
))}
</ul>

View File

@ -82,8 +82,7 @@ with "Top500Weapon" as (
select
"BuildWithWeapon".*,
"User"."discordId",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"PlusTier"."tier" as "plusTier",
json_group_array(
json_object(
@ -184,10 +183,7 @@ order by
`);
type BuildsByWeaponIdRow = BuildsByUserRow &
Pick<
UserWithPlusTier,
"discordId" | "discordName" | "discordDiscriminator" | "plusTier"
>;
Pick<UserWithPlusTier, "discordId" | "username" | "plusTier">;
export function buildsByWeaponId({
weaponId,

View File

@ -95,8 +95,7 @@ export async function findById({
"User.id as authorId",
"CalendarEventDate.startTime",
"CalendarEventDate.eventId",
"User.discordName",
"User.discordDiscriminator",
"User.username",
"User.discordId",
"User.discordAvatar",
hasBadge,
@ -157,8 +156,7 @@ export async function findAllBetweenTwoTimestamps({
"CalendarEventDate.id as eventDateId",
"CalendarEventDate.eventId",
"CalendarEventDate.startTime",
"User.discordName",
"User.discordDiscriminator",
"User.username",
"CalendarEventRanks.nthAppearance",
eb
.selectFrom("UserSubmittedImage")
@ -364,7 +362,7 @@ export async function findResultsByEventId(eventId: number) {
.select([
"CalendarEventResultPlayer.userId as id",
"CalendarEventResultPlayer.name",
"User.discordName",
"User.username",
"User.discordId",
"User.discordAvatar",
])

View File

@ -37,7 +37,7 @@ import {
validate,
type SendouRouteHandle,
} from "~/utils/remix";
import { discordFullName, makeTitle } from "~/utils/strings";
import { makeTitle } from "~/utils/strings";
import {
CALENDAR_PAGE,
calendarEditPage,
@ -304,7 +304,7 @@ function Results() {
className="stack horizontal xs items-center"
>
<Avatar user={player as any} size="xxs" />{" "}
{player.discordName}
{player.username}
</Link>
)}
</li>
@ -353,7 +353,7 @@ function Description() {
<div className="stack sm">
<div className="event__author">
<Avatar user={data.event} size="xs" />
{discordFullName(data.event)}
{data.event.username}
</div>
{data.event.description && (
<div className="whitespace-pre-wrap">{data.event.description}</div>

View File

@ -31,7 +31,7 @@ import {
weekNumberToDate,
} from "~/utils/dates";
import { type SendouRouteHandle } from "~/utils/remix";
import { discordFullName, makeTitle } from "~/utils/strings";
import { makeTitle } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import {
CALENDAR_PAGE,
@ -451,7 +451,7 @@ function EventsList({
</time>
<div className="calendar__event__author">
{t("from", {
author: discordFullName(calendarEvent),
author: calendarEvent.username,
})}
</div>
{sectionWeekday !== eventWeekday ? (

View File

@ -17,7 +17,7 @@ import { soundPath } from "~/utils/urls";
import { useTranslation } from "react-i18next";
import { logger } from "~/utils/logger";
type ChatUser = Pick<User, "discordName" | "discordId" | "discordAvatar"> & {
type ChatUser = Pick<User, "username" | "discordId" | "discordAvatar"> & {
chatNameColor: string | null;
title?: string;
};
@ -239,7 +239,7 @@ function Message({
: undefined
}
>
{user?.discordName ?? missingUserName}
{user?.username ?? missingUserName}
</div>
{user?.title ? (
<div className="text-xs text-theme-secondary font-semi-bold">

View File

@ -202,7 +202,7 @@ function TournamentCard({
</div>
<ul className="front__tournament-card__first-placers">
{tournament.firstPlacers.map((p) => (
<li key={p.id}>{p.discordName}</li>
<li key={p.id}>{p.username}</li>
))}
</ul>
</>
@ -228,7 +228,7 @@ function LogInButton() {
<Avatar
user={user}
alt={t("common:header.loggedInAs", {
userName: `${user.discordName}`,
userName: `${user.username}`,
})}
className="front__avatar"
size="sm"

View File

@ -11,9 +11,8 @@ const getStm = (where = "") =>
"XRankPlacement"."weaponSplId",
"XRankPlacement"."name",
"User"."id",
"User"."discordName",
"User"."username",
"User"."discordAvatar",
"User"."discordDiscriminator",
"User"."discordId",
"User"."customUrl",
max("XRankPlacement"."power") as "power",
@ -45,9 +44,8 @@ export interface XPLeaderboardItem {
id: User["id"];
name: XRankPlacement["name"];
playerId: XRankPlacement["playerId"];
discordName: User["discordName"] | null;
username: User["username"] | null;
discordAvatar: User["discordAvatar"] | null;
discordDiscriminator: User["discordDiscriminator"] | null;
discordId: User["discordId"] | null;
customUrl: User["customUrl"] | null;
placementRank: number;

View File

@ -8,7 +8,7 @@ const stm = sql.prepare(/* sql */ `
"Skill"."id" as "entryId",
"Skill"."ordinal",
"User"."id",
"User"."discordName",
"User"."username",
"User"."discordAvatar",
"User"."discordId",
"User"."customUrl",
@ -39,7 +39,7 @@ export interface UserSPLeaderboardItem {
entryId: number;
power: number;
id: User["id"];
discordName: User["discordName"];
username: User["username"];
discordAvatar: User["discordAvatar"];
discordId: User["discordId"];
customUrl: User["customUrl"];

View File

@ -372,7 +372,7 @@ function OwnEntryPeek({
height={32}
/>
) : null}
<div className="placements__table__name">{entry.discordName}</div>
<div className="placements__table__name">{entry.username}</div>
<div className="placements__table__power">{entry.power}</div>
</div>
</Link>
@ -435,7 +435,7 @@ function PlayersTable({
/>
) : null}
<div className="placements__table__name">
{entry.discordName}
{entry.username}
</div>
{entry.pendingPlusTier ? (
<div className="text-xs text-theme whitespace-nowrap">
@ -493,7 +493,7 @@ function TeamTable({
{entry.members.map((member, i) => {
return (
<React.Fragment key={member.id}>
<Link to={userPage(member)}>{member.discordName}</Link>
<Link to={userPage(member)}>{member.username}</Link>
{i !== entry.members.length - 1 ? ", " : null}
</React.Fragment>
);

View File

@ -210,7 +210,7 @@ function PostTeamMember({
<div className="stack sm items-center">
<Avatar size="xs" user={member} />
<Link to={userPage(member)} className="lfg__post-team-member-name">
{member.discordName}
{member.username}
</Link>
{tier ? <TierImage tier={tier} width={32} /> : null}
</div>
@ -232,7 +232,7 @@ function PostUserHeader({
<div>
<div className="stack horizontal sm items-center text-md font-bold">
<Link to={userPage(author)} className="lfg__post-user-name">
{author.discordName}
{author.username}
</Link>{" "}
{author.country ? <Flag countryCode={author.country} tiny /> : null}
</div>

View File

@ -16,13 +16,13 @@ type FindAllByMonthRow = {
createdAt: number;
author: {
id: number;
discordName: string;
username: string;
discordId: string;
discordAvatar: string | null;
};
suggested: {
id: number;
discordName: string;
username: string;
discordId: string;
discordAvatar: string | null;
bio: string | null;

View File

@ -106,7 +106,7 @@ export default function PlusCommentModalPage() {
<input type="hidden" name="tier" value={tierSuggestedTo} />
<input type="hidden" name="suggestedId" value={targetUserId} />
<h2 className="plus__modal-title">
{userBeingCommented.suggested.discordName}&apos;s +{tierSuggestedTo}{" "}
{userBeingCommented.suggested.username}&apos;s +{tierSuggestedTo}{" "}
suggestion
</h2>
<CommentTextarea maxLength={PlUS_SUGGESTION_COMMENT_MAX_LENGTH} />

View File

@ -309,7 +309,7 @@ function SuggestedUser({
<Avatar user={suggestion.suggested} size="md" />
<h2>
<Link className="all-unset" to={userPage(suggestion.suggested)}>
{suggestion.suggested.discordName}
{suggestion.suggested.username}
</Link>
</h2>
{canAddCommentToSuggestionFE({
@ -366,7 +366,7 @@ export function PlusSuggestionComments({
{suggestion.suggestions.map((suggestion) => {
return (
<fieldset key={suggestion.id} className="plus__comment">
<legend>{suggestion.author.discordName}</legend>
<legend>{suggestion.author.username}</legend>
{suggestion.text}
<div className="stack horizontal xs items-center">
<span className="plus__comment-time">
@ -388,9 +388,7 @@ export function PlusSuggestionComments({
<CommentDeleteButton
suggestionId={suggestion.id}
tier={deleteButtonArgs.tier}
suggestedDiscordName={
deleteButtonArgs.suggested.discordName
}
suggestedUsername={deleteButtonArgs.suggested.username}
isFirstSuggestion={
deleteButtonArgs.suggestions.length === 1
}
@ -408,12 +406,12 @@ export function PlusSuggestionComments({
function CommentDeleteButton({
suggestionId,
tier,
suggestedDiscordName,
suggestedUsername,
isFirstSuggestion = false,
}: {
suggestionId: PlusSuggestion["id"];
tier: string;
suggestedDiscordName: string;
suggestedUsername: string;
isFirstSuggestion?: boolean;
}) {
return (
@ -424,8 +422,8 @@ function CommentDeleteButton({
]}
dialogHeading={
isFirstSuggestion
? `Delete your suggestion of ${suggestedDiscordName} to +${tier}?`
: `Delete your comment to ${suggestedDiscordName}'s +${tier} suggestion?`
? `Delete your suggestion of ${suggestedUsername} to +${tier}?`
: `Delete your comment to ${suggestedUsername}'s +${tier} suggestion?`
}
>
<Button

View File

@ -24,7 +24,7 @@ const resultsByMonthYearQuery = (args: MonthYear) =>
])
.where("PlusVotingResult.month", "=", args.month)
.where("PlusVotingResult.year", "=", args.year)
.orderBy(sql`"User"."discordName" collate nocase`, "asc");
.orderBy(sql`"User"."username" collate nocase`, "asc");
type ResultsByMonthYearQueryReturnType = InferResult<
ReturnType<typeof resultsByMonthYearQuery>
>;
@ -67,7 +67,7 @@ function groupPlusVotingResults(rows: ResultsByMonthYearQueryReturnType) {
export type UsersForVoting = {
user: Pick<
Tables["User"],
"id" | "discordId" | "discordName" | "discordAvatar" | "bio"
"id" | "discordId" | "username" | "discordAvatar" | "bio"
>;
suggestion?: PlusSuggestionRepository.FindAllByMonthItem;
}[];
@ -96,7 +96,7 @@ export async function usersForVoting(loggedInUser: {
user: {
id: member.id,
discordId: member.discordId,
discordName: member.discordName,
username: member.username,
discordAvatar: member.discordAvatar,
bio: member.bio,
},
@ -108,7 +108,7 @@ export async function usersForVoting(loggedInUser: {
user: {
id: suggestion.suggested.id,
discordId: suggestion.suggested.discordId,
discordName: suggestion.suggested.discordName,
username: suggestion.suggested.username,
discordAvatar: suggestion.suggested.discordAvatar,
bio: suggestion.suggested.bio,
},

View File

@ -203,7 +203,7 @@ function Results({
{user.wasSuggested ? (
<span className="plus-history__suggestion-s">S</span>
) : null}
{user.discordName}
{user.username}
</Link>
))}
</div>

View File

@ -275,7 +275,7 @@ function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
{previous.score > 0 ? "+" : ""}
{previous.score}
</span>{" "}
on {previous.user.discordName}.
on {previous.user.username}.
<Button className="ml-auto" variant="minimal" onClick={undoLast}>
Undo?
</Button>
@ -286,7 +286,7 @@ function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
{currentUser ? (
<div className="stack md items-center">
<Avatar user={currentUser.user} size="lg" />
<h2>{currentUser.user.discordName}</h2>
<h2>{currentUser.user.username}</h2>
<div className="stack horizontal lg">
<Button
className="plus-voting__vote-button downvote"

View File

@ -53,7 +53,7 @@ export interface GroupForMatch {
members: Array<{
id: Tables["GroupMember"]["userId"];
discordId: Tables["User"]["discordId"];
discordName: Tables["User"]["discordName"];
username: Tables["User"]["username"];
discordAvatar: Tables["User"]["discordAvatar"];
role: Tables["GroupMember"]["role"];
customUrl: Tables["User"]["customUrl"];

View File

@ -18,7 +18,7 @@ export function AddPrivateNoteDialog({
}: {
aboutUser?: Pick<
GroupForMatch["members"][number],
"id" | "discordName" | "privateNote"
"id" | "username" | "privateNote"
>;
close: () => void;
}) {
@ -33,7 +33,7 @@ export function AddPrivateNoteDialog({
<input type="hidden" name="targetId" value={aboutUser.id} />
<div className="stack horizontal items-center justify-between">
<h2 className="text-md">
{t("q:privateNote.header", { name: aboutUser.discordName })}
{t("q:privateNote.header", { name: aboutUser.username })}
</h2>
<Button
variant="minimal-destructive"

View File

@ -727,11 +727,11 @@ function TrustedUsers() {
>
<Avatar user={trustedUser} size="xxs" />
<div className="text-sm font-semi-bold">
{trustedUser.discordName}
{trustedUser.username}
</div>
<FormWithConfirm
dialogHeading={t("q:settings.trusted.confirm", {
name: trustedUser.discordName,
name: trustedUser.username,
})}
fields={[
["_action", "REMOVE_TRUST"],

View File

@ -56,7 +56,7 @@ export default function SendouQStreamsPage() {
className="q-stream__stream__user-container"
>
<Avatar size="xxs" user={streamedMatch.user} />{" "}
{streamedMatch.user.discordName}
{streamedMatch.user.username}
</Link>
<div className="stack horizontal sm">
{streamedMatch.weaponSplId ? (

View File

@ -301,7 +301,7 @@ export async function usersThatTrusted(userId: number) {
.select(COMMON_USER_FIELDS)
.where("TrustRelationship.trustReceiverUserId", "=", userId),
)
.orderBy("User.discordName asc")
.orderBy("User.username asc")
.execute();
const rowsWithoutBanned = rows.filter((row) => !userIsBanned(row.id));

View File

@ -275,7 +275,7 @@ function GroupMember({
})}
</div>
<DeletePrivateNoteForm
name={member.discordName}
name={member.username}
targetId={member.id}
/>
</div>
@ -296,7 +296,7 @@ function GroupMember({
{inGameNameWithoutDiscriminator(member.inGameName)}
</>
) : (
member.discordName
member.username
)}
</Link>
</div>

View File

@ -127,7 +127,7 @@ function TrusterDropdown({
{trustersNotInGroup.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.discordName}
{player.username}
</option>
);
})}

View File

@ -27,7 +27,7 @@ export type LookingGroup = {
members?: {
id: number;
discordId: string;
discordName: string;
username: string;
discordAvatar: string | null;
noScreen?: number;
customUrl?: User["customUrl"];

View File

@ -9,7 +9,7 @@ const stm = sql.prepare(/* sql */ `
json_group_array(
json_object(
'id', "User"."id",
'discordName', "User"."discordName",
'username', "User"."username",
'role', "GroupMember"."role"
)
) as "members"
@ -26,7 +26,7 @@ const stm = sql.prepare(/* sql */ `
export function findGroupByInviteCode(inviteCode: string): {
id: number;
status: Group["status"];
members: { id: number; discordName: string; role: GroupMember["role"] }[];
members: { id: number; username: string; role: GroupMember["role"] }[];
} | null {
const row = stm.get({ inviteCode }) as any;
if (!row) return null;

View File

@ -10,7 +10,7 @@ const stm = sql.prepare(/* sql */ `
"Group"."inviteCode",
"User"."id" as "userId",
"User"."discordId",
"User"."discordName",
"User"."username",
"User"."discordAvatar",
"User"."qWeaponPool",
"GroupMember"."role",
@ -33,7 +33,7 @@ const stm = sql.prepare(/* sql */ `
json_object(
'id', "q1"."userId",
'discordId', "q1"."discordId",
'discordName', "q1"."discordName",
'username', "q1"."username",
'discordAvatar', "q1"."discordAvatar",
'role', "q1"."role",
'note', "q1"."note",

View File

@ -47,7 +47,7 @@ const stm = sql.prepare(/* sql */ `
json_group_array(
json_object(
'id', "User"."id",
'discordName', "User"."discordName",
'username', "User"."username",
'discordId', "User"."discordId",
'discordAvatar', "User"."discordAvatar"
)
@ -63,7 +63,7 @@ const stm = sql.prepare(/* sql */ `
json_group_array(
json_object(
'id', "User"."id",
'discordName', "User"."discordName",
'username', "User"."username",
'discordId', "User"."discordId",
'discordAvatar', "User"."discordAvatar"
)
@ -107,14 +107,14 @@ interface SeasonMatchByUserId {
spDiff: number | null;
groupAlphaMembers: Array<{
id: User["id"];
discordName: User["discordName"];
username: User["username"];
discordId: User["discordId"];
discordAvatar: User["discordAvatar"];
weaponSplId?: MainWeaponId;
}>;
groupBravoMembers: Array<{
id: User["id"];
discordName: User["discordName"];
username: User["username"];
discordId: User["discordId"];
discordAvatar: User["discordAvatar"];
weaponSplId?: MainWeaponId;

View File

@ -9,7 +9,7 @@ const stm = sql.prepare(/* sql */ `
"setLosses",
json_object(
'id', "User"."id",
'discordName', "User"."discordName",
'username', "User"."username",
'discordAvatar', "User"."discordAvatar",
'discordId', "User"."discordId",
'customUrl', "User"."customUrl"
@ -44,7 +44,7 @@ export function seasonsMatesEnemiesByUserId({
setLosses: number;
user: Pick<
User,
"id" | "discordName" | "discordAvatar" | "discordId" | "customUrl"
"id" | "username" | "discordAvatar" | "discordId" | "customUrl"
>;
}>;
}

View File

@ -362,7 +362,7 @@ export const action: ActionFunction = async ({ request }) => {
NotificationService.notify({
room: targetChatCode,
type: "USER_LEFT",
context: { name: user.discordName },
context: { name: user.username },
});
}

View File

@ -121,9 +121,9 @@ export const meta: MetaFunction = (args) => {
{
name: "description",
content: `${joinListToNaturalString(
data.groupAlpha.members.map((m) => m.discordName),
data.groupAlpha.members.map((m) => m.username),
)} vs. ${joinListToNaturalString(
data.groupBravo.members.map((m) => m.discordName),
data.groupBravo.members.map((m) => m.username),
)}`,
},
];
@ -325,7 +325,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
room: match.chatCode,
type: type(),
context: {
name: user.discordName,
name: user.username,
},
});
}
@ -689,7 +689,7 @@ function Score({
<div
className={clsx("text-xs text-lighter", { invisible: !isMounted })}
>
{t("q:match.reportedBy", { name: reporter?.discordName ?? "admin" })}{" "}
{t("q:match.reportedBy", { name: reporter?.username ?? "admin" })}{" "}
{isMounted
? databaseTimestampToDate(reportedAt).toLocaleString(
i18n.language,
@ -955,7 +955,7 @@ function ReportWeaponsForm() {
)}
</>
) : (
member.discordName
member.username
)}
</div>
<div className="stack horizontal sm items-center">
@ -1522,7 +1522,7 @@ function MapListMap({
...data.groupBravo.members,
].find((m) => m.id === userId);
return member?.discordName ?? "";
return member?.username ?? "";
};
return (
@ -1811,7 +1811,7 @@ function MapListMapPickInfo({
className="stack sm horizontal items-center xs"
>
<Avatar user={user} size="xxs" />
{user?.discordName}
{user?.username}
</div>
);
})}
@ -1825,7 +1825,7 @@ function MapListMapPickInfo({
className="q-settings__radio__emoji"
width={18}
/>
{userIdToUser(userId)?.discordName}
{userIdToUser(userId)?.username}
</div>
);
})

View File

@ -394,7 +394,7 @@ function JoinTeamDialog({
open: boolean;
close: () => void;
members: {
discordName: string;
username: string;
role: GroupMember["role"];
}[];
}) {
@ -412,7 +412,7 @@ function JoinTeamDialog({
className="text-center"
>
{t("q:front.join.header", {
members: joinListToNaturalString(members.map((m) => m.discordName)),
members: joinListToNaturalString(members.map((m) => m.username)),
})}
<fetcher.Form
className="stack horizontal justify-center sm mt-4 flex-wrap"
@ -427,7 +427,7 @@ function JoinTeamDialog({
variant="outlined"
>
{t("q:front.join.joinWithTrustAction", {
inviterName: owner.discordName,
inviterName: owner.username,
})}
</SubmitButton>
<Button onClick={close} variant="destructive">

View File

@ -11,8 +11,8 @@ const stm = sql.prepare(/* sql */ `
json_object(
'id',
"User"."id",
'discordName',
"User"."discordName",
'username',
"User"."username",
'plusTier',
"PlusTier"."tier"
)
@ -28,7 +28,7 @@ const stm = sql.prepare(/* sql */ `
export type AllTeams = Array<
Pick<Team, "customUrl" | "name"> & {
avatarSrc?: string;
members: Pick<UserWithPlusTier, "id" | "plusTier" | "discordName">[];
members: Pick<UserWithPlusTier, "id" | "plusTier" | "username">[];
}
>;

View File

@ -27,10 +27,9 @@ const teamStm = sql.prepare(/*sql*/ `
const membersStm = sql.prepare(/*sql*/ `
select
"User"."id",
"User"."discordName",
"User"."username",
"User"."discordAvatar",
"User"."discordId",
"User"."discordDiscriminator",
"User"."patronTier",
"TeamMember"."role",
"TeamMember"."isOwner",
@ -57,15 +56,7 @@ type TeamRow =
| null;
type MemberRows = Array<
Pick<
User,
| "id"
| "discordName"
| "discordAvatar"
| "discordId"
| "discordDiscriminator"
| "patronTier"
> &
Pick<User, "id" | "username" | "discordAvatar" | "discordId" | "patronTier"> &
Pick<TeamMember, "role" | "isOwner"> & { weapons: string }
>;
@ -93,8 +84,7 @@ export function findByIdentifier(
id: member.id,
discordAvatar: member.discordAvatar,
discordId: member.discordId,
discordName: member.discordName,
discordDiscriminator: member.discordDiscriminator,
username: member.username,
patronTier: member.patronTier,
role: member.role ?? undefined,
isOwner: Boolean(member.isOwner),

View File

@ -20,7 +20,7 @@ import { useUser } from "~/features/auth/core/user";
import { requireUserId } from "~/features/auth/core/user.server";
import type { SendouRouteHandle } from "~/utils/remix";
import { notFoundIfFalsy, parseRequestFormData, validate } from "~/utils/remix";
import { discordFullName, makeTitle } from "~/utils/strings";
import { makeTitle } from "~/utils/strings";
import { assertUnreachable } from "~/utils/types";
import {
joinTeamPage,
@ -223,7 +223,7 @@ function MemberRow({
className="team__roster__members__member"
data-testid={`member-row-${number}`}
>
{discordFullName(member)}
{member.username}
</div>
<div>
<select
@ -255,7 +255,7 @@ function MemberRow({
<FormWithConfirm
dialogHeading={t("team:kick.header", {
teamName: team.name,
user: discordFullName(member),
user: member.username,
})}
deleteButtonText={t("team:actionButtons.kick")}
fields={[
@ -276,7 +276,7 @@ function MemberRow({
<FormWithConfirm
dialogHeading={t("team:transferOwnership.header", {
teamName: team.name,
user: discordFullName(member),
user: member.username,
})}
deleteButtonText={t("team:actionButtons.transferOwnership.confirm")}
fields={[

View File

@ -297,7 +297,7 @@ function MemberRow({
<div className="team__member__avatar">
<Avatar user={member} size="md" />
</div>
{member.discordName}
{member.username}
</Link>
<div className="stack horizontal md">
{member.weapons.map(({ weaponSplId, isFavorite }) => (
@ -323,7 +323,7 @@ function MobileMemberCard({ member }: { member: DetailedTeamMember }) {
<div className="team__member-card">
<Link to={userPage(member)} className="stack items-center">
<Avatar user={member} size="md" />
<div className="team__member-card__name">{member.discordName}</div>
<div className="team__member-card__name">{member.username}</div>
</Link>
{member.weapons.length > 0 ? (
<div className="stack horizontal md">

View File

@ -63,7 +63,7 @@ export default function TeamSearchPage() {
if (team.name.toLowerCase().includes(lowerCaseInput)) return true;
if (
team.members.some((m) =>
m.discordName.toLowerCase().includes(lowerCaseInput),
m.username.toLowerCase().includes(lowerCaseInput),
)
) {
return true;
@ -136,9 +136,9 @@ export default function TeamSearchPage() {
</div>
<div className="team-search__team__members">
{team.members.length === 1
? team.members[0].discordName
? team.members[0].username
: joinListToNaturalString(
team.members.map((member) => member.discordName),
team.members.map((member) => member.username),
"&",
)}
</div>

View File

@ -15,10 +15,9 @@ export interface DetailedTeam {
export interface DetailedTeamMember {
id: number;
discordName: string;
username: string;
discordId: string;
discordAvatar: string | null;
discordDiscriminator: string;
isOwner: boolean;
weapons: Array<Pick<UserWeapon, "weaponSplId" | "isFavorite">>;
role?: MemberRole;

View File

@ -176,7 +176,7 @@ function MatchRow({
<div
className={clsx("stack horizontal", { "text-lighter": isLoser })}
data-participant-id={team?.id}
title={team?.members.map((m) => m.discordName).join(", ")}
title={team?.members.map((m) => m.username).join(", ")}
>
<div
className={clsx("bracket__match__seed", {

View File

@ -84,7 +84,7 @@ export function MatchRosters({
})}
>
<Avatar user={p} size="xxs" />
{p.discordName}
{p.username}
</Link>
</li>
);
@ -132,7 +132,7 @@ export function MatchRosters({
})}
>
<Avatar user={p} size="xxs" />
{p.discordName}
{p.username}
</Link>
</li>
);

View File

@ -463,7 +463,7 @@ function TeamRosterInputsCheckboxes({
<span className="tournament-bracket__during-match-actions__player-name__inner">
{member.inGameName
? inGameNameWithoutDiscriminator(member.inGameName)
: member.discordName}
: member.username}
</span>
</label>
</div>

View File

@ -18,7 +18,7 @@ const createTeam = (teamId: number, userIds: number[]): TournamentDataTeam => ({
customUrl: null,
discordAvatar: null,
discordId: "123",
discordName: "test",
username: "test",
inGameName: "test",
isOwner: 0,
plusTier: null,

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,7 @@ export const testTournament = (
customUrl: null,
discordAvatar: null,
discordId: "123",
discordName: "test",
username: "test",
id: 1,
},
...partialCtx,

View File

@ -23,8 +23,8 @@ const stm = sql.prepare(/* sql */ `
json_object(
'id',
"User"."id",
'discordName',
"User"."discordName",
'username',
"User"."username",
'tournamentTeamId',
"TournamentTeamMember"."tournamentTeamId",
'inGameName',
@ -78,7 +78,7 @@ export const findMatchById = (id: number) => {
opponentTwo: JSON.parse(row.opponentTwo) as Match["opponent2"],
players: parseDBArray(row.players) as Array<{
id: User["id"];
discordName: User["discordName"];
username: User["username"];
tournamentTeamId: TournamentTeamMember["tournamentTeamId"];
inGameName: User["inGameName"];
discordId: User["discordId"];

View File

@ -4,7 +4,7 @@ import type { User } from "~/db/types";
const stm = sql.prepare(/* sql */ `
select
"User"."id",
"User"."discordName",
"User"."username",
"User"."discordAvatar",
"User"."discordId",
"User"."customUrl",
@ -27,7 +27,7 @@ const stm = sql.prepare(/* sql */ `
export type PlayerThatPlayedByTeamId = Pick<
User,
"id" | "discordName" | "discordAvatar" | "discordId" | "customUrl" | "country"
"id" | "username" | "discordAvatar" | "discordId" | "customUrl" | "country"
> & { tournamentTeamId: number };
export function playersThatPlayedByTournamentId(tournamentId: number) {

View File

@ -721,7 +721,7 @@ function FinalStandings() {
to={userPage(player)}
className="stack items-center text-xs mt-auto"
>
{player.discordName}
{player.username}
</Link>
</div>
);
@ -781,7 +781,7 @@ function FinalStandings() {
to={userPage(player)}
className="stack items-center text-xs mt-auto"
>
{player.discordName}
{player.username}
</Link>
</div>
);

View File

@ -11,8 +11,7 @@ const stm = sql.prepare(/* sql */ `
"TournamentSub"."visibility",
"TournamentSub"."createdAt",
"TournamentSub"."userId",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"User"."discordAvatar",
"User"."country",
"User"."discordId",
@ -39,8 +38,7 @@ export interface SubByTournamentId {
visibility: TournamentSub["visibility"];
createdAt: TournamentSub["createdAt"];
userId: TournamentSub["userId"];
discordName: UserWithPlusTier["discordName"];
discordDiscriminator: UserWithPlusTier["discordDiscriminator"];
username: UserWithPlusTier["username"];
discordAvatar: UserWithPlusTier["discordAvatar"];
discordId: UserWithPlusTier["discordId"];
customUrl: UserWithPlusTier["customUrl"];

View File

@ -19,7 +19,6 @@ import { getUser, requireUser } from "~/features/auth/core/user.server";
import { tournamentIdFromParams } from "~/features/tournament";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { discordFullName } from "~/utils/strings";
import { assertUnreachable } from "~/utils/types";
import { tournamentRegisterPage, userPage } from "~/utils/urls";
import { deleteSub } from "../queries/deleteSub.server";
@ -170,7 +169,7 @@ function SubInfoSection({ sub }: { sub: SubByTournamentId }) {
<section className="sub__section">
<Avatar user={sub} size="sm" className="sub__section__avatar" />
<Link to={userPage(sub)} className="sub__section__name">
{discordFullName(sub)}
{sub.username}
</Link>
<div className="sub__section__spacer" />
<div className="sub__section__info">{infos}</div>
@ -214,7 +213,7 @@ function SubInfoSection({ sub }: { sub: SubByTournamentId }) {
dialogHeading={
user?.id === sub.userId
? "Delete your sub post?"
: `Delete sub post by ${sub.discordName}?`
: `Delete sub post by ${sub.username}?`
}
fields={[["userId", sub.userId]]}
>

View File

@ -94,7 +94,7 @@ export async function findById(id: number) {
.leftJoin("PlusTier", "User.id", "PlusTier.userId")
.select([
"User.id as userId",
"User.discordName",
"User.username",
"User.discordId",
"User.discordAvatar",
"User.customUrl",
@ -250,7 +250,7 @@ export async function forShowcase() {
.where("TournamentResult.placement", "=", 1)
.select([
"User.id",
"User.discordName",
"User.username",
"TournamentTeam.name as teamName",
]),
).as("firstPlacers"),

View File

@ -89,7 +89,7 @@ export function TeamWithRoster({
to={userPage(member)}
className="tournament__team-member-name"
>
{member.discordName}{" "}
{member.username}{" "}
</Link>
</div>
{friendCode ? (

View File

@ -38,7 +38,7 @@ export function TournamentStream({
<div className="stack md horizontal justify-between">
{user && team ? (
<div className="tournament__stream__user-container">
<Avatar size="xxs" user={user} /> {user.discordName}
<Avatar size="xxs" user={user} /> {user.username}
<span className="text-theme-secondary">{team.name}</span>
</div>
) : (

View File

@ -32,7 +32,7 @@ export interface PlayedSet {
roster: Array<
Pick<
User,
"id" | "discordName" | "discordAvatar" | "discordId" | "customUrl"
"id" | "username" | "discordAvatar" | "discordId" | "customUrl"
>
>;
};

View File

@ -19,8 +19,7 @@ select
"CalendarEvent"."bracketUrl",
"CalendarEvent"."authorId",
"CalendarEventDate"."startTime",
"User"."discordName",
"User"."discordDiscriminator",
"User"."username",
"User"."discordId"
from "Tournament"
left join "CalendarEvent" on "Tournament"."id" = "CalendarEvent"."tournamentId"
@ -35,7 +34,7 @@ type FindByIdentifierRow = (Pick<
"bracketUrl" | "name" | "description" | "authorId"
> &
Pick<Tournament, "id" | "mapPickingStyle" | "showMapListGenerator"> &
Pick<User, "discordId" | "discordName" | "discordDiscriminator"> &
Pick<User, "discordId" | "username"> &
Pick<CalendarEventDate, "startTime">) & {
eventId: CalendarEvent["id"];
} & { settings: string };
@ -46,7 +45,7 @@ export function findByIdentifier(identifier: string | number) {
const tournament = { ...rows[0], startTime: resolveEarliestStartTime(rows) };
const { discordId, discordName, discordDiscriminator, ...rest } = tournament;
const { discordId, username, ...rest } = tournament;
return {
...rest,
@ -55,8 +54,7 @@ export function findByIdentifier(identifier: string | number) {
) as Tables["Tournament"]["settings"],
author: {
discordId,
discordName,
discordDiscriminator,
username,
},
};
}

View File

@ -58,8 +58,8 @@ const stm = sql.prepare(/* sql */ `
json_object(
'id',
"u"."id",
'discordName',
"u"."discordName",
'username',
"u"."username",
'discordAvatar',
"u"."discordAvatar",
'discordId',
@ -94,10 +94,7 @@ export interface SetHistoryByTeamIdItem {
wasWinner: number;
}[];
players: Array<
Pick<
User,
"id" | "discordName" | "discordAvatar" | "discordId" | "customUrl"
>
Pick<User, "id" | "username" | "discordAvatar" | "discordId" | "customUrl">
>;
}

View File

@ -539,7 +539,7 @@ function TeamActions() {
<select id="memberId" name="memberId">
{selectedTeam.members.map((member) => (
<option key={member.userId} value={member.userId}>
{member.discordName}
{member.username}
</option>
))}
</select>
@ -682,7 +682,7 @@ function StaffList() {
>
<Avatar size="xs" user={staff} />{" "}
<div className="mr-4">
<div>{staff.discordName}</div>
<div>{staff.username}</div>
<div className="text-lighter text-xs text-capitalize">
{t(`tournament:staff.role.${staff.role}`)}
</div>
@ -703,7 +703,7 @@ function RemoveStaffButton({
return (
<FormWithConfirm
dialogHeading={`Remove ${staff.discordName} as ${t(
dialogHeading={`Remove ${staff.username} as ${t(
`tournament:staff.role.${staff.role}`,
)}?`}
fields={[
@ -760,12 +760,12 @@ function DownloadParticipants() {
const nonOwners = team.members.filter((user) => !user.isOwner);
let result = `-- ${team.name} --\n(C) ${owner.discordName} (IGN: ${owner.inGameName ?? ""}) - <@${owner.discordId}>`;
let result = `-- ${team.name} --\n(C) ${owner.username} (IGN: ${owner.inGameName ?? ""}) - <@${owner.discordId}>`;
result += nonOwners
.map(
(user) =>
`\n${user.discordName} (IGN: ${user.inGameName ?? ""}) - <@${user.discordId}>`,
`\n${user.username} (IGN: ${user.inGameName ?? ""}) - <@${user.discordId}>`,
)
.join("");
@ -789,7 +789,7 @@ function DownloadParticipants() {
return `${i + 1}) ${team.name} - ${databaseTimestampToDate(
team.createdAt,
).toISOString()} - ${team.members
.map((member) => `${member.discordName} - <@${member.discordId}>`)
.map((member) => `${member.username} - <@${member.discordId}>`)
.join(" / ")}`;
})
.join("\n")
@ -803,7 +803,7 @@ function DownloadParticipants() {
.filter((team) => team.checkIns.length === 0)
.map((team) => {
return `${team.name} - ${team.members
.map((member) => `${member.discordName} - <@${member.discordId}>`)
.map((member) => `${member.username} - <@${member.discordId}>`)
.join(" / ")}`;
})
.join("\n");

View File

@ -171,7 +171,7 @@ export default function JoinTeamPage() {
<input id={id} type="checkbox" name="trust" />{" "}
<label htmlFor={id} className="mb-0">
{t("tournament:join.giveTrust", {
name: captain ? captain.discordName : "",
name: captain ? captain.username : "",
})}
</label>
</div>

View File

@ -328,7 +328,7 @@ export default function TournamentRegisterPage() {
className="stack horizontal xs items-center text-lighter"
>
<UserIcon className="tournament__info__icon" />{" "}
{tournament.ctx.author.discordName}
{tournament.ctx.author.username}
</Link>
<div className="stack horizontal xs items-center">
<ClockIcon className="tournament__info__icon" />{" "}
@ -979,7 +979,7 @@ function FillRoster({
data-testid={`member-num-${i + 1}`}
>
<Avatar size="xsm" user={member} />
{member.discordName}
{member.username}
</div>
);
})}
@ -1018,7 +1018,7 @@ function FillRoster({
function DirectlyAddPlayerSelect({
players,
}: {
players: { id: number; discordName: string }[];
players: { id: number; username: string }[];
}) {
const { t } = useTranslation(["tournament", "common"]);
const fetcher = useFetcher();
@ -1034,7 +1034,7 @@ function DirectlyAddPlayerSelect({
{players.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.discordName}
{player.username}
</option>
);
})}
@ -1078,7 +1078,7 @@ function DeleteMember({ members }: { members: TournamentDataTeam["members"] }) {
.filter((member) => !member.isOwner)
.map((member) => (
<option key={member.userId} value={member.userId}>
{member.discordName}
{member.username}
</option>
))}
</select>

View File

@ -314,7 +314,7 @@ function RowContents({
target="_blank"
className="tournament__seeds__team-member__name"
>
{member.discordName}
{member.username}
</Link>
{member.plusTier ? (
<div

View File

@ -261,7 +261,7 @@ function SetInfo({ set, team }: { set: PlayedSet; team: TournamentDataTeam }) {
className="tournament__team__set__opponent__member"
>
<Avatar user={user} size="xxs" />
{user.discordName}
{user.username}
</Link>
);
})}

View File

@ -36,9 +36,10 @@ export function findByIdentifier(identifier: string) {
.leftJoin("PlusTier", "PlusTier.userId", "User.id")
.select(({ eb }) => [
"User.discordAvatar",
"User.discordDiscriminator",
"User.discordId",
"User.discordName",
"User.username",
"User.customName",
"User.showDiscordUniqueName",
"User.discordUniqueName",
"User.customUrl",
@ -107,13 +108,7 @@ export function findLeanById(id: number) {
export function findAllPatrons() {
return db
.selectFrom("User")
.select([
"User.id",
"User.discordId",
"User.discordName",
"User.discordDiscriminator",
"User.patronTier",
])
.select(["User.id", "User.discordId", "User.username", "User.patronTier"])
.where("User.patronTier", "is not", null)
.orderBy("User.patronTier", "desc")
.orderBy("User.patronSince", "asc")
@ -264,7 +259,7 @@ export async function search({
.select(searchSelectedFields)
.where((eb) =>
eb.or([
eb("User.discordName", "like", query),
eb("User.username", "like", query),
eb("User.inGameName", "like", query),
eb("User.discordUniqueName", "like", query),
eb("User.twitter", "like", query),
@ -293,7 +288,7 @@ export async function search({
.where((eb) =>
eb
.or([
eb("User.discordName", "like", fuzzyQuery),
eb("User.username", "like", fuzzyQuery),
eb("User.inGameName", "like", fuzzyQuery),
eb("User.discordUniqueName", "like", fuzzyQuery),
eb("User.twitter", "like", fuzzyQuery),
@ -368,7 +363,6 @@ export function upsert(
TablesInsertable["User"],
| "discordId"
| "discordName"
| "discordDiscriminator"
| "discordAvatar"
| "discordUniqueName"
| "twitch"
@ -393,6 +387,7 @@ type UpdateProfileArgs = Pick<
| "country"
| "bio"
| "customUrl"
| "customName"
| "motionSens"
| "stickSens"
| "inGameName"
@ -432,6 +427,7 @@ export function updateProfile(args: UpdateProfileArgs) {
country: args.country,
bio: args.bio,
customUrl: args.customUrl,
customName: args.customName,
motionSens: args.motionSens,
stickSens: args.stickSens,
inGameName: args.inGameName,

View File

@ -135,7 +135,7 @@ export function UserResultsTable({
className="stack horizontal xs items-center"
>
<Avatar user={player as any} size="xxs" />
{player.discordName}
{player.username}
</Link>
)}
</li>

View File

@ -82,6 +82,10 @@ const userEditActionSchema = z
.transform((val) => val?.toLowerCase())
.nullable(),
),
customName: z.preprocess(
falsyToNull,
z.string().trim().max(USER.CUSTOM_NAME_MAX_LENGTH).nullable(),
),
stickSens: z.preprocess(
processMany(actualNumber, undefinedToNull),
z
@ -232,6 +236,7 @@ export default function UserEditPage() {
{canAddCustomizedColorsToUserProfile(user) ? (
<CustomizedColorsInput initialColors={parentRouteData.css} />
) : null}
<CustomNameInput parentRouteData={parentRouteData} />
<CustomUrlInput parentRouteData={parentRouteData} />
<InGameNameInputs parentRouteData={parentRouteData} />
<SensSelects parentRouteData={parentRouteData} />
@ -290,6 +295,31 @@ function CustomUrlInput({
);
}
function CustomNameInput({
parentRouteData,
}: {
parentRouteData: UserPageLoaderData;
}) {
const { t } = useTranslation(["user"]);
return (
<div className="w-full">
<Label htmlFor="customName">{t("user:customName")}</Label>
<Input
name="customName"
id="customName"
maxLength={USER.CUSTOM_NAME_MAX_LENGTH}
defaultValue={parentRouteData.customName ?? undefined}
/>
<FormMessage type="info">
{t("user:forms.customName.info", {
discordName: parentRouteData.discordName,
})}
</FormMessage>
</div>
);
}
function InGameNameInputs({
parentRouteData,
}: {

View File

@ -12,7 +12,7 @@ import { YouTubeIcon } from "~/components/icons/YouTube";
import { useTranslation } from "react-i18next";
import { modesShort } from "~/modules/in-game-lists";
import { type SendouRouteHandle } from "~/utils/remix";
import { isNewDiscordUniqueName, rawSensToString } from "~/utils/strings";
import { rawSensToString } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import { assertUnreachable } from "~/utils/types";
import {
@ -41,16 +41,8 @@ export default function UserInfoPage() {
<Avatar user={data} size="lg" className="u__avatar" />
<div>
<h2 className="u__name">
<div>{data.discordName}</div>
<div>{data.username}</div>
<div>
<span className="u__discriminator">
{!isNewDiscordUniqueName(data.discordDiscriminator) ? (
<>
#{data.discordDiscriminator}
<wbr />
</>
) : null}
</span>
{data.country ? <Flag countryCode={data.country} tiny /> : null}
</div>
</h2>

View File

@ -579,7 +579,7 @@ function Players({
className="u__season__player-name"
>
<Avatar user={player.user} size="xs" className="mx-auto" />
{player.user.discordName}
{player.user.username}
</Link>
<div
className={clsx("text-xs font-bold", {
@ -803,7 +803,7 @@ function MatchMembersRow({
<div key={member.discordId} className="u__season__match__user">
<Avatar user={member} size="xxs" />
<span className="u__season__match__user__name">
{member.discordName}
{member.username}
</span>
{typeof member.weaponSplId === "number" ? (
<WeaponImage

View File

@ -14,7 +14,7 @@ import { useUser } from "~/features/auth/core/user";
import { getUserId } from "~/features/auth/core/user.server";
import { canAddCustomizedColorsToUserProfile, isAdmin } from "~/permissions";
import { notFoundIfFalsy, type SendouRouteHandle } from "~/utils/remix";
import { discordFullName, makeTitle } from "~/utils/strings";
import { makeTitle } from "~/utils/strings";
import {
isCustomUrl,
navIconUrl,
@ -41,7 +41,7 @@ import "~/styles/u.css";
export const meta: MetaFunction<typeof loader> = ({ data }) => {
if (!data) return [];
return [{ title: makeTitle(discordFullName(data)) }];
return [{ title: makeTitle(data.username) }];
};
export const handle: SendouRouteHandle = {
@ -58,7 +58,7 @@ export const handle: SendouRouteHandle = {
type: "IMAGE",
},
{
text: data.discordName,
text: data.username,
href: userPage(data),
type: "TEXT",
},

View File

@ -102,7 +102,7 @@ function UsersList() {
<div className="u-search__user">
<Avatar size="sm" user={user} />
<div>
<div>{user.discordName}</div>
<div>{user.username}</div>
{user.inGameName ? (
<div className="u-search__ign">
{t("user:ign.short")}: {user.inGameName}

View File

@ -1,6 +1,5 @@
import { Link } from "@remix-run/react";
import { Avatar } from "~/components/Avatar";
import { discordFullName } from "~/utils/strings";
import { userVodsPage } from "~/utils/urls";
import type { Vod } from "../vods-types";
@ -14,7 +13,7 @@ export function PovUser({ pov }: { pov: Vod["pov"] }) {
return (
<Link to={userVodsPage(pov)} className="stack horizontal xs">
<Avatar user={pov} size="xxs" />
<span className="text-sm font-semi-bold">{discordFullName(pov)}</span>
<span className="text-sm font-semi-bold">{pov.username}</span>
</Link>
);
}

View File

@ -24,14 +24,12 @@ const videoMatchesStm = sql.prepare(/* sql */ `
json_group_array("vp"."playerName") as "playerNames",
json_group_array(
json_object(
'discordName',
"u"."discordName",
'username',
"u"."username",
'discordId',
"u"."discordId",
'discordAvatar',
"u"."discordAvatar",
'discordDiscriminator',
"u"."discordDiscriminator",
'customUrl',
"u"."customUrl",
'id',

View File

@ -16,14 +16,12 @@ const query = (byUser?: true) => /* sql */ `
json_group_array("vp"."playerName") as "playerNames",
json_group_array(
json_object(
'discordName',
"u"."discordName",
'username',
"u"."username",
'discordId',
"u"."discordId",
'discordAvatar',
"u"."discordAvatar",
'discordDiscriminator',
"u"."discordDiscriminator",
'customUrl',
"u"."customUrl"
)

View File

@ -14,12 +14,7 @@ export interface Vod {
pov?:
| Pick<
User,
| "discordName"
| "discordId"
| "discordAvatar"
| "discordDiscriminator"
| "customUrl"
| "id"
"username" | "discordId" | "discordAvatar" | "customUrl" | "id"
>
| string;
title: Video["title"];

View File

@ -112,7 +112,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
loginDisabled: process.env["LOGIN_DISABLED"] === "true",
user: user
? {
discordName: user.discordName,
username: user.username,
discordAvatar: user.discordAvatar,
discordId: user.discordId,
id: user.id,

View File

@ -30,11 +30,7 @@
align-items: center;
grid-area: name;
overflow-wrap: anywhere;
}
.u__discriminator {
color: var(--text-lighter);
margin-inline-end: var(--s-2);
gap: var(--s-2-5);
}
.u__country-name {

View File

@ -113,7 +113,6 @@ export const database = {
Array.from({ length: count }).map((_, i) => ({
id: i + 1,
discordName: `user${i + 1}`,
discordDiscriminator: "0",
discordId: String(i),
})),
)

View File

@ -3,7 +3,7 @@ import type { Tables } from "~/db/tables";
export const COMMON_USER_FIELDS = [
"User.id",
"User.discordName",
"User.username",
"User.discordId",
"User.discordAvatar",
"User.customUrl",
@ -11,7 +11,7 @@ export const COMMON_USER_FIELDS = [
export type CommonUser = Pick<
Tables["User"],
"id" | "discordName" | "discordId" | "discordAvatar" | "customUrl"
"id" | "username" | "discordId" | "discordAvatar" | "customUrl"
>;
export const userChatNameColor = sql<

View File

@ -1,18 +1,6 @@
import type { GearType, User } from "~/db/types";
import type { GearType } from "~/db/types";
import { assertUnreachable } from "./types";
export const isNewDiscordUniqueName = (discordDiscriminator: string) =>
discordDiscriminator === "0";
export function discordFullName(
user: Pick<User, "discordName" | "discordDiscriminator">,
) {
if (isNewDiscordUniqueName(user.discordDiscriminator)) {
return user.discordName;
}
return `${user.discordName}#${user.discordDiscriminator}`;
}
export function inGameNameWithoutDiscriminator(inGameName: string) {
return inGameName.split("#")[0];
}

Binary file not shown.

View File

@ -190,7 +190,7 @@ test.describe("Tournament", () => {
await teamSelect.selectOption(String(teamWithSpace.id));
await selectUser({
labelName: "User",
userName: firstNonOwnerMember.discordName,
userName: firstNonOwnerMember.username,
page,
});
await submit(page);

View File

@ -1,5 +1,6 @@
{
"customUrl": "Custom URL",
"customName": "Custom name",
"ign": "In-game name",
"ign.short": "IGN",
"country": "Country",
@ -18,6 +19,7 @@
"forms.commissionsOpen": "Commissions open",
"forms.commissionText": "Commission info",
"forms.commissionText.info": "Price, slots open or other info related to commissioning you",
"forms.customName.info": "If missing, your Discord display name is used: \"{{discordName}}\"",
"results.title": "Results",
"results.placing": "Placing",

View File

@ -0,0 +1,13 @@
export function up(db) {
db.transaction(() => {
db.prepare(/* sql */ `alter table "User" add "customName" text`).run();
})();
db.prepare(
/* sql */ `alter table "User" add "username" text generated always as (coalesce("customName", "discordName")) virtual`,
).run();
db.prepare(
/* sql */ `alter table "User" drop column "discordDiscriminator"`,
).run();
}