/* * MHSF, Minehut Server List * All external content is rather licensed under the ECA Agreement * located here: https://mhsf.app/docs/legal/external-content-agreement * * All code under MHSF is licensed under the MIT License * by open source contributors * * Copyright (c) 2025 dvelo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ import type { OnlineServer } from "@/lib/types/mh-server"; import IconDisplay from "../icons/minecraft-icon-display"; import { Material } from "@/components/ui/material"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { toast } from "sonner"; import { useEffectOnce } from "@/lib/useEffectOnce"; import { allTags } from "@/config/tags"; import { type ReactNode, useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Copy } from "lucide-react"; import useClipboard from "@/lib/useClipboard"; import { useRouter } from "next/navigation"; import { MOTDRenderer } from "../server-page/motd/motd-renderer"; export default function ServerCard({ server }: { server: OnlineServer }) { const clipboard = useClipboard(); const router = useRouter(); return ( router.push(`/server/v2/minehut/${server.staticInfo._id}`)} tabIndex={0} onKeyDown={(e) => { // Only send user when they hit "Enter" if (e.key === "Enter") router.push(`/server/v2/minehut/${server.staticInfo._id}`); }} > Hit{" "} Enter {" "} to go to {server.name} {server.name} e.stopPropagation()} > Copy the server address to your clipboard. MHSF automatically adds .mhsf in-between the server name and minehut.gg to tell server owners where you came from. by {server.author || "Nobody"} e.stopPropagation()} > {server.author ? ( {server.name} is owned by{" "} ) : ( This server doesn't have a recorded owner because the server owner never linked their Minecraft account to their Minehut account. )} {server.motd && ( {server.motd} )} ); } export type BadgeColor = | "default" | "red" | "green" | "yellow" | "gray" | "blue" | "purple" | "red-subtle" | "green-subtle" | "yellow-subtle" | "gray-subtle" | "blue-subtle" | "purple-subtle" | "custom" | "rainbow"; export function TagShower(props: { server: OnlineServer; className?: string; unclickable?: boolean; }) { const [loading, setLoading] = useState(true); const [compatiableTags, setCompatiableTags] = useState< Array<{ name: ReactNode; docsName?: string; tooltip: string; htmlDocs: string; role: BadgeColor; }> >([]); useEffectOnce(() => { if (loading) { // biome-ignore lint/complexity/noForEach: no. allTags.forEach((tag) => { if (!tag.condition) { tag.name({ online: props.server }).then((n) => { compatiableTags.push({ name: n, docsName: tag.docsName, tooltip: tag.tooltipDesc, htmlDocs: tag.htmlDocs, role: tag.role === undefined ? "default" : tag.role, }); setLoading(false); }); } else tag.condition({ online: props.server }).then((b) => { if (b) { tag.name({ online: props.server }).then((n) => { compatiableTags.push({ name: n, docsName: tag.docsName, tooltip: tag.tooltipDesc, htmlDocs: tag.htmlDocs, role: tag.role === undefined ? "default" : tag.role, }); setLoading(false); }); } }); }); } }); if (loading) { return <>; } return (
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} > {compatiableTags.map((t, i) => ( {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
)}
))}
); } function RankColoring({ rank, author }: { rank: string; author: string }) { switch (rank.toLocaleLowerCase()) { case "default": return {author}; case "vip": return [VIP] {author}; case "vip_plus": return [VIP+] {author}; case "pro": return [PRO] {author}; case "legend": return [LEGEND] {author}; case "patron": return [PATRON] {author}; case "mod": return [MOD] {author}; default: return [STAFF] {author}; } }