"use client"; import { useEffect, useRef, useState } from "react"; import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { Badge } from "./ui/badge"; import ServersList from "@/lib/list"; import { CircleUser, Network, Sun, Check, XIcon, Info, ArrowDownZA, LogIn, ImageIcon, } from "lucide-react"; import Stat from "./Stat"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import toast from "react-hot-toast"; import { allTags, allCategories } from "@/allTags"; import IconDisplay from "./IconDisplay"; import InfiniteScroll from "react-infinite-scroll-component"; import { Spinner } from "./ui/spinner"; import { CommandIcon } from "lucide-react"; import { OnlineServer } from "@/lib/types/mh-server"; import { useEffectOnce } from "@/lib/useEffectOnce"; import ServerCard from "./ServerCard"; import events from "@/lib/commandEvent"; import { BorderBeam } from "@/components/effects/border-beam"; import { useRouter } from "@/lib/useRouter"; import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, } from "@/components/ui/menubar"; import ClientFadeIn from "./ClientFadeIn"; import { Skeleton } from "./ui/skeleton"; import useClipboard from "@/lib/useClipboard"; import { SignedIn, SignedOut, useUser } from "@clerk/nextjs"; import Link from "next/link"; import SparklesText from "./effects/sparkles-text"; import Particles from "./effects/particles"; import { useTheme } from "next-themes"; import { ChatBubbleIcon, InputIcon } from "@radix-ui/react-icons"; import Marquee from "./effects/marquee"; import { cn } from "@/lib/utils"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { SignInPopover } from "./clerk/SignInPopoverButton"; import { BentoCard, BentoGrid } from "./effects/bento-grid"; const features = [ { Icon: ChatBubbleIcon, name: "Add a Discord widget", description: "Show where your players talk to each-other, including an online users count.", href: "/help/how-to-customize", cta: "Learn more", background: , className: "lg:row-start-1 lg:row-end-2 lg:col-start-2 lg:col-end-3", }, { Icon: InputIcon, name: "Descriptions", href: "/help/how-to-customize", cta: "Learn more", description: "Format your descriptions using Markdown to show what your server has to offer.", background: , className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-2", }, { Icon: ImageIcon, name: "Banners", href: "/help/how-to-customize", cta: "Learn more", description: "Show a banner with can contain images that show on your server page.", background: , className: "lg:col-start-3 lg:col-end-4 lg:row-start-1 lg:row-end-2", }, ]; export default function ServerList() { const [loading, setLoading]: any = useState(true); const [randomText, setRandomText] = useState(""); const [motdList, setMotdList] = useState({}); const allText = [""]; const getRandomText = () => { return allText[Math.floor(Math.random() * allText.length)]; }; const [templateFilter, setTemplateFilter] = useState(false); const [random, setRandom] = useState(false); const [serverList, setServerList] = useState(new ServersList([])); const [textCopied, setTextCopied] = useState(false); const [padding, setPadding] = useState(0); const bigger = async (server: OnlineServer) => server.playerData.playerCount > 15; const smaller = async (server: OnlineServer) => !server.staticInfo.alwaysOnline && server.playerData.playerCount < 15 && server.playerData.playerCount > 7; const [nameFilters, setNameFilters] = useState({}); const [inErrState, setErrState] = useState(false); const [servers, setServers] = useState>([]); const clipboard = useClipboard(); const router = useRouter(); const [pOS, setpOS] = useState(false); const [ipr, setIPR] = useState("4"); const [filters, setFilters] = useState< Array<(server: OnlineServer) => Promise> >([]); const [randomData, setRandomData] = useState( undefined ); const { resolvedTheme } = useTheme(); const [color, setColor] = useState("#ffffff"); useEffect(() => { setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000"); }, [resolvedTheme]); useEffectOnce(() => { setRandomText(getRandomText()); serverList .fetchDataAndFilter() .then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach((b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; }); setMotdList(updatedSL); setServers(serverList.currentServers); setLoading(false); }); }) .catch(() => setErrState(true)); }); const ref = useRef(null); const [clickedPage, setClickedPage] = useState("banners"); const [hero, setHero] = useState(false); const { isSignedIn } = useUser(); if (inErrState) { return ( <>

Hmm. Something is wrong. Reload the page.
); } if (loading) { return ( <>


); } return (
<> {(!isSignedIn || hero) && (
<> Meet MHSF,
the modern server finder

MHSF is the next generation server list for Minehut, with interactive filters,
{" "} intuitive keyboard shortcuts, and everything between.

Hero Image Hero Image


For players

Find what you want now, not later

Use interactive filters and customization modes to find the server of your choice
in less than 10 minutes.

{serverList.currentServers.slice(0, 20).map((server) => (
router.push(`/server/${server.name}`)} >
{server.name}
{server.author && (

by {server.author}

)}
))}
{serverList.currentServers.slice(0, 20).map((server) => (
router.push(`/server/${server.name}`)} >
{server.name}
{server.author && (

by {server.author}

)}
))}

For server owners

Make your server stand out

Servers can have custom banners, Discord widgets, color schemes, and descriptions, making your server stand out with information that can be shown to players.

{features.map((feature, idx) => ( ))}
)}
= 3200 ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500" : "" } > Servers online{" "}
} className="relative z-0" desc={
= 3200 ? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 " : "" } > {serverList.getExtraData().total_servers.toString()}
{serverList.getExtraData().total_servers >= 3200 && ( The server amount is over 3.2k, meaning that new servers have to go into a queue before being able to be online.{" "}
(the server count isn't entirely accurate, so sometimes you might not go into a queue even when the server count is at 3.2k)
)}
} icon={Network} > {serverList.getExtraData().total_servers >= 3200 && ( )} {serverList.currentServers[0] != undefined ? serverList.currentServers[0].name : "None"}{" "} {serverList.currentServers[0] != undefined && ( )} } icon={Sun} />

