From d44ebcec552d53aca6bcd838db32a7f8b4259897 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:54:08 +0200 Subject: [PATCH] Delete /t page --- ...ou.ink-y2wo--remove-and-redirect-t-page.md | 4 +- app/components/layout/TopNavMenus.tsx | 3 +- app/components/layout/WeaponSearch.tsx | 2 +- app/components/layout/nav-items.ts | 5 - .../actions/t.$customUrl.edit.server.test.ts | 2 +- ...{t.server.test.ts => t.new.server.test.ts} | 2 +- .../actions/{t.server.ts => t.new.server.ts} | 0 app/features/team/loaders/t.new.server.ts | 13 ++ app/features/team/loaders/t.server.ts | 63 ------ .../team/routes/t.$customUrl.edit.test.ts | 2 +- app/features/team/routes/t.$customUrl.test.ts | 2 +- app/features/team/routes/t.new.tsx | 60 ++++++ app/features/team/routes/t.tsx | 202 +----------------- app/routes.ts | 1 + app/utils/urls.ts | 2 +- 15 files changed, 86 insertions(+), 277 deletions(-) rename app/features/team/actions/{t.server.test.ts => t.new.server.test.ts} (92%) rename app/features/team/actions/{t.server.ts => t.new.server.ts} (100%) create mode 100644 app/features/team/loaders/t.new.server.ts delete mode 100644 app/features/team/loaders/t.server.ts create mode 100644 app/features/team/routes/t.new.tsx diff --git a/.beans/sendou.ink-y2wo--remove-and-redirect-t-page.md b/.beans/sendou.ink-y2wo--remove-and-redirect-t-page.md index 8f6d6c291..9503c61b7 100644 --- a/.beans/sendou.ink-y2wo--remove-and-redirect-t-page.md +++ b/.beans/sendou.ink-y2wo--remove-and-redirect-t-page.md @@ -1,11 +1,11 @@ --- # sendou.ink-y2wo title: Remove and redirect /t page -status: todo +status: completed type: task created_at: 2026-01-11T08:58:56Z updated_at: 2026-01-11T08:58:56Z parent: sendou.ink-6eko --- -Deprecate and remove the /t page. Add redirect to new location. Location: app/features/team/routes/t.tsx:51 \ No newline at end of file +Deprecate and remove the /t page. Add redirect to new location. Location: app/features/team/routes/t.tsx:51 diff --git a/app/components/layout/TopNavMenus.tsx b/app/components/layout/TopNavMenus.tsx index 302117f54..1739969ac 100644 --- a/app/components/layout/TopNavMenus.tsx +++ b/app/components/layout/TopNavMenus.tsx @@ -26,7 +26,6 @@ const NAV_CATEGORIES = [ items: [ { name: "analyzer", url: "analyzer" }, { name: "comp-analyzer", url: "comp-analyzer" }, - { name: "builds", url: "builds" }, { name: "object-damage-calculator", url: "object-damage-calculator" }, { name: "plans", url: "plans" }, { name: "tier-list-maker", url: "tier-list-maker" }, @@ -35,8 +34,8 @@ const NAV_CATEGORIES = [ { name: "community", items: [ + { name: "builds", url: "builds" }, { name: "badges", url: "badges" }, - { name: "t", url: "t" }, { name: "vods", url: "vods" }, { name: "art", url: "art" }, { name: "articles", url: "a" }, diff --git a/app/components/layout/WeaponSearch.tsx b/app/components/layout/WeaponSearch.tsx index 9a02cf7ab..73a3e1b36 100644 --- a/app/components/layout/WeaponSearch.tsx +++ b/app/components/layout/WeaponSearch.tsx @@ -106,7 +106,7 @@ export function WeaponDestinationMenu({ selectedWeapon: SelectedWeapon; onBack: () => void; onSelect: (key: React.Key) => void; - listBoxRef: React.RefObject; + listBoxRef: React.RefObject; }) { const { t } = useTranslation(["common"]); diff --git a/app/components/layout/nav-items.ts b/app/components/layout/nav-items.ts index ee1b1714d..5a7949362 100644 --- a/app/components/layout/nav-items.ts +++ b/app/components/layout/nav-items.ts @@ -91,11 +91,6 @@ export const navItems = [ url: "art", prefetch: false, }, - { - name: "t", - url: "t", - prefetch: false, - }, { name: "tier-list-maker", url: "tier-list-maker", diff --git a/app/features/team/actions/t.$customUrl.edit.server.test.ts b/app/features/team/actions/t.$customUrl.edit.server.test.ts index b250e21fd..188bbbbac 100644 --- a/app/features/team/actions/t.$customUrl.edit.server.test.ts +++ b/app/features/team/actions/t.$customUrl.edit.server.test.ts @@ -5,7 +5,7 @@ import { dbReset, wrappedAction, } from "~/utils/Test"; -import { action as teamIndexPageAction } from "../actions/t.server"; +import { action as teamIndexPageAction } from "../actions/t.new.server"; import type { createTeamSchema } from "../team-schemas"; import type { editTeamSchema } from "../team-schemas.server"; import { action as _editTeamProfileAction } from "./t.$customUrl.edit.server"; diff --git a/app/features/team/actions/t.server.test.ts b/app/features/team/actions/t.new.server.test.ts similarity index 92% rename from app/features/team/actions/t.server.test.ts rename to app/features/team/actions/t.new.server.test.ts index 8ffd3c295..52d32814f 100644 --- a/app/features/team/actions/t.server.test.ts +++ b/app/features/team/actions/t.new.server.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { dbInsertUsers, dbReset, wrappedAction } from "~/utils/Test"; -import { action as teamIndexPageAction } from "../actions/t.server"; +import { action as teamIndexPageAction } from "../actions/t.new.server"; import type { createTeamSchema } from "../team-schemas"; const action = wrappedAction({ diff --git a/app/features/team/actions/t.server.ts b/app/features/team/actions/t.new.server.ts similarity index 100% rename from app/features/team/actions/t.server.ts rename to app/features/team/actions/t.new.server.ts diff --git a/app/features/team/loaders/t.new.server.ts b/app/features/team/loaders/t.new.server.ts new file mode 100644 index 000000000..47b215dd4 --- /dev/null +++ b/app/features/team/loaders/t.new.server.ts @@ -0,0 +1,13 @@ +import { requireUser } from "~/features/auth/core/user.server"; +import * as TeamRepository from "../TeamRepository.server"; + +export const loader = async () => { + const user = requireUser(); + + const teams = await TeamRepository.findAllUndisbanded(); + const teamMemberOfCount = teams.filter((team) => + team.members.some((m) => m.id === user.id), + ).length; + + return { teamMemberOfCount }; +}; diff --git a/app/features/team/loaders/t.server.ts b/app/features/team/loaders/t.server.ts deleted file mode 100644 index 4d09126c5..000000000 --- a/app/features/team/loaders/t.server.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as R from "remeda"; -import type { UserWithPlusTier } from "~/db/tables"; -import { getUser } from "~/features/auth/core/user.server"; -import * as TeamRepository from "../TeamRepository.server"; - -export const loader = async () => { - const user = getUser(); - - const unsortedTeams = await TeamRepository.findAllUndisbanded(); - - const teams = unsortedTeams.sort((teamA, teamB) => { - // show own team first always - if (user && teamA.members.some((m) => m.id === user.id)) { - return -1; - } - - if (user && teamB.members.some((m) => m.id === user.id)) { - return 1; - } - - // then full teams - if (teamA.members.length >= 4 && teamB.members.length < 4) { - return -1; - } - - if (teamA.members.length < 4 && teamB.members.length >= 4) { - return 1; - } - - const teamAPlusTierRating = membersToCommonPlusTierRating(teamA.members); - const teamBPlusTierRating = membersToCommonPlusTierRating(teamB.members); - - // and as tiebreaker teams with a higher plus server tier member first (4 best considered) - if (teamAPlusTierRating > teamBPlusTierRating) { - return 1; - } - - if (teamAPlusTierRating < teamBPlusTierRating) { - return -1; - } - - return 0; - }); - - return { - teams, - teamMemberOfCount: user - ? teams.filter((team) => team.members.some((m) => m.id === user.id)) - .length - : 0, - }; -}; - -const membersToCommonPlusTierRating = ( - members: Pick[], -) => { - return R.sum( - members - .map((m) => m.plusTier ?? 100) - .sort((a, b) => a - b) - .slice(0, 4), - ); -}; diff --git a/app/features/team/routes/t.$customUrl.edit.test.ts b/app/features/team/routes/t.$customUrl.edit.test.ts index 70bd7cfbc..2e7feced9 100644 --- a/app/features/team/routes/t.$customUrl.edit.test.ts +++ b/app/features/team/routes/t.$customUrl.edit.test.ts @@ -5,7 +5,7 @@ import { dbReset, wrappedAction, } from "~/utils/Test"; -import { action as teamIndexPageAction } from "../actions/t.server"; +import { action as teamIndexPageAction } from "../actions/t.new.server"; import { action as _editTeamAction } from "../routes/t.$customUrl.edit"; import type { createTeamSchema } from "../team-schemas"; import type { editTeamSchema } from "../team-schemas.server"; diff --git a/app/features/team/routes/t.$customUrl.test.ts b/app/features/team/routes/t.$customUrl.test.ts index f63611dbd..38d0cdcbe 100644 --- a/app/features/team/routes/t.$customUrl.test.ts +++ b/app/features/team/routes/t.$customUrl.test.ts @@ -11,7 +11,7 @@ import { } from "~/utils/Test"; import { loader as userProfileLoader } from "../../user-page/loaders/u.$identifier.index.server"; import { action as _teamPageAction } from "../actions/t.$customUrl.index.server"; -import { action as teamIndexPageAction } from "../actions/t.server"; +import { action as teamIndexPageAction } from "../actions/t.new.server"; import { action as _editTeamAction } from "../routes/t.$customUrl.edit"; import * as TeamRepository from "../TeamRepository.server"; import type { createTeamSchema } from "../team-schemas"; diff --git a/app/features/team/routes/t.new.tsx b/app/features/team/routes/t.new.tsx new file mode 100644 index 000000000..d513cc8c3 --- /dev/null +++ b/app/features/team/routes/t.new.tsx @@ -0,0 +1,60 @@ +import { useTranslation } from "react-i18next"; +import type { MetaFunction } from "react-router"; +import { useLoaderData } from "react-router"; +import { Alert } from "~/components/Alert"; +import { Main } from "~/components/Main"; +import { SendouForm } from "~/form"; +import { useHasRole } from "~/modules/permissions/hooks"; +import { metaTags } from "~/utils/remix"; +import type { SendouRouteHandle } from "~/utils/remix.server"; +import { NEW_TEAM_PAGE, navIconUrl } from "~/utils/urls"; +import { action } from "../actions/t.new.server"; +import { loader } from "../loaders/t.new.server"; +import { TEAM } from "../team-constants"; +import { createTeamSchema } from "../team-schemas"; +export { loader, action }; + +export const meta: MetaFunction = (args) => { + return metaTags({ + title: "New team", + location: args.location, + }); +}; + +export const handle: SendouRouteHandle = { + i18n: ["team", "forms"], + breadcrumb: () => ({ + imgPath: navIconUrl("t"), + href: NEW_TEAM_PAGE, + type: "IMAGE", + }), +}; + +export default function NewTeamPage() { + const { t } = useTranslation(["team"]); + const data = useLoaderData(); + const isSupporter = useHasRole("SUPPORTER"); + + const canAddNewTeam = () => { + if (isSupporter) { + return data.teamMemberOfCount < TEAM.MAX_TEAM_COUNT_PATRON; + } + + return data.teamMemberOfCount < TEAM.MAX_TEAM_COUNT_NON_PATRON; + }; + + return ( +
+ {!canAddNewTeam() ? ( + + You can't add another team (max 2 for non-supporters and 5 for + supporters). + + ) : ( + + {({ FormField }) => } + + )} +
+ ); +} diff --git a/app/features/team/routes/t.tsx b/app/features/team/routes/t.tsx index 0abdd4569..f00d98522 100644 --- a/app/features/team/routes/t.tsx +++ b/app/features/team/routes/t.tsx @@ -1,201 +1,5 @@ -import { Search } from "lucide-react"; -import * as React from "react"; -import { useTranslation } from "react-i18next"; -import type { MetaFunction } from "react-router"; -import { Link, useLoaderData, useSearchParams } from "react-router"; -import { AddNewButton } from "~/components/AddNewButton"; -import { Alert } from "~/components/Alert"; -import { SendouDialog } from "~/components/elements/Dialog"; -import { Input } from "~/components/Input"; -import { Main } from "~/components/Main"; -import { Pagination } from "~/components/Pagination"; -import { useUser } from "~/features/auth/core/user"; -import { SendouForm } from "~/form"; -import { usePagination } from "~/hooks/usePagination"; -import { useHasRole } from "~/modules/permissions/hooks"; -import { metaTags } from "~/utils/remix"; -import type { SendouRouteHandle } from "~/utils/remix.server"; -import { - NEW_TEAM_PAGE, - navIconUrl, - TEAM_SEARCH_PAGE, - teamPage, -} from "~/utils/urls"; -import { action } from "../actions/t.server"; -import { loader } from "../loaders/t.server"; -import { TEAM, TEAMS_PER_PAGE } from "../team-constants"; -import { createTeamSchema } from "../team-schemas"; -export { loader, action }; +import { redirect } from "react-router"; -import styles from "../team.module.css"; - -export const meta: MetaFunction = (args) => { - return metaTags({ - title: "Team Search", - ogTitle: "Splatoon team search", - description: - "List of all teams on sendou.ink and their members. Search for teams by name or member name.", - location: args.location, - }); +export const loader = () => { + return redirect("/?search=open&type=teams"); }; - -export const handle: SendouRouteHandle = { - i18n: ["team", "forms"], - breadcrumb: () => ({ - imgPath: navIconUrl("t"), - href: TEAM_SEARCH_PAGE, - type: "IMAGE", - }), -}; - -export default function TeamSearchPage() { - const { t, i18n } = useTranslation(["team"]); - const [inputValue, setInputValue] = React.useState(""); - const data = useLoaderData(); - - const filteredTeams = () => { - if (!inputValue) return data.teams; - - const lowerCaseInput = inputValue.toLowerCase(); - const matchingTeams = data.teams.filter((team) => { - if (team.name.toLowerCase().includes(lowerCaseInput)) return true; - if (team.tag && team.tag.toLowerCase() === lowerCaseInput) return true; - if ( - team.members.some((m) => - m.username.toLowerCase().includes(lowerCaseInput), - ) - ) { - return true; - } - - return false; - }); - - return matchingTeams.sort((a, b) => { - const aTagExactMatch = a.tag && a.tag.toLowerCase() === lowerCaseInput; - const bTagExactMatch = b.tag && b.tag.toLowerCase() === lowerCaseInput; - - if (aTagExactMatch && !bTagExactMatch) return -1; - if (!aTagExactMatch && bTagExactMatch) return 1; - return 0; - }); - }; - - const { - itemsToDisplay, - everythingVisible, - currentPage, - pagesCount, - nextPage, - previousPage, - setPage, - } = usePagination({ - items: filteredTeams(), - pageSize: TEAMS_PER_PAGE, - }); - - return ( -
- -
- } - value={inputValue} - onChange={(e) => setInputValue(e.target.value)} - placeholder={t("team:teamSearch.placeholder")} - testId="team-search-input" - /> - -
-
- {itemsToDisplay.map((team, i) => ( - - {team.avatarUrl ? ( - - ) : ( -
- {team.name[0]} -
- )} -
-
- {team.name} - {team.tag ? ( - {team.tag} - ) : null} -
-
- {team.members.length === 1 - ? team.members[0].username - : new Intl.ListFormat(i18n.language, { - style: "short", - }).format(team.members.map((member) => member.username))} -
-
- - ))} -
- {!everythingVisible ? ( - - ) : null} -
- ); -} - -function NewTeamDialog() { - const { t } = useTranslation(["common", "team"]); - const [searchParams] = useSearchParams(); - const user = useUser(); - const isSupporter = useHasRole("SUPPORTER"); - const data = useLoaderData(); - - const isOpen = searchParams.get("new") === "true"; - - const canAddNewTeam = () => { - if (!user) return false; - if (isSupporter) { - return data.teamMemberOfCount < TEAM.MAX_TEAM_COUNT_PATRON; - } - - return data.teamMemberOfCount < TEAM.MAX_TEAM_COUNT_NON_PATRON; - }; - - if (isOpen && !canAddNewTeam()) { - return ( - - You can't add another team (max 2 for non-supporters and 5 for - supporters). - - ); - } - - return ( - - - {({ FormField }) => } - - - ); -} diff --git a/app/routes.ts b/app/routes.ts index 0af5c00f2..fe07d4c5a 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -147,6 +147,7 @@ export default [ route("/support", "features/info/routes/support.tsx"), route("/t", "features/team/routes/t.tsx"), + route("/t/new", "features/team/routes/t.new.tsx"), route("/t/:customUrl", "features/team/routes/t.$customUrl.tsx", [ index("features/team/routes/t.$customUrl.index.tsx"), route("edit", "features/team/routes/t.$customUrl.edit.tsx"), diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 57ef22e11..1441a11b9 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -90,7 +90,7 @@ export const CONTRIBUTIONS_PAGE = "/contributions"; export const BADGES_PAGE = "/badges"; export const BUILDS_PAGE = "/builds"; export const TEAM_SEARCH_PAGE = "/t"; -export const NEW_TEAM_PAGE = "/t?new=true"; +export const NEW_TEAM_PAGE = "/t/new"; export const CALENDAR_PAGE = "/calendar"; export const CALENDAR_NEW_PAGE = "/calendar/new"; export const TOURNAMENT_NEW_PAGE = "/calendar/new?tournament=true";