Enable rest of the Remix future flags
Some checks are pending
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run

This commit is contained in:
Kalle 2024-12-02 23:11:33 +02:00
parent b2b13e6aa7
commit 98edb4896f
60 changed files with 182 additions and 188 deletions

View File

@ -38,7 +38,7 @@ export function NavDialog({
to={`/${item.url}`}
className="layout__overlay-nav__nav-item"
key={item.name}
prefetch={item.prefetch ? "render" : undefined}
prefetch={item.prefetch ? "intent" : undefined}
onClick={close}
>
<div className="layout__overlay-nav__nav-image-container">

View File

@ -25,5 +25,5 @@ export const action: ActionFunction = async ({ request }) => {
await seed(variation);
return null;
return Response.json(null);
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import { z } from "zod";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
@ -35,7 +35,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
: null,
}));
return await cors(request, json(result));
return await cors(request, Response.json(result));
};
function fetchEventsOfWeek(args: { week: number; year: number }) {

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { cors } from "remix-utils/cors";
import { z } from "zod";
@ -73,5 +73,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
})),
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { cors } from "remix-utils/cors";
import { z } from "zod";
@ -155,5 +155,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
mapList: await mapList(),
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import { z } from "zod";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
@ -36,5 +36,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
})),
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import { z } from "zod";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
@ -46,5 +46,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
},
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import { cors } from "remix-utils/cors";
import { z } from "zod";
@ -143,5 +143,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
};
});
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { cors } from "remix-utils/cors";
import { z } from "zod";
@ -88,5 +88,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
isFinalized: Boolean(tournament.isFinalized),
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,4 +1,4 @@
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { cors } from "remix-utils/cors";
import { z } from "zod";
@ -119,5 +119,5 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
})),
};
return await cors(request, json(result));
return await cors(request, Response.json(result));
};

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
@ -16,6 +12,7 @@ import i18next from "~/modules/i18n/i18next.server";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { makeTitle } from "~/utils/strings";
import { artPage, navIconUrl } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { ArtGrid } from "../components/ArtGrid";
import { allArtTags } from "../queries/allArtTags.server";
import {

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import Markdown from "markdown-to-jsx";
import * as React from "react";
@ -17,6 +13,7 @@ import {
articlePreviewUrl,
navIconUrl,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { articleBySlug } from "../core/bySlug.server";
export const handle: SendouRouteHandle = {

View File

@ -1,4 +1,4 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { Outlet, useLoaderData, useMatches, useParams } from "@remix-run/react";
import clsx from "clsx";
import { useTranslation } from "react-i18next";
@ -8,6 +8,7 @@ import { Redirect } from "~/components/Redirect";
import { useUser } from "~/features/auth/core/user";
import { canEditBadgeOwners, isMod } from "~/permissions";
import { BADGES_PAGE } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import * as BadgeRepository from "../BadgeRepository.server";
import { badgeExplanationText } from "../badges-utils";
import type { BadgesLoaderData } from "./badges";

View File

@ -1,4 +1,3 @@
import type { SerializeFrom } from "@remix-run/node";
import { NavLink, Outlet, useLoaderData } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@ -10,6 +9,7 @@ import { SearchIcon } from "~/components/icons/Search";
import { useUser } from "~/features/auth/core/user";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { BADGES_DOC_LINK, BADGES_PAGE, navIconUrl } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import * as BadgeRepository from "../BadgeRepository.server";
import "~/styles/badges.css";

View File

@ -1,9 +1,5 @@
import { cachified } from "@epic-web/cachified";
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import { useTranslation } from "react-i18next";
@ -24,6 +20,7 @@ import {
outlinedMainWeaponImageUrl,
weaponBuildPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { popularBuilds } from "../build-stats-utils";
import { abilitiesByWeaponId } from "../queries/abilitiesByWeaponId.server";

View File

@ -1,9 +1,5 @@
import { cachified } from "@epic-web/cachified";
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Ability } from "~/components/Ability";
@ -24,6 +20,7 @@ import {
outlinedMainWeaponImageUrl,
weaponBuildPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { abilityPointCountsToAverages } from "../build-stats-utils";
import { averageAbilityPoints } from "../queries/averageAbilityPoints.server";

View File

@ -1,4 +1,4 @@
import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import {
type ShouldRevalidateFunction,
useLoaderData,
@ -24,7 +24,7 @@ import {
PATCHES,
} from "~/constants";
import { safeJSONParse } from "~/utils/json";
import { isRevalidation } from "~/utils/remix";
import { type SerializeFrom, isRevalidation } from "~/utils/remix";
import type { SendouRouteHandle } from "~/utils/remix.server";
import type { Unpacked } from "~/utils/types";
import {

View File

@ -1,9 +1,5 @@
import { redirect } from "@remix-run/node";
import type {
ActionFunction,
LoaderFunctionArgs,
SerializeFrom,
} from "@remix-run/node";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
@ -28,6 +24,7 @@ import {
import type { Unpacked } from "~/utils/types";
import { calendarEventPage } from "~/utils/urls";
import { actualNumber, id, safeJSONParse, toArray } from "~/utils/zod";
import type { SerializeFrom } from "../../../utils/remix";
const playersSchema = z
.array(

View File

@ -2,7 +2,6 @@ import type {
ActionFunction,
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
@ -55,6 +54,7 @@ import {
userPage,
} from "~/utils/urls";
import { actualNumber, id } from "~/utils/zod";
import type { SerializeFrom } from "../../../utils/remix";
import { Tags } from "../components/Tags";
import "~/styles/calendar-event.css";

View File

@ -1,4 +1,4 @@
import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import { Form, useFetcher, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import Compressor from "compressorjs";
@ -37,6 +37,7 @@ import invariant from "~/utils/invariant";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { pathnameFromPotentialURL } from "~/utils/strings";
import { CREATING_TOURNAMENT_DOC_LINK, userSubmittedImage } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import {
CALENDAR_EVENT,
REG_CLOSES_AT_OPTIONS,

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import clsx from "clsx";
import { addDays, addMonths, subDays, subMonths } from "date-fns";
@ -49,6 +45,7 @@ import type {
CalendarEventTag,
PersistedCalendarEventTag,
} from "../../../db/types";
import type { SerializeFrom } from "../../../utils/remix";
import * as CalendarRepository from "../CalendarRepository.server";
import { calendarEventTagSchema } from "../actions/calendar.new.server";
import { CALENDAR_EVENT } from "../calendar-constants";

View File

@ -1,5 +1,5 @@
import type { SerializeFrom } from "@remix-run/node";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import type { SerializeFrom } from "../../../utils/remix";
export const loader = async () => {
return {

View File

@ -75,7 +75,7 @@ function DesktopSideNav() {
<Link
to={`/${item.url}`}
key={item.name}
prefetch={item.prefetch ? "render" : undefined}
prefetch={item.prefetch ? "intent" : undefined}
className="front-page__side-nav-item"
>
<Image

View File

@ -1,11 +1,12 @@
import type { SerializeFrom } from "@remix-run/node";
import { json } from "@remix-run/node";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import type { Unwrapped } from "../../../utils/types";
export type PatronsListLoaderData = SerializeFrom<typeof loader>;
export type PatronsListLoaderData = {
patrons: Array<Unwrapped<typeof UserRepository.findAllPatrons>>;
};
export const loader = async () => {
return json(
return Response.json(
{
patrons: await UserRepository.findAllPatrons(),
},

View File

@ -39,7 +39,7 @@ export async function cachedFullUserLeaderboard(season: number) {
});
}
function addTiers(entries: UserSPLeaderboardItem[], season: number) {
function addTiers<T extends { id: number }>(entries: T[], season: number) {
const tiers = freshUserSkills(season);
const encounteredTiers = new Set<string>();
@ -140,7 +140,7 @@ export function ownEntryPeek({
userId,
season,
}: {
leaderboard: UserSPLeaderboardItem[];
leaderboard: UserLeaderboardWithAdditionsItem[];
userId: number;
season: number;
}) {

View File

@ -1,9 +1,5 @@
import { cachified } from "@epic-web/cachified";
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import React from "react";
import { useTranslation } from "react-i18next";
@ -40,6 +36,7 @@ import {
userSubmittedImage,
} from "~/utils/urls";
import { InfoPopover } from "../../../components/InfoPopover";
import type { SerializeFrom } from "../../../utils/remix";
import { TopTenPlayer } from "../components/TopTenPlayer";
import {
cachedFullUserLeaderboard,
@ -340,7 +337,7 @@ function OwnEntryPeek({
entry,
nextTier,
}: {
entry: NonNullable<SerializeFrom<typeof loader>["userLeaderboard"]>[number];
entry: NonNullable<SerializeFrom<typeof loader>["ownEntryPeek"]>["entry"];
nextTier?: SkillTierInterval;
}) {
const data = useLoaderData<typeof loader>();

View File

@ -1,4 +1,4 @@
import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import { useFetcher, useLoaderData } from "@remix-run/react";
import { add, sub } from "date-fns";
import React from "react";
@ -13,6 +13,7 @@ import type { SendouRouteHandle } from "~/utils/remix.server";
import { makeTitle } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import { LFG_PAGE, navIconUrl } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { LFGAddFilterButton } from "../components/LFGAddFilterButton";
import { LFGFilters } from "../components/LFGFilters";
import { LFGPost } from "../components/LFGPost";

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import * as React from "react";
@ -28,6 +24,7 @@ import {
ipLabsMaps,
navIconUrl,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { generateMapList } from "../core/map-list-generator/map-list";
import { modesOrder } from "../core/map-list-generator/modes";
import { mapPoolToNonEmptyModes } from "../core/map-list-generator/utils";

View File

@ -1,8 +1,4 @@
import type {
ActionFunction,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { ActionFunction, MetaFunction } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Link, Outlet, useLoaderData, useSearchParams } from "@remix-run/react";
import clsx from "clsx";
@ -41,6 +37,7 @@ import { makeTitle } from "~/utils/strings";
import { assertUnreachable } from "~/utils/types";
import { userPage } from "~/utils/urls";
import { _action, actualNumber } from "~/utils/zod";
import type { SerializeFrom } from "../../../utils/remix";
export const meta: MetaFunction = () => {
return [
@ -341,7 +338,7 @@ function SuggestedUser({
size="tiny"
variant="outlined"
to={`comment/${tier}/${suggestion.suggested.id}?tier=${tier}`}
prefetch="render"
prefetch="intent"
>
Comment
</LinkButton>

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import type { UserWithPlusTier } from "~/db/types";
@ -14,6 +10,7 @@ import { roundToNDecimalPlaces } from "~/utils/number";
import { makeTitle } from "~/utils/strings";
import { PLUS_SERVER_DISCORD_URL, userPage } from "~/utils/urls";
import { isAtLeastFiveDollarTierPatreon } from "~/utils/users";
import type { SerializeFrom } from "../../../utils/remix";
import "~/styles/plus-history.css";

View File

@ -1,4 +1,3 @@
import type { SerializeFrom } from "@remix-run/server-runtime";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { db } from "~/db/sql";
import type { UserMapModePreferences } from "~/db/tables";
@ -11,6 +10,7 @@ import {
wrappedLoader,
} from "~/utils/Test";
import invariant from "~/utils/invariant";
import type { SerializeFrom } from "../../../utils/remix";
import type { lookingSchema, matchSchema } from "../q-schemas.server";
import { loader, action as rawLookingAction } from "./q.looking";
import { action as rawMatchAction } from "./q.match.$id";

View File

@ -3,7 +3,6 @@ import type {
ActionFunctionArgs,
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { redirect } from "@remix-run/node";
import type { FetcherWithComponents } from "@remix-run/react";
@ -83,6 +82,7 @@ import {
teamPage,
userSubmittedImage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { GroupCard } from "../components/GroupCard";
import { matchEndedAtIndex } from "../core/match";
import { compareMatchToReportedScores } from "../core/match.server";

View File

@ -2,7 +2,6 @@ import type {
ActionFunction,
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { Link, useFetcher, useLoaderData } from "@remix-run/react";
@ -56,6 +55,7 @@ import {
userSeasonsPage,
} from "~/utils/urls";
import { isAtLeastFiveDollarTierPatreon } from "~/utils/users";
import type { SerializeFrom } from "../../../utils/remix";
import { FULL_GROUP_SIZE, JOIN_CODE_SEARCH_PARAM_KEY } from "../q-constants";
import { frontPageSchema } from "../q-schemas.server";
import {

View File

@ -1,6 +1,7 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { requireUserId } from "~/features/auth/core/user.server";
import * as QRepository from "~/features/sendouq/QRepository.server";
import type { SerializeFrom } from "../../../utils/remix";
export type TrustersLoaderData = SerializeFrom<typeof loader>;

View File

@ -1,5 +1,6 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { parseSearchParams } from "~/utils/remix.server";
import type { SerializeFrom } from "../../../utils/remix";
import { weaponUsageSearchParamsSchema } from "../q-schemas.server";
import { weaponUsageStats } from "../queries/weaponUsageStats.server";

View File

@ -2,7 +2,6 @@ import type {
ActionFunction,
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { Form, Link, useLoaderData } from "@remix-run/react";
@ -34,6 +33,7 @@ import {
teamPage,
uploadImagePage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import * as TeamRepository from "../TeamRepository.server";
import { TEAM } from "../team-constants";
import { editTeamSchema, teamParamsSchema } from "../team-schemas.server";

View File

@ -3,7 +3,6 @@ import type {
ActionFunction,
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { Form, useFetcher, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
@ -32,6 +31,7 @@ import {
navIconUrl,
teamPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import * as TeamRepository from "../TeamRepository.server";
import { editRole } from "../queries/editRole.server";
import { inviteCodeById } from "../queries/inviteCodeById.server";

View File

@ -1,4 +1,3 @@
import type { SerializeFrom } from "@remix-run/node";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { REGULAR_USER_TEST_ID } from "~/db/seed/constants";
import { db } from "~/db/sql";
@ -8,6 +7,7 @@ import {
wrappedAction,
wrappedLoader,
} from "~/utils/Test";
import type { SerializeFrom } from "../../../utils/remix";
import { loader as userProfileLoader } from "../../user-page/loaders/u.$identifier.index.server";
import * as TeamRepository from "../TeamRepository.server";
import { action as _teamPageAction } from "../actions/t.$customUrl.server";

View File

@ -1,4 +1,4 @@
import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import { Link, useFetcher, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import React from "react";
@ -31,6 +31,7 @@ import {
userPage,
userSubmittedImage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import type * as TeamRepository from "../TeamRepository.server";
import { isTeamMember, isTeamOwner } from "../team-utils";

View File

@ -1,7 +1,7 @@
import {
type ActionFunction,
type LoaderFunction,
json,
data,
redirect,
} from "@remix-run/node";
import { isTheme } from "../core/provider";
@ -14,21 +14,21 @@ export const action: ActionFunction = async ({ request }) => {
const theme = form.get("theme");
if (theme === "auto") {
return json(
return data(
{ success: true },
{ headers: { "Set-Cookie": await themeSession.destroy() } },
);
}
if (!isTheme(theme)) {
return json({
return data({
success: false,
message: `theme value of ${theme ?? "null"} is not a valid theme`,
});
}
themeSession.setTheme(theme);
return json(
return data(
{ success: true },
{ headers: { "Set-Cookie": await themeSession.commit() } },
);

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Main } from "~/components/Main";
@ -16,6 +12,7 @@ import {
topSearchPlayerPage,
userPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { PlacementsTable } from "../components/Placements";
import { findPlacementsByPlayerId } from "../queries/findPlacements.server";

View File

@ -1,4 +1,3 @@
import type { SerializeFrom } from "@remix-run/node";
import type { TFunction } from "i18next";
import * as React from "react";
import { useTranslation } from "react-i18next";
@ -7,6 +6,7 @@ import { Dialog } from "~/components/Dialog";
import { MapIcon } from "~/components/icons/Map";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { nullFilledArray } from "~/utils/arrays";
import type { SerializeFrom } from "../../../utils/remix";
import type { TournamentMatchLoaderData } from "../routes/to.$id.matches.$mid";
import { pickInfoText } from "../tournament-bracket-utils";

View File

@ -1,4 +1,3 @@
import type { SerializeFrom } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import type { TFunction } from "i18next";
@ -27,6 +26,7 @@ import {
specialWeaponImageUrl,
stageImageUrl,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import type { Bracket } from "../core/Bracket";
import * as PickBan from "../core/PickBan";
import type { TournamentDataTeam } from "../core/Tournament.server";

View File

@ -1,9 +1,9 @@
import type { SerializeFrom } from "@remix-run/node";
import clsx from "clsx";
import { LinkButton } from "~/components/Button";
import type { MonthYear } from "~/features/plus-voting/core";
import { useIsMounted } from "~/hooks/useIsMounted";
import { databaseTimestampToDate, nullPaddedDatesOfMonth } from "~/utils/dates";
import type { SerializeFrom } from "../../../utils/remix";
import type { loader } from "../loaders/org.$slug.server";
interface EventCalendarProps {

View File

@ -1,4 +1,4 @@
import type { MetaFunction, SerializeFrom } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
@ -24,6 +24,7 @@ import {
userPage,
userSubmittedImage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { EventCalendar } from "../components/EventCalendar";
import { SocialLinksList } from "../components/SocialLinksList";
import { TOURNAMENT_SERIES_EVENTS_PER_PAGE } from "../tournament-organization-constants";

View File

@ -1,8 +1,8 @@
import type { SerializeFrom } from "@remix-run/node";
import { Avatar } from "~/components/Avatar";
import { UserIcon } from "~/components/icons/User";
import { twitchThumbnailUrlToSrc } from "~/modules/twitch/utils";
import { twitchUrl } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { useTournament } from "../routes/to.$id";
import type { TournamentStreamsLoader } from "../routes/to.$id.streams";

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import {
Outlet,
type ShouldRevalidateFunction,
@ -29,6 +25,7 @@ import {
tournamentPage,
userSubmittedImage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { streamsByTournamentId } from "../core/streams.server";
import {
HACKY_resolvePicture,
@ -248,10 +245,10 @@ export function TournamentLayout() {
<SubNavLink to="register" data-testid="register-tab" prefetch="intent">
{tournament.hasStarted ? "Info" : t("tournament:tabs.register")}
</SubNavLink>
<SubNavLink to="brackets" data-testid="brackets-tab" prefetch="render">
<SubNavLink to="brackets" data-testid="brackets-tab" prefetch="intent">
{t("tournament:tabs.brackets")}
</SubNavLink>
<SubNavLink to="teams" end={false} prefetch="render">
<SubNavLink to="teams" end={false} prefetch="intent">
{t("tournament:tabs.teams", { count: tournament.ctx.teams.length })}
</SubNavLink>
{!tournament.everyBracketOver && tournament.subsFeatureEnabled && (

View File

@ -1,6 +1,7 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { notFoundIfFalsy } from "~/utils/remix.server";
import type { SerializeFrom } from "../../../utils/remix";
export type UserResultsLoaderData = SerializeFrom<typeof loader>;

View File

@ -1,4 +1,4 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import {
Link,
useLoaderData,
@ -55,6 +55,7 @@ import invariant from "~/utils/invariant";
import { cutToNDecimalPlaces, roundToNDecimalPlaces } from "~/utils/number";
import { type SendouRouteHandle, notFoundIfFalsy } from "~/utils/remix.server";
import { TIERS_PAGE, sendouQMatchPage, userSeasonsPage } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import {
seasonsSearchParamsSchema,
userParamsSchema,

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Outlet, useLoaderData, useLocation } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Main } from "~/components/Main";
@ -23,6 +19,7 @@ import {
userSeasonsPage,
userVodsPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import "~/styles/u.css";

View File

@ -1,4 +1,4 @@
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { Link, useLoaderData, useSearchParams } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
@ -15,6 +15,7 @@ import {
} from "~/utils/remix.server";
import { USER_SEARCH_PAGE, navIconUrl, userPage } from "~/utils/urls";
import { queryToUserIdentifier } from "~/utils/users";
import type { SerializeFrom } from "../../../utils/remix";
import "~/styles/u.css";

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
@ -30,6 +26,7 @@ import {
stageImageUrl,
vodVideoPage,
} from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { PovUser } from "../components/VodPov";
import { findVodById } from "../queries/findVodById.server";
import type { Vod } from "../vods-types";

View File

@ -1,8 +1,4 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
@ -14,6 +10,7 @@ import { mainWeaponIds, modesShort, stageIds } from "~/modules/in-game-lists";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { makeTitle } from "~/utils/strings";
import { VODS_PAGE, navIconUrl } from "~/utils/urls";
import type { SerializeFrom } from "../../../utils/remix";
import { VodListing } from "../components/VodListing";
import { findVods } from "../queries/findVods.server";
import { VODS_PAGE_BATCH_SIZE, videoMatchTypes } from "../vods-constants";

View File

@ -1,9 +1,5 @@
import type {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { data, redirect } from "@remix-run/node";
import {
Links,
Meta,
@ -37,9 +33,11 @@ import {
} from "./features/theme/core/provider";
import { getThemeSession } from "./features/theme/core/session.server";
import { useIsMounted } from "./hooks/useIsMounted";
import { useVisibilityChange } from "./hooks/useVisibilityChange";
import { DEFAULT_LANGUAGE } from "./modules/i18n/config";
import i18next, { i18nCookie } from "./modules/i18n/i18next.server";
import type { Namespace } from "./modules/i18n/resources.server";
import { type SerializeFrom, isRevalidation } from "./utils/remix";
import { COMMON_PREVIEW_IMAGE, SUSPENDED_PAGE } from "./utils/urls";
import "nprogress/nprogress.css";
@ -49,8 +47,6 @@ import "~/styles/layout.css";
import "~/styles/reset.css";
import "~/styles/utils.css";
import "~/styles/vars.css";
import { useVisibilityChange } from "./hooks/useVisibilityChange";
import { isRevalidation } from "./utils/remix";
export const shouldRevalidate: ShouldRevalidateFunction = (args) => {
if (isRevalidation(args)) return true;
@ -92,7 +88,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
return redirect(SUSPENDED_PAGE);
}
return json(
return data(
{
locale,
theme: themeSession.getTheme(),
@ -281,7 +277,7 @@ export default function App() {
// useLoaderData can't be used in CatchBoundary and layout is rendered in it as well
//
// Update 14.10.23: not sure if this still applies as the CatchBoundary is gone
const data = useLoaderData<RootLoaderData>();
const data = useLoaderData<typeof loader>();
return (
<ThemeProvider

View File

@ -83,10 +83,3 @@ export function isNotVisible(locator: Locator) {
export function modalClickConfirmButton(page: Page) {
return page.getByTestId("confirm-button").click();
}
export async function fetchSendouInk<T>(url: string) {
const res = await fetch(`http://localhost:5173${url}`);
if (!res.ok) throw new Error("Response not successful");
return res.json() as T;
}

View File

@ -1,4 +1,4 @@
import { json } from "@remix-run/node";
import { data } from "@remix-run/node";
import {
unstable_composeUploadHandlers as composeUploadHandlers,
unstable_createMemoryUploadHandler as createMemoryUploadHandler,
@ -255,8 +255,8 @@ export type SendouRouteHandle = {
* To be used when the response is different for each user. This is especially useful when the response
* is prefetched on link hover.
*/
export function privatelyCachedJson<T>(data: T) {
return json(data, {
export function privatelyCachedJson<T>(value: T) {
return data(value, {
headers: { "Cache-Control": "private, max-age=5" },
});
}

View File

@ -1,7 +1,13 @@
import type { ShouldRevalidateFunctionArgs } from "@remix-run/react";
import type {
ShouldRevalidateFunctionArgs,
useLoaderData,
} from "@remix-run/react";
export function isRevalidation(args: ShouldRevalidateFunctionArgs) {
return (
args.defaultShouldRevalidate && args.nextUrl.href === args.currentUrl.href
);
}
// https://remix.run/docs/en/main/start/future-flags#serializefrom
export type SerializeFrom<T> = ReturnType<typeof useLoaderData<T>>;

View File

@ -2,12 +2,10 @@ import { expect, test } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import type { TournamentLoaderData } from "~/features/tournament/routes/to.$id";
import type { StageId } from "~/modules/in-game-lists";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import invariant from "~/utils/invariant";
import {
fetchSendouInk,
impersonate,
isNotVisible,
navigate,
@ -16,35 +14,41 @@ import {
submit,
} from "~/utils/playwright";
import { tournamentBracketsPage, tournamentPage } from "~/utils/urls";
import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server";
const fetchTournamentLoaderData = () =>
fetchSendouInk<TournamentLoaderData>(
"/to/1/admin?_data=features%2Ftournament%2Froutes%2Fto.%24id",
);
tournamentFromDB({ tournamentId: 1, user: { id: ADMIN_ID } });
const getIsOwnerOfUser = ({
data,
teams,
userId,
teamId,
}: {
data: TournamentLoaderData;
teams: Array<{
id: number;
members: Array<{ userId: number; isOwner: number }>;
}>;
userId: number;
teamId: number;
}) => {
const team = data.tournament.ctx.teams.find((t) => t.id === teamId);
const team = teams.find((t) => t.id === teamId);
invariant(team, "Team not found");
return team.members.find((m) => m.userId === userId)?.isOwner;
};
const getTeamCheckedInAt = ({
data,
teams,
teamId,
}: {
data: TournamentLoaderData;
teams: Array<{
id: number;
checkIns: unknown[];
members: Array<{ userId: number }>;
}>;
teamId: number;
}) => {
const team = data.tournament.ctx.teams.find((t) => t.id === teamId);
const team = teams.find((t) => t.id === teamId);
invariant(team, "Team not found");
return team.checkIns.length > 0;
};
@ -128,43 +132,67 @@ test.describe("Tournament", () => {
await page.getByLabel("Team name").fill("NSTC");
await submit(page);
const data = await fetchTournamentLoaderData();
const firstTeam = data.tournament.ctx.teams.find((t) => t.id === 1);
const tournament = await fetchTournamentLoaderData();
const firstTeam = tournament.ctx.teams.find((t) => t.id === 1);
invariant(firstTeam, "First team not found");
expect(firstTeam.name).toBe("NSTC");
}
// Change team owner
let data = await fetchTournamentLoaderData();
expect(getIsOwnerOfUser({ data, userId: ADMIN_ID, teamId: 1 })).toBe(1);
let tournament = await fetchTournamentLoaderData();
expect(
getIsOwnerOfUser({
teams: tournament.ctx.teams,
userId: ADMIN_ID,
teamId: 1,
}),
).toBe(1);
await actionSelect.selectOption("CHANGE_TEAM_OWNER");
await teamSelect.selectOption("1");
await memberSelect.selectOption("2");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getIsOwnerOfUser({ data, userId: ADMIN_ID, teamId: 1 })).toBe(0);
expect(getIsOwnerOfUser({ data, userId: NZAP_TEST_ID, teamId: 1 })).toBe(1);
tournament = await fetchTournamentLoaderData();
expect(
getIsOwnerOfUser({
teams: tournament.ctx.teams,
userId: ADMIN_ID,
teamId: 1,
}),
).toBe(0);
expect(
getIsOwnerOfUser({
teams: tournament.ctx.teams,
userId: NZAP_TEST_ID,
teamId: 1,
}),
).toBe(1);
// Check in team
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeFalsy();
expect(
getTeamCheckedInAt({ teams: tournament.ctx.teams, teamId: 1 }),
).toBeFalsy();
await actionSelect.selectOption("CHECK_IN");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeTruthy();
tournament = await fetchTournamentLoaderData();
expect(
getTeamCheckedInAt({ teams: tournament.ctx.teams, teamId: 1 }),
).toBeTruthy();
// Check out team
await actionSelect.selectOption("CHECK_OUT");
await submit(page);
data = await fetchTournamentLoaderData();
expect(getTeamCheckedInAt({ data, teamId: 1 })).toBeFalsy();
tournament = await fetchTournamentLoaderData();
expect(
getTeamCheckedInAt({ teams: tournament.ctx.teams, teamId: 1 }),
).toBeFalsy();
// Remove member...
const firstTeam = data.tournament.ctx.teams.find((t) => t.id === 1);
const firstTeam = tournament.ctx.teams.find((t) => t.id === 1);
invariant(firstTeam, "First team not found");
const firstNonOwnerMember = firstTeam.members.find(
(m) => m.userId !== 1 && !m.isOwner,
@ -175,13 +203,13 @@ test.describe("Tournament", () => {
await memberSelect.selectOption(String(firstNonOwnerMember.userId));
await submit(page);
data = await fetchTournamentLoaderData();
const firstTeamAgain = data.tournament.ctx.teams.find((t) => t.id === 1);
tournament = await fetchTournamentLoaderData();
const firstTeamAgain = tournament.ctx.teams.find((t) => t.id === 1);
invariant(firstTeamAgain, "First team again not found");
expect(firstTeamAgain.members.length).toBe(firstTeam.members.length - 1);
// ...and add to another team
const teamWithSpace = data.tournament.ctx.teams.find(
const teamWithSpace = tournament.ctx.teams.find(
(t) => t.id !== 1 && t.members.length === 4,
);
invariant(teamWithSpace, "Team with space not found");
@ -195,8 +223,8 @@ test.describe("Tournament", () => {
});
await submit(page);
data = await fetchTournamentLoaderData();
const teamWithSpaceAgain = data.tournament.ctx.teams.find(
tournament = await fetchTournamentLoaderData();
const teamWithSpaceAgain = tournament.ctx.teams.find(
(t) => t.id === teamWithSpace.id,
);
invariant(teamWithSpaceAgain, "Team with space again not found");
@ -210,7 +238,7 @@ test.describe("Tournament", () => {
await teamSelect.selectOption("1");
await submit(page);
data = await fetchTournamentLoaderData();
expect(data.tournament.ctx.teams.find((t) => t.id === 1)).toBeFalsy();
tournament = await fetchTournamentLoaderData();
expect(tournament.ctx.teams.find((t) => t.id === 1)).toBeFalsy();
});
});

View File

@ -20,7 +20,7 @@
"biome:fix:unsafe": "npx @biomejs/biome check --write --unsafe .",
"typecheck": "tsc --noEmit",
"test:unit": "cross-env VITE_SITE_DOMAIN=http://localhost:5173 vitest run",
"test:e2e": "npx playwright test",
"test:e2e": "cross-env DB_PATH=db.sqlite3 npx playwright test",
"checks": "npm run biome:fix && npm run test:unit && npm run check-translation-jsons && npm run typecheck"
},
"dependencies": {

View File

@ -1,10 +1,13 @@
import { vitePlugin as remix } from "@remix-run/dev";
import { installGlobals } from "@remix-run/node";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { configDefaults } from "vitest/config";
installGlobals();
declare module "@remix-run/server-runtime" {
interface Future {
v3_singleFetch: true;
}
}
export default defineConfig(() => {
return {
@ -20,6 +23,8 @@ export default defineConfig(() => {
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
v3_routeConfig: true,
v3_singleFetch: true,
v3_lazyRouteDiscovery: true,
},
}),
tsconfigPaths(),