Servers events.emit("search-request-event")} > Search Servers +Shift+K { setRandomData(serverList.getRandomServer()); setRandom(true); }} > Pick Random Server { toast.promise( new Promise((s, e) => { setLoading(true); serverList .fetchDataAndFilter() .then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach( (b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; } ); setMotdList(updatedSL); setServers(serverList.currentServers); setLoading(false); s(false); }); }) .catch(() => { e(); }); }), { success: "Succesfully refreshed servers", loading: "Refreshing...", error: "Error while refreshing", } ); }} > Refresh Filter { toast.promise( new Promise((g, b) => { if (v == "smaller") { setTemplateFilter(true); var filt = nameFilters; filt["smaller-tf"] = true; filt["bigger-tf"] = false; setNameFilters(filt); var filt2 = filters; filt2.push(smaller); if (filt2.includes(bigger)) { filt2.splice(filt2.indexOf(bigger), 1); } setFilters(filt2); serverList.editFilters(filters); serverList.fetchDataAndFilter().then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach( (b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; } ); setMotdList(updatedSL); setServers(serverList.currentServers); g(undefined); }); }); } else if (v == "bigger") { setTemplateFilter(true); var filt = nameFilters; filt["smaller-tf"] = false; filt["bigger-tf"] = true; setNameFilters(filt); var filt2 = filters; filt2.push(bigger); filt2.splice(filt2.indexOf(smaller), 1); setFilters(filt2); serverList.editFilters(filters); serverList.fetchDataAndFilter().then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach( (b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; } ); setMotdList(updatedSL); setServers(serverList.currentServers); g(undefined); }); }); } else { var filt = nameFilters; filt["smaller-tf"] = false; filt["bigger-tf"] = false; setNameFilters(filt); setTemplateFilter(false); var filt2 = filters; filt2.splice(filt2.indexOf(smaller), 1); filt2.splice(filt2.indexOf(bigger), 1); setFilters(filt2); console.log(filters, filters.includes(smaller)); serverList.editFilters(filters); serverList.fetchDataAndFilter().then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach( (b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; } ); setMotdList(updatedSL); setServers(serverList.currentServers); g(undefined); }); }); } }), { error: "Error while changing filters", loading: "Changing filters...", success: "Changed filters!", } ); }} value={(() => { if (nameFilters["smaller-tf"]) { return "smaller"; } else if (nameFilters["bigger-tf"]) { return "bigger"; } else { return "none"; } })()} >
Only allow smaller servers
Only allow servers that have the player range 7-15, and cannot
be Always Online.
Only allow bigger servers
Only allow servers with more than 15 players.
No/custom requirements
Tags {allTags.map((tag) => (
{tag.docsName && tag.__filter == undefined && ( { return nameFilters["t-" + tag.docsName]; })()} onCheckedChange={async (b) => { var filt = nameFilters; filt["t-" + tag.docsName] = b; setNameFilters(filt); if (b) { var filt2 = filters; filt2.push(tag.condition); setFilters(filt2); } else { var filt2 = filters; filt2.splice(filt2.indexOf(tag.condition), 1); setFilters(filt2); } serverList.editFilters(filters); serverList.fetchDataAndFilter().then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach( (b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; } ); setMotdList(updatedSL); setServers(serverList.currentServers); }); }); }} > {tag.docsName} )}
))} Categories {allCategories.map((categorie) => ( { var filt = nameFilters; filt["c-" + categorie.name] = b; setNameFilters(filt); if (b) { var filt2 = filters; filt2.push(categorie.condition); setFilters(filt2); } else { var filt2 = filters; filt2.splice(filt2.indexOf(categorie.condition), 1); setFilters(filt2); } serverList.editFilters(filters); serverList.fetchDataAndFilter().then(() => { serverList.moveListDown(); let stringList: Array<{ server: string; motd: string; }> = []; let obj: any = {}; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach((b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; }); setMotdList(updatedSL); setServers(serverList.currentServers); }); }); }} checked={(() => { return nameFilters["c-" + categorie.name]; })()} > {categorie.name} ))}
View Grid 4 items per row 5 items per row 6 items per row Padding setPadding(Number(v))} > Default 15px 30px 40px 60px 100px 200px Only use padding on servers Sort c == "favorites" && router.push("/sort/favorites") } > Players Online Favorites Show Hero
{randomData == undefined && <>No data to randomize} {randomData != undefined && ( {randomData.name} {randomData.author != undefined ? (
by {randomData.author}
) : (
)}
{randomData.playerData.playerCount == 0 ? (
) : (
)} {randomData.playerData.playerCount}{" "} {randomData.playerData.playerCount == 1 ? "player" : "players"}{" "} currently online
Server IP

{randomData.name}.minehut.gg{" "} )}

