This commit is contained in:
Kalle 2025-11-09 14:47:40 +02:00 committed by GitHub
parent 24008775aa
commit 9dbe19e0b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 161 additions and 14 deletions

View File

@ -45,6 +45,8 @@ export interface Team {
inviteCode: string;
name: string;
bsky: string | null;
/** Team's tag, typically used in-game in front of users' names to indicate they are a member of the team. */
tag: string | null;
}
export interface TeamMember {

View File

@ -20,6 +20,7 @@ export function findAllUndisbanded() {
.select(({ eb }) => [
"Team.customUrl",
"Team.name",
"Team.tag",
concatUserSubmittedImagePrefix(eb.ref("UserSubmittedImage.url")).as(
"avatarUrl",
),
@ -75,6 +76,7 @@ export function findByCustomUrl(
"Team.name",
"Team.bsky",
"Team.bio",
"Team.tag",
"Team.customUrl",
"Team.css",
concatUserSubmittedImagePrefix(eb.ref("AvatarImage.url")).as("avatarUrl"),
@ -277,10 +279,11 @@ export async function update({
customUrl,
bio,
bsky,
tag,
css,
}: Pick<
Insertable<Tables["Team"]>,
"id" | "name" | "customUrl" | "bio" | "bsky"
"id" | "name" | "customUrl" | "bio" | "bsky" | "tag"
> & { css: string | null }) {
return db
.updateTable("AllTeam")
@ -289,6 +292,7 @@ export async function update({
customUrl,
bio,
bsky,
tag,
css,
})
.where("id", "=", id)

View File

@ -22,6 +22,7 @@ const DEFAULT_FIELDS = {
name: "Team 1",
bio: "",
bsky: "",
tag: "",
} as const;
describe("team page editing", () => {

View File

@ -59,6 +59,7 @@ export default function EditTeamPage() {
<CustomizedColorsInput initialColors={css} />
) : null}
<NameInput />
<TagInput />
<BlueskyInput />
<BioTextarea />
<SubmitButton
@ -174,6 +175,26 @@ function NameInput() {
);
}
function TagInput() {
const { t } = useTranslation(["team"]);
const { team } = useLoaderData<typeof loader>();
const [value, setValue] = React.useState(team.tag ?? "");
return (
<div>
<Label htmlFor="tag">{t("team:forms.fields.tag")}</Label>
<input
id="tag"
name="tag"
maxLength={TEAM.TAG_MAX_LENGTH}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<FormMessage type="info">{t("team:forms.info.tag")}</FormMessage>
</div>
);
}
function BlueskyInput() {
const { t } = useTranslation(["team"]);
const { team } = useLoaderData<typeof loader>();

View File

@ -98,6 +98,11 @@ function TeamBanner() {
})}
</div>
<div className="team__banner__name">
{team.tag ? (
<div className="team__banner__tag team__banner__tag__desktop">
{team.tag}
</div>
) : null}
{team.name} <BskyLink />
</div>
</div>
@ -124,6 +129,11 @@ function MobileTeamNameCountry() {
{team.name}
<BskyLink />
</div>
{team.tag ? (
<div className="team__banner__tag team__banner__tag__mobile">
{team.tag}
</div>
) : null}
</div>
);
}

View File

@ -54,22 +54,33 @@ export default function TeamSearchPage() {
const [inputValue, setInputValue] = React.useState("");
const data = useLoaderData<typeof loader>();
const filteredTeams = data.teams.filter((team) => {
if (!inputValue) return true;
const filteredTeams = () => {
if (!inputValue) return data.teams;
const lowerCaseInput = inputValue.toLowerCase();
const matchingTeams = data.teams.filter((team) => {
if (team.name.toLowerCase().includes(lowerCaseInput)) return true;
if (team.tag && team.tag.toLowerCase() === lowerCaseInput) return true;
if (
team.members.some((m) =>
m.username.toLowerCase().includes(lowerCaseInput),
)
) {
return true;
}
if (team.name.toLowerCase().includes(lowerCaseInput)) return true;
if (
team.members.some((m) =>
m.username.toLowerCase().includes(lowerCaseInput),
)
) {
return true;
}
return false;
});
return false;
});
return matchingTeams.sort((a, b) => {
const aTagExactMatch = a.tag && a.tag.toLowerCase() === lowerCaseInput;
const bTagExactMatch = b.tag && b.tag.toLowerCase() === lowerCaseInput;
if (aTagExactMatch && !bTagExactMatch) return -1;
if (!aTagExactMatch && bTagExactMatch) return 1;
return 0;
});
};
const {
itemsToDisplay,
@ -80,7 +91,7 @@ export default function TeamSearchPage() {
previousPage,
setPage,
} = usePagination({
items: filteredTeams,
items: filteredTeams(),
pageSize: TEAMS_PER_PAGE,
});
@ -125,6 +136,9 @@ export default function TeamSearchPage() {
data-testid={`team-${i}`}
>
{team.name}
{team.tag ? (
<span className="team-search__team__tag">{team.tag}</span>
) : null}
</div>
<div className="team-search__team__members">
{team.members.length === 1

View File

@ -3,6 +3,7 @@ export const TEAM = {
NAME_MIN_LENGTH: 2,
BIO_MAX_LENGTH: 2000,
BSKY_MAX_LENGTH: 50,
TAG_MAX_LENGTH: 6,
MAX_MEMBER_COUNT: 10,
MAX_TEAM_COUNT_NON_PATRON: 2,
MAX_TEAM_COUNT_PATRON: 5,

View File

@ -47,6 +47,10 @@ export const editTeamSchema = z.union([
falsyToNull,
z.string().max(TEAM.BSKY_MAX_LENGTH).nullable(),
),
tag: z.preprocess(
falsyToNull,
z.string().max(TEAM.TAG_MAX_LENGTH).nullable(),
),
css: customCssVarObject,
}),
]);

View File

@ -28,6 +28,15 @@
color: var(--text-lighter);
}
.team-search__team__tag {
font-size: var(--fonts-xs);
color: var(--theme);
padding: var(--s-0-5) var(--s-1);
border-radius: var(--rounded-xs);
margin-left: var(--s-2);
font-weight: var(--bold);
}
.team-search__team__avatar-placeholder {
height: 64px;
min-width: 64px;
@ -78,6 +87,7 @@
display: none;
align-items: center;
gap: var(--s-3);
position: relative;
}
.team__bsky-link {
@ -134,6 +144,27 @@
gap: var(--s-2);
}
.team__banner__tag {
font-size: var(--fonts-sm);
background-color: var(--theme-transparent);
color: var(--theme);
padding: var(--s-1) var(--s-1-5);
border-radius: var(--rounded);
}
.team__banner__tag__desktop {
position: absolute;
bottom: 41px;
right: 0;
display: none;
}
.team__banner__tag__mobile {
font-size: var(--fonts-xs);
padding: var(--s-0-5) var(--s-1);
margin-block: var(--s-1);
}
.team__banner__avatar img {
border-radius: 100%;
}
@ -301,6 +332,9 @@
.team__banner__name {
display: flex;
}
.team__banner__tag__desktop {
display: initial;
}
.team__banner__avatar > div {
width: 10rem;

Binary file not shown.

View File

@ -50,6 +50,25 @@ test.describe("Team search page", () => {
await expect(page).toHaveURL(/chimera/);
});
test("filters teams by tag & displays tag", async ({ page }) => {
await seed(page);
await impersonate(page, ADMIN_ID);
await navigate({ page, url: teamPage("alliance-rogue") });
await page.getByTestId("edit-team-button").click();
await page.getByLabel("Tag").fill("AR");
await page.getByTestId("edit-team-submit-button").click();
await navigate({ page, url: TEAM_SEARCH_PAGE });
const searchInput = page.getByTestId("team-search-input");
await searchInput.fill("ar");
const firstTeamName = page.getByTestId("team-0");
await expect(firstTeamName).toContainText("Alliance Rogue");
await expect(firstTeamName).toContainText("AR");
});
});
test.describe("Team page", () => {

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Upload billeder",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Profilbillede",
"forms.fields.uploadImages.banner": "Holdbanner",
"forms.info.name": "Note: Hvis du ændrer dit holds navn, så kan andre hold overtage det tidligere holdnavn og URL-adresse.",
"forms.info.tag": "",
"forms.errors.duplicateName": "Holdnavnet er taget af et andet hold",
"roster.teamFull": "Holdet kan ikke få flere medlemmer",
"roster.inviteLink.header": "Del invitationslinket for at tilføje medlemmer",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Bilder hochladen",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Profilbild",
"forms.fields.uploadImages.banner": "Teambild-Banner",
"forms.info.name": "Hinweis: Wenn du den Namen deines Teams änderst, können andere Teams den Namen und und die URL für sich beanspruchen.",
"forms.info.tag": "",
"forms.errors.duplicateName": "Es gibt bereits ein Team mit diesem Namen",
"roster.teamFull": "Team ist voll",
"roster.inviteLink.header": "Teile den Einladungslink, um Mitglieder hinzuzufügen",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "Cheerleader",
"forms.fields.teamBsky": "Team Bluesky",
"forms.fields.bio": "Bio",
"forms.fields.tag": "Tag",
"forms.fields.uploadImages": "Upload images",
"forms.fields.removeImages": "Remove images",
"forms.fields.uploadImages.pfp": "Profile Picture",
"forms.fields.uploadImages.banner": "Team Picture Banner",
"forms.info.name": "Note that if you change your team's name then someone else can claim the name and URL for their team",
"forms.info.tag": "Typically used before in-game name to indicate membership of a team (e.g. [TAG] PlayerName)",
"forms.errors.duplicateName": "There is already a team with this name",
"roster.teamFull": "Team is full",
"roster.inviteLink.header": "Share invite link to add members",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Subir imágenes",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Imagen de perfil",
"forms.fields.uploadImages.banner": "Banner del equipo",
"forms.info.name": "Nota que si cambias el nombre de tu equipo, el nombre y la URL estarán libres para que otro equipo los tome",
"forms.info.tag": "",
"forms.errors.duplicateName": "Ya existe un equipo con ese nombre",
"roster.teamFull": "El equipo esta lleno",
"roster.inviteLink.header": "Comparte el link de invitación para agregar más miembros",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Subir imágenes",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Imagen de perfil",
"forms.fields.uploadImages.banner": "Banner del equipo",
"forms.info.name": "Nota que si cambias el nombre de tu equipo, el nombre y la URL estarán libres para que otro equipo los tome",
"forms.info.tag": "",
"forms.errors.duplicateName": "Ya existe un equipo con ese nombre",
"roster.teamFull": "El equipo esta lleno",
"roster.inviteLink.header": "Comparte el link de invitación para agregar más miembros",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Soumettre images",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Emblème",
"forms.fields.uploadImages.banner": "Bannière d'équipe",
"forms.info.name": "Veuillez noter que si vous changer le nom de l'équipe, quelqu'un d'autre pourra s'emparer de l'ancien nom et URL",
"forms.info.tag": "",
"forms.errors.duplicateName": "Il y a déjà une équipe avec ce nom",
"roster.teamFull": "L'équipe est complète",
"roster.inviteLink.header": "Partager le lien d'invitation pour ajouter des membres",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "Cheerleader",
"forms.fields.teamBsky": "Team Bluesky",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Soumettre images",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Emblème",
"forms.fields.uploadImages.banner": "Bannière d'équipe",
"forms.info.name": "Veuillez noter que si vous changer le nom de l'équipe, quelqu'un d'autre pourra s'emparer de l'ancien nom et URL",
"forms.info.tag": "",
"forms.errors.duplicateName": "Il y a déjà une équipe avec ce nom",
"roster.teamFull": "L'équipe est complète",
"roster.inviteLink.header": "Partager le lien d'invitation pour ajouter des membres",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "ביו",
"forms.fields.tag": "",
"forms.fields.uploadImages": "העלאת תמונות",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "תמונת פרופיל",
"forms.fields.uploadImages.banner": "תמונת באנר של הצוות",
"forms.info.name": "שימו לב שאם תשנו את שם הצוות שלכם, מישהו אחר יוכל לקחת בעלות על השם ועל כתובת האתר עבור הצוות שלו",
"forms.info.tag": "",
"forms.errors.duplicateName": "יש כבר צוות בשם הזה",
"roster.teamFull": "הצוות מלא",
"roster.inviteLink.header": "שיתוף קישור הזמנה להוספת חברי צוות",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "Cheerleader",
"forms.fields.teamBsky": "Bluesky del team",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Carica immagini",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Foto profilo",
"forms.fields.uploadImages.banner": "Foto banner del team",
"forms.info.name": "Nota che se cambi il nome del team, qualcun altro può assumere nome e URL per il proprio team",
"forms.info.tag": "",
"forms.errors.duplicateName": "Esiste già un team con questo nome",
"roster.teamFull": "Il team è completo",
"roster.inviteLink.header": "Condividi link di invito per aggiungere membri",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "引き立て役(チアリーダー)",
"forms.fields.teamBsky": "チームの Bluesky",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "画像をアップロード",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "プロファイル画像",
"forms.fields.uploadImages.banner": "チーム画像バナー",
"forms.info.name": "注意: チーム名を変更した場合、他のプレイヤーが変更前の名前と URL を別のチームのために使用することができるようになります。",
"forms.info.tag": "",
"forms.errors.duplicateName": "そのチーム名はすでに使用されています",
"roster.teamFull": "チームは満員です",
"roster.inviteLink.header": "招待リンクを共有する",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "",
"forms.fields.tag": "",
"forms.fields.uploadImages": "",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "",
"forms.fields.uploadImages.banner": "",
"forms.info.name": "",
"forms.info.tag": "",
"forms.errors.duplicateName": "",
"roster.teamFull": "",
"roster.inviteLink.header": "",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "",
"forms.fields.tag": "",
"forms.fields.uploadImages": "",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "",
"forms.fields.uploadImages.banner": "",
"forms.info.name": "",
"forms.info.tag": "",
"forms.errors.duplicateName": "",
"roster.teamFull": "",
"roster.inviteLink.header": "",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Opis",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Wstaw zdjęcia",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Zdjęcie profilowe",
"forms.fields.uploadImages.banner": "Grafika drużyny",
"forms.info.name": "Uwaga: Jeśli zmienisz imię drużyny, ktoś inny może użyć twoje stare imię i URL",
"forms.info.tag": "",
"forms.errors.duplicateName": "Istnieje już drużyna o tym imieniu",
"roster.teamFull": "Drużyna jest pełna",
"roster.inviteLink.header": "Udostępnij link by dodać członków",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "Bio",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Fazer upload de imagens",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Imagem do perfil",
"forms.fields.uploadImages.banner": "Capa do perfil",
"forms.info.name": "Lembre-se que se você mudar o nome do seu time, alguém pode resgatar o nome e o URL para o time dele(a)",
"forms.info.tag": "",
"forms.errors.duplicateName": "Já existe um time com esse nome",
"roster.teamFull": "O time está cheio",
"roster.inviteLink.header": "Compartilhe o link de convite para adicionar membros",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "Чирлидер",
"forms.fields.teamBsky": "Bluesky команды",
"forms.fields.bio": "Описание",
"forms.fields.tag": "",
"forms.fields.uploadImages": "Загрузить изображения",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "Аватар команды",
"forms.fields.uploadImages.banner": "Баннер команды",
"forms.info.name": "Обратите внимание, что если вы измените название команды, то кто-то другой может забрать себе URL и название для своей команды",
"forms.info.tag": "",
"forms.errors.duplicateName": "Уже существует команда с таким названием",
"roster.teamFull": "Команда заполнена",
"roster.inviteLink.header": "Поделитесь ссылкой на приглашение, чтобы добавить участников",

View File

@ -33,11 +33,13 @@
"roles.CHEERLEADER": "",
"forms.fields.teamBsky": "",
"forms.fields.bio": "简介",
"forms.fields.tag": "",
"forms.fields.uploadImages": "上传图片",
"forms.fields.removeImages": "",
"forms.fields.uploadImages.pfp": "头像",
"forms.fields.uploadImages.banner": "队伍横幅",
"forms.info.name": "请注意如果您更改了队名那么其他人便可以使用之前的队名和URL了。",
"forms.info.tag": "",
"forms.errors.duplicateName": "该队名已被使用",
"roster.teamFull": "成员已满",
"roster.inviteLink.header": "分享邀请链接来添加成员",

View File

@ -0,0 +1,5 @@
export function up(db) {
db.transaction(() => {
db.prepare(/* sql */ `alter table "AllTeam" add "tag" text`).run();
})();
}