import type { MetaFunction } from "@remix-run/node"; import { Form, Link, useFetcher, useLoaderData } from "@remix-run/react"; import clsx from "clsx"; import Compressor from "compressorjs"; import * as React from "react"; import { useTranslation } from "react-i18next"; import type { AlertVariation } from "~/components/Alert"; import { Alert } from "~/components/Alert"; import { Badge } from "~/components/Badge"; import { DateInput } from "~/components/DateInput"; import { Divider } from "~/components/Divider"; import { SendouButton } from "~/components/elements/Button"; import { FormMessage } from "~/components/FormMessage"; import { Input } from "~/components/Input"; import { CrossIcon } from "~/components/icons/Cross"; import { TrashIcon } from "~/components/icons/Trash"; import { Label } from "~/components/Label"; import { Main } from "~/components/Main"; import { MapPoolSelector } from "~/components/MapPoolSelector"; import { RequiredHiddenInput } from "~/components/RequiredHiddenInput"; import { SubmitButton } from "~/components/SubmitButton"; import type { CalendarEventTag, Tables } from "~/db/tables"; import { MapPool } from "~/features/map-list-generator/core/map-pool"; import * as Progression from "~/features/tournament-bracket/core/Progression"; import { useIsMounted } from "~/hooks/useIsMounted"; import type { RankedModeShort } from "~/modules/in-game-lists/types"; import { databaseTimestampToDate, getDateAtNextFullHour, getDateWithHoursOffset, } from "~/utils/dates"; import invariant from "~/utils/invariant"; import type { SendouRouteHandle } from "~/utils/remix.server"; import { pathnameFromPotentialURL } from "~/utils/strings"; import { CREATING_TOURNAMENT_DOC_LINK, FAQ_PAGE } from "~/utils/urls"; import { CALENDAR_EVENT, REG_CLOSES_AT_OPTIONS, type RegClosesAtOption, } from "../calendar-constants"; import { calendarEventMaxDate, calendarEventMinDate, datesToRegClosesAt, regClosesAtToDisplayName, } from "../calendar-utils"; import { BracketProgressionSelector } from "../components/BracketProgressionSelector"; import { Tags } from "../components/Tags"; import "~/styles/calendar-new.css"; import { SendouSwitch } from "~/components/elements/Switch"; import { useHasRole } from "~/modules/permissions/hooks"; import { logger } from "~/utils/logger"; import { metaTags } from "~/utils/remix"; import { action } from "../actions/calendar.new.server"; import { loader } from "../loaders/calendar.new.server"; export { loader, action }; export const meta: MetaFunction = (args) => { if (!args.data) return []; const what = args.data.isAddingTournament ? "tournament" : "calendar event"; return metaTags({ title: args.data.eventToEdit ? `Editing ${what}` : `New ${what}`, location: args.location, }); }; export const handle: SendouRouteHandle = { i18n: ["calendar", "game-misc", "tournament"], }; const useBaseEvent = () => { const { eventToEdit, eventToCopy } = useLoaderData(); return eventToCopy ?? eventToEdit; }; export default function CalendarNewEventPage() { const baseEvent = useBaseEvent(); const isCalendarEventAdder = useHasRole("CALENDAR_EVENT_ADDER"); const data = useLoaderData(); if (!data.eventToEdit && !isCalendarEventAdder) { return (
You can't add a new event at this time (Discord account too young)
); } if ( !data.eventToEdit && data.isAddingTournament && data.organizations.length === 0 ) { return (
No permissions to add tournaments. Tournaments are in beta, accessible by Patreon supporters and established TO's. See{" "} FAQ for more info.
); } return (

{data.isAddingTournament ? "New tournament" : "New calendar event"}

{data.isAddingTournament ? ( ? ) : null}
{data.isAddingTournament ? : null}
); } function TemplateTournamentForm() { const { recentTournaments } = useLoaderData(); const [eventId, setEventId] = React.useState(""); if (!recentTournaments) return null; return ( <>
Use template

); } function EventForm() { const fetcher = useFetcher(); const { t } = useTranslation(); const { eventToEdit, eventToCopy } = useLoaderData(); const ref = React.useRef(null); const [avatarImg, setAvatarImg] = React.useState(null); const baseEvent = useBaseEvent(); const [isInvitational, setIsInvitational] = React.useState( baseEvent?.tournament?.ctx.settings.isInvitational ?? false, ); const data = useLoaderData(); const [bracketProgressionErrored, setBracketProgressionErrored] = React.useState(false); const handleSubmit = () => { const isValid = ref.current?.checkValidity(); if (!isValid) { ref.current?.reportValidity(); return; } const formData = new FormData(ref.current!); // if "avatarImgId" it means they want to reuse an existing avatar const includeImage = avatarImg && !formData.has("avatarImgId"); if (includeImage) { // replace with the compressed version formData.delete("img"); formData.append("img", avatarImg, avatarImg.name); } fetcher.submit(formData, { encType: includeImage ? "multipart/form-data" : undefined, method: "post", }); }; const submitButtonDisabled = () => { if (fetcher.state !== "idle") return true; if (bracketProgressionErrored) return true; return false; }; return (
{eventToEdit && ( )} {eventToCopy?.tournamentId ? ( ) : null} {data.isAddingTournament ? ( ) : null} {data.isAddingTournament ? : null} {!data.isAddingTournament ? : null} {data.isAddingTournament ? ( ) : null} {data.isAddingTournament ? ( <> Tournament settings {!eventToEdit ? : null} ) : null} {data.isAddingTournament ? ( ) : ( )} {data.isAddingTournament ? (
Tournament format
) : null} {t("actions.submit")} ); } function NameInput() { const { t } = useTranslation(); const { eventToEdit } = useLoaderData(); return (
); } function DescriptionTextarea({ supportsMarkdown, }: { supportsMarkdown?: boolean; }) { const { t } = useTranslation(); const baseEvent = useBaseEvent(); const [value, setValue] = React.useState(baseEvent?.description ?? ""); return (