"use client"; import { useEffect, useRef, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { isEmpty, sortBy } from "lodash"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { FormProvider, useForm } from "react-hook-form"; import { z } from "zod"; import { Spinner } from "@/components/Spinner"; import { SelectField } from "@/components/form/SelectField"; import { TextField } from "@/components/form/TextField"; import { fetchTournamentResultsSchema } from "@/schemas"; import { trpc } from "@/utils/trpc"; import { KFactor } from "./KFactor"; import { ManualEloForm } from "./ManualEloForm"; import { TournamentResults } from "./TournamentResults"; const kFactors = ["40", "20", "10"]; const formSchema = fetchTournamentResultsSchema.extend({ player: z.string().optional(), kFactor: z.string(), }); type FetchResultsFormValues = z.infer; export default function Elo() { const t = useTranslations("Elo"); type TranslationKey = Parameters[0]; const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const current = new URLSearchParams(Array.from(searchParams.entries())); const url = searchParams.get("url") ?? ""; const kFactorParam = searchParams.get("k"); const player = searchParams.get("player") ?? ""; const kFactor = kFactorParam && kFactors.includes(kFactorParam) ? kFactorParam : "20"; const hasUrl = !isEmpty(url.trim()); const { data: allResults, isFetching, error, } = trpc.fetchTournamentResults.useQuery( { url: url?.trim(), }, { refetchOnWindowFocus: false, staleTime: Infinity, cacheTime: 10 * 60 * 1000, enabled: hasUrl, retry: false, }, ); const playerOptions = sortBy( (allResults ?? []).map((player) => ({ value: player.id, label: player.name, })), "label", ); const form = useForm({ resolver: zodResolver(fetchTournamentResultsSchema), defaultValues: { url: url ?? "", kFactor: kFactor ?? "20", player: player ?? "", }, }); const onSubmit = async (input: FetchResultsFormValues) => { current.set("url", input.url); if (input.kFactor) current.set("k", input.kFactor); const search = current.toString(); const query = search ? `?${search}` : ""; // Push the new URL router.push(`${pathname}${query}`); }; const clearForm = () => { current.delete("url"); current.delete("k"); current.delete("player"); form.setValue("url", ""); form.setValue("kFactor", "20"); form.setValue("player", ""); const search = current.toString(); const query = search ? `?${search}` : ""; router.push(`${pathname}${query}`); }; useEffect(() => { // We subscribe to form changes and update the URL parameters const subscription = form.watch((value, { name, type }) => { let update = false; if (type === "change") { if (name === "player" && value.player !== player && value.player) { current.set("player", value.player); update = true; } else if ( name === "kFactor" && value.kFactor !== kFactor && value.kFactor ) { current.set("k", value.kFactor); update = true; } if (update) { const search = current.toString(); const query = search ? `?${search}` : ""; router.replace(`${pathname}${query}`); } } }); return () => subscription.unsubscribe(); }, [form.watch, kFactor, player]); useEffect(() => { // When the URL changes, we update the form values if (url !== form.getValues("url")) { form.setValue("url", url); } if (player !== form.getValues("player")) { form.setValue("player", player); } if (kFactor !== form.getValues("kFactor")) { form.setValue("kFactor", form.getValues("kFactor")); } }, [searchParams, form]); if (error) { console.error(error); } const playerResults = allResults?.find((p) => p.id === player); return (

{t("title")}

{t("info")}

{hasUrl && !isFetching && !error && (
)}
{isFetching && (
)} {error && (
{error.message.startsWith("ERR_") ? t(error.message as TranslationKey) : t.rich("unknownError", { contact: (chunks) => ( {chunks} ), })}
)} {!isFetching && playerOptions.length > 0 && (
({ value: k, label: k, }))} isClearable={false} required />
)}
{hasUrl && !isFetching && player && playerResults && !error && ( )} {((!hasUrl && !isFetching) || !!error) && ( <>
{t("useManualLabel")}
)}
); }