From 03ad348fdfe5572ed2d9374ffa80bd634bef9143 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:07:35 +0300 Subject: [PATCH] Tournament org page improvements (#2569) --- app/db/tables.ts | 1 + .../map-list-generator/core/MapList.test.ts | 48 +++--- ...TournamentOrganizationRepository.server.ts | 23 ++- .../actions/org.$slug.server.ts | 19 ++- .../components/BanUserModal.tsx | 8 + .../components/BannedPlayersList.module.css | 29 +++- .../components/BannedPlayersList.tsx | 141 ++++++++++++------ .../tournament-organization-schemas.ts | 12 ++ biome.json | 3 +- e2e/org.spec.ts | 7 +- locales/da/org.json | 6 +- locales/de/org.json | 6 +- locales/en/org.json | 6 +- locales/es-ES/org.json | 6 +- locales/es-US/org.json | 6 +- locales/fr-CA/org.json | 6 +- locales/fr-EU/org.json | 6 +- locales/he/org.json | 6 +- locales/it/org.json | 6 +- locales/ja/org.json | 6 +- locales/ko/org.json | 6 +- locales/nl/org.json | 6 +- locales/pl/org.json | 6 +- locales/pt-BR/org.json | 6 +- locales/ru/org.json | 6 +- locales/zh/org.json | 6 +- migrations/097-tournament-org-ban-expires.js | 9 ++ 27 files changed, 305 insertions(+), 91 deletions(-) create mode 100644 migrations/097-tournament-org-ban-expires.js diff --git a/app/db/tables.ts b/app/db/tables.ts index 894aa11ac..3b41cc7a4 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -752,6 +752,7 @@ export interface TournamentOrganizationBannedUser { userId: number; privateNote: string | null; updatedAt: Generated; + expiresAt: number | null; } /** Indicates a user trusts another. Allows direct adding to groups/teams without invite links. */ diff --git a/app/features/map-list-generator/core/MapList.test.ts b/app/features/map-list-generator/core/MapList.test.ts index b4766dc47..0c2ac8553 100644 --- a/app/features/map-list-generator/core/MapList.test.ts +++ b/app/features/map-list-generator/core/MapList.test.ts @@ -310,6 +310,7 @@ describe("MapList.generate()", () => { } }); + // TODO: fix flaky it("replenishes the stage id pool when exhausted", { retry: 10 }, () => { const gen = initGenerator( new MapPool({ @@ -329,30 +330,35 @@ describe("MapList.generate()", () => { } }); - it("replenishes the stage id pool with different order", () => { - const gen = initGenerator( - new MapPool({ - TW: [], - SZ: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - TC: [], - RM: [], - CB: [], - }), - ); - const first = gen.next({ amount: 5 }).value.map((m) => m.stageId); - gen.next({ amount: 5 }); - const third = gen.next({ amount: 5 }).value.map((m) => m.stageId); + // TODO: fix flaky + it( + "replenishes the stage id pool with different order", + { retry: 10 }, + () => { + const gen = initGenerator( + new MapPool({ + TW: [], + SZ: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + TC: [], + RM: [], + CB: [], + }), + ); + const first = gen.next({ amount: 5 }).value.map((m) => m.stageId); + gen.next({ amount: 5 }); + const third = gen.next({ amount: 5 }).value.map((m) => m.stageId); - let someDifferent = false; - for (let i = 0; i < 5; i++) { - if (first[i] !== third[i]) { - someDifferent = true; - break; + let someDifferent = false; + for (let i = 0; i < 5; i++) { + if (first[i] !== third[i]) { + someDifferent = true; + break; + } } - } - expect(someDifferent).toBe(true); - }); + expect(someDifferent).toBe(true); + }, + ); it("replenishes accordingly if considerGuaranteed is true (Bo3)", () => { for (let i = 0; i < 10; i++) { diff --git a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts index 3ff397183..da0489496 100644 --- a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts +++ b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts @@ -1,8 +1,13 @@ +import { isFuture } from "date-fns"; import { sql } from "kysely"; import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite"; import { db } from "~/db/sql"; import type { Tables, TablesInsertable } from "~/db/tables"; -import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; +import { + databaseTimestampNow, + databaseTimestampToDate, + dateToDatabaseTimestamp, +} from "~/utils/dates"; import { COMMON_USER_FIELDS } from "~/utils/kysely.server"; import { mySlugify } from "~/utils/urls"; import { userSubmittedImage } from "~/utils/urls-img"; @@ -64,7 +69,12 @@ export async function findBySlug(slug: string) { "TournamentOrganizationMember.organizationId", "=", "TournamentOrganization.id", - ), + ) + .orderBy( + sql`coalesce(TournamentOrganizationMember.roleDisplayName, TournamentOrganizationMember.role)`, + "asc", + ) + .orderBy("User.username", "asc"), ).as("members"), jsonArrayFrom( eb @@ -472,6 +482,7 @@ export function allBannedUsersByOrganizationId(organizationId: number) { .select([ "TournamentOrganizationBannedUser.privateNote", "TournamentOrganizationBannedUser.updatedAt", + "TournamentOrganizationBannedUser.expiresAt", ...COMMON_USER_FIELDS, ]) .where( @@ -495,10 +506,14 @@ export async function isUserBannedByOrganization({ }) { const result = await db .selectFrom("TournamentOrganizationBannedUser") - .select("userId") + .select(["userId", "expiresAt"]) .where("organizationId", "=", organizationId) .where("userId", "=", userId) .executeTakeFirst(); - return Boolean(result); + if (!result) return false; + + if (!result.expiresAt) return true; + + return isFuture(databaseTimestampToDate(result.expiresAt)); } diff --git a/app/features/tournament-organization/actions/org.$slug.server.ts b/app/features/tournament-organization/actions/org.$slug.server.ts index 05f0fb2e6..8733a10dd 100644 --- a/app/features/tournament-organization/actions/org.$slug.server.ts +++ b/app/features/tournament-organization/actions/org.$slug.server.ts @@ -1,6 +1,12 @@ import type { ActionFunctionArgs } from "@remix-run/node"; +import { isFuture } from "date-fns"; import { requireUser } from "~/features/auth/core/user.server"; import { requirePermission } from "~/modules/permissions/guards.server"; +import { + databaseTimestampToDate, + dateToDatabaseTimestamp, + dayMonthYearToDate, +} from "~/utils/dates"; import { logger } from "~/utils/logger"; import { errorToast, parseRequestPayload } from "~/utils/remix.server"; import { assertUnreachable } from "~/utils/types"; @@ -21,12 +27,18 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { switch (data._action) { case "BAN_USER": { - const bannedUsers = + const allBannedUsers = await TournamentOrganizationRepository.allBannedUsersByOrganizationId( organization.id, ); + const currentlyBannedUsers = allBannedUsers.filter( + (bu) => + !bu.expiresAt || isFuture(databaseTimestampToDate(bu.expiresAt)), + ); - if (bannedUsers.length >= TOURNAMENT_ORGANIZATION.MAX_BANNED_USERS) { + if ( + currentlyBannedUsers.length >= TOURNAMENT_ORGANIZATION.MAX_BANNED_USERS + ) { errorToast( `Organization cannot ban more than ${TOURNAMENT_ORGANIZATION.MAX_BANNED_USERS} users`, ); @@ -36,6 +48,9 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { organizationId: organization.id, userId: data.userId, privateNote: data.privateNote, + expiresAt: data.expiresAt + ? dateToDatabaseTimestamp(dayMonthYearToDate(data.expiresAt)) + : null, }); logger.info( diff --git a/app/features/tournament-organization/components/BanUserModal.tsx b/app/features/tournament-organization/components/BanUserModal.tsx index 4196b8a40..edd664d48 100644 --- a/app/features/tournament-organization/components/BanUserModal.tsx +++ b/app/features/tournament-organization/components/BanUserModal.tsx @@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next"; import type { z } from "zod/v4"; import { SendouButton } from "~/components/elements/Button"; import { SendouDialog } from "~/components/elements/Dialog"; +import { DateFormField } from "~/components/form/DateFormField"; import { SendouForm } from "~/components/form/SendouForm"; import { TextAreaFormField } from "~/components/form/TextAreaFormField"; import { UserSearchFormField } from "~/components/form/UserSearchFormField"; @@ -29,6 +30,7 @@ export function BanUserModal() { _action: "BAN_USER", userId: undefined, privateNote: null, + expiresAt: null, }} > @@ -42,6 +44,12 @@ export function BanUserModal() { maxLength={TOURNAMENT_ORGANIZATION.BAN_REASON_MAX_LENGTH} bottomText={t("org:banned.banModal.noteHelp")} /> + + + label={t("org:banned.banModal.expiresAt")} + name="expiresAt" + bottomText={t("org:banned.banModal.expiresAtHelp")} + /> ); diff --git a/app/features/tournament-organization/components/BannedPlayersList.module.css b/app/features/tournament-organization/components/BannedPlayersList.module.css index 79c3afe9c..a536bd5fb 100644 --- a/app/features/tournament-organization/components/BannedPlayersList.module.css +++ b/app/features/tournament-organization/components/BannedPlayersList.module.css @@ -9,12 +9,35 @@ .reasonCell { max-width: 250px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +} + +.noteContainer { + display: flex; + flex-direction: column; + gap: var(--s-0-5); +} + +.expandButton { + background: none; + border: none; + color: var(--theme); + cursor: pointer; + font-size: var(--fonts-xxs); + padding: 0; + text-align: left; + text-decoration: underline; +} + +.expandButton:hover { + opacity: 0.8; } .banPlayerButton { display: flex; justify-content: flex-end; } + +.expiredBan { + text-decoration: line-through; + color: var(--text-lighter); +} diff --git a/app/features/tournament-organization/components/BannedPlayersList.tsx b/app/features/tournament-organization/components/BannedPlayersList.tsx index a3c5d02fb..91719fc53 100644 --- a/app/features/tournament-organization/components/BannedPlayersList.tsx +++ b/app/features/tournament-organization/components/BannedPlayersList.tsx @@ -1,5 +1,7 @@ import { Link } from "@remix-run/react"; import clsx from "clsx"; +import { isFuture } from "date-fns"; +import * as React from "react"; import { useTranslation } from "react-i18next"; import { Avatar } from "~/components/Avatar"; import { SendouButton } from "~/components/elements/Button"; @@ -11,6 +13,8 @@ import { databaseTimestampToDate } from "~/utils/dates"; import { userPage } from "~/utils/urls"; import styles from "../components/BannedPlayersList.module.css"; +const MAX_NOTE_LENGTH = 30; + export function BannedUsersList({ bannedUsers, }: { @@ -43,54 +47,75 @@ export function BannedUsersList({ {t("org:banned.player")} {t("org:banned.note")} {t("org:banned.date")} + {t("org:banned.expires")} {t("org:banned.actions")} - {bannedUsers.map((bannedUser) => ( - - - { + const isExpired = + bannedUser.expiresAt && + isFuture(databaseTimestampToDate(bannedUser.expiresAt)); + + return ( + + + + + + {bannedUser.username} + + + + - - {bannedUser.username} - - - - {bannedUser.privateNote ?? "-"} - - - {databaseTimestampToDate( - bannedUser.updatedAt, - ).toLocaleDateString(i18n.language, { - day: "numeric", - month: "short", - year: "numeric", - })} - - - + + + {databaseTimestampToDate( + bannedUser.updatedAt, + ).toLocaleDateString(i18n.language, { + day: "numeric", + month: "short", + year: "numeric", })} - submitButtonText={t("org:banned.unban")} - > - - {t("org:banned.unban")} - - - - - ))} + + + {bannedUser.expiresAt + ? databaseTimestampToDate( + bannedUser.expiresAt, + ).toLocaleDateString(i18n.language, { + day: "numeric", + month: "short", + year: "numeric", + }) + : t("org:banned.permanent")} + + + + + {t("org:banned.unban")} + + + + + ); + })} @@ -100,3 +125,35 @@ export function BannedUsersList({ ); } + +function BanNote({ note }: { note: string | null }) { + const { t } = useTranslation(["common"]); + const [isExpanded, setIsExpanded] = React.useState(false); + + if (!note) { + return "-"; + } + + const shouldTruncate = note.length > MAX_NOTE_LENGTH; + const displayText = + shouldTruncate && !isExpanded + ? `${note.slice(0, MAX_NOTE_LENGTH)}...` + : note; + + return ( +
+ {displayText} + {shouldTruncate ? ( + + ) : null} +
+ ); +} diff --git a/app/features/tournament-organization/tournament-organization-schemas.ts b/app/features/tournament-organization/tournament-organization-schemas.ts index 240931052..6463dc07f 100644 --- a/app/features/tournament-organization/tournament-organization-schemas.ts +++ b/app/features/tournament-organization/tournament-organization-schemas.ts @@ -1,9 +1,12 @@ +import { isFuture } from "date-fns"; import { z } from "zod/v4"; import { TOURNAMENT_ORGANIZATION_ROLES } from "~/db/tables"; import { TOURNAMENT_ORGANIZATION } from "~/features/tournament-organization/tournament-organization-constants"; +import { dayMonthYearToDate } from "~/utils/dates"; import { mySlugify } from "~/utils/urls"; import { _action, + dayMonthYear, falsyToNull, id, safeNullableStringSchema, @@ -93,6 +96,15 @@ export const banUserActionSchema = z.object({ privateNote: safeNullableStringSchema({ max: TOURNAMENT_ORGANIZATION.BAN_REASON_MAX_LENGTH, }), + expiresAt: dayMonthYear.nullish().refine( + (data) => { + if (!data) return true; + return isFuture(dayMonthYearToDate(data)); + }, + { + message: "Date must be in the future", + }, + ), }); export const unbanUserActionSchema = z.object({ diff --git a/biome.json b/biome.json index 00de0a5c4..d4da68c3c 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,8 @@ "!scripts/output/**/*", "!app/db/seed/placements.json", "!build/**/*", - "!test-results/**/*" + "!test-results/**/*", + "!playwright-report/**/*" ] }, "linter": { diff --git a/e2e/org.spec.ts b/e2e/org.spec.ts index f0bdbab74..80cf00b33 100644 --- a/e2e/org.spec.ts +++ b/e2e/org.spec.ts @@ -35,8 +35,11 @@ test.describe("Tournament Organization", () => { await impersonate(page, ADMIN_ID); await navigate({ page, url }); await editButtonLocator.click(); - // Add member as admin - await page.getByLabel("Role").first().selectOption("ADMIN"); + // Add member as admin - find the N-ZAP user's fieldset and change their role + const nzapFieldset = page.locator("fieldset").filter({ hasText: "N-ZAP" }); + await nzapFieldset + .getByLabel("Role", { exact: true }) + .selectOption("ADMIN"); await submit(page); // 3. As the promoted user, verify edit controls are visible and page can be accessed diff --git a/locales/da/org.json b/locales/da/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/da/org.json +++ b/locales/da/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/de/org.json b/locales/de/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/de/org.json +++ b/locales/de/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/en/org.json b/locales/en/org.json index 55f525c48..827bcd282 100644 --- a/locales/en/org.json +++ b/locales/en/org.json @@ -28,6 +28,8 @@ "banned.player": "Player", "banned.note": "Note", "banned.date": "Banned on", + "banned.expires": "Expires", + "banned.permanent": "Permanent", "banned.actions": "Actions", "banned.unban": "Unban", "banned.unbanConfirm": "Are you sure you want to unban {{username}}?", @@ -35,5 +37,7 @@ "banned.banModal.title": "Banning a player", "banned.banModal.player": "Player", "banned.banModal.note": "Private note", - "banned.banModal.noteHelp": "This note is only visible to organization admins." + "banned.banModal.noteHelp": "This note is only visible to organization admins.", + "banned.banModal.expiresAt": "Ban expiration date", + "banned.banModal.expiresAtHelp": "Leave empty for a permanent ban" } diff --git a/locales/es-ES/org.json b/locales/es-ES/org.json index 6cd490596..3623c2a8e 100644 --- a/locales/es-ES/org.json +++ b/locales/es-ES/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/es-US/org.json b/locales/es-US/org.json index 6cd490596..3623c2a8e 100644 --- a/locales/es-US/org.json +++ b/locales/es-US/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/fr-CA/org.json b/locales/fr-CA/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/fr-CA/org.json +++ b/locales/fr-CA/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/fr-EU/org.json b/locales/fr-EU/org.json index a221f816e..f4cdec905 100644 --- a/locales/fr-EU/org.json +++ b/locales/fr-EU/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/he/org.json b/locales/he/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/he/org.json +++ b/locales/he/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/it/org.json b/locales/it/org.json index 049c535ea..c4d2522c3 100644 --- a/locales/it/org.json +++ b/locales/it/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/ja/org.json b/locales/ja/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/ja/org.json +++ b/locales/ja/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/ko/org.json b/locales/ko/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/ko/org.json +++ b/locales/ko/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/nl/org.json b/locales/nl/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/nl/org.json +++ b/locales/nl/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/pl/org.json b/locales/pl/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/pl/org.json +++ b/locales/pl/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/pt-BR/org.json b/locales/pt-BR/org.json index 1e71ab8a5..61256e25a 100644 --- a/locales/pt-BR/org.json +++ b/locales/pt-BR/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/ru/org.json b/locales/ru/org.json index 54bd9aa79..7c11cf2ce 100644 --- a/locales/ru/org.json +++ b/locales/ru/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/locales/zh/org.json b/locales/zh/org.json index 96ae8f679..f2d2861e8 100644 --- a/locales/zh/org.json +++ b/locales/zh/org.json @@ -28,6 +28,8 @@ "banned.player": "", "banned.note": "", "banned.date": "", + "banned.expires": "", + "banned.permanent": "", "banned.actions": "", "banned.unban": "", "banned.unbanConfirm": "", @@ -35,5 +37,7 @@ "banned.banModal.title": "", "banned.banModal.player": "", "banned.banModal.note": "", - "banned.banModal.noteHelp": "" + "banned.banModal.noteHelp": "", + "banned.banModal.expiresAt": "", + "banned.banModal.expiresAtHelp": "" } diff --git a/migrations/097-tournament-org-ban-expires.js b/migrations/097-tournament-org-ban-expires.js new file mode 100644 index 000000000..275532845 --- /dev/null +++ b/migrations/097-tournament-org-ban-expires.js @@ -0,0 +1,9 @@ +export function up(db) { + db.transaction(() => { + db.prepare( + /* sql */ ` + alter table "TournamentOrganizationBannedUser" add column "expiresAt" integer + `, + ).run(); + })(); +}