"use client"; import { Command } from "cmdk"; import { useQueryClient } from "@tanstack/react-query"; import { useSession, signIn, signOut } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; type Mod = { filename: string; displayName: string; modId: string }; type Snapshot = { dirName: string; name: string }; type Backup = { name: string }; type PlayerData = { ops: { name: string }[]; whitelist: { name: string }[]; banned: { name: string }[]; }; const TABS: { value: string; label: string }[] = [ { value: "server", label: "Server" }, { value: "players", label: "Players" }, { value: "chat", label: "Chat" }, { value: "mods", label: "Mods" }, { value: "backups", label: "Backups" }, { value: "logs", label: "Logs" }, ]; function toggleTheme() { const html = document.documentElement; const dark = html.classList.contains("dark"); const next = dark ? "light" : "dark"; html.classList.toggle("dark", next === "dark"); html.classList.toggle("light", next === "light"); html.style.colorScheme = next; localStorage.setItem("theme", next); } export function CommandPalette() { const [open, setOpen] = useState(false); const router = useRouter(); const queryClient = useQueryClient(); const { data: session } = useSession(); const authed = !!session; useEffect(() => { const onKey = (e: KeyboardEvent) => { if ((e.key === "k" || e.key === "K") && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((v) => !v); } if (e.key === "Escape") setOpen(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); const close = useCallback(() => setOpen(false), []); const run = useCallback( (fn: () => void | Promise) => () => { close(); Promise.resolve(fn()).catch(() => {}); }, [close] ); const goToTab = (tab: string) => { if (typeof window === "undefined") return; if (!window.location.pathname.startsWith("/admin")) { router.push(`/admin#${tab}`); } else { window.location.hash = tab; // force AdminTabs to sync by dispatching a hashchange window.dispatchEvent(new HashChangeEvent("hashchange")); } }; const mods = (queryClient.getQueryData(["mods"]) || []).slice(0, 20); const snapshots = (queryClient.getQueryData(["snapshots"]) || []).slice(0, 10); const backups = (queryClient.getQueryData(["backups"]) || []).slice(0, 10); const players = queryClient.getQueryData(["players"]); const playerList = useMemo(() => { if (!players) return [] as { name: string; group: string }[]; const out: { name: string; group: string }[] = []; players.ops.forEach((p) => out.push({ name: p.name, group: "Ops" })); players.whitelist.forEach((p) => out.push({ name: p.name, group: "Whitelist" })); return out.slice(0, 20); }, [players]); const serverAction = async (act: "start" | "stop" | "restart") => { const res = await fetch(`/api/server/${act}`, { method: "POST" }); if (res.ok) { toast.success(`${act} command sent`); queryClient.invalidateQueries({ queryKey: ["status"] }); } else { const data = await res.json().catch(() => ({})); toast.error(`${act} failed`, { description: data?.error }); } }; const createBackup = async () => { toast.loading("Creating backup...", { id: "cmdk-backup" }); const res = await fetch("/api/backups", { method: "POST" }); const data = await res.json(); toast.dismiss("cmdk-backup"); if (res.ok) { toast.success(data.message || "Backup created"); queryClient.invalidateQueries({ queryKey: ["backups"] }); } else { toast.error("Backup failed", { description: data.error }); } }; if (!open) return null; return (
{ if (e.target === e.currentTarget) close(); }} >
⌘K
No matches. router.push("/"))}>Go to Home {authed ? ( TABS.map((t) => ( goToTab(t.value))}> Admin · {t.label} )) ) : ( signIn())}>Log in )} toggleTheme())}>Toggle theme {authed && ( <> serverAction("start"))}>Start server serverAction("restart"))}>Restart server serverAction("stop"))}>Stop server createBackup())}>Create world backup now { queryClient.invalidateQueries({ queryKey: ["mod-updates"] }); toast.success("Checking Modrinth for updates..."); })} > Check for mod updates signOut({ callbackUrl: "/" }))}> Log out {mods.length > 0 && ( {mods.map((m) => ( goToTab("mods"))} > {m.displayName} ))} )} {playerList.length > 0 && ( {playerList.map((p, i) => ( goToTab("players"))} > {p.name} {p.group} ))} )} {snapshots.length > 0 && ( {snapshots.map((s) => ( goToTab("mods"))} > {s.name} ))} )} {backups.length > 0 && ( {backups.map((b) => ( goToTab("backups"))} > {b.name} ))} )} )}
↑↓ navigate · ↵ select · esc close ⌘K
); } function Item({ children, onSelect, value, }: { children: React.ReactNode; onSelect: () => void; value?: string; }) { return ( {children} ); }