Refactor app/constants away

This commit is contained in:
Kalle 2025-05-31 13:54:34 +03:00
parent cc4b1037ba
commit 84cdd58fe8
75 changed files with 254 additions and 282 deletions

View File

@ -2,7 +2,7 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useDebounce } from "react-use";
import { CUSTOM_CSS_VAR_COLORS } from "~/constants";
import { CUSTOM_CSS_VAR_COLORS } from "~/features/user-page/user-page-constants";
import { Button } from "./Button";
import { InfoPopover } from "./InfoPopover";
import { Label } from "./Label";

View File

@ -1,146 +0,0 @@
import type { BuildAbilitiesTupleWithUnknown } from "./modules/in-game-lists/types";
export const TWEET_LENGTH_MAX_LENGTH = 280;
export const DISCORD_MESSAGE_MAX_LENGTH = 2000;
export const USER = {
BIO_MAX_LENGTH: DISCORD_MESSAGE_MAX_LENGTH,
CUSTOM_URL_MAX_LENGTH: 32,
CUSTOM_NAME_MAX_LENGTH: 32,
BATTLEFY_MAX_LENGTH: 32,
IN_GAME_NAME_TEXT_MAX_LENGTH: 20,
IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH: 5,
WEAPON_POOL_MAX_SIZE: 5,
COMMISSION_TEXT_MAX_LENGTH: 1000,
};
export const PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH = 500;
export const PlUS_SUGGESTION_COMMENT_MAX_LENGTH = TWEET_LENGTH_MAX_LENGTH;
export const CALENDAR_EVENT_RESULT = {
MAX_PARTICIPANTS_COUNT: 1000,
MAX_PLAYERS_LENGTH: 8,
MAX_TEAM_NAME_LENGTH: 100,
MAX_TEAM_PLACEMENT: 256,
MAX_PLAYER_NAME_LENGTH: 100,
} as const;
export const BUILD = {
TITLE_MIN_LENGTH: 1,
TITLE_MAX_LENGTH: 50,
DESCRIPTION_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH,
MAX_WEAPONS_COUNT: 5,
MAX_COUNT: 250,
} as const;
export const MAPS = {
CODE_MIN_LENGTH: 2,
CODE_MAX_LENGTH: 32,
};
export const BUILDS_PAGE_BATCH_SIZE = 24;
export const BUILDS_PAGE_MAX_BUILDS = 240;
export const INVITE_CODE_LENGTH = 10;
export const EMPTY_BUILD: BuildAbilitiesTupleWithUnknown = [
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
];
export const PLUS_TIERS = [1, 2, 3];
export const PLUS_UPVOTE = 1;
export const PLUS_DOWNVOTE = -1;
export const ADMIN_DISCORD_ID = "79237403620945920";
export const ADMIN_ID = process.env.NODE_ENV === "test" ? 1 : 274;
// Panda Scep
export const STAFF_IDS = [11329, 9719];
export const STAFF_DISCORD_IDS = ["138757634500067328", "184478601171828737"];
export const LOHI_TOKEN_HEADER_NAME = "Lohi-Token";
export const SKALOP_TOKEN_HEADER_NAME = "Skalop-Token";
export const CUSTOMIZED_CSS_VARS_NAME = "css";
export const MAX_AP = 57;
export const TEN_MINUTES_IN_MS = 10 * 60 * 1000;
export const HALF_HOUR_IN_MS = 30 * 60 * 1000;
export const ONE_HOUR_IN_MS = 60 * 60 * 1000;
export const TWO_HOURS_IN_MS = 2 * 60 * 60 * 1000;
export const SPLATOON_3_XP_BADGE_VALUES = [
5000, 4500, 4000, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
] as const;
export const findSplatoon3XpBadgeValue = (xPower: number) => {
for (const value of SPLATOON_3_XP_BADGE_VALUES) {
if (xPower >= value) {
return value;
}
}
return null;
};
export const PATCHES = [
{
patch: "9.3.0",
date: "2025-03-13",
},
{
patch: "9.2.0",
date: "2024-11-20",
},
{
patch: "9.0.0",
date: "2024-08-29",
},
// {
// patch: "8.1.0",
// date: "2024-07-17",
// },
// {
// patch: "8.0.0",
// date: "2024-05-31",
// },
// {
// patch: "7.2.0",
// date: "2024-04-17",
// },
// {
// patch: "7.0.0",
// date: "2024-02-21",
// },
// {
// patch: "6.1.0",
// date: "2024-01-24",
// },
// {
// patch: "6.0.0",
// date: "2023-11-29",
// },
// {
// patch: "5.1.0",
// date: "2023-10-17",
// },
// {
// patch: "5.0.0",
// date: "2023-08-30",
// },
];
export const CUSTOM_CSS_VAR_COLORS = [
"bg",
"bg-darker",
"bg-lighter",
"bg-lightest",
"text",
"text-lighter",
"theme",
"theme-secondary",
"chat",
] as const;

View File

