Customize badges order (supporters) (#2220)

* Initial

* wip

* E2E tests

* imports order

* Lint
This commit is contained in:
Kalle 2025-04-26 12:13:09 +03:00 committed by GitHub
parent d2551d2706
commit 6a9d7755a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 341 additions and 270 deletions

View File

@ -124,7 +124,6 @@ const basicSeeds = (variation?: SeedVariation | null) => [
syncPlusTiers,
lastMonthSuggestions,
thisMonthsSuggestions,
badgesToAdmin,
badgesToUsers,
badgeManagers,
patrons,
@ -165,7 +164,6 @@ const basicSeeds = (variation?: SeedVariation | null) => [
realVideo,
realVideoCast,
xRankPlacements,
userFavBadges,
arts,
commissionsOpen,
playedMatches,
@ -604,26 +602,6 @@ function syncPlusTiers() {
.run();
}
function badgesToAdmin() {
const availableBadgeIds = R.shuffle(
(sql.prepare(`select "id" from "Badge"`).all() as any[]).map((b) => b.id),
).slice(0, 8) as number[];
const badgesWithDuplicates = availableBadgeIds.flatMap((id) =>
new Array(faker.helpers.arrayElement([1, 1, 1, 2, 3, 4]))
.fill(null)
.map(() => id),
);
for (const id of badgesWithDuplicates) {
sql
.prepare(
`insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)`,
)
.run({ id, userId: ADMIN_ID });
}
}
function getAvailableBadgeIds() {
return R.shuffle(
(sql.prepare(`select "id" from "Badge"`).all() as any[]).map((b) => b.id),
@ -635,10 +613,16 @@ function badgesToUsers() {
let userIds = (
sql
.prepare(`select "id" from "User" where id != 2`) // no badges for N-ZAP
.prepare(
`select "id" from "User" where id != ${NZAP_TEST_ID} and id != ${ADMIN_ID}`,
)
.all() as any[]
).map((u) => u.id) as number[];
const insertTournamentBadgeOwnerStm = sql.prepare(
`insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)`,
);
for (const id of availableBadgeIds) {
userIds = R.shuffle(userIds);
for (
@ -650,21 +634,21 @@ function badgesToUsers() {
});
i++
) {
let userToGetABadge = userIds.shift()!;
if (userToGetABadge === NZAP_TEST_ID && id === 1) {
// e2e test assumes N-ZAP does not have badge id = 1
userToGetABadge = userIds.shift()!;
}
const userToGetABadge = userIds.shift()!;
sql
.prepare(
`insert into "TournamentBadgeOwner" ("badgeId", "userId") values ($id, $userId)`,
)
.run({ id, userId: userToGetABadge });
insertTournamentBadgeOwnerStm.run({ id, userId: userToGetABadge });
userIds.push(userToGetABadge);
}
}
for (const badgeId of nullFilledArray(20).map((_, i) => i + 1)) {
insertTournamentBadgeOwnerStm.run({ id: badgeId, userId: ADMIN_ID });
}
for (const badgeId of [5, 6, 7]) {
insertTournamentBadgeOwnerStm.run({ id: badgeId, userId: NZAP_TEST_ID });
}
}
function badgeManagers() {
@ -685,19 +669,24 @@ function patrons() {
.all() as any[]
)
.map((u) => u.id)
.filter((id) => id !== NZAP_TEST_ID);
.filter((id) => id !== NZAP_TEST_ID && id !== ADMIN_ID) as number[];
const givePatronStm = sql.prepare(
`update user set "patronTier" = $patronTier, "patronSince" = $patronSince where id = $id`,
);
for (const id of userIds) {
sql
.prepare(
`update user set "patronTier" = $patronTier, "patronSince" = $patronSince where id = $id`,
)
.run({
id,
patronSince: dateToDatabaseTimestamp(faker.date.past()),
patronTier: faker.helpers.arrayElement([1, 1, 2, 2, 2, 3, 3, 4]),
});
givePatronStm.run({
id,
patronSince: dateToDatabaseTimestamp(faker.date.past()),
patronTier: faker.helpers.arrayElement([1, 1, 2, 2, 2, 3, 3, 4]),
});
}
givePatronStm.run({
id: ADMIN_ID,
patronSince: dateToDatabaseTimestamp(faker.date.past()),
patronTier: 2,
});
}
function userIdsInRandomOrder(specialLast = false) {
@ -1849,24 +1838,6 @@ function xRankPlacements() {
})();
}
function userFavBadges() {
// randomly choose Sendou's favorite badge
const badgeList = R.shuffle(
(
sql
.prepare(
`select "badgeId" from "BadgeOwner" where "userId" = ${ADMIN_ID}`,
)
.all() as any[]
).map((row) => row.badgeId),
);
sql
.prepare(
`update "User" set "favoriteBadgeId" = $id where "id" = ${ADMIN_ID}`,
)
.run({ id: badgeList[0] });
}
const addArtStm = sql.prepare(/* sql */ `
insert into "Art" (
"imgId",

View File

@ -828,7 +828,8 @@ export interface User {
/** coalesce(customName, discordName) */
username: ColumnType<string, never, never>;
discordUniqueName: string | null;
favoriteBadgeId: number | null;
/** User's favorite badges they want to show on the front page of the badge display. Index = 0 big badge. */
favoriteBadgeIds: ColumnType<number[] | null, string | null, string | null>;
id: GeneratedAlways<number>;
inGameName: string | null;
isArtist: Generated<number | null>;

View File

@ -87,43 +87,6 @@ export async function findById(badgeId: number) {
return addPermissions(row);
}
export async function findByOwnerId({
userId,
favoriteBadgeId,
}: {
userId: number;
favoriteBadgeId: number | null;
}) {
const badges = await db
.selectFrom("BadgeOwner")
.innerJoin("Badge", "Badge.id", "BadgeOwner.badgeId")
.select(({ fn }) => [
fn.count<number>("BadgeOwner.badgeId").as("count"),
"Badge.id",
"Badge.displayName",
"Badge.code",
"Badge.hue",
])
.where("BadgeOwner.userId", "=", userId)
.groupBy(["BadgeOwner.badgeId", "BadgeOwner.userId"])
.orderBy("Badge.id", "asc")
.execute();
if (!favoriteBadgeId) {
return badges;
}
return badges.sort((a, b) => {
if (a.id === favoriteBadgeId) {
return -1;
}
if (b.id === favoriteBadgeId) {
return 1;
}
return 0;
});
}
export function findByManagersList(userIds: number[]) {
return db
.selectFrom("Badge")

View File

@ -0,0 +1,3 @@
export const BADGE = {
SMALL_BADGES_PER_DISPLAY_PAGE: 9,
};

View File

@ -6,25 +6,30 @@ import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { TrashIcon } from "~/components/icons/Trash";
import type { Tables } from "~/db/tables";
import { BADGE } from "~/features/badges/badges-contants";
import { usePagination } from "~/hooks/usePagination";
import type { Unpacked } from "~/utils/types";
import { badgeExplanationText } from "../badges-utils";
import styles from "./BadgeDisplay.module.css";
interface BadgeDisplayProps {
export interface BadgeDisplayProps {
badges: Array<Omit<Tables["Badge"], "authorId"> & { count?: number }>;
onBadgeRemove?: (badgeId: number) => void;
onChange?: (badgeIds: number[]) => void;
children?: React.ReactNode;
}
export function BadgeDisplay({
badges: _badges,
onBadgeRemove,
onChange,
children,
}: BadgeDisplayProps) {
const { t } = useTranslation("badges");
const [badges, setBadges] = React.useState(_badges);
const [bigBadge, ...smallBadges] = badges;
const isPaginated = !onChange;
const {
itemsToDisplay,
everythingVisible,
@ -33,42 +38,38 @@ export function BadgeDisplay({
setPage,
} = usePagination({
items: smallBadges,
pageSize: 9,
pageSize: isPaginated ? BADGE.SMALL_BADGES_PER_DISPLAY_PAGE : 1000,
scrollToTop: false,
});
if (!bigBadge) return null;
const setBadgeFirst = (badge: Unpacked<BadgeDisplayProps["badges"]>) => {
setBadges(
badges.map((b, i) => {
if (i === 0) return badge;
if (b.id === badge.id) return badges[0];
const newBadges = badges.map((b, i) => {
if (i === 0) return badge;
if (b.id === badge.id) return badges[0];
return b;
}),
);
return b;
});
setBadges(newBadges);
onChange?.(newBadges.map((b) => b.id));
};
return (
<div>
<div className={styles.badgeExplanation}>
{badgeExplanationText(t, bigBadge)}
{onBadgeRemove ? (
<Button
icon={<TrashIcon />}
variant="minimal-destructive"
onClick={() => onBadgeRemove(bigBadge.id)}
/>
) : null}
</div>
<div data-testid="badge-display">
{isPaginated ? (
<div className={styles.badgeExplanation}>
{badgeExplanationText(t, bigBadge)}
</div>
) : null}
<div
className={clsx(styles.badges, {
"justify-center": smallBadges.length === 0,
})}
>
<Badge badge={bigBadge} size={125} isAnimated />
{smallBadges.length > 0 ? (
{!children && smallBadges.length > 0 ? (
<div className={styles.smallBadges}>
{itemsToDisplay.map((badge) => (
<div key={badge.id} className={styles.smallBadgeContainer}>
@ -85,7 +86,24 @@ export function BadgeDisplay({
))}
</div>
) : null}
{children}
</div>
{!isPaginated ? (
<div className={styles.badgeExplanation}>
{badgeExplanationText(t, bigBadge)}
{onChange ? (
<Button
icon={<TrashIcon />}
variant="minimal-destructive"
onClick={() =>
onChange(
badges.filter((b) => b.id !== bigBadge.id).map((b) => b.id),
)
}
/>
) : null}
</div>
) : null}
{!everythingVisible ? (
<BadgePagination
pagesCount={pagesCount}
@ -119,6 +137,7 @@ function BadgePagination({
className={clsx(styles.paginationButton, {
[styles.paginationButtonActive]: currentPage === i + 1,
})}
data-testid="badge-pagination-button"
>
{i + 1}
</SendouButton>

View File

@ -0,0 +1,63 @@
import { useTranslation } from "react-i18next";
import {
BadgeDisplay,
type BadgeDisplayProps,
} from "~/features/badges/components/BadgeDisplay";
export function BadgesSelector({
options,
selectedBadges,
onChange,
onBlur,
children,
maxCount,
}: {
options: BadgeDisplayProps["badges"];
selectedBadges: number[];
onChange: (newBadges: number[]) => void;
onBlur?: () => void;
children?: React.ReactNode;
maxCount?: number;
}) {
const { t } = useTranslation(["common"]);
return (
<div className="stack md">
{selectedBadges.length > 0 ? (
<BadgeDisplay
badges={options
.filter((badge) => selectedBadges.includes(badge.id))
.sort((a, b) => {
const aIdx = selectedBadges.indexOf(a.id);
const bIdx = selectedBadges.indexOf(b.id);
return aIdx - bIdx;
})}
onChange={onChange}
key={selectedBadges.join(",")}
>
{children}
</BadgeDisplay>
) : (
<div className="text-lighter text-md font-bold">
{t("common:badges.selector.none")}
</div>
)}
<select
onBlur={onBlur}
onChange={(e) => onChange([...selectedBadges, Number(e.target.value)])}
disabled={Boolean(maxCount && selectedBadges.length >= maxCount)}
data-testid="badges-selector"
>
<option>{t("common:badges.selector.select")}</option>
{options
.filter((badge) => !selectedBadges.includes(badge.id))
.map((badge) => (
<option key={badge.id} value={badge.id}>
{badge.displayName}
</option>
))}
</select>
</div>
);
}

View File

@ -77,6 +77,11 @@ const PERKS = [
name: "customizedColorsUser",
extraInfo: false,
},
{
tier: 2,
name: "favoriteBadges",
extraInfo: true,
},
{
tier: 2,
name: "customizedColorsTeam",
@ -147,7 +152,7 @@ export default function SupportPage() {
>
your Discord on Patreon.com
</a>
. Afterwards the perks will take effect within 2 hours. If any
. Afterwards the perks will take effect within an hour. If any
questions or problems contact Sendou for support.
</Trans>
</p>

View File

@ -15,16 +15,16 @@ import { TextFormField } from "~/components/form/TextFormField";
import { ToggleFormField } from "~/components/form/ToggleFormField";
import { UserSearchFormField } from "~/components/form/UserSearchFormField";
import { TOURNAMENT_ORGANIZATION_ROLES } from "~/db/tables";
import { BadgeDisplay } from "~/features/badges/components/BadgeDisplay";
import { BadgesSelector } from "~/features/badges/components/BadgesSelector";
import { wrapToValueStringArrayWithDefault } from "~/utils/form";
import type { Unpacked } from "~/utils/types";
import { uploadImagePage } from "~/utils/urls";
import { TOURNAMENT_ORGANIZATION } from "../tournament-organization-constants";
import { organizationEditSchema } from "../tournament-organization-schemas";
import { action } from "../actions/org.$slug.edit.server";
import { loader } from "../loaders/org.$slug.edit.server";
import { handle, meta } from "../routes/org.$slug";
import { TOURNAMENT_ORGANIZATION } from "../tournament-organization-constants";
export { action, handle, loader, meta };
type FormFields = z.infer<typeof organizationEditSchema> & {
@ -235,6 +235,7 @@ function SeriesFieldset({
function BadgesFormField() {
const { t } = useTranslation(["org"]);
const methods = useFormContext<FormFields>();
const data = useLoaderData<typeof loader>();
return (
<div>
@ -244,6 +245,7 @@ function BadgesFormField() {
name="badges"
render={({ field: { onChange, onBlur, value } }) => (
<BadgesSelector
options={data.badgeOptions}
selectedBadges={value}
onBlur={onBlur}
onChange={onChange}
@ -253,49 +255,3 @@ function BadgesFormField() {
</div>
);
}
function BadgesSelector({
selectedBadges,
onChange,
onBlur,
}: {
selectedBadges: number[];
onChange: (newBadges: number[]) => void;
onBlur: () => void;
}) {
const { t } = useTranslation(["org"]);
const data = useLoaderData<typeof loader>();
return (
<div className="stack md">
{selectedBadges.length > 0 ? (
<BadgeDisplay
badges={data.badgeOptions.filter((badge) =>
selectedBadges.includes(badge.id),
)}
onBadgeRemove={(badgeId) =>
onChange(selectedBadges.filter((id) => id !== badgeId))
}
key={selectedBadges.join(",")}
/>
) : (
<div className="text-lighter text-md font-bold">
{t("org:edit.form.badges.none")}
</div>
)}
<select
onBlur={onBlur}
onChange={(e) => onChange([Number(e.target.value), ...selectedBadges])}
>
<option>{t("org:edit.form.badges.select")}</option>
{data.badgeOptions
.filter((badge) => !selectedBadges.includes(badge.id))
.map((badge) => (
<option key={badge.id} value={badge.id}>
{badge.displayName}
</option>
))}
</select>
</div>
);
}

View File

@ -11,6 +11,7 @@ import type {
} from "~/db/tables";
import type { ChatUser } from "~/features/chat/components/Chat";
import { userRoles } from "~/modules/permissions/mapper.server";
import { isSupporter } from "~/modules/permissions/utils";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import type { CommonUser } from "~/utils/kysely.server";
@ -157,7 +158,8 @@ export async function findProfileByIdentifier(
"User.discordName",
"User.showDiscordUniqueName",
"User.discordUniqueName",
"User.favoriteBadgeId",
"User.favoriteBadgeIds",
"User.patronTier",
"PlusTier.tier as plusTier",
jsonArrayFrom(
eb
@ -223,22 +225,30 @@ export async function findProfileByIdentifier(
return null;
}
const favoriteBadgeIds = isSupporter(row)
? row.favoriteBadgeIds
: row.favoriteBadgeIds
? [row.favoriteBadgeIds[0]]
: null;
return {
...row,
team: row.teams.find((t) => t.isMainTeam),
secondaryTeams: row.teams.filter((t) => !t.isMainTeam),
teams: undefined,
// TODO: sort in SQL
favoriteBadgeIds,
badges: row.badges.sort((a, b) => {
if (a.id === row.favoriteBadgeId) {
return -1;
const aIdx = favoriteBadgeIds?.indexOf(a.id) ?? -1;
const bIdx = favoriteBadgeIds?.indexOf(b.id) ?? -1;
if (aIdx !== bIdx) {
if (aIdx === -1) return 1;
if (bIdx === -1) return -1;
return aIdx - bIdx;
}
if (b.id === row.favoriteBadgeId) {
return 1;
}
return a.id - b.id;
return b.id - a.id;
}),
discordUniqueName:
forceShowDiscordUniqueName || row.showDiscordUniqueName
@ -283,7 +293,7 @@ export async function findLeanById(id: number) {
"User.isVideoAdder",
"User.isTournamentOrganizer",
"User.patronTier",
"User.favoriteBadgeId",
"User.favoriteBadgeIds",
"User.languages",
"User.inGameName",
"User.preferences",
@ -668,13 +678,13 @@ type UpdateProfileArgs = Pick<
| "inGameName"
| "battlefy"
| "css"
| "favoriteBadgeId"
| "showDiscordUniqueName"
| "commissionText"
| "commissionsOpen"
> & {
userId: number;
weapons: Pick<TablesInsertable["UserWeapon"], "weaponSplId" | "isFavorite">[];
favoriteBadgeIds: number[] | null;
};
export function updateProfile(args: UpdateProfileArgs) {
return db.transaction().execute(async (trx) => {
@ -709,7 +719,9 @@ export function updateProfile(args: UpdateProfileArgs) {
inGameName: args.inGameName,
css: args.css,
battlefy: args.battlefy,
favoriteBadgeId: args.favoriteBadgeId,
favoriteBadgeIds: args.favoriteBadgeIds
? JSON.stringify(args.favoriteBadgeIds)
: null,
showDiscordUniqueName: args.showDiscordUniqueName,
commissionText: args.commissionText,
commissionsOpen: args.commissionsOpen,

View File

@ -27,7 +27,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
return {
user: userProfile,
favoriteBadgeId: user.favoriteBadgeId,
favoriteBadgeIds: user.favoriteBadgeIds,
discordUniqueName: userProfile.discordUniqueName,
countries: Object.entries(countries)
.map(([code, country]) => ({

View File

@ -16,7 +16,7 @@ const DEFAULT_FIELDS = {
country: "FI",
customName: null,
customUrl: null,
favoriteBadgeId: null,
favoriteBadgeIds: null,
inGameNameDiscriminator: null,
inGameNameText: null,
motionSens: null,

View File

@ -17,6 +17,8 @@ import { StarFilledIcon } from "~/components/icons/StarFilled";
import { TrashIcon } from "~/components/icons/Trash";
import { USER } from "~/constants";
import type { Tables } from "~/db/tables";
import { BADGE } from "~/features/badges/badges-contants";
import { BadgesSelector } from "~/features/badges/components/BadgesSelector";
import type { MainWeaponId } from "~/modules/in-game-lists";
import { useHasRole } from "~/modules/permissions/hooks";
import invariant from "~/utils/invariant";
@ -371,34 +373,42 @@ function BioTextarea({
function FavBadgeSelect() {
const data = useLoaderData<typeof loader>();
const { t } = useTranslation(["user"]);
const [value, setValue] = React.useState(data.favoriteBadgeIds ?? []);
const isSupporter = useHasRole("SUPPORTER");
// doesn't make sense to select favorite badge
// if user has no badges or only has 1 badge
if (data.user.badges.length < 2) return null;
// user's current favorite badge is the initial value
const initialBadge = data.user.badges.find(
(badge) => badge.id === data.favoriteBadgeId,
);
const onChange = (newBadges: number[]) => {
if (isSupporter) {
setValue(newBadges);
} else {
// non-supporters can only set which badge is the big one
setValue(newBadges.length > 0 ? [newBadges[0]] : []);
}
};
return (
<div>
<label htmlFor="favoriteBadgeId">{t("user:favoriteBadge")}</label>
<select
className=""
name="favoriteBadgeId"
id="favoriteBadgeId"
defaultValue={initialBadge?.id}
<input
type="hidden"
name="favoriteBadgeIds"
value={JSON.stringify(value)}
/>
<label htmlFor="favoriteBadgeIds">{t("user:favoriteBadges")}</label>
<BadgesSelector
options={data.user.badges}
selectedBadges={value}
onChange={onChange}
maxCount={BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1}
>
{data.user.badges.map((badge) => (
<option key={badge.id} value={badge.id}>
{`${badge.displayName}`}
</option>
))}
</select>
<FormMessage type="info">
{t("user:forms.info.favoriteBadge")}
</FormMessage>
{!isSupporter ? (
<div className="text-sm text-lighter font-semi-bold text-center">
{t("user:forms.favoriteBadges.nonSupporter")}
</div>
) : null}
</BadgesSelector>
</div>
);
}

View File

@ -37,7 +37,7 @@ export const meta: MetaFunction<typeof loader> = (args) => {
};
export const handle: SendouRouteHandle = {
i18n: "user",
i18n: ["user", "badges"],
breadcrumb: ({ match }) => {
const data = match.data as UserPageLoaderData | undefined;

View File

@ -2,12 +2,14 @@ import { countries } from "countries-list";
import { z } from "zod";
import { USER } from "~/constants";
import "~/styles/u-edit.css";
import { BADGE } from "~/features/badges/badges-contants";
import { isCustomUrl } from "~/utils/urls";
import {
actualNumber,
checkboxValueToDbBoolean,
customCssVarObject,
dbBoolean,
emptyArrayToNull,
falsyToNull,
id,
processMany,
@ -108,9 +110,13 @@ export const userEditActionSchema = z
)
.max(USER.WEAPON_POOL_MAX_SIZE),
),
favoriteBadgeId: z.preprocess(
processMany(actualNumber, undefinedToNull),
id.nullable(),
favoriteBadgeIds: z.preprocess(
processMany(safeJSONParse, emptyArrayToNull),
z
.array(id)
.min(1)
.max(BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1)
.nullable(),
),
showDiscordUniqueName: z.preprocess(checkboxValueToDbBoolean, dbBoolean),
commissionsOpen: z.preprocess(checkboxValueToDbBoolean, dbBoolean),

Binary file not shown.

View File

@ -4,9 +4,9 @@ import { badgePage } from "~/utils/urls";
import { NZAP_TEST_ID } from "../app/db/seed/constants";
test.describe("Badges", () => {
test("adds a badge sending a notification", async ({ page }) => {
test("adds a badge owner sending a notification", async ({ page }) => {
await seed(page);
await impersonate(page);
await impersonate(page, NZAP_TEST_ID);
await navigate({
page,
url: badgePage(1),
@ -16,13 +16,13 @@ test.describe("Badges", () => {
await selectUser({
page,
userName: "N-ZAP",
userName: "Sendou",
labelName: "Add new owner",
});
await page.getByRole("button", { name: "Save", exact: true }).click();
await impersonate(page, NZAP_TEST_ID);
await impersonate(page);
await navigate({
page,
url: "/",

View File

@ -1,7 +1,15 @@
import { type Page, expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID } from "~/constants";
import { impersonate, navigate, seed, selectWeapon } from "~/utils/playwright";
import { userPage } from "~/utils/urls";
import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants";
import {
impersonate,
isNotVisible,
navigate,
seed,
selectWeapon,
submit,
} from "~/utils/playwright";
import { userEditProfilePage, userPage } from "~/utils/urls";
const goToEditPage = (page: Page) =>
page.getByText("Edit", { exact: true }).click();
@ -9,6 +17,71 @@ const submitEditForm = (page: Page) =>
page.getByText("Save", { exact: true }).click();
test.describe("User page", () => {
test("uses badge pagination", async ({ page }) => {
await seed(page);
await navigate({
page,
url: userPage({ discordId: NZAP_TEST_DISCORD_ID }),
});
await expect(page.getByTestId("badge-display")).toBeVisible();
await isNotVisible(page.getByTestId("badge-pagination-button"));
await navigate({
page,
url: userPage({ discordId: ADMIN_DISCORD_ID, customUrl: "sendou" }),
});
await expect(page.getByAltText("Paddling Pool Weekly")).toBeVisible();
await page.getByTestId("badge-pagination-button").nth(1).click();
// test changing the big badge
await page.getByAltText("Lobster Crossfire").click();
expect(page.getByAltText("Lobster Crossfire")).toHaveAttribute(
"width",
"125",
);
});
test("customize which badge is shown as big by default as normal user", async ({
page,
}) => {
await seed(page);
await impersonate(page, NZAP_TEST_ID);
await navigate({
page,
url: userEditProfilePage({ discordId: NZAP_TEST_DISCORD_ID }),
});
const badgeSelect = page.getByTestId("badges-selector");
await badgeSelect.selectOption("5");
await submit(page);
await expect(
page.getByAltText("It's Dangerous to go Alone"),
).toHaveAttribute("width", "125");
});
test("customize big badge + small badge first page order as supporter", async ({
page,
}) => {
await seed(page);
await impersonate(page);
await navigate({
page,
url: userEditProfilePage({ discordId: ADMIN_DISCORD_ID }),
});
const badgeSelect = page.getByTestId("badges-selector");
await badgeSelect.selectOption("1");
await expect(page.getByTestId("badge-display")).toBeVisible();
await badgeSelect.selectOption("11");
await submit(page);
await expect(page.getByAltText("4v4 Sundaes")).toBeVisible();
await expect(page.getByAltText("Lobster Crossfire")).toBeVisible();
});
test("edits user profile", async ({ page }) => {
await seed(page);
await impersonate(page);

View File

@ -144,7 +144,6 @@
"support.intro.first": "Hej! Mit navn er Sendou og sendou.ink er mit projekt, hvis formål er at stille redskaber og ressourcer til rådighed for Splatoon-fællesskabet. Målet er at hjælpe alle med at nyde og blive bedre til Splatoon. Hvad enten om du lige er startet eller har spillet Splatoon i mange timer.",
"support.intro.second": "Hvis du kan lide, hvad jeg laver på denne hjemmeside, så du støtte mit værk. Denne side beskriver, hvordan du kan støtte mit arbejde og opnå frynsegoder på sendou.ink. Din støtte hjælper mig med at betale for at hoste hjemmesiden, samt sponsorere den tid, som jeg fortsat bruger på at forbedre hjemmesiden.",
"support.action": "Støt via Patreon",
"support.footer": "Efter at du er blevet en ny patreon til hjemmesiden, så kan du tilknytte <2>din discord-profil til Patreon.com </2>. Derefter vil frynsegoderne træde i kraft indenfor 2 timer. Hvis der er nogle spørgsmål eller problemer, så kontakt venligt Sendou for at hjælp.",
"support.perk.supportMyWork": "Du støtter mit arbejde",
"support.perk.adFree": "Reklamefri sendou.ink",
"support.perk.nameInFooter": "Discordnavn i bundtekst",

View File

@ -12,7 +12,6 @@
"sens": "Følsomhed",
"weaponPool": "Våbenpulje",
"discordExplanation": "Brugernavn, Profilbillede, Youtube-, Bluesky- og Twitch-konter er hentet via din Discord-konto. Se <1>FAQ</1> for yderligere information.",
"favoriteBadge": "Yndlingsmærke",
"battlefy": "Battlefy brugernavn",
"forms.showDiscordUniqueName": "Vis Discord-brugernavn",
@ -39,7 +38,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "Brugerdefineret URL må ikke indeholde specialtegn (Gælder også æ, ø og å)",
"forms.errors.invalidCustomUrl.duplicate": "Brugerdefineret URL er allerede i brug",
"forms.errors.invalidSens": "Bevægelsesfølsomhed kan ikke indstilles før at Styrepindsfølsomheden er indstillet",
"forms.info.favoriteBadge": "Dit yndlingsmærke bliver som standard vist i stor størrelse på din profil",
"forms.info.battlefy": "Battlefy-brugernavn bruges til seeding og bekræftelse i nogle turneringer",
"search.noResults": "Søgningen {{query}} fandt ingen brugere",

View File

@ -140,7 +140,6 @@
"support.intro.first": "Hallo! Ich bin Sendou und sendou.ink ist mein Projekt, um Tools und Ressourcen für Splatoon-Community bereitzustellen. Das Ziel ist es, jedem zu helfen, sich zu verbessern und Splatoon zu genießen - egal ob man ganz neu im Spiel ist, oder ein erfahrener Veteran.",
"support.intro.second": "Wenn dir die Seite gefällt und sie unterstützen willst und Vorteile erhälten möchtest, findest du auf dieser Seite dazu Details. Dein Support hilft mir, die Serverkosten zu bezahlen und ermöglicht mir Zeit in das Projekt zu investieren, um es ständig zu verbessern.",
"support.action": "Auf Patreon unterstützen",
"support.footer": "Nachdem du Patron geworden bist, solltest du <2>dein Discord-Konto auf Patreon.com</2> verknüpfen. Danach werden die Vorteile innerhalb von 2 Stunden freigeschaltet. Bei Fragen oder Problemen, kontaktiere Sendou für Unterstützung.",
"support.perk.supportMyWork": "Unterstütze meine Arbeit",
"support.perk.adFree": "Werbefreies Browsen",
"support.perk.nameInFooter": "Name im Footer",

View File

@ -221,7 +221,7 @@
"support.intro.first": "Hello! I'm Sendou and sendou.ink is my project to provide tools and resources for the Splatoon community. The goal is to help everyone to improve and enjoy Splatoon whether you are brand new to the game or a seasoned veteran.",
"support.intro.second": "If you like what I'm doing this page details how you can support my work and gain perks. Your support helps me pay for the hosting as well as sponsor my time spent on continuously improving the project.",
"support.action": "Support on Patreon",
"support.footer": "After becoming a new patron you should connect <2>your Discord on Patreon.com</2>. Afterward, the perks will take effect within 2 hours. If any questions or problems contact Sendou for support.",
"support.footer": "After becoming a new patron you should connect <2>your Discord on Patreon.com</2>. Afterward, the perks will take effect within an hour. If any questions or problems contact Sendou for support.",
"support.perk.supportMyWork": "Support my work",
"support.perk.adFree": "Ad-free browsing",
"support.perk.nameInFooter": "Name in the footer",
@ -236,6 +236,8 @@
"support.perk.userShortLink": "User page short link",
"support.perk.userShortLink.extra": "Instead of e.g. sendou.ink/u/sendou you can also use snd.ink/sendou when linking to your user profile.",
"support.perk.customizedColorsUser": "Customize colors (user page)",
"support.perk.favoriteBadges": "Set profile first page badges",
"support.perk.favoriteBadges.extra": "You can set which badges appear on the first page and the order. Normally it's only possible to choose the first badge.",
"support.perk.customizedColorsTeam": "Customize colors (team page)",
"support.perk.customizedColorsTeam.extra": "It is enough that one member of the team is a patron to get this perk.",
"support.perk.privateDiscord": "Access to an exclusive Discord channel",
@ -304,5 +306,8 @@
"settings.notifications.description": "Receive push notifications to your device even if you don't currently have sendou.ink open.",
"settings.notifications.disableInfo": "To disable push notifications check your browser settings",
"settings.notifications.browserNotSupported": "Push notifications are not supported on this browser",
"settings.notifications.permissionDenied": "Push notifications were denied. Check your browser settings to re-enable"
"settings.notifications.permissionDenied": "Push notifications were denied. Check your browser settings to re-enable",
"badges.selector.none": "No badges selected",
"badges.selector.select": "Select badge to add"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "Series name",
"edit.form.series.showLeaderboard.title": "Show leaderboard",
"edit.form.badges.title": "Badges",
"edit.form.badges.none": "No badges selected",
"edit.form.badges.select": "Select badge to add",
"edit.form.errors.noUnadmin": "Can't remove yourself as an admin"
}

View File

@ -12,7 +12,7 @@
"sens": "Sens",
"weaponPool": "Weapon pool",
"discordExplanation": "Username, profile picture, YouTube, Bluesky and Twitch accounts come from your Discord account. See <1>FAQ</1> for more information.",
"favoriteBadge": "Favorite Badge",
"favoriteBadges": "Favorite badges",
"battlefy": "Battlefy account name",
"forms.showDiscordUniqueName": "Show Discord username",
@ -22,6 +22,7 @@
"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}}\"",
"forms.country.search.placeholder": "Search countries",
"forms.favoriteBadges.nonSupporter": "Become supporter to set which badges appear on the first page and the order",
"results.title": "All results",
"results.placing": "Placing",
@ -41,7 +42,6 @@
"forms.errors.invalidCustomUrl.duplicate": "Someone is already using this custom URL",
"forms.errors.invalidSens": "Motion sens can't be set if R-stick sens isn't",
"forms.info.customUrl": "For patrons (Supporter & above) short link is available. E.g. instead of sendou.ink/u/sendou, snd.ink/sendou can be used.",
"forms.info.favoriteBadge": "Your favorite badge is shown as big by default on your profile.",
"forms.info.battlefy": "Battlefy account name is used for seeding and verification in some tournaments",
"search.info": "Search for users by Discord or Splatoon 3 name",

View File

@ -151,7 +151,6 @@
"support.intro.first": "¡Hola! Soy Sendou y sendou.ink es mi proyecto para proveer herramientas y recursos para la comunidad de Splatoon. La meta es ayudar a todos a mejorar y disfrutar Splatoon ya sean principiantes o veteranos.",
"support.intro.second": "Si te gusta lo que hago, esta página detalla como puedes dar tu apoyo a mi trabajo y recibir beneficios. Tu apoyo me ayuda pagar el costo del sitio y me hace habil pasar tiempo mejorando el proyecto.",
"support.action": "Apoyanos en Patreon",
"support.footer": "Ya que des tu apoyo en Patreon, debes conectar <2>tu Discord en Patreon.com</2>. Los beneficios seran disponibles dentro de 2 horas. Si tienes preguntas o problemas, contacta a Sendou por ayuda.",
"support.perk.supportMyWork": "Apoya mi trabajo",
"support.perk.adFree": "Sitio sin anuncios",
"support.perk.nameInFooter": "Tu nombre en la página",
@ -210,5 +209,8 @@
"chat.systemMsg.cancelConfirmed": "{{name}} confirmó cancelar el partido. El partido está cerrado",
"chat.systemMsg.userLeft": "{{name}} se salió del grupo",
"fc.title": "Clave de amigo"
"fc.title": "Clave de amigo",
"badges.selector.none": "Ninguna insignia seleccionada",
"badges.selector.select": "Seleccionar insignia añadir"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "Nombre de la serie",
"edit.form.series.showLeaderboard.title": "Mostrar tablas de posición",
"edit.form.badges.title": "Insignias",
"edit.form.badges.none": "Ninguna insignia seleccionada",
"edit.form.badges.select": "Seleccionar insignia añadir",
"edit.form.errors.noUnadmin": "No se puede eliminar como admin"
}

View File

@ -11,7 +11,6 @@
"sens": "Sens",
"weaponPool": "Grupo de armas",
"discordExplanation": "Tu nombre, foto, y cuentas de YouTube, Bluesky y Twitch se obtienen por tu cuenta en Discord. Ver <1>FAQ</1> para más información.",
"favoriteBadge": "Insignia favorita",
"forms.showDiscordUniqueName": "Mostrar usuario de Discord",
"forms.showDiscordUniqueName.info": "¿Mostrar tu nombre de Discord ({{discordUniqueName}}) publicamente?",
@ -33,7 +32,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "Enlace personalizado no puede contener caracteres especiales",
"forms.errors.invalidCustomUrl.duplicate": "Alguien ya tiene ese enlace personalizado",
"forms.errors.invalidSens": "Motion sens can't be set if R-stick sens isn't",
"forms.info.favoriteBadge": "Tu insignia favorita se muestra en forma grande en tu perfil.",
"search.noResults": "No se encontraron usuarios que coincidan con '{{query}}'",

View File

@ -161,7 +161,6 @@
"support.intro.first": "¡Hola! Soy Sendou y sendou.ink es mi proyecto para proveer herramientas y recursos para la comunidad de Splatoon. La meta es ayudar a todos a mejorar y disfrutar Splatoon ya sean principiantes o veteranos.",
"support.intro.second": "Si te gusta lo que hago, esta página detalla como puedes dar tu apoyo a mi trabajo y recibir beneficios. Tu apoyo me ayuda pagar el costo del sitio y me hace habil pasar tiempo mejorando el proyecto.",
"support.action": "Apoyanos en Patreon",
"support.footer": "Ya que des tu apoyo en Patreon, debes conectar <2>tu Discord en Patreon.com</2>. Los beneficios seran disponibles dentro de 2 horas. Si tienes preguntas o problemas, contacta a Sendou por ayuda.",
"support.perk.supportMyWork": "Apoya mi trabajo",
"support.perk.adFree": "Sitio sin anuncios",
"support.perk.nameInFooter": "Tu nombre en la página",
@ -221,5 +220,8 @@
"chat.systemMsg.userLeft": "{{name}} se salió del grupo",
"chat.newMessages": "Nuevo mensajes",
"fc.title": "Clave de amigo"
"fc.title": "Clave de amigo",
"badges.selector.none": "Ninguna insignia seleccionada",
"badges.selector.select": "Seleccionar insignia añadir"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "Nombre de la serie",
"edit.form.series.showLeaderboard.title": "Mostrar tablas de posición",
"edit.form.badges.title": "Insignias",
"edit.form.badges.none": "Ninguna insignia seleccionada",
"edit.form.badges.select": "Seleccionar insignia añadir",
"edit.form.errors.noUnadmin": "No se puede eliminar como admin"
}

View File

@ -12,7 +12,6 @@
"sens": "Sens",
"weaponPool": "Grupo de armas",
"discordExplanation": "Tu nombre, foto, y cuentas de YouTube, Bluesky y Twitch se obtienen por tu cuenta en Discord. Ver <1>FAQ</1> para más información.",
"favoriteBadge": "Insignia favorita",
"battlefy": "Nombre de cuenta de Battlefy",
"forms.showDiscordUniqueName": "Mostrar usuario de Discord",
@ -40,7 +39,6 @@
"forms.errors.invalidCustomUrl.duplicate": "Alguien ya tiene ese enlace personalizado",
"forms.errors.invalidSens": "Sens de giroscopio no se poner sin la sens de palanca",
"forms.info.favoriteBadge": "Tu insignia favorita se muestra en forma grande en tu perfil.",
"forms.info.battlefy": "El nombre de tu cuenta de Battlefy se utiliza para la clasificación y verificación en algunos torneos.",
"search.noResults": "No se encontraron usuarios que coincidan con '{{query}}'",

View File

@ -147,7 +147,6 @@
"support.intro.first": "Salut! Je suis Sendou et sendou.ink est mon projet pour fournir des outils et ressources pour la communauté Splatoon. L'objectif est d'aider tout le monde à s'améliorer et à profiter de Splatoon, que vous soyez tout nouveau dans le jeu ou un vétéran chevronné.",
"support.intro.second": "Si vous aimez ce que je fais, cette page détaille comment vous pouvez soutenir mon travail et gagner des avantages. Votre soutien m'aide à payer l'hébergement ainsi qu'à sponsoriser mon temps passé à améliorer continuellement le projet.",
"support.action": "Soutenir sur Patreon",
"support.footer": "Après être devenu un nouveau supporter vous devez lié <2>votre Discord sur Patreon.com</2>. Après ça, les avantages devraient prendre effet dans les 2 heures qui suivent. Si vous avez des questions ou des problèmes, contactez Sendou pour obtenir de l'aide.",
"support.perk.supportMyWork": "Soutenez mon travail",
"support.perk.adFree": "Navigation sans pub",
"support.perk.nameInFooter": "Nom dans le pied de page",

View File

@ -11,7 +11,6 @@
"sens": "Sens",
"weaponPool": "Armes jouées",
"discordExplanation": "Votre pseudo, votre photo de profil et vos comptes Youtube, Bluesky et Twitch viennent de votre compte Discord. Voir la <1>FAQ</1> pour plus d'informations.",
"favoriteBadge": "Badge favori",
"forms.showDiscordUniqueName": "Montrer le pseudo Discord",
"forms.showDiscordUniqueName.info": "Show your unique Discord name ({{discordUniqueName}}) publicly?",
@ -33,7 +32,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "Votre URL personnalisée ne peut pas contenir de caractères spéciaux",
"forms.errors.invalidCustomUrl.duplicate": "Cette URL a déjà été choisie par quelqu'un",
"forms.errors.invalidSens": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
"forms.info.favoriteBadge": "Votre badge favoris est affiché en grand par défaut sur votre profil.",
"search.noResults": "Aucun utilisateur correspondant à '{{query}}' n'a été trouvé"
}

View File

@ -211,7 +211,6 @@
"support.intro.first": "Salut! Je suis Sendou et sendou.ink est mon projet pour fournir des outils et ressources pour la communauté Splatoon. L'objectif est d'aider tout le monde à s'améliorer et à profiter de Splatoon, que vous soyez tout nouveau dans le jeu ou un vétéran chevronné.",
"support.intro.second": "Si vous aimez ce que je fais, cette page détaille comment vous pouvez soutenir mon travail et gagner des avantages. Votre soutien m'aide à payer l'hébergement ainsi qu'à sponsoriser mon temps passé à améliorer continuellement le projet.",
"support.action": "Soutenir sur Patreon",
"support.footer": "Après être devenu un nouveau supporter vous devez lié <2>votre Discord sur Patreon.com</2>. Après ça, les avantages devraient prendre effet dans les 2 heures qui suivent. Si vous avez des questions ou des problèmes, contactez Sendou pour obtenir de l'aide.",
"support.perk.supportMyWork": "Soutenez mon travail",
"support.perk.adFree": "Navigation sans pub",
"support.perk.nameInFooter": "Nom dans le pied de page",
@ -285,5 +284,8 @@
"settings.notifications.description": "Recevoir une Receive notification push sur votre appareil quand vous n'êtes pas dessus.",
"settings.notifications.disableInfo": "Regarder les paramètre de votre navigateur pour désactiver les notification push.",
"settings.notifications.browserNotSupported": "Les notifications push ne sont pas supporter par cet navigateur",
"settings.notifications.permissionDenied": "Les notifications push ont été refusées. Vérifiez les paramètres de votre navigateur pour les réactiver"
"settings.notifications.permissionDenied": "Les notifications push ont été refusées. Vérifiez les paramètres de votre navigateur pour les réactiver",
"badges.selector.none": "Aucun badges est sélectionné",
"badges.selector.select": "Selectionner un badge pour l'ajouter"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "Nom de la série ",
"edit.form.series.showLeaderboard.title": "Montrer le leaderboard",
"edit.form.badges.title": "Badges",
"edit.form.badges.none": "Aucun badges est sélectionné",
"edit.form.badges.select": "Selectionner un badge pour l'ajouter",
"edit.form.errors.noUnadmin": "Vous ne pouvez pas vous supprimer en tant qu'administrateur"
}

View File

@ -12,7 +12,6 @@
"sens": "Sens",
"weaponPool": "Armes jouées",
"discordExplanation": "Votre pseudo, votre photo de profil et vos comptes Youtube, Bluesky et Twitch viennent de votre compte Discord. Voir la <1>FAQ</1> pour plus d'informations.",
"favoriteBadge": "Badge favori",
"battlefy": "Nom du compte Battlefy",
"forms.showDiscordUniqueName": "Montrer le pseudo Discord",
@ -40,7 +39,6 @@
"forms.errors.invalidCustomUrl.duplicate": "Cette URL a déjà été choisie par quelqu'un",
"forms.errors.invalidSens": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
"forms.info.customUrl": "Pour les Supporter patrons (& plus), les liens courts sont disponibles. Exemple: Au mieux de sendou.ink/u/sendou, snd.ink/sendou peut être utilisé.",
"forms.info.favoriteBadge": "Votre badge favoris est affiché en grand par défaut sur votre profil.",
"forms.info.battlefy": "Votre nom Battlefy est utiliser pour le seeding et la verification de certains tournois",
"search.info": "Recherchez avec le pseudo Discord ou Splatoon 3 du compte",

View File

@ -147,7 +147,6 @@
"support.intro.first": "שלום! אני Sendou ו- sendou.ink הוא הפרויקט שלי לספק כלים ומשאבים לקהילת Splatoon. המטרה היא לעזור לכולם להשתפר וליהנות מ-Splatoon בין אם אתם חדשים במשחק או שחקנים ותיקים.",
"support.intro.second": "אם אתם אוהבים את מה שאני עושה העמוד הזה מפרט איך אתם יכול לתמוך בעבודה שלי ולהרוויח הטבות. התמיכה שלכם עוזרת לי לשלם עבור האירוח, וגם לתת חסות לזמן המושקע בשיפור המתמשך של הפרויקט.",
"support.action": "תמיכה בפטראון",
"support.footer": "לאחר הפיכה לפטרון יש לחבר את הדיסקורד שלכם ב-<2>Pateron.com</2>. לאחר מכן, ההטבות ייכנסו לתוקף תוך שעתיים. אם יש שאלות או בעיות, פנו אל Sendou לקבלת תמיכה.",
"support.perk.supportMyWork": "תמיכה בעבודה שלי",
"support.perk.adFree": "גלישה ללא פרסומות",
"support.perk.nameInFooter": "שם בכותרת התחתונה",

View File

@ -11,7 +11,6 @@
"sens": "רגישות",
"weaponPool": "מאגר נשקים",
"discordExplanation": "שם משתמש, תמונת פרופיל, חשבונות YouTube, Bluesky ו-Twitch מגיעים מחשבון Discord שלך. ראו <1>שאלות נפוצות</1> למידע נוסף.",
"favoriteBadge": "תג אהוב",
"forms.showDiscordUniqueName": "הראה שם משתמש Discord",
"forms.showDiscordUniqueName.info": "להראות את שם ה-Discord היחודי שלכם ({{discordUniqueName}}) בפומבי?",
@ -33,7 +32,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "כתובת URL מותאמת אישית לא יכולה להכיל תווים מיוחדים",
"forms.errors.invalidCustomUrl.duplicate": "מישהו כבר משתמש בכתובת URL המותאמת אישית הזו",
"forms.errors.invalidSens": "לא ניתן להגדיר את רגישות התנועה אם רגישות הסטיק לא מוגדרת",
"forms.info.favoriteBadge": "התג האהוב עליכם מוצג בגדול כברירת מחדל בפרופיל שלכם.",
"search.noResults": "לא נמצאו משתמשים התואמים '{{query}}'"
}

View File

@ -208,7 +208,6 @@
"support.intro.first": "Ciao! Sono Sendou e sendou.ink è il mio progetto per fornire tools e risorse alla community di Splatoon. L'obiettivo è quello di aiutare tutti a migliorare e divertirsi con Splatoon sia se sei un giocatore nuovo che un veterano.",
"support.intro.second": "Se ti piace quel che sto facendo, questa pagina indica come tu possa supportare il mio lavoro e ottenere vantaggi. Il tuo supporto mi aiuta a coprire le spese di hosting così come supportare il tempo che ho speso sul migliorare continuamente il progetto.",
"support.action": "Supporta su Patreon",
"support.footer": "Dopo esserti iscritto/a al Patreon, devi connettere <2>il tuo Discord su Patreon.com</2>. Dopodiché, i vantaggi diverranno effettivi entro 2 ore. Per ulteriori informazioni o problemi contattare Sendou per ottenere supporto",
"support.perk.supportMyWork": "Supporta il mio lavoro",
"support.perk.adFree": "Navigazione ad-free",
"support.perk.nameInFooter": "Nome a piè di pagina",
@ -284,5 +283,8 @@
"settings.notifications.browserNotSupported": "Le notifiche push non sono supportate su questo browser",
"settings.notifications.permissionDenied": "Le notifiche push sono state negate. Controlla le impostazioni del browser per riattivarle",
"articles.by": "da {{author}}"
"articles.by": "da {{author}}",
"badges.selector.none": "Nessuna medaglia selezionata",
"badges.selector.select": "Seleziona medaglia da aggiungere"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "Nome serie",
"edit.form.series.showLeaderboard.title": "Mostra classifica",
"edit.form.badges.title": "Medaglia",
"edit.form.badges.none": "Nessuna medaglia selezionata",
"edit.form.badges.select": "Seleziona medaglia da aggiungere",
"edit.form.errors.noUnadmin": "Non puoi rimuoverti dal ruolo di admin"
}

View File

@ -12,7 +12,6 @@
"sens": "Sens.",
"weaponPool": "Pool armi",
"discordExplanation": "Username, foto profilo, account YouTube, Bluesky e Twitch vengono dal tuo account Discord. Visita <1>FAQ</1> per ulteriori informazioni.",
"favoriteBadge": "Medaglia preferita",
"battlefy": "Nome account Battlefy",
"forms.showDiscordUniqueName": "Mostra username Discord",
@ -40,7 +39,6 @@
"forms.errors.invalidCustomUrl.duplicate": "L'URL personalizzato è già in uso da un altro utente",
"forms.errors.invalidSens": "La sensibilità del giroscopio non può essere impostata se non hai impostato la sensibilità del joystick destro",
"forms.info.customUrl": "Per gli iscritti al Patreon (Supporter compreso in su) è disponibile il link corto. Es. invece di sendou.ink/u/sendou, può essere usato snd.ink/sendou.",
"forms.info.favoriteBadge": "La tua medaglia preferita è mostrata più grande di default sul tuo profilo.",
"forms.info.battlefy": "Il nome dell'account Battlefy è usato per il seeding e verifica in alcuni tornei",
"search.info": "Cerca utenti tramite nome Discord o Splatoon 3",

View File

@ -174,7 +174,6 @@
"support.intro.first": "やあ、ぼくは Sendou です。sendou.ink はぼくの個人プロジェクトで、スプラトゥーンコミュニティーのためのツールや情報などを開発しています。このプロジェクトの目的は、新規プレイヤーからベテランまですべての人がスプラトゥーンのウデマエを磨いたり楽しんだりできるようにすることです。",
"support.intro.second": "もしこの活動を気に入ってくれたなら、このページでぼくの作業とモチベーションをサポートする方法を説明しています。あなたのサポートが、サーバーのホスティングやプロジェクトの改善のための開発に対する支援になります。",
"support.action": "Patreon で支援する",
"support.footer": "新規で支援者になったら、<2>Patreon.com で Discord のアカウント</2>と連携してください。その後、約2時間ほどで特典が有効になります。質問があれば、Sendou にご連絡ください",
"support.perk.supportMyWork": "プロジェクトを支援する",
"support.perk.adFree": "広告なしの閲覧",
"support.perk.nameInFooter": "フッターの名前",

View File

@ -12,7 +12,6 @@
"sens": "感度",
"weaponPool": "使用ブキ",
"discordExplanation": "ユーザー名、プロファイル画像、YouTube、Bluesky と Twitch アカウントは Discord のアカウントに設定されているものが使用されます。詳しくは <1>FAQ</1> をご覧ください。",
"favoriteBadge": "お気に入りバッジ",
"battlefy": "Battlefyアカウント名",
"forms.showDiscordUniqueName": "Discord のユーザー名を表示する",
@ -39,7 +38,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "カスタム URL は特殊文字を含めることはできません",
"forms.errors.invalidCustomUrl.duplicate": "このカスタム URL はすでに使用されています",
"forms.errors.invalidSens": "右スティックの感度が設定されていない場合、感度を設定することはできません",
"forms.info.favoriteBadge": "お気に入りバッジはデフォルトでプロファイルに大きく表示されます",
"forms.info.battlefy": "Battlefyのアカウント名は特定のトーナメンでシーディング及びにプレイヤー情報の確認に使用されます。",
"search.noResults": "該当ユーザーが見つかりません '{{query}}'",

View File

@ -134,7 +134,6 @@
"support.intro.first": "Cześć! Jestem Sendou i sendou.ink jest moim projektem by dostarczyć narzędzia i zasoby dla społeczności Splatoon. Moim celem jest pomaganie każdemu się udoskonalić oraz bardziej cieszyć z Splatoona, czy jesteś początkowym graczem czy weteranem.",
"support.intro.second": "Jeśli podoba ci się to, co robię, ta strona przedstawia jak możesz mnie wesprzeć oraz uzyskać korzyści. Twoje wsparcie pomaga mi opłacać hosting oraz sponsoruje mój czas spędzony na udoskonalanie projektu.",
"support.action": "Wesprzyj na Patreonie",
"support.footer": "Po zostaniu nowym patronem powinieneś/powinnaś <2>your Discord on Patreon.com</2>. Po tym w czasie dwóch godzin powinieneś/powinnaś dostać korzyści. Jeśli masz jakiekolwiek pytania lub problemu skontaktuj się z Sendou.",
"support.perk.supportMyWork": "Wesprzyj moją prace",
"support.perk.adFree": "Przeglądanie bez reklam",
"support.perk.nameInFooter": "Nazwa w footerze",

View File

@ -157,7 +157,6 @@
"support.intro.first": "Oi! Eu sou o Sendou e o sendou.ink é o meu projeto que consiste em fornecer ferramentas e recursos para a comunidade do Splatoon. O objetivo é ajudar todos a aprimorar suas habilidades e aproveitar o Splatoon, não importando se você é um novato novinho em folha ou veterano de longa data.",
"support.intro.second": "Se você gosta do que eu estou fazendo, essa página dá detalhes em como você pode apoiar meu trabalho e ganhar vantagens. Seu apoio me ajuda a pagar pela hospedagem do site e também patrocina o meu tempo investido em melhorar o projeto constantemente.",
"support.action": "Apoiar no Patreon",
"support.footer": "Após se tonar um novo patrono ou patronesse, você deve conectar <2>seu Discord no Patreon.com</2>. Após isso, suas vantagens devem ser ativadas em até 2 horas. Se você tiver perguntas ou problemas, entre em contato com o Sendou para obter suporte.",
"support.perk.supportMyWork": "Apoiar meu trabalho",
"support.perk.adFree": "Navegação sem anúncios",
"support.perk.nameInFooter": "Nome no rodapé",

View File

@ -11,7 +11,6 @@
"sens": "Sens",
"weaponPool": "Seleção de armas",
"discordExplanation": "Nome de usuário, foto de perfil, conta do YouTube, Bluesky e Twitch vêm da sua conta do Discord. Veja o <1>Perguntas Frequentes</1> para mais informações.",
"favoriteBadge": "Insígnia Favorita",
"forms.showDiscordUniqueName": "Mostrar nome de usuário Discord",
"forms.showDiscordUniqueName.info": "Deixe ativado para mostrar seu nome de usuário único do Discord ({{discordUniqueName}}) publicamente.",
@ -33,7 +32,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "URL personalizado não pode conter caracteres especiais",
"forms.errors.invalidCustomUrl.duplicate": "Alguém já está usando esse URL personalizado",
"forms.errors.invalidSens": "A sensibilidade de Movimento não pode ser definida se a sensibilidade do Analógico Direito não está",
"forms.info.favoriteBadge": "Por padrão, sua insígnia favorita é mostrada em tamanho maior no seu perfil.",
"search.noResults": "Nenhum usuário encontrado com o termo '{{query}}'",

View File

@ -149,7 +149,6 @@
"support.intro.first": "Привет! Я Sendou и sendou.ink — мой проект, дающий инструменты и ресурсы для сообщества Splatoon. Цель состоит в том, чтобы помочь каждому стать лучше и наслаждаться Splatoon, независимо от того, являетесь ли вы новичком в игре или опытным ветераном.",
"support.intro.second": "Если вам нравится то, что я делаю, на этой странице подробно расписано, как вы можете поддержать мой труд и получить бонусы. Ваша поддержка помогает мне оплачивать хостинг, а также спонсирует моё время, которое идёт на постоянное улучшение проекта.",
"support.action": "Поддержать на Patreon",
"support.footer": "После того, как вы станете новым патроном, вы должны подключить <2>свою учетную запись Discord на Patreon.com</2>. После этого вы сможете получить свои бонусы в течение 2 часов. В случае вопросов или проблем обращайтесь к Sendou за поддержкой.",
"support.perk.supportMyWork": "Поддержать мою работу",
"support.perk.adFree": "Отсутствие рекламы",
"support.perk.nameInFooter": "Имя в футере",

View File

@ -11,7 +11,6 @@
"sens": "Чувствительность",
"weaponPool": "Используемое оружие",
"discordExplanation": "Имя пользователя, аватар, ссылка на аккаунты YouTube, Bluesky и Twitch берутся из вашего аккаунта в Discord. Посмотрите <1>FAQ</1> для дополнительной информации.",
"favoriteBadge": "Любимый значок",
"forms.showDiscordUniqueName": "Показать пользовательское имя Discord",
"forms.showDiscordUniqueName.info": "Показывать ваше уникальное Discord имя ({{discordUniqueName}})?",
@ -33,7 +32,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "Пользовательский URL не может содержать особые символы",
"forms.errors.invalidCustomUrl.duplicate": "Кто-то уже использует этот пользовательский URL",
"forms.errors.invalidSens": "Чувствительность наклона не может быть указана, если не указана чувствительность стика",
"forms.info.favoriteBadge": "По умолчанию ваш любимый значок отображён большим в вашем профиле.",
"search.noResults": "Не найден пользователь по запросу '{{query}}'",

View File

@ -161,7 +161,6 @@
"support.intro.first": "您好我是Sendousendou.ink是我的个人项目用来给斯普拉遁社群提供工具和资源。我的目标是帮助新老玩家进步并享受这个游戏。",
"support.intro.second": "如果您喜欢我所做的,本页详细展示了支持我的方式以及可获得的特权。您的支持将帮助我支付运营费用,并且赞助我持续完善这个项目。",
"support.action": "成为Patreon支持者",
"support.footer": "在成为Patreon支持者后请 <2>在Patreon.com绑定您的Discord</2>。特权将在两小时后生效。如果您有任何问题请联系Sendou。",
"support.perk.supportMyWork": "支持我的工作",
"support.perk.adFree": "无广告",
"support.perk.nameInFooter": "在页脚展示您的名字",
@ -221,5 +220,8 @@
"chat.systemMsg.userLeft": "{{name}} 离开了小队",
"chat.newMessages": "新消息",
"fc.title": "好友码"
"fc.title": "好友码",
"badges.selector.none": "没有选择任何徽章",
"badges.selector.select": "选择徽章并添加"
}

View File

@ -21,7 +21,5 @@
"edit.form.series.seriesName.title": "系列名称",
"edit.form.series.showLeaderboard.title": "显示排行榜",
"edit.form.badges.title": "徽章",
"edit.form.badges.none": "没有选择任何徽章",
"edit.form.badges.select": "选择徽章并添加",
"edit.form.errors.noUnadmin": "您不能移除自己的管理者身份"
}

View File

@ -12,7 +12,6 @@
"sens": "感度",
"weaponPool": "武器池",
"discordExplanation": "用户名、头像、Youtube、Bluesky和Twitch账号皆来自您的Discord账号。查看 <1>FAQ</1> 以获得更多相关信息。",
"favoriteBadge": "最喜爱的徽章",
"battlefy": "Battlefy用户名",
"forms.showDiscordUniqueName": "显示Discord用户名",
@ -39,7 +38,6 @@
"forms.errors.invalidCustomUrl.strangeCharacter": "自定义URL不能包含特殊符号",
"forms.errors.invalidCustomUrl.duplicate": "这个自定义URL已被使用",
"forms.errors.invalidSens": "设置体感感度前请先设置摇杆感度",
"forms.info.favoriteBadge": "您最喜爱的徽章将会默认在资料页展示为大徽章",
"forms.info.battlefy": "Battlefy用户名会在部分比赛被用于种子排名和验证",
"search.noResults": "没有符合 '{{query}}' 的用户",

View File

@ -0,0 +1,16 @@
export function up(db) {
db.transaction(() => {
db.prepare(
/* sql */ `alter table "User" add "favoriteBadgeIds" text`,
).run();
db.prepare(/* sql */ `update "User"
set "favoriteBadgeIds" =
case
when "favoriteBadgeId" is not null then '[' || "favoriteBadgeId" || ']'
else null
end;`).run();
db.prepare(
/* sql */ `alter table "User" drop column "favoriteBadgeId"`,
).run();
})();
}