"use client"; import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from "@hexahost/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Link, useRouter } from "@/i18n/navigation"; import { ApiError } from "@/lib/api/auth"; import { createServer, listPlans } from "@/lib/api/servers"; import { createServerSchema, MINECRAFT_VERSIONS, SOFTWARE_FAMILIES, type CreateServerFormValues, } from "@/lib/schemas/server"; const inputClassName = "flex h-10 w-full rounded-md border border-zinc-300 bg-white px-3 text-sm text-zinc-900 placeholder:text-zinc-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100"; const STEPS = ["basics", "software", "review"] as const; type WizardStep = (typeof STEPS)[number]; export function CreateServerWizard() { const t = useTranslations("servers"); const tw = useTranslations("servers.wizard"); const tv = useTranslations("validation"); const router = useRouter(); const [step, setStep] = useState("basics"); const [apiError, setApiError] = useState(null); const { data: plansData, isLoading: plansLoading } = useQuery({ queryKey: ["plans"], queryFn: listPlans, retry: false, }); const schema = createServerSchema({ nameRequired: tv("serverNameRequired"), nameMax: tv("serverNameMax"), planRequired: tv("planRequired"), planInvalid: tv("planInvalid"), versionRequired: tv("versionRequired"), softwareRequired: tv("softwareRequired"), eulaRequired: tv("eulaRequired"), }); const form = useForm({ resolver: zodResolver(schema), defaultValues: { name: "", planId: "", minecraftVersion: MINECRAFT_VERSIONS[0], softwareFamily: "VANILLA", eulaAccepted: false, }, mode: "onChange", }); const { register, handleSubmit, trigger, watch, formState: { errors, isSubmitting }, } = form; const createMutation = useMutation({ mutationFn: createServer, onSuccess: (server) => { router.push(`/servers/${server.id}`); }, onError: (error: unknown) => { setApiError(error instanceof ApiError ? (error.detail ?? error.title) : t("errors.generic")); }, }); const plans = plansData?.plans ?? []; const values = watch(); const stepIndex = STEPS.indexOf(step); async function goNext() { setApiError(null); if (step === "basics") { const valid = await trigger(["name", "planId"]); if (valid) setStep("software"); return; } if (step === "software") { const valid = await trigger(["softwareFamily", "minecraftVersion"]); if (valid) setStep("review"); } } function goBack() { setApiError(null); if (step === "software") setStep("basics"); if (step === "review") setStep("software"); } async function onSubmit(formValues: CreateServerFormValues) { setApiError(null); await createMutation.mutateAsync({ ...formValues, eulaAccepted: true, }); } const selectedPlan = plans.find((plan) => plan.id === values.planId); return ( {t("createTitle")} {t("createDescription")}
    {STEPS.map((stepKey, index) => (
  1. {tw(`steps.${stepKey}`)}
  2. ))}
{step === "basics" ? ( <>
{errors.name ? (

{errors.name.message}

) : null}
{errors.planId ? (

{errors.planId.message}

) : null} {!plansLoading && plans.length === 0 ? (

{t("noPlans")}

) : null}
) : null} {step === "software" ? ( <>
{errors.softwareFamily ? (

{errors.softwareFamily.message}

) : null}
{errors.minecraftVersion ? (

{errors.minecraftVersion.message}

) : null}
) : null} {step === "review" ? ( <>
{t("name")}
{values.name}
{t("plan")}
{selectedPlan?.name ?? "—"}
{t("software")}
{t(`softwareOptions.${values.softwareFamily}`)}
{t("version")}
{values.minecraftVersion}
{errors.eulaAccepted ? (

{errors.eulaAccepted.message}

) : null}
) : null} {apiError ? (

{apiError}

) : null}
{step !== "basics" ? ( ) : null} {step !== "review" ? ( ) : ( )} {t("cancel")}
); }