mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-27 05:36:28 -05:00
Refactor Seasons module
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { addPendingPlusTiers } from "~/features/leaderboards/core/leaderboards.server";
|
||||
import { userSPLeaderboard } from "~/features/leaderboards/queries/userSPLeaderboard.server";
|
||||
import { currentSeason, previousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
|
||||
import { seasonToVotingRange } from "~/features/plus-voting/core/voting-time";
|
||||
import invariant from "~/utils/invariant";
|
||||
@@ -26,10 +26,10 @@ function fromLeaderboard(
|
||||
newMembersFromVoting: Array<{ userId: number; plusTier: number }>,
|
||||
) {
|
||||
const now = new Date();
|
||||
const lastCompletedSeason = previousSeason(now);
|
||||
const lastCompletedSeason = Seasons.previous();
|
||||
invariant(lastCompletedSeason, "No previous season found");
|
||||
|
||||
const currSeason = currentSeason(now);
|
||||
const currSeason = Seasons.current();
|
||||
if (currSeason) {
|
||||
const range = seasonToVotingRange(currSeason);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Main } from "~/components/Main";
|
||||
import { SendouSwitch } from "~/components/elements/Switch";
|
||||
import { UsersIcon } from "~/components/icons/Users";
|
||||
import type { CalendarEventTag } from "~/db/tables";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { joinListToNaturalString } from "~/utils/arrays";
|
||||
@@ -431,7 +431,7 @@ function EventsList({
|
||||
);
|
||||
const tournamentRankedStatus = () => {
|
||||
if (!calendarEvent.tournamentSettings) return undefined;
|
||||
if (!currentSeason(startTimeDate)) return undefined;
|
||||
if (!Seasons.current(startTimeDate)) return undefined;
|
||||
|
||||
return calendarEvent.tournamentSettings.isRanked &&
|
||||
(!calendarEvent.tournamentSettings.minMembersPerTeam ||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getUserId } from "~/features/auth/core/user.server";
|
||||
import * as Changelog from "~/features/front-page/core/Changelog.server";
|
||||
import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
|
||||
import { cachedFullUserLeaderboard } from "~/features/leaderboards/core/leaderboards.server";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { cache, ttl } from "~/utils/cache.server";
|
||||
import {
|
||||
discordAvatarUrl,
|
||||
@@ -59,7 +59,7 @@ function cachedLeaderboards(): Promise<{
|
||||
ttl: ttl(ONE_HOUR_IN_MS),
|
||||
staleWhileRevalidate: ttl(TWO_HOURS_IN_MS),
|
||||
async getFreshValue() {
|
||||
const season = currentOrPreviousSeason(new Date())?.nth ?? 1;
|
||||
const season = Seasons.currentOrPrevious()?.nth ?? 1;
|
||||
|
||||
const [team, user] = await Promise.all([
|
||||
LeaderboardRepository.teamLeaderboardBySeason({
|
||||
|
||||
@@ -21,11 +21,7 @@ import { UsersIcon } from "~/components/icons/Users";
|
||||
import { navItems } from "~/components/layout/nav-items";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import type * as Changelog from "~/features/front-page/core/Changelog.server";
|
||||
import {
|
||||
currentOrPreviousSeason,
|
||||
nextSeason,
|
||||
previousSeason,
|
||||
} from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
@@ -109,8 +105,8 @@ function DesktopSideNav() {
|
||||
|
||||
function SeasonBanner() {
|
||||
const { t, i18n } = useTranslation(["front"]);
|
||||
const season = nextSeason(new Date()) ?? currentOrPreviousSeason(new Date())!;
|
||||
const _previousSeason = previousSeason(new Date());
|
||||
const season = Seasons.next(new Date()) ?? Seasons.currentOrPrevious()!;
|
||||
const _previousSeason = Seasons.previous();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const isInFuture = new Date() < season.starts;
|
||||
@@ -422,7 +418,7 @@ function ResultHighlights() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const season = currentOrPreviousSeason(new Date())!;
|
||||
const season = Seasons.currentOrPrevious()!;
|
||||
|
||||
const recentResults = (
|
||||
<>
|
||||
|
||||
@@ -6,8 +6,8 @@ import { db } from "~/db/sql";
|
||||
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
||||
import { dateToDatabaseTimestamp } from "../../utils/dates";
|
||||
import invariant from "../../utils/invariant";
|
||||
import * as Seasons from "../mmr/core/Seasons";
|
||||
import { ordinalToSp } from "../mmr/mmr-utils";
|
||||
import { seasonObject } from "../mmr/season";
|
||||
import {
|
||||
DEFAULT_LEADERBOARD_MAX_SIZE,
|
||||
IGNORED_TEAMS,
|
||||
@@ -129,7 +129,7 @@ async function filterOutNonSqPlayers(args: {
|
||||
}
|
||||
|
||||
async function userIdsWithEnoughSqMatchesForTeamLeaderboard(seasonNth: number) {
|
||||
const season = seasonObject(seasonNth);
|
||||
const season = Seasons.nthToDateRange(seasonNth);
|
||||
invariant(season, "Season not found in sqMatchCountByUserId");
|
||||
|
||||
const userIds = await db
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cachified } from "@epic-web/cachified";
|
||||
import { HALF_HOUR_IN_MS } from "~/constants";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { USER_LEADERBOARD_MIN_ENTRIES_FOR_LEVIATHAN } from "~/features/mmr/mmr-constants";
|
||||
import { spToOrdinal } from "~/features/mmr/mmr-utils";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import { freshUserSkills, userSkills } from "~/features/mmr/tiered.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
@@ -29,7 +29,7 @@ export async function cachedFullUserLeaderboard(season: number) {
|
||||
const withTiers = addTiers(leaderboard, season);
|
||||
|
||||
const shouldAddPendingPlusTier =
|
||||
season === currentSeason(new Date())?.nth &&
|
||||
season === Seasons.current()?.nth &&
|
||||
leaderboard.length >= USER_LEADERBOARD_MIN_ENTRIES_FOR_LEVIATHAN;
|
||||
const withPendingPlusTiers = shouldAddPendingPlusTier
|
||||
? addPendingPlusTiers(
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { HALF_HOUR_IN_MS } from "~/constants";
|
||||
import { getUser } from "~/features/auth/core/user.server";
|
||||
import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
|
||||
import { allSeasons, currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
RankedModeShort,
|
||||
@@ -41,9 +41,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
LEADERBOARD_TYPES.find((type) => type === unvalidatedType) ??
|
||||
LEADERBOARD_TYPES[0];
|
||||
const season =
|
||||
allSeasons(new Date()).find(
|
||||
Seasons.allStarted().find(
|
||||
(s) => unvalidatedSeason && s === Number(unvalidatedSeason),
|
||||
) ?? currentOrPreviousSeason(new Date())!.nth;
|
||||
) ?? Seasons.currentOrPrevious()!.nth;
|
||||
|
||||
const fullUserLeaderboard = type.includes("USER")
|
||||
? await cachedFullUserLeaderboard(season)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { seasonObject } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "../leaderboards-constants";
|
||||
@@ -34,7 +34,7 @@ export type SeasonPopularUsersWeapon = Record<
|
||||
export function seasonPopularUsersWeapon(
|
||||
season: number,
|
||||
): SeasonPopularUsersWeapon {
|
||||
const { starts, ends } = seasonObject(season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
const rows = stm.all({
|
||||
season,
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useTranslation } from "react-i18next";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import { TierImage, WeaponImage } from "~/components/Image";
|
||||
import { Main } from "~/components/Main";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { ordinalToSp } from "~/features/mmr/mmr-utils";
|
||||
import { allSeasons, currentSeason } from "~/features/mmr/season";
|
||||
import type { SkillTierInterval } from "~/features/mmr/tiered.server";
|
||||
import { weaponCategories } from "~/modules/in-game-lists";
|
||||
import { rankedModesShort } from "~/modules/in-game-lists/modes";
|
||||
@@ -115,7 +115,7 @@ export default function LeaderboardsPage() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
{allSeasons(new Date()).map((season) => {
|
||||
{Seasons.allStarted().map((season) => {
|
||||
return (
|
||||
<optgroup label={`SP - Season ${season}`} key={season}>
|
||||
{LEADERBOARD_TYPES.filter((type) => !type.includes("XP")).map(
|
||||
@@ -222,7 +222,7 @@ export default function LeaderboardsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!data.xpLeaderboard && data.season === currentSeason(new Date())?.nth ? (
|
||||
{!data.xpLeaderboard && data.season === Seasons.current()?.nth ? (
|
||||
<div className="text-xs text-lighter text-center">
|
||||
{t("common:leaderboard.updateInfo")}
|
||||
</div>
|
||||
@@ -359,7 +359,7 @@ function TeamTable({
|
||||
}) {
|
||||
const { t } = useTranslation(["common"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const isCurrentSeason = data.season === currentSeason(new Date())?.nth;
|
||||
const isCurrentSeason = data.season === Seasons.current()?.nth;
|
||||
const showQualificationDividers =
|
||||
_showQualificationDividers && isCurrentSeason && entries.length > 20;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Image, TierImage, WeaponImage } from "~/components/Image";
|
||||
import { EditIcon } from "~/components/icons/Edit";
|
||||
import { TrashIcon } from "~/components/icons/Trash";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { TieredSkill } from "~/features/mmr/tiered.server";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useHasRole } from "~/modules/permissions/hooks";
|
||||
@@ -336,7 +336,7 @@ function PostTimezonePillPlaceholder() {
|
||||
return <div className={clsx(styles.pill, styles.pillPlaceholder)} />;
|
||||
}
|
||||
|
||||
const currentSeasonNth = currentOrPreviousSeason(new Date())!.nth;
|
||||
const currentSeasonNth = Seasons.currentOrPrevious()!.nth;
|
||||
|
||||
function PostSkillPills({
|
||||
tiers,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { getUser } from "~/features/auth/core/user.server";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { TieredSkill } from "~/features/mmr/tiered.server";
|
||||
import { userSkills } from "~/features/mmr/tiered.server";
|
||||
import type { Unpacked } from "~/utils/types";
|
||||
@@ -19,7 +19,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
function postsUsersTiersMap(
|
||||
posts: Unpacked<ReturnType<typeof LFGRepository.posts>>,
|
||||
) {
|
||||
const latestSeason = currentOrPreviousSeason(new Date())!.nth;
|
||||
const latestSeason = Seasons.currentOrPrevious()!.nth;
|
||||
const previousSeason = latestSeason - 1;
|
||||
|
||||
const latestSeasonSkills = userSkills(latestSeason).userSkills;
|
||||
|
||||
160
app/features/mmr/core/Seasons.ts
Normal file
160
app/features/mmr/core/Seasons.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* List of seasons with their respective start and end dates.
|
||||
*
|
||||
* Each season is represented as an object with the following properties:
|
||||
* - `nth`: The sequential number of the season (starting from 0).
|
||||
* - `starts`: The start date of the season as a `Date` object.
|
||||
* - `ends`: The end date of the season as a `Date` object.
|
||||
*
|
||||
* Note: The value is conditionally set based on the environment. In development mode,
|
||||
* the end date of the first season is set to a later date for testing purposes (ensures a season is always open).
|
||||
*
|
||||
* @example
|
||||
* console.log(Seasons.list[0].starts); // Logs the start date of the first season
|
||||
*/
|
||||
export const list =
|
||||
process.env.NODE_ENV === "development" &&
|
||||
import.meta.env.VITE_PROD_MODE !== "true"
|
||||
? ([
|
||||
{
|
||||
nth: 0,
|
||||
starts: new Date("2023-08-14T17:00:00.000Z"),
|
||||
ends: new Date("2023-08-27T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 1,
|
||||
starts: new Date("2023-09-11T17:00:00.000Z"),
|
||||
ends: new Date("2030-11-17T20:59:59.999Z"),
|
||||
},
|
||||
] as const)
|
||||
: ([
|
||||
{
|
||||
nth: 0,
|
||||
starts: new Date("2023-08-14T17:00:00.000Z"),
|
||||
ends: new Date("2023-08-27T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 1,
|
||||
starts: new Date("2023-09-11T17:00:00.000Z"),
|
||||
ends: new Date("2023-11-19T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 2,
|
||||
starts: new Date("2023-12-04T17:00:00.000Z"),
|
||||
ends: new Date("2024-02-18T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 3,
|
||||
starts: new Date("2024-03-04T17:00:00.000Z"),
|
||||
ends: new Date("2024-05-19T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 4,
|
||||
starts: new Date("2024-06-03T17:00:00.000Z"),
|
||||
ends: new Date("2024-08-18T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 5,
|
||||
starts: new Date("2024-09-02T17:00:00.000Z"),
|
||||
ends: new Date("2024-11-17T22:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 6,
|
||||
starts: new Date("2024-12-02T18:00:00.000Z"),
|
||||
ends: new Date("2025-02-16T21:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 7,
|
||||
starts: new Date("2025-03-07T18:00:00.000Z"),
|
||||
ends: new Date("2025-05-25T21:59:59.999Z"),
|
||||
},
|
||||
] as const);
|
||||
|
||||
/**
|
||||
* Represents an individual item from the `Seasons.list` array.
|
||||
*/
|
||||
export type ListItem = (typeof list)[number];
|
||||
|
||||
/**
|
||||
* Determines the current season relative to the provided date (defaults to now), or falls back to the previous season if no current season is found.
|
||||
*
|
||||
* @returns The current season if it exists; otherwise, the previous season.
|
||||
*/
|
||||
export function currentOrPrevious(date = new Date()): ListItem | null {
|
||||
const _currentSeason = current(date);
|
||||
if (_currentSeason) return _currentSeason;
|
||||
|
||||
return previous(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the previous season relative to the provided date (defaults to now).
|
||||
*
|
||||
* @returns The previous season if one exists.
|
||||
*/
|
||||
export function previous(date = new Date()): ListItem | null {
|
||||
let latestPreviousSeason: ListItem | null = null;
|
||||
for (const season of list) {
|
||||
if (date > season.ends) latestPreviousSeason = season;
|
||||
}
|
||||
|
||||
return latestPreviousSeason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current ongoing season relative to the provided date (defaults to now).
|
||||
*
|
||||
* @returns The current season if one exists.
|
||||
*/
|
||||
export function current(date = new Date()): ListItem | null {
|
||||
for (const season of list) {
|
||||
if (date >= season.starts && date <= season.ends) return season;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the next upcoming season relative to the provided date (defaults to now).
|
||||
*
|
||||
* @returns The next season if one exists.
|
||||
*/
|
||||
export function next(date = new Date()): ListItem | null {
|
||||
for (const season of list) {
|
||||
if (date < season.starts) return season;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the date range for a specific season based on its number.
|
||||
*
|
||||
* @returns An object containing the start and end dates of the specified season.
|
||||
* @throws {Error} If the season does not exist.
|
||||
*/
|
||||
export function nthToDateRange(nth: number) {
|
||||
const seasonObject = list.at(nth);
|
||||
if (!seasonObject) {
|
||||
throw new Error(`Season ${nth} not found`);
|
||||
}
|
||||
|
||||
return {
|
||||
starts: seasonObject.starts,
|
||||
ends: seasonObject.ends,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of season numbers that have started based on the provided date (defaults to now).
|
||||
*
|
||||
* @returns An array of season numbers in asceding order. If no seasons have started, returns an array containing only `[0]`.
|
||||
*/
|
||||
export function allStarted(date = new Date()) {
|
||||
const startedSeasons = list.filter((s) => date >= s.starts);
|
||||
if (startedSeasons.length > 0) {
|
||||
return startedSeasons.map((s) => s.nth).reverse();
|
||||
}
|
||||
|
||||
return [0];
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
export const SEASONS =
|
||||
process.env.NODE_ENV === "development" &&
|
||||
import.meta.env.VITE_PROD_MODE !== "true"
|
||||
? ([
|
||||
{
|
||||
nth: 0,
|
||||
starts: new Date("2023-08-14T17:00:00.000Z"),
|
||||
ends: new Date("2023-08-27T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 1,
|
||||
starts: new Date("2023-09-11T17:00:00.000Z"),
|
||||
ends: new Date("2030-11-17T20:59:59.999Z"),
|
||||
},
|
||||
] as const)
|
||||
: ([
|
||||
{
|
||||
nth: 0,
|
||||
starts: new Date("2023-08-14T17:00:00.000Z"),
|
||||
ends: new Date("2023-08-27T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 1,
|
||||
starts: new Date("2023-09-11T17:00:00.000Z"),
|
||||
ends: new Date("2023-11-19T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 2,
|
||||
starts: new Date("2023-12-04T17:00:00.000Z"),
|
||||
ends: new Date("2024-02-18T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 3,
|
||||
starts: new Date("2024-03-04T17:00:00.000Z"),
|
||||
ends: new Date("2024-05-19T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 4,
|
||||
starts: new Date("2024-06-03T17:00:00.000Z"),
|
||||
ends: new Date("2024-08-18T20:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 5,
|
||||
starts: new Date("2024-09-02T17:00:00.000Z"),
|
||||
ends: new Date("2024-11-17T22:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 6,
|
||||
starts: new Date("2024-12-02T18:00:00.000Z"),
|
||||
ends: new Date("2025-02-16T21:59:59.999Z"),
|
||||
},
|
||||
{
|
||||
nth: 7,
|
||||
starts: new Date("2025-03-07T18:00:00.000Z"),
|
||||
ends: new Date("2025-05-25T21:59:59.999Z"),
|
||||
},
|
||||
] as const);
|
||||
|
||||
export type RankingSeason = (typeof SEASONS)[number];
|
||||
|
||||
export function currentOrPreviousSeason(date: Date) {
|
||||
const _currentSeason = currentSeason(date);
|
||||
if (_currentSeason) return _currentSeason;
|
||||
|
||||
return previousSeason(date);
|
||||
}
|
||||
|
||||
export function previousSeason(date: Date) {
|
||||
let latestPreviousSeason: (typeof SEASONS)[number] | null = null;
|
||||
for (const season of SEASONS) {
|
||||
if (date > season.ends) latestPreviousSeason = season;
|
||||
}
|
||||
|
||||
return latestPreviousSeason;
|
||||
}
|
||||
|
||||
export function currentSeason(date: Date) {
|
||||
for (const season of SEASONS) {
|
||||
if (date >= season.starts && date <= season.ends) return season;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function nextSeason(date: Date) {
|
||||
for (const season of SEASONS) {
|
||||
if (date < season.starts) return season;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function seasonObject(nth: number) {
|
||||
return SEASONS[nth];
|
||||
}
|
||||
|
||||
export function allSeasons(date: Date) {
|
||||
const startedSeasons = SEASONS.filter((s) => date >= s.starts);
|
||||
if (startedSeasons.length > 0) {
|
||||
return startedSeasons.map((s) => s.nth).reverse();
|
||||
}
|
||||
|
||||
return [0];
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { Tables, UserWithPlusTier } from "~/db/tables";
|
||||
import type * as PlusSuggestionRepository from "~/features/plus-suggestions/PlusSuggestionRepository.server";
|
||||
import { isAdmin } from "~/modules/permissions/utils";
|
||||
import { allTruthy } from "~/utils/arrays";
|
||||
import { currentSeason, nextSeason } from "../mmr/season";
|
||||
import * as Seasons from "../mmr/core/Seasons";
|
||||
import { isVotingActive } from "../plus-voting/core";
|
||||
|
||||
interface CanAddCommentToSuggestionArgs {
|
||||
@@ -142,7 +142,7 @@ export function canSuggestNewUser({
|
||||
const votingActive =
|
||||
process.env.NODE_ENV === "test" ? false : isVotingActive();
|
||||
|
||||
const existsSeason = currentSeason(new Date()) || nextSeason(new Date());
|
||||
const existsSeason = Seasons.current() || Seasons.next();
|
||||
|
||||
return allTruthy([
|
||||
!votingActive,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type RankingSeason, SEASONS } from "~/features/mmr/season";
|
||||
import * as Seasons from "../../mmr/core/Seasons";
|
||||
import type { MonthYear } from "./types";
|
||||
|
||||
export function lastCompletedVoting(now: Date): MonthYear {
|
||||
let match: { startDate: Date; endDate: Date } | null = null;
|
||||
for (const season of SEASONS) {
|
||||
for (const season of Seasons.list) {
|
||||
const range = seasonToVotingRange(season);
|
||||
|
||||
if (now.getTime() > range.endDate.getTime()) {
|
||||
@@ -21,7 +21,7 @@ export function lastCompletedVoting(now: Date): MonthYear {
|
||||
}
|
||||
|
||||
export function nextNonCompletedVoting(now: Date) {
|
||||
for (const season of SEASONS) {
|
||||
for (const season of Seasons.list) {
|
||||
const range = seasonToVotingRange(season);
|
||||
|
||||
if (now.getTime() < range.endDate.getTime()) {
|
||||
@@ -39,7 +39,7 @@ export function rangeToMonthYear(range: { startDate: Date; endDate: Date }) {
|
||||
};
|
||||
}
|
||||
|
||||
export function seasonToVotingRange(season: RankingSeason) {
|
||||
export function seasonToVotingRange(season: Seasons.ListItem) {
|
||||
const { ends: date } = season;
|
||||
|
||||
if (date.getUTCDay() !== 0) {
|
||||
@@ -59,7 +59,7 @@ export function seasonToVotingRange(season: RankingSeason) {
|
||||
export function isVotingActive() {
|
||||
const now = new Date();
|
||||
|
||||
for (const season of SEASONS) {
|
||||
for (const season of Seasons.list) {
|
||||
const { startDate, endDate } = seasonToVotingRange(season);
|
||||
|
||||
if (
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ReportedWeapon } from "~/db/tables";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
|
||||
import type { ChatMessage } from "~/features/chat/chat-types";
|
||||
import { currentOrPreviousSeason, currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { refreshUserSkills } from "~/features/mmr/tiered.server";
|
||||
import * as QMatchRepository from "~/features/sendouq-match/QMatchRepository.server";
|
||||
import { refreshStreamsCache } from "~/features/sendouq-streams/core/streams.server";
|
||||
@@ -197,7 +197,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
// this is kind of useless to do when admin reports since skills don't change
|
||||
// but it's not the most common case so it's ok
|
||||
try {
|
||||
refreshUserSkills(currentOrPreviousSeason(new Date())!.nth);
|
||||
refreshUserSkills(Seasons.currentOrPrevious()!.nth);
|
||||
} catch (error) {
|
||||
logger.warn("Error refreshing user skills", error);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
break;
|
||||
}
|
||||
case "LOOK_AGAIN": {
|
||||
const season = currentSeason(new Date());
|
||||
const season = Seasons.current();
|
||||
errorToastIfFalsy(season, "Season is not active");
|
||||
|
||||
const previousGroup = await QMatchRepository.findGroupById({
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type DbMapPoolList,
|
||||
MapPool,
|
||||
} from "~/features/map-list-generator/core/map-pool";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { userSkills } from "~/features/mmr/tiered.server";
|
||||
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
|
||||
import { addSkillsToGroups } from "~/features/sendouq/core/groups.server";
|
||||
@@ -275,7 +275,7 @@ type CreateMatchMementoArgs = {
|
||||
export function createMatchMemento(
|
||||
args: CreateMatchMementoArgs,
|
||||
): Omit<ParsedMemento, "mapPreferences"> {
|
||||
const skills = userSkills(currentOrPreviousSeason(new Date())!.nth);
|
||||
const skills = userSkills(Seasons.currentOrPrevious()!.nth);
|
||||
const withTiers = addSkillsToGroups({
|
||||
groups: {
|
||||
neutral: [],
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
UserSkillDifference,
|
||||
} from "~/db/tables";
|
||||
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import {
|
||||
ordinalToSp,
|
||||
rate,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
queryCurrentUserRating,
|
||||
queryTeamPlayerRatingAverage,
|
||||
} from "~/features/mmr/mmr-utils.server";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { roundToNDecimalPlaces } from "~/utils/number";
|
||||
|
||||
@@ -56,7 +56,7 @@ export function calculateMatchSkills({
|
||||
> = [];
|
||||
const differences: MementoSkillDifferences = { users: {}, groups: {} };
|
||||
|
||||
const season = currentOrPreviousSeason(new Date())?.nth;
|
||||
const season = Seasons.currentOrPrevious()?.nth;
|
||||
invariant(typeof season === "number", "No ranked season for skills");
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { winnersArrayToWinner } from "../q-match-utils";
|
||||
import type { MatchById } from "../queries/findMatchById.server";
|
||||
@@ -13,7 +13,7 @@ export function summarizeMaps({
|
||||
winners: ("ALPHA" | "BRAVO")[];
|
||||
members: { id: number; groupId: number }[];
|
||||
}) {
|
||||
const season = currentOrPreviousSeason(new Date())?.nth;
|
||||
const season = Seasons.currentOrPrevious()?.nth;
|
||||
invariant(typeof season === "number", "No ranked season for skills");
|
||||
|
||||
const result: Array<Tables["MapResult"]> = [];
|
||||
@@ -63,7 +63,7 @@ export function summarizePlayerResults({
|
||||
winners: ("ALPHA" | "BRAVO")[];
|
||||
members: { id: number; groupId: number }[];
|
||||
}) {
|
||||
const season = currentOrPreviousSeason(new Date())?.nth;
|
||||
const season = Seasons.currentOrPrevious()?.nth;
|
||||
invariant(typeof season === "number", "No ranked season for skills");
|
||||
|
||||
const result: Array<Tables["PlayerResult"]> = [];
|
||||
|
||||
@@ -32,7 +32,7 @@ import { ScaleIcon } from "~/components/icons/Scale";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { Chat, type ChatProps, useChat } from "~/features/chat/components/Chat";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { AddPrivateNoteDialog } from "~/features/sendouq-match/components/AddPrivateNoteDialog";
|
||||
import type { ReportedWeaponForMerging } from "~/features/sendouq-match/core/reported-weapons.server";
|
||||
import { GroupCard } from "~/features/sendouq/components/GroupCard";
|
||||
@@ -355,7 +355,7 @@ function AfterMatchActions({
|
||||
const wasReportedInTheLastHour =
|
||||
databaseTimestampToDate(reportedAt).getTime() > Date.now() - 3600 * 1000;
|
||||
|
||||
const season = currentSeason(new Date());
|
||||
const season = Seasons.current();
|
||||
const showLookAgain = role === "OWNER" && wasReportedInTheLastHour && season;
|
||||
|
||||
const wasReportedInTheLastWeek =
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
type UserLeaderboardWithAdditionsItem,
|
||||
cachedFullUserLeaderboard,
|
||||
} from "~/features/leaderboards/core/leaderboards.server";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { TIERS } from "~/features/mmr/mmr-constants";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as QStreamsRepository from "~/features/sendouq-streams/QStreamsRepository.server";
|
||||
import { getStreams } from "~/modules/twitch";
|
||||
import type { MappedStream } from "~/modules/twitch/streams";
|
||||
@@ -13,7 +13,7 @@ import { cache, ttl } from "~/utils/cache.server";
|
||||
import { SENDOUQ_STREAMS_KEY } from "../q-streams-constants";
|
||||
|
||||
export function cachedStreams() {
|
||||
const season = currentOrPreviousSeason(new Date())!;
|
||||
const season = Seasons.currentOrPrevious()!;
|
||||
|
||||
return cachified({
|
||||
key: SENDOUQ_STREAMS_KEY,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ActionFunctionArgs } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { notify } from "~/features/notifications/core/notify.server";
|
||||
import * as QMatchRepository from "~/features/sendouq-match/QMatchRepository.server";
|
||||
import * as QRepository from "~/features/sendouq/QRepository.server";
|
||||
@@ -33,7 +33,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const season = currentSeason(new Date());
|
||||
const season = Seasons.current();
|
||||
errorToastIfFalsy(season, "Season is not active");
|
||||
|
||||
switch (data._action) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sql } from "~/db/sql";
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { refreshBannedCache } from "~/features/ban/core/banned.server";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import * as QRepository from "~/features/sendouq/QRepository.server";
|
||||
import { giveTrust } from "~/features/tournament/queries/giveTrust.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
@@ -130,7 +130,7 @@ async function validateCanJoinQ(user: { id: number; discordId: string }) {
|
||||
errorToastIfFalsy(friendCode, "No friend code");
|
||||
const canJoinQueue = userCanJoinQueueAt(user, friendCode) === "NOW";
|
||||
|
||||
errorToastIfFalsy(currentSeason(new Date()), "Season is not active");
|
||||
errorToastIfFalsy(Seasons.current(), "Season is not active");
|
||||
errorToastIfFalsy(!findCurrentGroupByUserId(user.id), "Already in a group");
|
||||
errorToastIfFalsy(canJoinQueue, "Can't join queue right now");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { getUser } from "~/features/auth/core/user.server";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { userSkills } from "~/features/mmr/tiered.server";
|
||||
import { cachedStreams } from "~/features/sendouq-streams/core/streams.server";
|
||||
import * as QRepository from "~/features/sendouq/QRepository.server";
|
||||
@@ -68,7 +68,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
likes: currentGroup ? findLikes(currentGroup.id) : [],
|
||||
});
|
||||
|
||||
const season = currentOrPreviousSeason(new Date());
|
||||
const season = Seasons.currentOrPrevious();
|
||||
|
||||
const {
|
||||
intervals,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { getUserId } from "~/features/auth/core/user.server";
|
||||
import { currentSeason, nextSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { JOIN_CODE_SEARCH_PARAM_KEY } from "../q-constants";
|
||||
import { groupRedirectLocationByCurrentLocation } from "../q-utils";
|
||||
@@ -26,9 +26,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
|
||||
const groupInvitedTo = code && user ? findGroupByInviteCode(code) : undefined;
|
||||
|
||||
const now = new Date();
|
||||
const season = currentSeason(now);
|
||||
const upcomingSeason = !season ? nextSeason(now) : undefined;
|
||||
const season = Seasons.current();
|
||||
const upcomingSeason = !season ? Seasons.next() : undefined;
|
||||
|
||||
return {
|
||||
season,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { userSkills } from "~/features/mmr/tiered.server";
|
||||
|
||||
export const loader = () => {
|
||||
const season = currentOrPreviousSeason(new Date());
|
||||
const season = Seasons.currentOrPrevious();
|
||||
const { intervals } = userSkills(season!.nth);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { add } from "date-fns";
|
||||
import { sql } from "~/db/sql";
|
||||
import type { ParsedMemento, Tables } from "~/db/tables";
|
||||
import { seasonObject } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { MATCHES_PER_SEASONS_PAGE } from "~/features/user-page/user-page-constants";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
@@ -129,7 +129,7 @@ export function seasonMatchesByUserId({
|
||||
season: number;
|
||||
page: number;
|
||||
}): SeasonMatchByUserId[] {
|
||||
const { starts, ends } = seasonObject(season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
const rows = stm.all({
|
||||
userId,
|
||||
@@ -193,7 +193,7 @@ export function seasonMatchesByUserIdPagesCount({
|
||||
userId: number;
|
||||
season: number;
|
||||
}): number {
|
||||
const { starts, ends } = seasonObject(season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
const row = pagesStm.get({
|
||||
userId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { seasonObject } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
|
||||
@@ -25,7 +25,7 @@ export function seasonReportedWeaponsByUserId({
|
||||
userId: number;
|
||||
season: number;
|
||||
}) {
|
||||
const { starts, ends } = seasonObject(season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
return stm.all({
|
||||
userId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { seasonObject } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import type { MainWeaponId, ModeShort, StageId } from "~/modules/in-game-lists";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
@@ -51,7 +51,7 @@ export function weaponUsageStats({
|
||||
stageId: StageId;
|
||||
season: number;
|
||||
}) {
|
||||
const { starts, ends } = seasonObject(season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(season);
|
||||
|
||||
const rows = stm.all({
|
||||
starts: dateToDatabaseTimestamp(starts),
|
||||
|
||||
@@ -16,7 +16,7 @@ import { UserIcon } from "~/components/icons/User";
|
||||
import { UsersIcon } from "~/components/icons/Users";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import type { RankingSeason } from "~/features/mmr/season";
|
||||
import type * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { useAutoRerender } from "~/hooks/useAutoRerender";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useHasRole } from "~/modules/permissions/hooks";
|
||||
@@ -300,7 +300,7 @@ function JoinTeamDialog({
|
||||
function ActiveSeasonInfo({
|
||||
season,
|
||||
}: {
|
||||
season: SerializeFrom<RankingSeason>;
|
||||
season: SerializeFrom<Seasons.ListItem>;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation(["q"]);
|
||||
const isMounted = useIsMounted();
|
||||
@@ -407,7 +407,7 @@ function QLink({
|
||||
function UpcomingSeasonInfo({
|
||||
season,
|
||||
}: {
|
||||
season: SerializeFrom<RankingSeason>;
|
||||
season: SerializeFrom<Seasons.ListItem>;
|
||||
}) {
|
||||
const { t } = useTranslation(["q"]);
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ActionFunction } from "@remix-run/node";
|
||||
import { sql } from "~/db/sql";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import {
|
||||
queryCurrentTeamRating,
|
||||
queryCurrentUserRating,
|
||||
queryCurrentUserSeedingRating,
|
||||
queryTeamPlayerRatingAverage,
|
||||
} from "~/features/mmr/mmr-utils.server";
|
||||
import { currentSeason } from "~/features/mmr/season";
|
||||
import { refreshUserSkills } from "~/features/mmr/tiered.server";
|
||||
import { notify } from "~/features/notifications/core/notify.server";
|
||||
import * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
@@ -239,7 +239,7 @@ export const action: ActionFunction = async ({ params, request }) => {
|
||||
const results = allMatchResultsByTournamentId(tournamentId);
|
||||
invariant(results.length > 0, "No results found");
|
||||
|
||||
const season = currentSeason(tournament.ctx.startTime)?.nth;
|
||||
const season = Seasons.current(tournament.ctx.startTime)?.nth;
|
||||
|
||||
const seedingSkillCountsFor = tournament.skillCountsFor;
|
||||
const summary = tournamentSummary({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { tournamentLogoUrl } from "~/utils/urls";
|
||||
import type { Tables, TournamentStageSettings } from "../../db/tables";
|
||||
import { assertUnreachable } from "../../utils/types";
|
||||
import { MapPool } from "../map-list-generator/core/map-pool";
|
||||
import { currentSeason } from "../mmr/season";
|
||||
import * as Seasons from "../mmr/core/Seasons";
|
||||
import { BANNED_MAPS } from "../sendouq-settings/banned-maps";
|
||||
import type { Tournament as TournamentClass } from "../tournament-bracket/core/Tournament";
|
||||
import type { TournamentData } from "../tournament-bracket/core/Tournament.server";
|
||||
@@ -264,7 +264,7 @@ export function tournamentIsRanked({
|
||||
}) {
|
||||
if (isTest) return false;
|
||||
|
||||
const seasonIsActive = Boolean(currentSeason(startTime));
|
||||
const seasonIsActive = Boolean(Seasons.current(startTime));
|
||||
if (!seasonIsActive) return false;
|
||||
|
||||
// 1v1, 2v2 and 3v3 are always considered "gimmicky"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { seasonAllMMRByUserId } from "~/features/mmr/queries/seasonAllMMRByUserId.server";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import { userSkills as _userSkills } from "~/features/mmr/tiered.server";
|
||||
import { seasonMapWinrateByUserId } from "~/features/sendouq/queries/seasonMapWinrateByUserId.server";
|
||||
import {
|
||||
@@ -26,7 +26,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
||||
const {
|
||||
info = "weapons",
|
||||
page = 1,
|
||||
season = currentOrPreviousSeason(new Date())!.nth,
|
||||
season = Seasons.currentOrPrevious()!.nth,
|
||||
} = parsedSearchParams.success ? parsedSearchParams.data : {};
|
||||
|
||||
const user = notFoundIfFalsy(
|
||||
|
||||
@@ -24,8 +24,8 @@ import { SendouPopover } from "~/components/elements/Popover";
|
||||
import { AlertIcon } from "~/components/icons/Alert";
|
||||
import { TopTenPlayer } from "~/features/leaderboards/components/TopTenPlayer";
|
||||
import { playerTopTenPlacement } from "~/features/leaderboards/leaderboards-utils";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { ordinalToSp } from "~/features/mmr/mmr-utils";
|
||||
import { allSeasons, seasonObject } from "~/features/mmr/season";
|
||||
import { useWeaponUsage } from "~/hooks/swr";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import {
|
||||
@@ -132,7 +132,7 @@ function SeasonHeader() {
|
||||
const { t, i18n } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const isMounted = useIsMounted();
|
||||
const { starts, ends } = seasonObject(data.season);
|
||||
const { starts, ends } = Seasons.nthToDateRange(data.season);
|
||||
|
||||
const isDifferentYears =
|
||||
new Date(starts).getFullYear() !== new Date(ends).getFullYear();
|
||||
@@ -140,7 +140,7 @@ function SeasonHeader() {
|
||||
return (
|
||||
<div>
|
||||
<div className="stack horizontal xs">
|
||||
{allSeasons(new Date()).map((s) => {
|
||||
{Seasons.allStarted().map((s) => {
|
||||
const isActive = s === data.season;
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
undefinedToNull,
|
||||
weaponSplId,
|
||||
} from "~/utils/zod";
|
||||
import { allSeasons } from "../mmr/season";
|
||||
import * as Seasons from "../mmr/core/Seasons";
|
||||
import {
|
||||
HIGHLIGHT_CHECKBOX_NAME,
|
||||
HIGHLIGHT_TOURNAMENT_CHECKBOX_NAME,
|
||||
@@ -32,7 +32,7 @@ export const seasonsSearchParamsSchema = z.object({
|
||||
season: z.coerce
|
||||
.number()
|
||||
.optional()
|
||||
.refine((nth) => !nth || allSeasons(new Date()).includes(nth)),
|
||||
.refine((nth) => !nth || Seasons.allStarted(new Date()).includes(nth)),
|
||||
});
|
||||
|
||||
export const userEditActionSchema = z
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currentSeason } from "../features/mmr/season";
|
||||
import * as Seasons from "../features/mmr/core/Seasons";
|
||||
import * as NotificationRepository from "../features/notifications/NotificationRepository.server";
|
||||
import { notify } from "../features/notifications/core/notify.server";
|
||||
import { isVotingActive } from "../features/plus-voting/core";
|
||||
@@ -10,7 +10,7 @@ export const NotifyPlusServerVotingRoutine = new Routine({
|
||||
func: async () => {
|
||||
if (!isVotingActive()) return;
|
||||
|
||||
const season = currentSeason(new Date())!;
|
||||
const season = Seasons.current()!;
|
||||
|
||||
const plusVotingNotifications = await NotificationRepository.findAllByType(
|
||||
"PLUS_VOTING_STARTED",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { add } from "date-fns";
|
||||
import { currentSeason } from "../features/mmr/season";
|
||||
import * as Seasons from "../features/mmr/core/Seasons";
|
||||
import { userSkills } from "../features/mmr/tiered.server";
|
||||
import * as NotificationRepository from "../features/notifications/NotificationRepository.server";
|
||||
import { notify } from "../features/notifications/core/notify.server";
|
||||
@@ -8,7 +8,7 @@ import { Routine } from "./routine.server";
|
||||
export const NotifySeasonStartRoutine = new Routine({
|
||||
name: "NotifySeasonStart",
|
||||
func: async () => {
|
||||
const season = currentSeason(new Date());
|
||||
const season = Seasons.current();
|
||||
|
||||
// old notifications get deleted after 14 days, make sure we don't send the same notification twice
|
||||
if (!season || add(season.starts, { days: 7 }) < new Date()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "dotenv/config";
|
||||
import { db } from "~/db/sql";
|
||||
import { currentSeason as _currentSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
@@ -10,7 +10,7 @@ const discordId = process.argv[2]?.trim();
|
||||
invariant(discordId, "discord id is required (argument 1)");
|
||||
|
||||
async function main() {
|
||||
const currentSeason = _currentSeason(new Date());
|
||||
const currentSeason = Seasons.current();
|
||||
if (!currentSeason) {
|
||||
logger.info("No current season found");
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "dotenv/config";
|
||||
import { sql } from "~/db/sql";
|
||||
import { currentOrPreviousSeason } from "~/features/mmr/season";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -8,7 +8,7 @@ const discordId = process.argv[2]?.trim();
|
||||
|
||||
invariant(discordId, "discord id is required (argument 1)");
|
||||
|
||||
const currentSeasonNth = currentOrPreviousSeason(new Date())?.nth;
|
||||
const currentSeasonNth = Seasons.currentOrPrevious()?.nth;
|
||||
|
||||
invariant(currentSeasonNth, "current season nth is required");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user