diff --git a/app/db/seed/index.ts b/app/db/seed/index.ts index 97f342753..c1dcc29d1 100644 --- a/app/db/seed/index.ts +++ b/app/db/seed/index.ts @@ -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", diff --git a/app/db/tables.ts b/app/db/tables.ts index 623c72b84..e87717aa3 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -828,7 +828,8 @@ export interface User { /** coalesce(customName, discordName) */ username: ColumnType; 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; id: GeneratedAlways; inGameName: string | null; isArtist: Generated; diff --git a/app/features/badges/BadgeRepository.server.ts b/app/features/badges/BadgeRepository.server.ts index d648b9ceb..275e87f72 100644 --- a/app/features/badges/BadgeRepository.server.ts +++ b/app/features/badges/BadgeRepository.server.ts @@ -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("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") diff --git a/app/features/badges/badges-contants.ts b/app/features/badges/badges-contants.ts new file mode 100644 index 000000000..8432e2462 --- /dev/null +++ b/app/features/badges/badges-contants.ts @@ -0,0 +1,3 @@ +export const BADGE = { + SMALL_BADGES_PER_DISPLAY_PAGE: 9, +}; diff --git a/app/features/badges/components/BadgeDisplay.tsx b/app/features/badges/components/BadgeDisplay.tsx index c52990f96..32412c35f 100644 --- a/app/features/badges/components/BadgeDisplay.tsx +++ b/app/features/badges/components/BadgeDisplay.tsx @@ -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 & { 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) => { - 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 ( -
-
- {badgeExplanationText(t, bigBadge)} - {onBadgeRemove ? ( -
+
+ {isPaginated ? ( +
+ {badgeExplanationText(t, bigBadge)} +
+ ) : null}
- {smallBadges.length > 0 ? ( + {!children && smallBadges.length > 0 ? (
{itemsToDisplay.map((badge) => (
@@ -85,7 +86,24 @@ export function BadgeDisplay({ ))}
) : null} + {children}
+ {!isPaginated ? ( +
+ {badgeExplanationText(t, bigBadge)} + {onChange ? ( +
+ ) : null} {!everythingVisible ? ( {i + 1} diff --git a/app/features/badges/components/BadgesSelector.tsx b/app/features/badges/components/BadgesSelector.tsx new file mode 100644 index 000000000..f3140d7fa --- /dev/null +++ b/app/features/badges/components/BadgesSelector.tsx @@ -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 ( +
+ {selectedBadges.length > 0 ? ( + 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} + + ) : ( +
+ {t("common:badges.selector.none")} +
+ )} + +
+ ); +} diff --git a/app/features/info/routes/support.tsx b/app/features/info/routes/support.tsx index 69d65e1f1..e55d16fbf 100644 --- a/app/features/info/routes/support.tsx +++ b/app/features/info/routes/support.tsx @@ -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 - . 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.

diff --git a/app/features/tournament-organization/routes/org.$slug.edit.tsx b/app/features/tournament-organization/routes/org.$slug.edit.tsx index 6ab6ffef5..2a519aae4 100644 --- a/app/features/tournament-organization/routes/org.$slug.edit.tsx +++ b/app/features/tournament-organization/routes/org.$slug.edit.tsx @@ -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 & { @@ -235,6 +235,7 @@ function SeriesFieldset({ function BadgesFormField() { const { t } = useTranslation(["org"]); const methods = useFormContext(); + const data = useLoaderData(); return (
@@ -244,6 +245,7 @@ function BadgesFormField() { name="badges" render={({ field: { onChange, onBlur, value } }) => ( ); } - -function BadgesSelector({ - selectedBadges, - onChange, - onBlur, -}: { - selectedBadges: number[]; - onChange: (newBadges: number[]) => void; - onBlur: () => void; -}) { - const { t } = useTranslation(["org"]); - const data = useLoaderData(); - - return ( -
- {selectedBadges.length > 0 ? ( - - selectedBadges.includes(badge.id), - )} - onBadgeRemove={(badgeId) => - onChange(selectedBadges.filter((id) => id !== badgeId)) - } - key={selectedBadges.join(",")} - /> - ) : ( -
- {t("org:edit.form.badges.none")} -
- )} - -
- ); -} diff --git a/app/features/user-page/UserRepository.server.ts b/app/features/user-page/UserRepository.server.ts index a0cfdc500..71b646ee2 100644 --- a/app/features/user-page/UserRepository.server.ts +++ b/app/features/user-page/UserRepository.server.ts @@ -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[]; + 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, diff --git a/app/features/user-page/loaders/u.$identifier.edit.server.ts b/app/features/user-page/loaders/u.$identifier.edit.server.ts index 221690cee..8b9dcd133 100644 --- a/app/features/user-page/loaders/u.$identifier.edit.server.ts +++ b/app/features/user-page/loaders/u.$identifier.edit.server.ts @@ -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]) => ({ diff --git a/app/features/user-page/routes/u.$identifier.edit.test.ts b/app/features/user-page/routes/u.$identifier.edit.test.ts index 9e92cfc8f..52cf3bc00 100644 --- a/app/features/user-page/routes/u.$identifier.edit.test.ts +++ b/app/features/user-page/routes/u.$identifier.edit.test.ts @@ -16,7 +16,7 @@ const DEFAULT_FIELDS = { country: "FI", customName: null, customUrl: null, - favoriteBadgeId: null, + favoriteBadgeIds: null, inGameNameDiscriminator: null, inGameNameText: null, motionSens: null, diff --git a/app/features/user-page/routes/u.$identifier.edit.tsx b/app/features/user-page/routes/u.$identifier.edit.tsx index 0a02fea5f..ed74e7ef7 100644 --- a/app/features/user-page/routes/u.$identifier.edit.tsx +++ b/app/features/user-page/routes/u.$identifier.edit.tsx @@ -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(); 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 (
- - - - {t("user:forms.info.favoriteBadge")} - + {!isSupporter ? ( +
+ {t("user:forms.favoriteBadges.nonSupporter")} +
+ ) : null} +
); } diff --git a/app/features/user-page/routes/u.$identifier.tsx b/app/features/user-page/routes/u.$identifier.tsx index 37e04350e..26dcf8005 100644 --- a/app/features/user-page/routes/u.$identifier.tsx +++ b/app/features/user-page/routes/u.$identifier.tsx @@ -37,7 +37,7 @@ export const meta: MetaFunction = (args) => { }; export const handle: SendouRouteHandle = { - i18n: "user", + i18n: ["user", "badges"], breadcrumb: ({ match }) => { const data = match.data as UserPageLoaderData | undefined; diff --git a/app/features/user-page/user-page-schemas.server.ts b/app/features/user-page/user-page-schemas.server.ts index 8bc258944..5aed95735 100644 --- a/app/features/user-page/user-page-schemas.server.ts +++ b/app/features/user-page/user-page-schemas.server.ts @@ -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), diff --git a/db-test.sqlite3 b/db-test.sqlite3 index d6d79d7b2..85ed3d9a5 100644 Binary files a/db-test.sqlite3 and b/db-test.sqlite3 differ diff --git a/e2e/badges.spec.ts b/e2e/badges.spec.ts index 6a3f995b0..bd67a757c 100644 --- a/e2e/badges.spec.ts +++ b/e2e/badges.spec.ts @@ -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: "/", diff --git a/e2e/user-page.spec.ts b/e2e/user-page.spec.ts index f8cdfeb07..3ff43de2c 100644 --- a/e2e/user-page.spec.ts +++ b/e2e/user-page.spec.ts @@ -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); diff --git a/locales/da/common.json b/locales/da/common.json index 327e61b4c..38fb5faa4 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -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 . 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", diff --git a/locales/da/user.json b/locales/da/user.json index 8b4aab560..b93b3cd56 100644 --- a/locales/da/user.json +++ b/locales/da/user.json @@ -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 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", diff --git a/locales/de/common.json b/locales/de/common.json index c37ad6e37..a4e367e78 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -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 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", diff --git a/locales/en/common.json b/locales/en/common.json index ca8de6a19..67f03fd28 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -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. 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. 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" } diff --git a/locales/en/org.json b/locales/en/org.json index bb35810ec..a5ff1e8d6 100644 --- a/locales/en/org.json +++ b/locales/en/org.json @@ -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" } diff --git a/locales/en/user.json b/locales/en/user.json index ba55ac5b5..3561d8b15 100644 --- a/locales/en/user.json +++ b/locales/en/user.json @@ -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 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", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index 786249a20..4d4d59238 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -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. 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" } diff --git a/locales/es-ES/org.json b/locales/es-ES/org.json index 5dbc96cba..f34fa5bcb 100644 --- a/locales/es-ES/org.json +++ b/locales/es-ES/org.json @@ -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" } diff --git a/locales/es-ES/user.json b/locales/es-ES/user.json index 173786c18..1ef85c76f 100644 --- a/locales/es-ES/user.json +++ b/locales/es-ES/user.json @@ -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 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}}'", diff --git a/locales/es-US/common.json b/locales/es-US/common.json index 19d0b9137..5dd59652d 100644 --- a/locales/es-US/common.json +++ b/locales/es-US/common.json @@ -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. 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" } diff --git a/locales/es-US/org.json b/locales/es-US/org.json index 5dbc96cba..f34fa5bcb 100644 --- a/locales/es-US/org.json +++ b/locales/es-US/org.json @@ -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" } diff --git a/locales/es-US/user.json b/locales/es-US/user.json index 008467660..9b3b373dd 100644 --- a/locales/es-US/user.json +++ b/locales/es-US/user.json @@ -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 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}}'", diff --git a/locales/fr-CA/common.json b/locales/fr-CA/common.json index 8e12446b4..bec7e9047 100644 --- a/locales/fr-CA/common.json +++ b/locales/fr-CA/common.json @@ -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. 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", diff --git a/locales/fr-CA/user.json b/locales/fr-CA/user.json index dfcfd9bb5..d9312aa4d 100644 --- a/locales/fr-CA/user.json +++ b/locales/fr-CA/user.json @@ -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 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é" } diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index 9e9c3f72d..ed02786a3 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -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. 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" } diff --git a/locales/fr-EU/org.json b/locales/fr-EU/org.json index c11ec9892..eb25a1140 100644 --- a/locales/fr-EU/org.json +++ b/locales/fr-EU/org.json @@ -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" } diff --git a/locales/fr-EU/user.json b/locales/fr-EU/user.json index bee911331..4c6209998 100644 --- a/locales/fr-EU/user.json +++ b/locales/fr-EU/user.json @@ -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 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", diff --git a/locales/he/common.json b/locales/he/common.json index e2c7fb865..d5838a443 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -147,7 +147,6 @@ "support.intro.first": "שלום! אני Sendou ו- sendou.ink הוא הפרויקט שלי לספק כלים ומשאבים לקהילת Splatoon. המטרה היא לעזור לכולם להשתפר וליהנות מ-Splatoon בין אם אתם חדשים במשחק או שחקנים ותיקים.", "support.intro.second": "אם אתם אוהבים את מה שאני עושה העמוד הזה מפרט איך אתם יכול לתמוך בעבודה שלי ולהרוויח הטבות. התמיכה שלכם עוזרת לי לשלם עבור האירוח, וגם לתת חסות לזמן המושקע בשיפור המתמשך של הפרויקט.", "support.action": "תמיכה בפטראון", - "support.footer": "לאחר הפיכה לפטרון יש לחבר את הדיסקורד שלכם ב-<2>Pateron.com. לאחר מכן, ההטבות ייכנסו לתוקף תוך שעתיים. אם יש שאלות או בעיות, פנו אל Sendou לקבלת תמיכה.", "support.perk.supportMyWork": "תמיכה בעבודה שלי", "support.perk.adFree": "גלישה ללא פרסומות", "support.perk.nameInFooter": "שם בכותרת התחתונה", diff --git a/locales/he/user.json b/locales/he/user.json index 341f0243d..0493c90c5 100644 --- a/locales/he/user.json +++ b/locales/he/user.json @@ -11,7 +11,6 @@ "sens": "רגישות", "weaponPool": "מאגר נשקים", "discordExplanation": "שם משתמש, תמונת פרופיל, חשבונות YouTube, Bluesky ו-Twitch מגיעים מחשבון Discord שלך. ראו <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}}'" } diff --git a/locales/it/common.json b/locales/it/common.json index 0bcc15b2a..f0b6a09db 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -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. 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" } diff --git a/locales/it/org.json b/locales/it/org.json index b70c64206..15072ce66 100644 --- a/locales/it/org.json +++ b/locales/it/org.json @@ -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" } diff --git a/locales/it/user.json b/locales/it/user.json index a278a0bc4..68126d3ee 100644 --- a/locales/it/user.json +++ b/locales/it/user.json @@ -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 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", diff --git a/locales/ja/common.json b/locales/ja/common.json index 88bdd9e59..bf2f8a19c 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -174,7 +174,6 @@ "support.intro.first": "やあ、ぼくは Sendou です。sendou.ink はぼくの個人プロジェクトで、スプラトゥーンコミュニティーのためのツールや情報などを開発しています。このプロジェクトの目的は、新規プレイヤーからベテランまですべての人がスプラトゥーンのウデマエを磨いたり楽しんだりできるようにすることです。", "support.intro.second": "もしこの活動を気に入ってくれたなら、このページでぼくの作業とモチベーションをサポートする方法を説明しています。あなたのサポートが、サーバーのホスティングやプロジェクトの改善のための開発に対する支援になります。", "support.action": "Patreon で支援する", - "support.footer": "新規で支援者になったら、<2>Patreon.com で Discord のアカウントと連携してください。その後、約2時間ほどで特典が有効になります。質問があれば、Sendou にご連絡ください", "support.perk.supportMyWork": "プロジェクトを支援する", "support.perk.adFree": "広告なしの閲覧", "support.perk.nameInFooter": "フッターの名前", diff --git a/locales/ja/user.json b/locales/ja/user.json index 8c1019411..0eec2e917 100644 --- a/locales/ja/user.json +++ b/locales/ja/user.json @@ -12,7 +12,6 @@ "sens": "感度", "weaponPool": "使用ブキ", "discordExplanation": "ユーザー名、プロファイル画像、YouTube、Bluesky と Twitch アカウントは Discord のアカウントに設定されているものが使用されます。詳しくは <1>FAQ をご覧ください。", - "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}}'", diff --git a/locales/pl/common.json b/locales/pl/common.json index f00df903f..cd4ed8330 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -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. 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", diff --git a/locales/pt-BR/common.json b/locales/pt-BR/common.json index b0b816e8e..27a5da663 100644 --- a/locales/pt-BR/common.json +++ b/locales/pt-BR/common.json @@ -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. 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é", diff --git a/locales/pt-BR/user.json b/locales/pt-BR/user.json index ebce6f61c..af9eda33c 100644 --- a/locales/pt-BR/user.json +++ b/locales/pt-BR/user.json @@ -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 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}}'", diff --git a/locales/ru/common.json b/locales/ru/common.json index 4345533d7..28e6b94ee 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -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 часов. В случае вопросов или проблем обращайтесь к Sendou за поддержкой.", "support.perk.supportMyWork": "Поддержать мою работу", "support.perk.adFree": "Отсутствие рекламы", "support.perk.nameInFooter": "Имя в футере", diff --git a/locales/ru/user.json b/locales/ru/user.json index a77175cb1..7a61b8311 100644 --- a/locales/ru/user.json +++ b/locales/ru/user.json @@ -11,7 +11,6 @@ "sens": "Чувствительность", "weaponPool": "Используемое оружие", "discordExplanation": "Имя пользователя, аватар, ссылка на аккаунты YouTube, Bluesky и Twitch берутся из вашего аккаунта в Discord. Посмотрите <1>FAQ для дополнительной информации.", - "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}}'", diff --git a/locales/zh/common.json b/locales/zh/common.json index c461df9b7..3d372529a 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -161,7 +161,6 @@ "support.intro.first": "您好!我是Sendou,sendou.ink是我的个人项目,用来给斯普拉遁社群提供工具和资源。我的目标是帮助新老玩家进步并享受这个游戏。", "support.intro.second": "如果您喜欢我所做的,本页详细展示了支持我的方式以及可获得的特权。您的支持将帮助我支付运营费用,并且赞助我持续完善这个项目。", "support.action": "成为Patreon支持者", - "support.footer": "在成为Patreon支持者后,请 <2>在Patreon.com绑定您的Discord。特权将在两小时后生效。如果您有任何问题,请联系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": "选择徽章并添加" } diff --git a/locales/zh/org.json b/locales/zh/org.json index 6636e97ba..e651b65b7 100644 --- a/locales/zh/org.json +++ b/locales/zh/org.json @@ -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": "您不能移除自己的管理者身份" } diff --git a/locales/zh/user.json b/locales/zh/user.json index 7b60f9a32..595a299cf 100644 --- a/locales/zh/user.json +++ b/locales/zh/user.json @@ -12,7 +12,6 @@ "sens": "感度", "weaponPool": "武器池", "discordExplanation": "用户名、头像、Youtube、Bluesky和Twitch账号皆来自您的Discord账号。查看 <1>FAQ 以获得更多相关信息。", - "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}}' 的用户", diff --git a/migrations/084-favorite-badges.js b/migrations/084-favorite-badges.js new file mode 100644 index 000000000..87e12f4b8 --- /dev/null +++ b/migrations/084-favorite-badges.js @@ -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(); + })(); +}