"use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { toast } from "sonner"; type PlanId = "starter" | "pro" | "network"; type Edition = "java" | "bedrock" | "modded"; type ModLoader = "paper" | "spigot" | "velocity" | "forge" | "fabric" | ""; type Region = "eu-nl" | "eu-de" | "us-east" | "us-west"; // ✅ Allow readonly arrays for safe assignment from const literals type Plan = { id: PlanId; name: string; price: string; specs: string; typical: string; features: readonly string[]; }; type RegionOpt = { id: Region; label: string }; export default function DeployWizard({ plans, regions, siteHostname, }: { plans: readonly Plan[]; regions: readonly RegionOpt[]; siteHostname: string; }) { const router = useRouter(); const [step, setStep] = useState(1); const [loading, setLoading] = useState(false); const [form, setForm] = useState<{ plan: PlanId | ""; edition: Edition | ""; modLoader: ModLoader; version: string; region: Region | ""; serverName: string; subdomain: string; email: string; acceptEula: boolean; notes: string; }>({ plan: "", edition: "", modLoader: "", version: "latest", region: "", serverName: "", subdomain: "", email: "", acceptEula: false, notes: "", }); const set = (key: K, value: (typeof form)[K]) => setForm((f) => ({ ...f, [key]: value })); // Preselect plan from hash/query if present if (typeof window !== "undefined" && !form.plan) { const hash = window.location.hash; const url = new URL(window.location.href); const qp = url.searchParams.get("plan") as PlanId | null; const fromHash = /plan=(starter|pro|network)/.exec(hash || "")?.[1] as PlanId | undefined; const initial = qp || fromHash; if (initial) set("plan", initial); } const canContinue1 = !!form.plan; const canContinue2 = !!form.edition && !!form.region && !!form.version && (form.edition !== "modded" || !!form.modLoader); const canSubmit = canContinue2 && !!form.serverName && !!form.email && !!form.subdomain && form.acceptEula; const handleSubmit = async () => { if (!canSubmit) return; setLoading(true); try { const res = await fetch("/api/minecraft/create-server", { method: "POST", headers: { "Content-Type": "application/json", "x-secret-token": process.env.NEXT_PUBLIC_FORM_SECRET || "", }, body: JSON.stringify(form), }); if (!res.ok) throw new Error("Provisioning failed"); toast.success("Server provisioning started! Check your email for details."); router.push("/minecraft/success"); } catch (err) { console.error(err); toast.error("Something went wrong. Please try again."); } finally { setLoading(false); } }; return (
{step === 1 && (

1) Choose a Plan

You can change plans later. Network is custom—contact us for sizing.

{plans.map((p) => ( ))}
)} {step === 2 && (

2) Configure

{/* Edition */}
{/* Mod loader */}

Velocity is recommended for multi-server networks.

{/* Version */}
set("version", e.target.value)} placeholder="latest or e.g. 1.20.6" className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2" />
{/* Region */}
)} {step === 3 && (

3) Details & Provision

set("serverName", e.target.value)} placeholder="My Community Server" className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2" />
set("email", e.target.value)} placeholder="you@example.com" className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2" />
set( "subdomain", e.target.value.replace(/[^a-z0-9-]/gi, "").toLowerCase() ) } placeholder="myserver" className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2" /> .{siteHostname}

We’ll map this to your server on provision.

set("notes", e.target.value)} placeholder="Plugins to preinstall, SFTP user, etc." className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2" />

We’ll send panel access and SFTP credentials to your email. All plans include backups and monitoring. Modpacks & networks depend on resources and may require custom sizing.

)}
); }