mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-24 03:55:49 -05:00
Tournament org page improvements (#2569)
This commit is contained in:
@@ -752,6 +752,7 @@ export interface TournamentOrganizationBannedUser {
|
||||
userId: number;
|
||||
privateNote: string | null;
|
||||
updatedAt: Generated<number>;
|
||||
expiresAt: number | null;
|
||||
}
|
||||
|
||||
/** Indicates a user trusts another. Allows direct adding to groups/teams without invite links. */
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
<UserSearchFormField<FormFields>
|
||||
@@ -42,6 +44,12 @@ export function BanUserModal() {
|
||||
maxLength={TOURNAMENT_ORGANIZATION.BAN_REASON_MAX_LENGTH}
|
||||
bottomText={t("org:banned.banModal.noteHelp")}
|
||||
/>
|
||||
|
||||
<DateFormField<FormFields>
|
||||
label={t("org:banned.banModal.expiresAt")}
|
||||
name="expiresAt"
|
||||
bottomText={t("org:banned.banModal.expiresAtHelp")}
|
||||
/>
|
||||
</SendouForm>
|
||||
</SendouDialog>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
<th>{t("org:banned.player")}</th>
|
||||
<th>{t("org:banned.note")}</th>
|
||||
<th>{t("org:banned.date")}</th>
|
||||
<th>{t("org:banned.expires")}</th>
|
||||
<th>{t("org:banned.actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{bannedUsers.map((bannedUser) => (
|
||||
<tr key={bannedUser.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={userPage(bannedUser)}
|
||||
className="stack horizontal xs items-center w-max"
|
||||
{bannedUsers.map((bannedUser) => {
|
||||
const isExpired =
|
||||
bannedUser.expiresAt &&
|
||||
isFuture(databaseTimestampToDate(bannedUser.expiresAt));
|
||||
|
||||
return (
|
||||
<tr key={bannedUser.id}>
|
||||
<td>
|
||||
<Link
|
||||
to={userPage(bannedUser)}
|
||||
className="stack horizontal xs items-center w-max"
|
||||
>
|
||||
<Avatar user={bannedUser} size="xs" />
|
||||
<span
|
||||
className={clsx({ [styles.expiredBan]: isExpired })}
|
||||
>
|
||||
{bannedUser.username}
|
||||
</span>
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
className={clsx("text-sm text-lighter", styles.reasonCell)}
|
||||
>
|
||||
<Avatar user={bannedUser} size="xs" />
|
||||
{bannedUser.username}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
className={clsx("text-sm text-lighter", styles.reasonCell)}
|
||||
title={bannedUser.privateNote ?? undefined}
|
||||
>
|
||||
{bannedUser.privateNote ?? "-"}
|
||||
</td>
|
||||
<td className="text-sm text-lighter whitespace-nowrap">
|
||||
{databaseTimestampToDate(
|
||||
bannedUser.updatedAt,
|
||||
).toLocaleDateString(i18n.language, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
<td className={styles.actionsCell}>
|
||||
<FormWithConfirm
|
||||
fields={[
|
||||
["_action", "UNBAN_USER"],
|
||||
["userId", bannedUser.id],
|
||||
]}
|
||||
dialogHeading={t("org:banned.unbanConfirm", {
|
||||
username: bannedUser.username,
|
||||
<BanNote note={bannedUser.privateNote} />
|
||||
</td>
|
||||
<td className="text-sm text-lighter whitespace-nowrap">
|
||||
{databaseTimestampToDate(
|
||||
bannedUser.updatedAt,
|
||||
).toLocaleDateString(i18n.language, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})}
|
||||
submitButtonText={t("org:banned.unban")}
|
||||
>
|
||||
<SendouButton variant="minimal-destructive" size="small">
|
||||
{t("org:banned.unban")}
|
||||
</SendouButton>
|
||||
</FormWithConfirm>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</td>
|
||||
<td className="text-sm text-lighter whitespace-nowrap">
|
||||
{bannedUser.expiresAt
|
||||
? databaseTimestampToDate(
|
||||
bannedUser.expiresAt,
|
||||
).toLocaleDateString(i18n.language, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
: t("org:banned.permanent")}
|
||||
</td>
|
||||
<td className={styles.actionsCell}>
|
||||
<FormWithConfirm
|
||||
fields={[
|
||||
["_action", "UNBAN_USER"],
|
||||
["userId", bannedUser.id],
|
||||
]}
|
||||
dialogHeading={t("org:banned.unbanConfirm", {
|
||||
username: bannedUser.username,
|
||||
})}
|
||||
submitButtonText={t("org:banned.unban")}
|
||||
>
|
||||
<SendouButton variant="minimal-destructive" size="small">
|
||||
{t("org:banned.unban")}
|
||||
</SendouButton>
|
||||
</FormWithConfirm>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
@@ -100,3 +125,35 @@ export function BannedUsersList({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={styles.noteContainer}>
|
||||
<span>{displayText}</span>
|
||||
{shouldTruncate ? (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.expandButton}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
{isExpanded
|
||||
? t("common:actions.showLess")
|
||||
: t("common:actions.showMore")}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"!scripts/output/**/*",
|
||||
"!app/db/seed/placements.json",
|
||||
"!build/**/*",
|
||||
"!test-results/**/*"
|
||||
"!test-results/**/*",
|
||||
"!playwright-report/**/*"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
9
migrations/097-tournament-org-ban-expires.js
Normal file
9
migrations/097-tournament-org-ban-expires.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export function up(db) {
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
/* sql */ `
|
||||
alter table "TournamentOrganizationBannedUser" add column "expiresAt" integer
|
||||
`,
|
||||
).run();
|
||||
})();
|
||||
}
|
||||
Reference in New Issue
Block a user