{ serverList.moveListDown(); let stringList: Array<{ server: string; motd: string }> = []; serverList.currentServers.forEach((b) => { stringList.push({ motd: b.motd, server: b.name }); }); serverList.getMOTDs(stringList).then((c) => { var updatedSL = motdList; c.forEach((b: { server: string; motd: string }) => { updatedSL[b.server] = b.motd; }); setMotdList(updatedSL); setServers(serverList.currentServers); setLoading(false); }); }} loader={} endMessage={

You've seen it all", }} /> } style={{ overflow: "hidden !important", paddingLeft: pOS ? padding : 6, paddingRight: pOS ? padding : 6, }} > {/** This looks stupid, but its the only way that works */}

{servers.map((b: any) => ( <> ))}
); } export function TagShower(props: { server: OnlineServer; className?: string; unclickable?: boolean; }) { const [loading, setLoading] = useState(true); const [compatiableTags, setCompatiableTags] = useState< Array<{ name: string; docsName?: string; tooltip: string; htmlDocs: string; role: | "default" | "destructive" | "outline" | "secondary" | "red" | "orange" | "yellow" | "green" | "lime" | "blue" | "teal" | "cyan" | "violet" | "indigo" | "purple" | "fuchsia" | "pink"; }> >([]); useEffectOnce(() => { if (loading) { allTags.forEach((tag) => { tag.condition(props.server).then((b) => { if (b && tag.primary) { tag.name(props.server).then((n) => { compatiableTags.push({ name: n, docsName: tag.docsName, tooltip: tag.tooltipDesc, htmlDocs: tag.htmlDocs, role: tag.role == undefined ? "secondary" : tag.role, }); setLoading(false); }); } }); }); } }); if (loading) { return <>; } return (
{compatiableTags.map((t) => ( <> {props.unclickable && ( {t.name} )} {!props.unclickable && ( {t.name}
{t.tooltip}
Click the tag to learn more about it.
{'"'} {t.docsName == undefined ? t.name : t.docsName} {'"'} documentation
)} ))}
); }