+ {data && data.teams.length > 0 && tournament.registrationOpen ? (
-
-
+
+
) : null}
-
-
- setTeamName(e.target.value)}
- readOnly={!tournament.registrationOpen || signUpWithTeam}
- />
-
+ {!signUpWithTeamId ? (
+
+
+ setTeamName(e.target.value)}
+ readOnly={
+ !tournament.registrationOpen || Boolean(signUpWithTeamId)
+ }
+ />
+
+ ) : (
+
+ )}
{tournament.registrationOpen || avatarUrl ? (
diff --git a/app/features/user-page/UserRepository.server.ts b/app/features/user-page/UserRepository.server.ts
index eb2468d1b..af3f9776e 100644
--- a/app/features/user-page/UserRepository.server.ts
+++ b/app/features/user-page/UserRepository.server.ts
@@ -171,10 +171,30 @@ export async function findProfileByIdentifier(
"Team.name",
"Team.customUrl",
"Team.id",
+ "TeamMember.role as userTeamRole",
"UserSubmittedImage.url as avatarUrl",
])
.whereRef("TeamMember.userId", "=", "User.id"),
).as("team"),
+ jsonArrayFrom(
+ eb
+ .selectFrom("TeamMemberWithSecondary")
+ .innerJoin("Team", "Team.id", "TeamMemberWithSecondary.teamId")
+ .leftJoin(
+ "UserSubmittedImage",
+ "UserSubmittedImage.id",
+ "Team.avatarImgId",
+ )
+ .select([
+ "Team.name",
+ "Team.customUrl",
+ "Team.id",
+ "TeamMemberWithSecondary.role as userTeamRole",
+ "UserSubmittedImage.url as avatarUrl",
+ ])
+ .whereRef("TeamMemberWithSecondary.userId", "=", "User.id")
+ .where("TeamMemberWithSecondary.isMainTeam", "=", 0),
+ ).as("secondaryTeams"),
jsonArrayFrom(
eb
.selectFrom("BadgeOwner")
diff --git a/app/features/user-page/routes/u.$identifier.index.tsx b/app/features/user-page/routes/u.$identifier.index.tsx
index 8676efcf4..57f11c2e2 100644
--- a/app/features/user-page/routes/u.$identifier.index.tsx
+++ b/app/features/user-page/routes/u.$identifier.index.tsx
@@ -25,11 +25,12 @@ import {
} from "~/utils/urls";
import type { UserPageLoaderData } from "./u.$identifier";
+import { Popover } from "~/components/Popover";
import { loader } from "../loaders/u.$identifier.index.server";
export { loader };
export const handle: SendouRouteHandle = {
- i18n: "badges",
+ i18n: ["badges", "team"],
};
export default function UserInfoPage() {
@@ -79,23 +80,90 @@ export default function UserInfoPage() {
}
function TeamInfo() {
+ const { t } = useTranslation(["team"]);
const data = useLoaderData
();
if (!data.user.team) return null;
return (
-
- {data.user.team.avatarUrl ? (
-
- ) : null}
- {data.user.team.name}
-
+
+
+ {data.user.team.avatarUrl ? (
+
})
+ ) : null}
+
+ {data.user.team.name}
+ {data.user.team.userTeamRole ? (
+
+ {t(`team:roles.${data.user.team.userTeamRole}`)}
+
+ ) : null}
+
+
+
+
+ );
+}
+
+function SecondaryTeamsPopover() {
+ const { t } = useTranslation(["team"]);
+
+ const data = useLoaderData();
+
+ if (data.user.secondaryTeams.length === 0) return null;
+
+ return (
+
+ +{data.user.secondaryTeams.length}
+
+ }
+ triggerClassName="minimal tiny focus-text-decoration"
+ >
+
+ {data.user.secondaryTeams.map((team) => (
+
+
+ {team.avatarUrl ? (
+
})
+ ) : null}
+ {team.name}
+
+ {team.userTeamRole ? (
+
+ {t(`team:roles.${team.userTeamRole}`)}
+
+ ) : null}
+
+ ))}
+
+
);
}
diff --git a/app/styles/common.css b/app/styles/common.css
index 2655f37ad..fc59c2613 100644
--- a/app/styles/common.css
+++ b/app/styles/common.css
@@ -958,6 +958,10 @@ dialog::backdrop {
gap: var(--s-12);
}
+.stack.xxl {
+ gap: var(--s-16);
+}
+
.stack.horizontal {
flex-direction: row;
}
diff --git a/app/styles/front.css b/app/styles/front.css
index 50a1c4a7d..6c5169bd9 100644
--- a/app/styles/front.css
+++ b/app/styles/front.css
@@ -172,3 +172,29 @@
background-color: var(--card-text);
color: var(--card-bg);
}
+
+.front__change-log__img {
+ max-height: 300px;
+ border-radius: var(--rounded-sm);
+}
+
+.front__change-log__icon-button {
+ display: flex;
+ flex-direction: row;
+ gap: var(--s-1-5);
+ justify-content: center;
+ align-items: center;
+ font-weight: var(--bold);
+ font-size: var(--fonts-xs);
+ border-radius: 100%;
+ color: var(--text-lighter);
+}
+
+.front__change-log__icon-button:hover {
+ color: var(--theme);
+}
+
+.front__external-link-icon {
+ width: 18px;
+ stroke-width: 2px;
+}
diff --git a/app/styles/u.css b/app/styles/u.css
index 65b96ed88..63b25e81f 100644
--- a/app/styles/u.css
+++ b/app/styles/u.css
@@ -18,10 +18,11 @@
.u__team {
display: flex;
- color: var(--text-lighter);
font-weight: var(--bold);
+ color: var(--text);
gap: var(--s-1-5);
grid-area: team;
+ align-items: center;
}
.u__name {
diff --git a/app/styles/utils.css b/app/styles/utils.css
index 496f34aae..94a7c4512 100644
--- a/app/styles/utils.css
+++ b/app/styles/utils.css
@@ -42,6 +42,10 @@
color: var(--text-lighter) !important;
}
+.text-theme-transparent {
+ color: var(--theme-transparent);
+}
+
.text-error {
color: var(--theme-error);
}
@@ -406,6 +410,11 @@
outline: 2px solid var(--theme);
}
+.focus-text-decoration:focus-visible {
+ outline: none !important;
+ text-decoration: underline;
+}
+
.label-no-spacing {
--label-margin: 0;
}
diff --git a/app/utils/playwright.ts b/app/utils/playwright.ts
index 44a94dc81..7468a2752 100644
--- a/app/utils/playwright.ts
+++ b/app/utils/playwright.ts
@@ -27,7 +27,7 @@ export async function selectUser({
await expect(combobox).not.toBeDisabled();
await combobox.clear();
- await combobox.type(userName);
+ await combobox.fill(userName);
await expect(page.getByTestId("combobox-option-0")).toBeVisible();
await page.keyboard.press("Enter");
}
@@ -68,11 +68,11 @@ export function impersonate(page: Page, userId = ADMIN_ID) {
return page.request.post(`/auth/impersonate?id=${userId}`);
}
-export async function submit(page: Page) {
+export async function submit(page: Page, testId?: string) {
const responsePromise = page.waitForResponse(
(res) => res.request().method() === "POST",
);
- await page.getByTestId("submit-button").click();
+ await page.getByTestId(testId ?? "submit-button").click();
await responsePromise;
}
diff --git a/bun.lockb b/bun.lockb
index e5c7659f12b784d53fbefc848f67bd12afeec7af..61f13808136a78c7f6a14d0ca4f0a25c03ec1da1 100755
GIT binary patch
delta 384
zcmX@`L-xQA*$H|IOlMw{w%R7~ZrSpvCg51ftoh5_8y7~(Mekv_RW15*i%+B9RzF6m
zqRA;C8q+5fF-m|KlLNdtrr#)H6ab3`gfViqOBOS3mn>$w^2nid>&k*FlFur>zRGWS
z>Ks(qQ!QJUyQXJ%b64Wp6Km%`PWfE2{f0yMAEhggBw8N|cE$ghSbX;Y)9U!lf@;--
z*oV_~!weC>X)^UM?^n}p;opKp=kJX;
zUGJOeEFa=_c=h#TM?;;a&s=#UO6>k*Mg|5(hJXD3fb@J81~_2pj@YDn>HfCWxz8h8
zCNEG-2y0ML_;^{w-g>S{zdM&)4dn
zkXUNjem9JH``s{>rxO?r+Ydiy*?#yrtBx|G;q(t&Y?44BE^fB%T-@xIInys?uq#gA
bP|nUdJ-3`)7^tGLoIRA$aQpRg_DmK4IDDoO
delta 384
zcmX@`L-xQA*$H|IGfvOA{PM=ilc&R^i_0}K>Q7#6T75$7zzOS;-s=nMRvr<>{bN^ibf`otT4o??dGq<|=Z_18+(^{_{;Ck?)
zRfS`^ZWyyfeb3WHZfjSTToC`4!`;moG3Vn;!Fk%o&o6p%1Mob>Q8z_g}{3
z*1x+?c${$Muhm|0CWO7k^Z2z1xj!nR85tND8UFGA1Jd(Z7~p{AicP!!;Z+llADY<8
z{N3h??+7oK;7e(O~)qE;dP^5EnPwb}nvq%be*+Eo_R@
cH {
test("filters teams", async ({ page }) => {
@@ -24,7 +24,7 @@ test.describe("Team search page", () => {
await expect(firstTeamName).toHaveText("Alliance Rogue");
await expect(secondTeamName).toBeVisible();
- await searchInput.type("Alliance Rogue");
+ await searchInput.fill("Alliance Rogue");
await expect(secondTeamName).not.toBeVisible();
await firstTeamName.click();
@@ -38,10 +38,10 @@ test.describe("Team search page", () => {
await page.getByTestId("new-team-button").click();
await expect(page).toHaveURL(/new=true/);
- await page.getByTestId("new-team-name-input").type("Team Olive");
+ await page.getByTestId("new-team-name-input").fill("Chimera");
await submit(page);
- await expect(page).toHaveURL(/team-olive/);
+ await expect(page).toHaveURL(/chimera/);
});
});
@@ -54,13 +54,13 @@ test.describe("Team page", () => {
await page.getByTestId("edit-team-button").click();
await page.getByTestId("name-input").clear();
- await page.getByTestId("name-input").type("Better Alliance Rogue");
+ await page.getByTestId("name-input").fill("Better Alliance Rogue");
await page.getByTestId("twitter-input").clear();
- await page.getByTestId("twitter-input").type("BetterAllianceRogue");
+ await page.getByTestId("twitter-input").fill("BetterAllianceRogue");
await page.getByTestId("bio-textarea").clear();
- await page.getByTestId("bio-textarea").type("shorter bio");
+ await page.getByTestId("bio-textarea").fill("shorter bio");
await page.getByTestId("edit-team-submit-button").click();
@@ -77,6 +77,9 @@ test.describe("Team page", () => {
await impersonate(page, ADMIN_ID);
await navigate({ page, url: teamPage("alliance-rogue") });
+ // Owner is Sendou
+ await expect(page.getByTestId(`member-owner-${ADMIN_ID}`)).toBeVisible();
+
await page.getByTestId("manage-roster-button").click();
await page.getByTestId("role-select-0").selectOption("SUPPORT");
@@ -92,7 +95,9 @@ test.describe("Team page", () => {
await expect(page.getByTestId("member-row-role-0")).toHaveText("Support");
await expect(page).not.toHaveURL(/roster/);
- await isNotVisible(page.getByTestId("manage-roster-button"));
+
+ // Owner is not Sendou
+ await isNotVisible(page.getByTestId(`member-owner-${ADMIN_ID}`));
});
test("deletes team", async ({ page }) => {
@@ -138,4 +143,35 @@ test.describe("Team page", () => {
await page.getByTestId("leave-team-button").isVisible();
});
+
+ test("joins a secondary team, makes main team & leaves making the seconary team the main one", async ({
+ page,
+ }) => {
+ await seed(page);
+ await impersonate(page, ADMIN_ID);
+ await navigate({ page, url: teamPage("team-olive") });
+
+ await page.getByTestId("manage-roster-button").click();
+
+ const inviteLink = await page.getByTestId("invite-link").innerText();
+ await navigate({ page, url: inviteLink });
+ await submit(page);
+
+ await submit(page, "make-main-team-button");
+
+ await navigate({ page, url: userPage({ discordId: ADMIN_DISCORD_ID }) });
+
+ await expect(page.getByTestId("secondary-team-trigger")).toBeVisible();
+ await isNotVisible(page.getByText("Alliance Rogue"));
+
+ await page.getByTestId("main-team-link").click();
+
+ await page.getByTestId("leave-team-button").click();
+ await modalClickConfirmButton(page);
+
+ await navigate({ page, url: userPage({ discordId: ADMIN_DISCORD_ID }) });
+
+ await isNotVisible(page.getByTestId("secondary-team-trigger"));
+ await expect(page.getByText("Alliance Rogue")).toBeVisible();
+ });
});
diff --git a/e2e/tournament.spec.ts b/e2e/tournament.spec.ts
index c7d8fbbe1..055ca6f66 100644
--- a/e2e/tournament.spec.ts
+++ b/e2e/tournament.spec.ts
@@ -61,7 +61,7 @@ test.describe("Tournament", () => {
await page.getByTestId("tab-Register").click();
- await page.getByLabel("Team name").type("Chimera");
+ await page.getByLabel("Pick-up name").fill("Chimera");
await page.getByTestId("save-team-button").click();
await page.getByTestId("add-player-button").click();
diff --git a/e2e/user-page.spec.ts b/e2e/user-page.spec.ts
index 5b92a38b1..11d2e0e8d 100644
--- a/e2e/user-page.spec.ts
+++ b/e2e/user-page.spec.ts
@@ -29,7 +29,7 @@ test.describe("User page", () => {
await page.getByLabel("R-stick sens").selectOption("0");
await page.getByLabel("Motion sens").selectOption("-50");
await page.getByLabel("Country").selectOption("SE");
- await page.getByLabel("Bio").type("My awesome bio");
+ await page.getByLabel("Bio").fill("My awesome bio");
await submitEditForm(page);
await page.getByTestId("flag-SV").isVisible();
diff --git a/e2e/vods.spec.ts b/e2e/vods.spec.ts
index b0c0ceab8..1d563339e 100644
--- a/e2e/vods.spec.ts
+++ b/e2e/vods.spec.ts
@@ -21,11 +21,11 @@ test.describe("VoDs page", () => {
await page
.getByLabel("YouTube URL")
- .type("https://www.youtube.com/watch?v=o7kWlMZP3lM");
+ .fill("https://www.youtube.com/watch?v=o7kWlMZP3lM");
await page
.getByLabel("Video title")
- .type("ITZXI Finals - Team Olive vs. Astral [CAMO TENTA PoV]");
+ .fill("ITZXI Finals - Team Olive vs. Astral [CAMO TENTA PoV]");
await page.getByLabel("Video date").fill("2021-06-20");
@@ -49,8 +49,8 @@ test.describe("VoDs page", () => {
await page.getByTestId("add-match").click();
- await page.getByTestId("match-2-seconds").type("55");
- await page.getByTestId("match-2-minutes").type("5");
+ await page.getByTestId("match-2-seconds").fill("55");
+ await page.getByTestId("match-2-minutes").fill("5");
await page.getByTestId("match-2-mode").selectOption("RM");
await page.getByTestId("match-2-stage").selectOption("6");
await selectWeapon({
@@ -76,11 +76,11 @@ test.describe("VoDs page", () => {
await page
.getByLabel("YouTube URL")
- .type("https://www.youtube.com/watch?v=QFk1Gf91SwI");
+ .fill("https://www.youtube.com/watch?v=QFk1Gf91SwI");
await page
.getByLabel("Video title")
- .type("BIG ! vs Starburst - Splatoon 3 Grand Finals - The Big House 10");
+ .fill("BIG ! vs Starburst - Splatoon 3 Grand Finals - The Big House 10");
await page.getByLabel("Video date").fill("2022-07-21");
diff --git a/locales/da/team.json b/locales/da/team.json
index dc268902f..a9fa2efb0 100644
--- a/locales/da/team.json
+++ b/locales/da/team.json
@@ -35,6 +35,5 @@
"validation.SHORT_CODE": "Invitationskoden har ikke den korrekte længde. Har du kopieret hele URL-adressen? ",
"validation.TEAM_FULL": "Holdet, som du prøver at blive medlem af, er fuldt",
"validation.INVITE_CODE_WRONG": "Ukorrekt invitationskode",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Du er allerede medlem af et hold. Du skal forlade det hold for at blive medlem af et andet.",
"validation.VALID": "Bliv medlem af {{teamName}}?"
}
diff --git a/locales/de/team.json b/locales/de/team.json
index 861fe63a0..8b3b84eb9 100644
--- a/locales/de/team.json
+++ b/locales/de/team.json
@@ -33,6 +33,5 @@
"validation.SHORT_CODE": "Einladungscode hat nicht die richtige Länge. Hast du die vollständige URL kopiert?",
"validation.TEAM_FULL": "Das Team, dem du beitreten willst, ist bereits voll.",
"validation.INVITE_CODE_WRONG": "Einladungscode ist falsch.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Du bist bereits in einem anderen Team. Verlasse es, bevor du einem neuen beitrittst.",
"validation.VALID": "{{teamName}} beitreten?"
}
diff --git a/locales/en/common.json b/locales/en/common.json
index 03e5a099a..7fa7d62b3 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -187,6 +187,7 @@
"support.perk.prioritySupport": "Priority support",
"support.perk.prioritySupport.extra": "Access to a separate helpdesk that I prioritize for support requests",
"support.perk.previewQ": "Preview groups in SendouQ before joining",
+ "support.perk.joinFive": "Join up to 5 teams",
"custom.colors.title": "Custom colors",
"custom.colors.bg": "Background",
diff --git a/locales/en/team.json b/locales/en/team.json
index 49d5f2533..293fba50d 100644
--- a/locales/en/team.json
+++ b/locales/en/team.json
@@ -3,6 +3,7 @@
"newTeam.header": "Creating a new team",
"teamSearch.placeholder": "Search for a team or player...",
"actionButtons.leaveTeam": "Leave Team",
+ "actionButtons.makeMainTeam": "Make main team",
"leaveTeam.header": "Are you sure you want to leave {{teamName}}?",
"actionButtons.leaveTeam.confirm": "Leave",
"actionButtons.editTeam": "Edit Team",
@@ -15,13 +16,16 @@
"actionButtons.transferOwnership.confirm": "Transfer",
"deleteTeam.header": "Are you sure you want to delete {{teamName}}?",
"roles.CAPTAIN": "Captain",
+ "roles.CO_CAPTAIN": "Co-Captain",
"roles.FRONTLINE": "Frontline",
+ "roles.SKIRMISHER": "Skirmisher",
"roles.SUPPORT": "Support",
"roles.MIDLINE": "Midline",
"roles.BACKLINE": "Backline",
"roles.FLEX": "Flex",
"roles.SUB": "Sub",
"roles.COACH": "Coach",
+ "roles.CHEERLEADER": "Cheerleader",
"forms.fields.teamTwitter": "Team Twitter",
"forms.fields.bio": "Bio",
"forms.fields.uploadImages": "Upload images",
@@ -35,6 +39,6 @@
"validation.SHORT_CODE": "Invite code is not the right length. Did you copy the full URL?",
"validation.TEAM_FULL": "Team you are trying to join is full.",
"validation.INVITE_CODE_WRONG": "Invite code is wrong.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "You are already in a different team. Leave it first before joining a new one.",
+ "validation.REACHED_TEAM_COUNT_LIMIT": "You have reached the maximum number of teams you can join. (Max 5 for patrons and 2 for others)",
"validation.VALID": "Join {{teamName}}?"
}
diff --git a/locales/es-ES/team.json b/locales/es-ES/team.json
index ef9e92f00..002e9c741 100644
--- a/locales/es-ES/team.json
+++ b/locales/es-ES/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "El código de invitación no tiene la longitud correcta. ¿Copiaste la URL completa?",
"validation.TEAM_FULL": "El equipo al que te intentas unir ya esta lleno.",
"validation.INVITE_CODE_WRONG": "El código de invitación es incorrecto.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Ya eres parte de otro equipo. Debes salir de este antes de entrar a otro.",
"validation.VALID": "¿Entrar a {{teamName}}?"
}
diff --git a/locales/es-US/team.json b/locales/es-US/team.json
index 3f72b408d..cbf4571bb 100644
--- a/locales/es-US/team.json
+++ b/locales/es-US/team.json
@@ -35,6 +35,5 @@
"validation.SHORT_CODE": "El código de invitación no tiene la longitud correcta. ¿Copiaste la URL completa?",
"validation.TEAM_FULL": "El equipo al que te intentas unir ya esta lleno.",
"validation.INVITE_CODE_WRONG": "El código de invitación es incorrecto.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Ya eres parte de otro equipo. Debes salir de este antes de entrar a otro.",
"validation.VALID": "¿Entrar a {{teamName}}?"
}
diff --git a/locales/fr-CA/team.json b/locales/fr-CA/team.json
index bf4816f93..e961b6ca6 100644
--- a/locales/fr-CA/team.json
+++ b/locales/fr-CA/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "Le code d'invitation n'a pas la bonne longueur. Avez-vous bien copier l'URL au complet ?",
"validation.TEAM_FULL": "L'équipe que vous essayez de rejoindre est complète.",
"validation.INVITE_CODE_WRONG": "Le code d'invitation est incorrect.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Vous êtes déjà dans une autre équipe. Quittez-la avant d'en rejoindre une nouvelle.",
"validation.VALID": "Rejoindre {{teamName}} ?"
}
diff --git a/locales/fr-EU/team.json b/locales/fr-EU/team.json
index bf4816f93..e961b6ca6 100644
--- a/locales/fr-EU/team.json
+++ b/locales/fr-EU/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "Le code d'invitation n'a pas la bonne longueur. Avez-vous bien copier l'URL au complet ?",
"validation.TEAM_FULL": "L'équipe que vous essayez de rejoindre est complète.",
"validation.INVITE_CODE_WRONG": "Le code d'invitation est incorrect.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Vous êtes déjà dans une autre équipe. Quittez-la avant d'en rejoindre une nouvelle.",
"validation.VALID": "Rejoindre {{teamName}} ?"
}
diff --git a/locales/he/team.json b/locales/he/team.json
index 48d9b017f..d638a281d 100644
--- a/locales/he/team.json
+++ b/locales/he/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "קוד ההזמנה אינו באורך המתאים. העתקתם את כתובת האתר המלאה?",
"validation.TEAM_FULL": "הצוות שאתם מנסים להצטרף אליו מלא.",
"validation.INVITE_CODE_WRONG": "קוד ההזמנה שגוי.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "הנכם בצוות אחר. עזבו אותו קודם לפני הצטרפות לצוות אחר.",
"validation.VALID": "הצטרפו ל-{{teamName}}?"
}
diff --git a/locales/ja/team.json b/locales/ja/team.json
index 5897db2ec..cc3fff5bf 100644
--- a/locales/ja/team.json
+++ b/locales/ja/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "招待コードの長さが正しくありません。URL をすべてコピーしましたか?",
"validation.TEAM_FULL": "参加しようとしているチームはすでに満員です。",
"validation.INVITE_CODE_WRONG": "招待コードが正しくありません。",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "すでに別のチームに参加済みです。まず参加済みのチームを抜けてから新しいチームに参加してください。",
"validation.VALID": "{{teamName}} に参加しますか?"
}
diff --git a/locales/pl/team.json b/locales/pl/team.json
index 85508e2fe..fd1170a44 100644
--- a/locales/pl/team.json
+++ b/locales/pl/team.json
@@ -32,6 +32,5 @@
"validation.SHORT_CODE": "Zaproszenie jest złej długości. Czy napewno skopiowałeś/aś pełne URL?",
"validation.TEAM_FULL": "Drużyna, do której próbujesz dołączyć, jest pełna.",
"validation.INVITE_CODE_WRONG": "Zaproszenie niepoprawne.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Jesteś już na innej drużynie. Opuść ją zanim dołączysz do nowej.",
"validation.VALID": "Dołączyć do {{teamName}}?"
}
diff --git a/locales/pt-BR/team.json b/locales/pt-BR/team.json
index 93b81e5c5..21a0ff49d 100644
--- a/locales/pt-BR/team.json
+++ b/locales/pt-BR/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "O código de convite não está no comprimento certo. Você copiou o URL completo?",
"validation.TEAM_FULL": "O time que você está tentando se juntar está cheio.",
"validation.INVITE_CODE_WRONG": "O código de convite está incorreto.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Você já está em um time diferente. Saia dele primeiro antes de entrar em outro.",
"validation.VALID": "Entrar no(a) {{teamName}}?"
}
diff --git a/locales/ru/team.json b/locales/ru/team.json
index 1bf996003..07f52850b 100644
--- a/locales/ru/team.json
+++ b/locales/ru/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "Код приглашения неверной длинны. Вы скопировали URL целиком?",
"validation.TEAM_FULL": "Команда, в которую вы пытаетесь попасть, уже заполнена.",
"validation.INVITE_CODE_WRONG": "Код приглашения неверен.",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "Вы уже в другой команде. Покиньте эту, прежде чем вступить в новую.",
"validation.VALID": "Присоединиться к {{teamName}}?"
}
diff --git a/locales/zh/team.json b/locales/zh/team.json
index 6c7dd2624..2f3142789 100644
--- a/locales/zh/team.json
+++ b/locales/zh/team.json
@@ -34,6 +34,5 @@
"validation.SHORT_CODE": "邀请码长度不符,请确保您复制了完整的URL。",
"validation.TEAM_FULL": "您想要加入的队伍成员已满。",
"validation.INVITE_CODE_WRONG": "邀请码有误。",
- "validation.ALREADY_IN_DIFFERENT_TEAM": "您已经在另一支队伍里了。如想要加入新队伍,请退出该队。",
"validation.VALID": "加入 {{teamName}} ?"
}
diff --git a/migrations/069-many-teams.js b/migrations/069-many-teams.js
new file mode 100644
index 000000000..5bc9af5a0
--- /dev/null
+++ b/migrations/069-many-teams.js
@@ -0,0 +1,38 @@
+export function up(db) {
+ db.transaction(() => {
+ db.prepare(
+ /* sql */ `alter table "AllTeamMember" add "isMainTeam" integer default 1`,
+ ).run();
+
+ db.prepare(/*sql */ `drop view "TeamMember"`).run();
+ db.prepare(
+ /*sql*/ `
+ create view "TeamMember"
+ as
+ select "AllTeamMember".*
+ from "AllTeamMember"
+ left join "Team" on "Team"."id" = "AllTeamMember"."teamId"
+ where "AllTeamMember"."leftAt" is null
+ and
+ -- if team id is null the team is deleted
+ "Team"."id" is not null
+ and
+ "AllTeamMember"."isMainTeam" = 1
+ `,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `
+ create view "TeamMemberWithSecondary"
+ as
+ select "AllTeamMember".*
+ from "AllTeamMember"
+ left join "Team" on "Team"."id" = "AllTeamMember"."teamId"
+ where "AllTeamMember"."leftAt" is null
+ and
+ -- if team id is null the team is deleted
+ "Team"."id" is not null
+ `,
+ ).run();
+ })();
+}
diff --git a/package.json b/package.json
index cc862b874..aa08ae561 100644
--- a/package.json
+++ b/package.json
@@ -88,7 +88,7 @@
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
- "@playwright/test": "^1.47.0",
+ "@playwright/test": "^1.47.1",
"@remix-run/dev": "^2.11.2",
"@types/better-sqlite3": "^7.6.11",
"@types/bun": "^1.1.8",
diff --git a/playwright.config.ts b/playwright.config.ts
index aed5fa20d..d6e2d9421 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -37,8 +37,7 @@ const config: PlaywrightTestConfig = {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:5173",
- // disabled because https://github.com/microsoft/playwright/issues/27048
- trace: "off",
+ trace: "retain-on-failure",
permissions: ["clipboard-read"],
},