@ -1,9 +1,8 @@
import { faker } from "@faker-js/faker";
import { add, sub } from "date-fns";
import { nanoid } from "nanoid";
import * as R from "remeda";
import { ADMIN_DISCORD_ID, ADMIN_ID, INVITE_CODE_LENGTH } from "~/constants";
import { db, sql } from "~/db/sql";
import { ADMIN_DISCORD_ID, ADMIN_ID } from "~/features/admin/admin-constants";
import type { SeedVariation } from "~/features/api-private/routes/seed";
import * as AssociationRepository from "~/features/associations/AssociationRepository.server";
import * as BuildRepository from "~/features/builds/BuildRepository.server";
@ -69,6 +68,7 @@ import type { TournamentMapListMap } from "~/modules/tournament-map-list-generat
import { SENDOUQ_DEFAULT_MAPS } from "~/modules/tournament-map-list-generator/constants";
import { nullFilledArray } from "~/utils/arrays";
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";
import invariant from "~/utils/invariant";
import { mySlugify } from "~/utils/urls";
import type { Tables, UserMapModePreferences } from "../tables";
@ -1269,7 +1269,7 @@ function calendarEventWithToToolsTeams(
name,
createdAt: dateToDatabaseTimestamp(new Date()),
tournamentId,
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
});
// in PICNIC & PP Chimera is not checked in + in LUTI no check-ins at all
@ -1605,7 +1605,7 @@ const detailedTeam = (seedVariation?: SeedVariation | null) => () => {
values (
'Alliance Rogue',
'alliance-rogue',
'${nanoid(INVITE_CODE_LENGTH)}',
'${shortNanoid()}',
'${faker.lorem.paragraph()}',
1,
2
@ -1682,7 +1682,7 @@ function otherTeams() {
id: i,
name: teamName,
customUrl: teamCustomUrl,
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
bio: faker.lorem.paragraph(),
});

View File

@ -0,0 +1,6 @@
export const ADMIN_DISCORD_ID = "79237403620945920";
export const ADMIN_ID = process.env.NODE_ENV === "test" ? 1 : 274;
// Panda Scep
export const STAFF_IDS = [11329, 9719];
export const STAFF_DISCORD_IDS = ["138757634500067328", "184478601171828737"];

View File

@ -1,9 +1,7 @@
import { TWEET_LENGTH_MAX_LENGTH } from "~/constants";
export const ART_PER_PAGE = 14;
export const ART = {
DESCRIPTION_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH / 2,
DESCRIPTION_MAX_LENGTH: 140,
LINKED_USERS_MAX_LENGTH: 10,
THUMBNAIL_WIDTH: 640,
TAG_MAX_LENGTH: 100,

View File

@ -1,9 +1,8 @@
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { nanoid } from "nanoid";
import { INVITE_CODE_LENGTH } from "~/constants";
import { db } from "~/db/sql";
import type { TablesInsertable, TablesUpdatable } from "~/db/tables";
import type { AssociationVirtualIdentifier } from "~/features/associations/associations-constants";
import { shortNanoid } from "~/utils/id";
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
import { logger } from "~/utils/logger";
@ -130,7 +129,7 @@ export function insert({ userId, ...associationArgs }: InsertArgs) {
return db.transaction().execute(async (trx) => {
const association = await trx
.insertInto("Association")
.values({ ...associationArgs, inviteCode: nanoid(INVITE_CODE_LENGTH) })
.values({ ...associationArgs, inviteCode: shortNanoid() })
.returning("id")
.executeTakeFirstOrThrow();
@ -155,7 +154,7 @@ export function update(
export function refreshInviteCode(associationId: number) {
return db
.updateTable("Association")
.set({ inviteCode: nanoid(INVITE_CODE_LENGTH) })
.set({ inviteCode: shortNanoid() })
.where("id", "=", associationId)
.execute();
}

View File

@ -0,0 +1,7 @@
export const BADGE = {
SMALL_BADGES_PER_DISPLAY_PAGE: 9,
};
export const SPLATOON_3_XP_BADGE_VALUES = [
5000, 4500, 4000, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
] as const;

View File

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

View File

@ -1,6 +1,6 @@
import type { TFunction } from "i18next";
import { SPLATOON_3_XP_BADGE_VALUES } from "~/constants";
import type { Tables } from "~/db/tables";
import { SPLATOON_3_XP_BADGE_VALUES } from "./badges-constants";
export function badgeExplanationText(
t: TFunction<"badges", undefined>,
@ -22,3 +22,13 @@ export function badgeExplanationText(
tournament: badge.displayName,
}).replace("&#39;", "'");
}
export const findSplatoon3XpBadgeValue = (xPower: number) => {
for (const value of SPLATOON_3_XP_BADGE_VALUES) {
if (xPower >= value) {
return value;
}
}
return null;
};

View File

@ -6,7 +6,7 @@ import { Button } from "~/components/Button";
import { SendouButton } from "~/components/elements/Button";
import { TrashIcon } from "~/components/icons/Trash";
import type { Tables } from "~/db/tables";
import { BADGE } from "~/features/badges/badges-contants";
import { BADGE } from "~/features/badges/badges-constants";
import { usePagination } from "~/hooks/usePagination";
import type { Unpacked } from "~/utils/types";
import { badgeExplanationText } from "../badges-utils";

View File

@ -1,9 +1,7 @@
import {
SPLATOON_3_XP_BADGE_VALUES,
findSplatoon3XpBadgeValue,
} from "~/constants";
import { sql } from "~/db/sql";
import invariant from "~/utils/invariant";
import { SPLATOON_3_XP_BADGE_VALUES } from "../badges-constants";
import { findSplatoon3XpBadgeValue } from "../badges-utils";
const badgeCodeToIdStm = sql.prepare(/* sql */ `
select "id"

View File

@ -124,3 +124,5 @@ export const multiShot = Object.fromEntries(
export const RAINMAKER_SPEED_PENALTY_MODIFIER = 0.8;
export const UNKNOWN_SHORT = "U";
export const MAX_AP = 57;

View File

@ -2,10 +2,10 @@ import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Ability } from "~/components/Ability";
import { MAX_AP } from "~/constants";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { SendouButton } from "../../../components/elements/Button";
import { SendouPopover } from "../../../components/elements/Popover";
import { MAX_AP } from "../analyzer-constants";
import type { FullInkTankOption } from "../analyzer-types";
import { fullInkTankOptions } from "../core/stats";
import { weaponParams } from "../core/utils";

View File

@ -1,4 +1,4 @@
import { MAX_AP } from "~/constants";
import { MAX_AP } from "../analyzer-constants";
import type { AbilityPoints } from "../analyzer-types";
export const SPECIAL_EFFECTS = [

View File

@ -1,4 +1,4 @@
import { EMPTY_BUILD } from "~/constants";
import { EMPTY_BUILD } from "~/features/builds/builds-constants";
import { abilities } from "~/modules/in-game-lists/abilities";
import type {
Ability,

View File

@ -13,7 +13,6 @@ import { Main } from "~/components/Main";
import { Table } from "~/components/Table";
import { Tab, Tabs } from "~/components/Tabs";
import { BeakerIcon } from "~/components/icons/Beaker";
import { MAX_AP } from "~/constants";
import { useUser } from "~/features/auth/core/user";
import { useIsMounted } from "~/hooks/useIsMounted";
import { abilitiesShort } from "~/modules/in-game-lists/abilities";
@ -50,6 +49,7 @@ import { SendouButton } from "../../../components/elements/Button";
import { SendouPopover } from "../../../components/elements/Popover";
import { metaTags } from "../../../utils/remix";
import {
MAX_AP,
MAX_LDE_INTENSITY,
damageTypeToWeaponType,
} from "../analyzer-constants";

View File

@ -1,8 +1,8 @@
import { MAX_AP } from "~/constants";
import { abilities } from "~/modules/in-game-lists/abilities";
import type { Ability } from "~/modules/in-game-lists/types";
import invariant from "~/utils/invariant";
import { roundToNDecimalPlaces } from "~/utils/number";
import { MAX_AP } from "../build-analyzer/analyzer-constants";
import { isStackableAbility } from "../build-analyzer/core/utils";
import type { AbilitiesByWeapon } from "./queries/abilitiesByWeaponId.server";
import type { AverageAbilityPointsResult } from "./queries/averageAbilityPoints.server";

View File

@ -1,8 +1,7 @@
import { cachified } from "@epic-web/cachified";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { ONE_HOUR_IN_MS } from "~/constants";
import { i18next } from "~/modules/i18n/i18next.server";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import { notFoundIfNullLike } from "~/utils/remix.server";
import { weaponNameSlugToId } from "~/utils/unslugify.server";
import { popularBuilds } from "../build-stats-utils";
@ -18,7 +17,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const cachedPopularBuilds = await cachified({
key: `popular-builds-${weaponId}`,
cache,
ttl: ttl(ONE_HOUR_IN_MS),
ttl: ttl(IN_MILLISECONDS.ONE_HOUR),
async getFreshValue() {
return popularBuilds(abilitiesByWeaponId(weaponId));
},

View File

@ -1,8 +1,7 @@
import { cachified } from "@epic-web/cachified";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { ONE_HOUR_IN_MS } from "~/constants";
import { i18next } from "~/modules/i18n/i18next.server";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import { notFoundIfNullLike } from "~/utils/remix.server";
import { weaponNameSlugToId } from "~/utils/unslugify.server";
import { abilityPointCountsToAverages } from "../build-stats-utils";
@ -17,7 +16,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const cachedStats = await cachified({
key: `build-stats-${weaponId}`,
cache,
ttl: ttl(ONE_HOUR_IN_MS),
ttl: ttl(IN_MILLISECONDS.ONE_HOUR),
async getFreshValue() {
return abilityPointCountsToAverages({
allAbilities: averageAbilityPoints(),

View File

@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { Ability } from "~/components/Ability";
import { WeaponImage } from "~/components/Image";
import { Main } from "~/components/Main";
import { MAX_AP } from "~/constants";
import type { SendouRouteHandle } from "~/utils/remix.server";
import {
BUILDS_PAGE,
@ -18,6 +17,7 @@ import { loader } from "../loaders/builds.$slug.stats.server";
export { loader };
import "../build-stats.css";
import { MAX_AP } from "~/features/build-analyzer/analyzer-constants";
export const meta: MetaFunction<typeof loader> = (args) => {
if (!args.data) return [];

View File

@ -1,3 +1,69 @@
import type { BuildAbilitiesTupleWithUnknown } from "~/modules/in-game-lists/types";
export const MAX_BUILD_FILTERS = 6;
export const FILTER_SEARCH_PARAM_KEY = "f";
export const PATCHES = [
{
patch: "9.3.0",
date: "2025-03-13",
},
{
patch: "9.2.0",
date: "2024-11-20",
},
{
patch: "9.0.0",
date: "2024-08-29",
},
// {
// patch: "8.1.0",
// date: "2024-07-17",
// },
// {
// patch: "8.0.0",
// date: "2024-05-31",
// },
// {
// patch: "7.2.0",
// date: "2024-04-17",
// },
// {
// patch: "7.0.0",
// date: "2024-02-21",
// },
// {
// patch: "6.1.0",
// date: "2024-01-24",
// },
// {
// patch: "6.0.0",
// date: "2023-11-29",
// },
// {
// patch: "5.1.0",
// date: "2023-10-17",
// },
// {
// patch: "5.0.0",
// date: "2023-08-30",
// },
];
export const BUILD = {
TITLE_MIN_LENGTH: 1,
TITLE_MAX_LENGTH: 50,
DESCRIPTION_MAX_LENGTH: 280,
MAX_WEAPONS_COUNT: 5,
MAX_COUNT: 250,
} as const;
export const BUILDS_PAGE_BATCH_SIZE = 24;
export const BUILDS_PAGE_MAX_BUILDS = 240;
export const EMPTY_BUILD: BuildAbilitiesTupleWithUnknown = [
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
];

View File

@ -3,13 +3,13 @@ import { Ability } from "~/components/Ability";
import { Button } from "~/components/Button";
import { ModeImage } from "~/components/Image";
import { CrossIcon } from "~/components/icons/Cross";
import { PATCHES } from "~/constants";
import { possibleApValues } from "~/features/build-analyzer";
import { abilities } from "~/modules/in-game-lists/abilities";
import { modesShort } from "~/modules/in-game-lists/modes";
import type { ModeShort } from "~/modules/in-game-lists/types";
import type { Ability as AbilityType } from "~/modules/in-game-lists/types";
import { dateToYYYYMMDD } from "~/utils/dates";
import { PATCHES } from "../builds-constants";
import type {
AbilityBuildFilter,
BuildFilter,

View File

@ -1,6 +1,6 @@
import { BUILDS_PAGE_MAX_BUILDS } from "~/constants";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { cache, syncCached } from "~/utils/cache.server";
import { BUILDS_PAGE_MAX_BUILDS } from "../builds-constants";
import { buildsByWeaponId } from "../queries/buildsBy.server";
const buildsCacheKey = (weaponSplId: MainWeaponId) => `builds-${weaponSplId}`;

View File

@ -1,10 +1,13 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { BUILDS_PAGE_BATCH_SIZE, BUILDS_PAGE_MAX_BUILDS } from "~/constants";
import { i18next } from "~/modules/i18n/i18next.server";
import { weaponIdIsNotAlt } from "~/modules/in-game-lists/weapon-ids";
import { weaponNameSlugToId } from "~/utils/unslugify.server";
import { mySlugify } from "~/utils/urls";
import { FILTER_SEARCH_PARAM_KEY } from "../builds-constants";
import {
BUILDS_PAGE_BATCH_SIZE,
BUILDS_PAGE_MAX_BUILDS,
FILTER_SEARCH_PARAM_KEY,
} from "../builds-constants";
import { buildFiltersSearchParams } from "../builds-schemas.server";
import { cachedBuildsByWeaponId } from "../core/cached-builds.server";
import { filterBuilds } from "../core/filter.server";

View File

@ -18,11 +18,6 @@ import { ChartBarIcon } from "~/components/icons/ChartBar";
import { FilterIcon } from "~/components/icons/Filter";
import { FireIcon } from "~/components/icons/Fire";
import { MapIcon } from "~/components/icons/Map";
import {
BUILDS_PAGE_BATCH_SIZE,
BUILDS_PAGE_MAX_BUILDS,
PATCHES,
} from "~/constants";
import { useUser } from "~/features/auth/core/user";
import { safeJSONParse } from "~/utils/json";
import { isRevalidation, metaTags } from "~/utils/remix";
@ -37,8 +32,11 @@ import {
weaponBuildStatsPage,
} from "~/utils/urls";
import {
BUILDS_PAGE_BATCH_SIZE,
BUILDS_PAGE_MAX_BUILDS,
FILTER_SEARCH_PARAM_KEY,
MAX_BUILD_FILTERS,
PATCHES,
} from "../builds-constants";
import type { BuildFiltersFromSearchParams } from "../builds-schemas.server";
import type { AbilityBuildFilter, BuildFilter } from "../builds-types";

View File

@ -103,3 +103,11 @@ export const EXCLUDED_TAGS: Array<CalendarEventTag> = [
"SZ",
"TW",
];
export const CALENDAR_EVENT_RESULT = {
MAX_PARTICIPANTS_COUNT: 1000,
MAX_PLAYERS_LENGTH: 8,
MAX_TEAM_NAME_LENGTH: 100,
MAX_TEAM_PLACEMENT: 256,
MAX_PLAYER_NAME_LENGTH: 100,
} as const;

View File

@ -1,5 +1,4 @@
import { z } from "zod";
import { CALENDAR_EVENT_RESULT } from "~/constants";
import { type CalendarEventTag, TOURNAMENT_STAGE_TYPES } from "~/db/tables";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
@ -14,7 +13,7 @@ import {
safeJSONParse,
toArray,
} from "~/utils/zod";
import { CALENDAR_EVENT } from "./calendar-constants";
import { CALENDAR_EVENT, CALENDAR_EVENT_RESULT } from "./calendar-constants";
import * as CalendarEvent from "./core/CalendarEvent";
export const calendarEventTagSchema = z

View File

@ -9,9 +9,9 @@ import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { UserSearch } from "~/components/elements/UserSearch";
import { CALENDAR_EVENT_RESULT } from "~/constants";
import type { SendouRouteHandle } from "~/utils/remix.server";
import type { Unpacked } from "~/utils/types";
import { CALENDAR_EVENT_RESULT } from "../calendar-constants";
import { action } from "../actions/calendar.$id.report-winners.server";
import { loader } from "../loaders/calendar.$id.report-winners.server";

View File

@ -1,8 +1,9 @@
import { nanoid } from "nanoid";
import { SKALOP_TOKEN_HEADER_NAME } from "~/constants";
import invariant from "~/utils/invariant";
import type { ChatMessage } from "./chat-types";
const SKALOP_TOKEN_HEADER_NAME = "Skalop-Token";
type PartialChatMessage = Pick<
ChatMessage,
"type" | "context" | "room" | "revalidateOnly"

View File

@ -1,9 +1,8 @@
import cachified from "@epic-web/cachified";
import { TWO_HOURS_IN_MS } from "~/constants";
import type { ShowcaseCalendarEvent } from "~/features/calendar/calendar-types";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import { tournamentIsRanked } from "~/features/tournament/tournament-utils";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import {
databaseTimestampToDate,
dateToDatabaseTimestamp,
@ -161,7 +160,7 @@ async function cachedTournaments() {
return cachified({
key: SHOWCASE_TOURNAMENTS_CACHE_KEY,
cache,
ttl: ttl(TWO_HOURS_IN_MS),
ttl: ttl(IN_MILLISECONDS.TWO_HOURS),
async getFreshValue() {
const tournaments = await TournamentRepository.forShowcase();

View File

@ -1,13 +1,12 @@
import cachified from "@epic-web/cachified";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from "~/constants";
import type { Tables } from "~/db/tables";
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 * as Seasons from "~/features/mmr/core/Seasons";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import {
discordAvatarUrl,
teamPage,
@ -24,8 +23,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
cachified({
key: "front-changelog",
cache,
ttl: ttl(ONE_HOUR_IN_MS),
staleWhileRevalidate: ttl(TWO_HOURS_IN_MS),
ttl: ttl(IN_MILLISECONDS.ONE_HOUR),
staleWhileRevalidate: ttl(IN_MILLISECONDS.TWO_HOURS),
async getFreshValue() {
return Changelog.get();
},
@ -56,8 +55,8 @@ function cachedLeaderboards(): Promise<{
return cachified({
key: "front-leaderboard",
cache,
ttl: ttl(ONE_HOUR_IN_MS),
staleWhileRevalidate: ttl(TWO_HOURS_IN_MS),
ttl: ttl(IN_MILLISECONDS.ONE_HOUR),
staleWhileRevalidate: ttl(IN_MILLISECONDS.TWO_HOURS),
async getFreshValue() {
const season = Seasons.currentOrPrevious()?.nth ?? 1;

View File

@ -1,5 +1,4 @@
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";
@ -7,7 +6,7 @@ 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/types";
import { weaponCategories } from "~/modules/in-game-lists/weapon-ids";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import type { Unwrapped } from "~/utils/types";
import { DEFAULT_LEADERBOARD_MAX_SIZE } from "../leaderboards-constants";
import { seasonHasTopTen } from "../leaderboards-utils";
@ -23,7 +22,7 @@ export async function cachedFullUserLeaderboard(season: number) {
return cachified({
key: `user-leaderboard-season-${season}`,
cache,
ttl: ttl(HALF_HOUR_IN_MS),
ttl: ttl(IN_MILLISECONDS.HALF_HOUR),
async getFreshValue() {
const leaderboard = userSPLeaderboard(season);
const withTiers = addTiers(leaderboard, season);

View File

@ -1,6 +1,5 @@
import { cachified } from "@epic-web/cachified";
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 * as Seasons from "~/features/mmr/core/Seasons";
@ -9,7 +8,7 @@ import type {
RankedModeShort,
} from "~/modules/in-game-lists/types";
import type { weaponCategories } from "~/modules/in-game-lists/weapon-ids";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import {
cachedFullUserLeaderboard,
filterByWeaponCategory,
@ -59,7 +58,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
? await cachified({
key: `team-leaderboard-season-${season}-${type}`,
cache,
ttl: ttl(HALF_HOUR_IN_MS),
ttl: ttl(IN_MILLISECONDS.HALF_HOUR),
async getFreshValue() {
return LeaderboardRepository.teamLeaderboardBySeason({
season,

View File

@ -0,0 +1,6 @@
export const PLUS_SUGGESTION = {
FIRST_COMMENT_MAX_LENGTH: 500,
COMMENT_MAX_LENGTH: 280,
};
export const PLUS_TIERS = [1, 2, 3];

View File

@ -1,15 +1,11 @@
import { z } from "zod";
import {
PLUS_TIERS,
PlUS_SUGGESTION_COMMENT_MAX_LENGTH,
PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH,
} from "~/constants";
import { _action, actualNumber, trimmedString } from "~/utils/zod";
import { PLUS_SUGGESTION, PLUS_TIERS } from "./plus-suggestions-constants";
export const followUpCommentActionSchema = z.object({
comment: z.preprocess(
trimmedString,
z.string().min(1).max(PlUS_SUGGESTION_COMMENT_MAX_LENGTH),
z.string().min(1).max(PLUS_SUGGESTION.COMMENT_MAX_LENGTH),
),
tier: z.preprocess(
actualNumber,
@ -31,7 +27,7 @@ export const firstCommentActionSchema = z.object({
),
comment: z.preprocess(
trimmedString,
z.string().min(1).max(PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH),
z.string().min(1).max(PLUS_SUGGESTION.FIRST_COMMENT_MAX_LENGTH),
),
userId: z.preprocess(actualNumber, z.number().positive()),
});

View File

@ -2,10 +2,10 @@ import { Form, useMatches, useParams } from "@remix-run/react";
import { Button } from "~/components/Button";
import { Redirect } from "~/components/Redirect";
import { SendouDialog } from "~/components/elements/Dialog";
import { PlUS_SUGGESTION_COMMENT_MAX_LENGTH } from "~/constants";
import { useUser } from "~/features/auth/core/user";
import { atOrError } from "~/utils/arrays";
import { plusSuggestionPage } from "~/utils/urls";
import { PLUS_SUGGESTION } from "../plus-suggestions-constants";
import { canAddCommentToSuggestionFE } from "../plus-suggestions-utils";
import type { PlusSuggestionsLoaderData } from "./plus.suggestions";
import { CommentTextarea } from "./plus.suggestions.new";
@ -49,7 +49,7 @@ export default function PlusCommentModalPage() {
<Form method="post" className="stack md">
<input type="hidden" name="tier" value={tierSuggestedTo} />
<input type="hidden" name="suggestedId" value={targetUserId} />
<CommentTextarea maxLength={PlUS_SUGGESTION_COMMENT_MAX_LENGTH} />
<CommentTextarea maxLength={PLUS_SUGGESTION.COMMENT_MAX_LENGTH} />
<div>
<Button type="submit">Submit</Button>
</div>

View File

@ -5,13 +5,10 @@ import { Redirect } from "~/components/Redirect";
import { SubmitButton } from "~/components/SubmitButton";
import { SendouDialog } from "~/components/elements/Dialog";
import { UserSearch } from "~/components/elements/UserSearch";
import {
PLUS_TIERS,
PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH,
} from "~/constants";
import { useUser } from "~/features/auth/core/user";
import { atOrError } from "~/utils/arrays";
import { plusSuggestionPage } from "~/utils/urls";
import { PLUS_SUGGESTION, PLUS_TIERS } from "../plus-suggestions-constants";
import { canSuggestNewUser } from "../plus-suggestions-utils";
import type { PlusSuggestionsLoaderData } from "./plus.suggestions";
@ -67,7 +64,7 @@ export default function PlusNewSuggestionModalPage() {
</select>
</div>
<UserSearch name="userId" label="Suggested user" isRequired />
<CommentTextarea maxLength={PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH} />
<CommentTextarea maxLength={PLUS_SUGGESTION.FIRST_COMMENT_MAX_LENGTH} />
<div>
<SubmitButton>Submit</SubmitButton>
</div>

View File

@ -1,5 +1,4 @@
import type { ActionFunction } from "@remix-run/node";
import { PLUS_UPVOTE } from "~/constants";
import { requireUser } from "~/features/auth/core/user.server";
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
import type { PlusVoteFromFE } from "~/features/plus-voting/core";
@ -11,6 +10,7 @@ import { isVotingActive } from "~/features/plus-voting/core/voting-time";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { badRequestIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import { PLUS_UPVOTE } from "../plus-voting-constants";
import { votingActionSchema } from "../plus-voting-schemas";
export const action: ActionFunction = async ({ request }) => {

View File

@ -1,8 +1,8 @@
import * as React from "react";
import { PLUS_DOWNVOTE, PLUS_UPVOTE } from "~/constants";
import type { Tables } from "~/db/tables";
import type * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
import invariant from "~/utils/invariant";
import { PLUS_DOWNVOTE, PLUS_UPVOTE } from "../plus-voting-constants";
import type { PlusVoteFromFE } from "./types";
import { nextNonCompletedVoting, rangeToMonthYear } from "./voting-time";

View File

@ -0,0 +1,2 @@
export const PLUS_UPVOTE = 1;
export const PLUS_DOWNVOTE = -1;

View File

@ -1,8 +1,8 @@
import { z } from "zod";
import { PLUS_DOWNVOTE, PLUS_UPVOTE } from "~/constants";
import type { PlusVoteFromFE } from "~/features/plus-voting/core";
import { assertType } from "~/utils/types";
import { safeJSONParse } from "~/utils/zod";
import { PLUS_DOWNVOTE, PLUS_UPVOTE } from "./plus-voting-constants";
export const voteSchema = z.object({
votedId: z.number(),

View File

@ -1,11 +1,10 @@
import { sub } from "date-fns";
import type { Insertable } from "kysely";
import { jsonArrayFrom, jsonBuildObject } from "kysely/helpers/sqlite";
import { nanoid } from "nanoid";
import type { Tables, TablesInsertable } from "~/db/tables";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
import { INVITE_CODE_LENGTH } from "../../constants";
import { db } from "../../db/sql";
import invariant from "../../utils/invariant";
import type { Unwrapped } from "../../utils/types";
@ -38,7 +37,7 @@ export function insert(args: InsertArgs) {
teamId: args.teamId,
text: args.text,
visibility: args.visibility ? JSON.stringify(args.visibility) : null,
chatCode: nanoid(INVITE_CODE_LENGTH),
chatCode: shortNanoid(),
})
.returning("id")
.executeTakeFirstOrThrow();

View File

@ -1,5 +1,4 @@
import cachified from "@epic-web/cachified";
import { HALF_HOUR_IN_MS } from "~/constants";
import {
type UserLeaderboardWithAdditionsItem,
cachedFullUserLeaderboard,
@ -9,7 +8,7 @@ import { TIERS } from "~/features/mmr/mmr-constants";
import * as QStreamsRepository from "~/features/sendouq-streams/QStreamsRepository.server";
import { getStreams } from "~/modules/twitch";
import type { MappedStream } from "~/modules/twitch/streams";
import { cache, ttl } from "~/utils/cache.server";
import { IN_MILLISECONDS, cache, ttl } from "~/utils/cache.server";
import { SENDOUQ_STREAMS_KEY } from "../q-streams-constants";
export function cachedStreams() {
@ -18,7 +17,7 @@ export function cachedStreams() {
return cachified({
key: SENDOUQ_STREAMS_KEY,
cache: cache,
ttl: ttl(HALF_HOUR_IN_MS),
ttl: ttl(IN_MILLISECONDS.HALF_HOUR),
async getFreshValue() {
return streamedMatches({
matchPlayers: await QStreamsRepository.activeMatchPlayers(),

View File

@ -1,8 +1,6 @@
import { sub } from "date-fns";
import { sql } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import { nanoid } from "nanoid";
import { INVITE_CODE_LENGTH } from "~/constants";
import { db } from "~/db/sql";
import type {
Tables,
@ -10,6 +8,7 @@ import type {
UserMapModePreferences,
} from "~/db/tables";
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
import { userIsBanned } from "../ban/core/banned.server";
import type { LookingGroupWithInviteCode } from "./q-types";
@ -165,8 +164,8 @@ export function createGroup(args: CreateGroupArgs) {
const createdGroup = await trx
.insertInto("Group")
.values({
inviteCode: nanoid(INVITE_CODE_LENGTH),
chatCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
chatCode: shortNanoid(),
status: args.status,
})
.returning("id")
@ -205,7 +204,7 @@ export async function createGroupFromPrevious(
.select((eb) => [
"Group.teamId",
"Group.chatCode",
eb.val(nanoid(INVITE_CODE_LENGTH)).as("inviteCode"),
eb.val(shortNanoid()).as("inviteCode"),
eb.val("PREPARING").as("status"),
])
.where("Group.id", "=", args.previousGroupId),

View File

@ -1,11 +1,9 @@
import { TWEET_LENGTH_MAX_LENGTH } from "~/constants";
export const SENDOUQ = {
SZ_MAP_COUNT: 6,
OTHER_MODE_MAP_COUNT: 3,
MAX_STAGE_REPEAT_COUNT: 2,
OWN_PUBLIC_NOTE_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH / 2,
PRIVATE_USER_NOTE_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH,
OWN_PUBLIC_NOTE_MAX_LENGTH: 160,
PRIVATE_USER_NOTE_MAX_LENGTH: 280,
} as const;
export const FRIEND_CODE_REGEXP_PATTERN =

View File

@ -1,11 +1,10 @@
import type { Insertable, Transaction } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { nanoid } from "nanoid";
import { INVITE_CODE_LENGTH } from "~/constants";
import { db } from "~/db/sql";
import type { DB, Tables } from "~/db/tables";
import * as LFGRepository from "~/features/lfg/LFGRepository.server";
import { databaseTimestampNow } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";
import invariant from "~/utils/invariant";
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
@ -139,7 +138,7 @@ export async function create(
.values({
name: args.name,
customUrl: args.customUrl,
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
})
.returning("id")
.executeTakeFirstOrThrow();
@ -294,7 +293,7 @@ export function resetInviteCode(teamId: number) {
return db
.updateTable("AllTeam")
.set({
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
})
.where("id", "=", teamId)
.execute();

View File

@ -1,7 +1,7 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { INVITE_CODE_LENGTH } from "~/constants";
import { requireUser } from "~/features/auth/core/user.server";
import { SHORT_NANOID_LENGTH } from "~/utils/id";
import { notFoundIfFalsy } from "~/utils/remix.server";
import { teamPage } from "~/utils/urls";
import * as TeamRepository from "../TeamRepository.server";
@ -58,7 +58,7 @@ export function validateInviteCode({
user?: { id: number; team?: { name: string } };
reachedTeamCountLimit: boolean;
}) {
if (inviteCode.length !== INVITE_CODE_LENGTH) {
if (inviteCode.length !== SHORT_NANOID_LENGTH) {
return "SHORT_CODE";
}
if (inviteCode !== realInviteCode) {

View File

@ -1,6 +1,4 @@
import { TWEET_LENGTH_MAX_LENGTH } from "~/constants";
export const TOURNAMENT_SUB = {
WEAPON_POOL_MAX_SIZE: 5,
MESSAGE_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH,
MESSAGE_MAX_LENGTH: 280,
};

View File

@ -2,11 +2,10 @@
import type { Transaction } from "kysely";
import { sql } from "kysely";
import { nanoid } from "nanoid";
import { INVITE_CODE_LENGTH } from "~/constants";
import { db } from "~/db/sql";
import type { DB, Tables } from "~/db/tables";
import { databaseTimestampNow } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";
import invariant from "~/utils/invariant";
export function setActiveRoster({
@ -133,7 +132,7 @@ export function create({
.values({
tournamentId,
name: team.name,
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
prefersNotToHost: team.prefersNotToHost,
noScreen: team.noScreen,
teamId: team.teamId,
@ -215,7 +214,7 @@ export function copyFromAnotherTournament({
.values({
...oldTeam,
tournamentId: destinationTournamentId,
inviteCode: nanoid(INVITE_CODE_LENGTH),
inviteCode: shortNanoid(),
seed,
})
.returning("id")

View File

@ -11,6 +11,7 @@ import {
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
import { USER } from "~/features/user-page/user-page-constants";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
import {
@ -21,7 +22,6 @@ import {
successToast,
} from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { USER } from "../../../constants";
import { _action, id, idObject } from "../../../utils/zod";
import { bracketIdx } from "../../tournament-bracket/tournament-bracket-schemas.server";
import * as TournamentRepository from "../TournamentRepository.server";

View File

@ -15,10 +15,10 @@ import { SubmitButton } from "~/components/SubmitButton";
import { SendouDialog } from "~/components/elements/Dialog";
import { UserSearch } from "~/components/elements/UserSearch";
import { TrashIcon } from "~/components/icons/Trash";
import { USER } from "~/constants";
import { useUser } from "~/features/auth/core/user";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import type { TournamentData } from "~/features/tournament-bracket/core/Tournament.server";
import { USER } from "~/features/user-page/user-page-constants";
import { databaseTimestampToDate } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { assertUnreachable } from "~/utils/types";

View File

@ -1,8 +1,8 @@
import * as R from "remeda";
import { INVITE_CODE_LENGTH } from "~/constants";
import { modesShort, rankedModesShort } from "~/modules/in-game-lists/modes";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
import { weekNumberToDate } from "~/utils/dates";
import { SHORT_NANOID_LENGTH } from "~/utils/id";
import { tournamentLogoUrl } from "~/utils/urls";
import type { Tables, TournamentStageSettings } from "../../db/tables";
import { assertUnreachable } from "../../utils/types";
@ -369,7 +369,7 @@ export function validateCanJoinTeam({
if (typeof userId !== "number") {
return "NOT_LOGGED_IN";
}
if (!teamToJoin && inviteCode.length !== INVITE_CODE_LENGTH) {
if (!teamToJoin && inviteCode.length !== SHORT_NANOID_LENGTH) {
return "SHORT_CODE";
}
if (!teamToJoin) {

View File

@ -1,9 +1,9 @@
import { type ActionFunction, redirect } from "@remix-run/node";
import * as R from "remeda";
import { z } from "zod";
import { BUILD } from "~/constants";
import { requireUser } from "~/features/auth/core/user.server";
import * as BuildRepository from "~/features/builds/BuildRepository.server";
import { BUILD } from "~/features/builds/builds-constants";
import { refreshBuildsCacheByWeaponSplIds } from "~/features/builds/core/cached-builds.server";
import type { BuildWeaponWithTop500Info } from "~/features/builds/queries/buildsBy.server";
import {

View File

@ -18,7 +18,6 @@ import { RequiredHiddenInput } from "~/components/RequiredHiddenInput";
import { SubmitButton } from "~/components/SubmitButton";
import { CrossIcon } from "~/components/icons/Cross";
import { PlusIcon } from "~/components/icons/Plus";
import { BUILD } from "~/constants";
import type { GearType } from "~/db/tables";
import {
validatedBuildFromSearchParams,
@ -35,6 +34,7 @@ import type { SendouRouteHandle } from "~/utils/remix.server";
import { modeImageUrl } from "~/utils/urls";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
import { BUILD } from "~/features/builds/builds-constants";
import { action } from "../actions/u.$identifier.builds.new.server";
import { loader } from "../loaders/u.$identifier.builds.new.server";
export { loader, action };

View File

@ -15,9 +15,8 @@ import { SendouSwitch } from "~/components/elements/Switch";
import { StarIcon } from "~/components/icons/Star";
import { StarFilledIcon } from "~/components/icons/StarFilled";
import { TrashIcon } from "~/components/icons/Trash";
import { USER } from "~/constants";
import type { Tables } from "~/db/tables";
import { BADGE } from "~/features/badges/badges-contants";
import { BADGE } from "~/features/badges/badges-constants";
import { BadgesSelector } from "~/features/badges/components/BadgesSelector";
import { useIsMounted } from "~/hooks/useIsMounted";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
@ -26,7 +25,7 @@ import invariant from "~/utils/invariant";
import { rawSensToString } from "~/utils/strings";
import { FAQ_PAGE } from "~/utils/urls";
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
import { COUNTRY_CODES } from "../user-page-constants";
import { COUNTRY_CODES, USER } from "../user-page-constants";
import { action } from "../actions/u.$identifier.edit.server";
import { loader } from "../loaders/u.$identifier.edit.server";

View File

@ -1,6 +1,29 @@
export const USER = {
BIO_MAX_LENGTH: 2000,
CUSTOM_URL_MAX_LENGTH: 32,
CUSTOM_NAME_MAX_LENGTH: 32,
BATTLEFY_MAX_LENGTH: 32,
IN_GAME_NAME_TEXT_MAX_LENGTH: 20,
IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH: 5,
WEAPON_POOL_MAX_SIZE: 5,
COMMISSION_TEXT_MAX_LENGTH: 1000,
};
export const MATCHES_PER_SEASONS_PAGE = 8;
export const DEFAULT_BUILD_SORT = ["WEAPON_POOL", "UPDATED_AT"] as const;
export const CUSTOM_CSS_VAR_COLORS = [
"bg",
"bg-darker",
"bg-lighter",
"bg-lightest",
"text",
"text-lighter",
"theme",
"theme-secondary",
"chat",
] as const;
/**
* An array of ISO 3166-1 alpha-2 country codes.
* Each entry is a two-letter uppercase string representing a country or territory. Sorted alphabetically.

View File

@ -1,7 +1,6 @@
import { z } from "zod";
import { USER } from "~/constants";
import "~/styles/u-edit.css";
import { BADGE } from "~/features/badges/badges-contants";
import { BADGE } from "~/features/badges/badges-constants";
import { isCustomUrl } from "~/utils/urls";
import {
actualNumber,
@ -22,7 +21,7 @@ import {
HIGHLIGHT_CHECKBOX_NAME,
HIGHLIGHT_TOURNAMENT_CHECKBOX_NAME,
} from "./components/UserResultsTable";
import { COUNTRY_CODES } from "./user-page-constants";
import { COUNTRY_CODES, USER } from "./user-page-constants";
export const userParamsSchema = z.object({ identifier: z.string() });

View File

@ -1,5 +1,5 @@
import type { z } from "zod";
import { STAFF_DISCORD_IDS } from "~/constants";
import { STAFF_DISCORD_IDS } from "~/features/admin/admin-constants";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import { fetchWithTimeout } from "~/utils/fetch";

View File

@ -1,4 +1,4 @@
import { ADMIN_ID, STAFF_IDS } from "~/constants";
import { ADMIN_ID, STAFF_IDS } from "~/features/admin/admin-constants";
export function isAdmin(user?: { id: number }) {
return user?.id === ADMIN_ID;

View File

@ -35,7 +35,6 @@ import { Catcher } from "./components/Catcher";
import { SendouToastRegion, toastQueue } from "./components/elements/Toast";
import { Layout } from "./components/layout";
import { Ramp } from "./components/ramp/Ramp";
import { CUSTOMIZED_CSS_VARS_NAME } from "./constants";
import { getUser } from "./features/auth/core/user.server";
import { userIsBanned } from "./features/ban/core/banned.server";
import {
@ -280,6 +279,8 @@ function usePreloadTranslation() {
}, []);
}
const CUSTOMIZED_CSS_VARS_NAME = "css";
function useCustomizedCSSVars() {
const matches = useMatches();

View File

@ -2,9 +2,9 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import type { Params } from "@remix-run/react";
import { expect } from "vitest";
import type { z } from "zod";
import { ADMIN_ID } from "~/constants";
import { REGULAR_USER_TEST_ID } from "~/db/seed/constants";
import { db, sql } from "~/db/sql";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import { SESSION_KEY } from "~/features/auth/core/authenticator.server";
import { authSessionStorage } from "~/features/auth/core/session.server";

View File

@ -24,3 +24,9 @@ export function syncCached<T>(key: string, getFreshValue: () => T) {
return value;
}
export const IN_MILLISECONDS = {
HALF_HOUR: 30 * 60 * 1000,
ONE_HOUR: 60 * 60 * 1000,
TWO_HOURS: 2 * 60 * 60 * 1000,
};

10
app/utils/id.ts Normal file
View File

@ -0,0 +1,10 @@
import { nanoid } from "nanoid";
export const SHORT_NANOID_LENGTH = 10;
/**
* Generates a short, unique identifier string (wraps nanoid using a smaller length than the default).
*/
export function shortNanoid() {
return nanoid(SHORT_NANOID_LENGTH);
}

View File

@ -1,5 +1,5 @@
import { type Locator, type Page, expect } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import type { SeedVariation } from "~/features/api-private/routes/seed";
import { tournamentBracketsPage } from "./urls";

View File

@ -9,7 +9,6 @@ import type { Namespace, TFunction } from "i18next";
import { nanoid } from "nanoid";
import type { z } from "zod";
import type { navItems } from "~/components/layout/nav-items";
import { LOHI_TOKEN_HEADER_NAME } from "~/constants";
import { s3UploadHandler } from "~/features/img-upload";
import invariant from "./invariant";
import { logger } from "./logger";
@ -182,6 +181,8 @@ function formDataToObject(formData: FormData) {
return result;
}
const LOHI_TOKEN_HEADER_NAME = "Lohi-Token";
/** Some endpoints can only be accessed with an auth token. Used by Lohi bot and cron jobs. */
export function canAccessLohiEndpoint(request: Request) {
invariant(process.env.LOHI_TOKEN, "LOHI_TOKEN is required");

View File

@ -1,6 +1,6 @@
import type { ZodType } from "zod";
import { z } from "zod";
import { CUSTOM_CSS_VAR_COLORS, INVITE_CODE_LENGTH } from "~/constants";
import { CUSTOM_CSS_VAR_COLORS } from "~/features/user-page/user-page-constants";
import {
abilities,
type abilitiesShort,
@ -8,6 +8,7 @@ import {
import { stageIds } from "~/modules/in-game-lists/stage-ids";
import { mainWeaponIds } from "~/modules/in-game-lists/weapon-ids";
import { FRIEND_CODE_REGEXP } from "../features/sendouq/q-constants";
import { SHORT_NANOID_LENGTH } from "./id";
import type { Unpacked } from "./types";
import { assertType } from "./types";
@ -17,7 +18,7 @@ export const idObject = z.object({
});
export const optionalId = z.coerce.number().int().positive().optional();
export const inviteCode = z.string().length(INVITE_CODE_LENGTH);
export const inviteCode = z.string().length(SHORT_NANOID_LENGTH);
export const inviteCodeObject = z.object({
inviteCode,
});

View File

@ -1,6 +1,6 @@
import test, { expect } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import {
impersonate,
isNotVisible,

View File

@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID } from "~/constants";
import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_DISCORD_ID } from "~/features/admin/admin-constants";
import {
impersonate,
navigate,

View File

@ -1,5 +1,5 @@
import test, { expect } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import {
impersonate,
navigate,

View File

@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID, ADMIN_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_DISCORD_ID, ADMIN_ID } from "~/features/admin/admin-constants";
import {
impersonate,
isNotVisible,

View File

@ -1,6 +1,6 @@
import { type Page, expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_DISCORD_ID } from "~/features/admin/admin-constants";
import {
impersonate,
isNotVisible,

View File

@ -1,6 +1,6 @@
import test, { expect } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import {
impersonate,
isNotVisible,

View File

@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
import { ADMIN_ID } from "~/constants";
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import { BANNED_MAPS } from "~/features/sendouq-settings/banned-maps";
import type { TournamentLoaderData } from "~/features/tournament/loaders/to.$id.server";
import { rankedModesShort } from "~/modules/in-game-lists/modes";

View File

@ -1,6 +1,6 @@
import { type Page, expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID } from "~/constants";
import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_DISCORD_ID } from "~/features/admin/admin-constants";
import {
impersonate,
isNotVisible,

View File

@ -2,8 +2,8 @@
import "dotenv/config";
import { z } from "zod";
import { ADMIN_ID } from "~/constants";
import { db } from "~/db/sql";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";