feat: add new embeds

This commit is contained in:
dvelo 2024-11-16 15:01:14 -06:00
parent bdf5b9b52e
commit ddea87ccf8
21 changed files with 1789 additions and 930 deletions

8
.idea/.gitignore generated vendored Normal file

@ -0,0 +1,8 @@
# Ignore everything for IntelliJ except for project essential code-styles
*
!copyright/*
!codeStyles/*
!.gitignore
!*/

7
.idea/codeStyles/Project.xml generated Normal file

@ -0,0 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JSCodeStyleSettings version="0">
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.vscode/.gitignore vendored Normal file

@ -0,0 +1,6 @@
# Ignore everything for VSCode except for project essential code-styles
*
!extensions.json
!settings.json
!.gitignore

5
.vscode/extensions.json vendored Normal file

@ -0,0 +1,5 @@
{
"recommendations": [
"sarfrajansari.copyright-header-injector"
]
}

3
.vscode/settings.json vendored Normal file

@ -0,0 +1,3 @@
{
"copyright-header-injector.copyrightText": "/*\n * MHSF, Minehut Server List\n * All external content is rather licensed under the ECA Agreement\n * located here: https://mhsf.app/docs/legal/external-content-agreement\n *\n * All code under MHSF is licensed under the MIT License\n * by open source contributors\n *\n * Copyright (c) 2024 dvelo\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n */"
}

@ -25,6 +25,7 @@
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-primitive": "^2.0.0", "@radix-ui/react-primitive": "^2.0.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-switch": "^1.1.0",
"@unocss/eslint-plugin": "^0.61.5", "@unocss/eslint-plugin": "^0.61.5",
"@unocss/postcss": "^0.61.5", "@unocss/postcss": "^0.61.5",
@ -105,7 +106,7 @@
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-resizable-panels": "^2.0.23", "react-resizable-panels": "^2.0.23",
"recharts": "^2.12.7", "recharts": "^2.12.7",
"shiki": "^1.22.2", "shiki": "^1.23.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5", "typescript": "^5",
"vaul": "^0.9.1", "vaul": "^0.9.1",

@ -0,0 +1,39 @@
/*
* 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) 2024 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 Embed from "@/components/feat/Embed";
export default function EmbedPage({
params,
}: {
params: { server: string };
}) {
return <Embed params={params} />;
}

@ -0,0 +1,58 @@
/*
* 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) 2024 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.
*/
"use client";
import { TooltipProvider } from "@/components/ui/tooltip";
import "../globals.css";
import { ThemeProvider } from "@/components/ThemeProvider";
import { useSearchParams } from "next/navigation";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const searchParams = useSearchParams();
const search = searchParams?.get("theme") || "light";
return (
<html lang="en">
<body>
<ThemeProvider
attribute="class"
disableTransitionOnChange
forcedTheme={search}
>
<TooltipProvider>{children}</TooltipProvider>
</ThemeProvider>
</body>
</html>
);
}

@ -33,123 +33,114 @@ import Banner from "@/components/Banner";
import ColorProvider from "@/components/ColorProvider"; import ColorProvider from "@/components/ColorProvider";
import ServerView from "@/components/ServerView"; import ServerView from "@/components/ServerView";
import TabServer from "@/components/misc/TabServer"; import TabServer from "@/components/misc/TabServer";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { CornerDownLeft } from "lucide-react";
import type { Metadata, ResolvingMetadata } from "next"; import type { Metadata, ResolvingMetadata } from "next";
import Link from "next/link";
type Props = { type Props = {
params: { server: string }; params: { server: string };
}; };
export async function generateMetadata( export async function generateMetadata(
{ params }: Props, { params }: Props,
parent: ResolvingMetadata, parent: ResolvingMetadata
): Promise<Metadata> { ): Promise<Metadata> {
// read route params // read route params
const { server } = params; const { server } = params;
const json = await ( const json = await (
await fetch("https://api.minehut.com/server/" + server + "?byName=true") await fetch("https://api.minehut.com/server/" + server + "?byName=true")
).json(); ).json();
return { return {
themeColor: "#000000", themeColor: "#000000",
title: title:
json.server == null json.server == null
? "Server doesn't exist | MHSF" ? "Server doesn't exist | MHSF"
: json.server.name + : json.server.name +
", " + ", " +
(json.server.online (json.server.online
? json.server.playerCount + ? json.server.playerCount +
(json.server.maxPlayers != 10 (json.server.maxPlayers != 10
? "/" + json.server.maxPlayers ? "/" + json.server.maxPlayers
: "") + : "") +
" online" " online"
: "Offline") + : "Offline") +
" | MHSF", " | MHSF",
description: description:
json.server == null json.server == null
? `The server ${server} doesn't exist.` ? `The server ${server} doesn't exist.`
: `View ${server} on Minehut Server Finder!`, : `View ${server} on Minehut Server Finder!`,
authors: json.server == null ? undefined : { name: json.server.owner }, authors: json.server == null ? undefined : { name: json.server.owner },
applicationName: "MHSF (Minehut Server Finder)", applicationName: "MHSF (Minehut Server Finder)",
icons: icons:
json.server == null json.server == null
? undefined ? undefined
: "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + : "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" +
(json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) + (json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) +
".png", ".png",
twitter: { twitter: {
title: title:
json.server == null json.server == null
? "Server doesn't exist | MHSF" ? "Server doesn't exist | MHSF"
: json.server.name + : json.server.name +
", " + ", " +
(json.server.online (json.server.online
? json.server.playerCount + ? json.server.playerCount +
(json.server.maxPlayers != 10 (json.server.maxPlayers != 10
? "/" + json.server.maxPlayers ? "/" + json.server.maxPlayers
: "") + : "") +
" online" " online"
: "Offline") + : "Offline") +
" | MHSF", " | MHSF",
description: description:
json.server == null json.server == null
? `The server ${server} doesn't exist.` ? `The server ${server} doesn't exist.`
: `View ${server} on Minehut Server Finder!`, : `View ${server} on Minehut Server Finder!`,
images: [ images: [
{ {
url: url:
"https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" +
json.server.icon + json.server.icon +
".png", ".png",
}, },
{ {
url: "/public/imgs/icon-cf.png", url: "/public/imgs/icon-cf.png",
}, },
], ],
}, },
openGraph: { openGraph: {
type: "profile", type: "profile",
siteName: "MHSF (Minehut Server Finder)", siteName: "MHSF (Minehut Server Finder)",
images: [ images: [
{ {
url: url:
"https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" +
json.server.icon + json.server.icon +
".png", ".png",
}, },
{ {
url: "/public/imgs/icon-cf.png", url: "/public/imgs/icon-cf.png",
}, },
], ],
}, },
}; };
} }
export default function ServerPage({ params }: { params: { server: string } }) { export default function ServerPage({ params }: { params: { server: string } }) {
return ( return (
<main> <main>
<ColorProvider server={params.server}> <ColorProvider server={params.server}>
<div className={"pt-16"}> <div className={"pt-16"}>
<Banner server={params.server} /> <Banner server={params.server} />
<Link href="/"> <TabServer server={params.server} tabDef="general" />
<Button variant="link" className="text-muted-foreground text-sm"> <div className="pt-8">
<CornerDownLeft size={16} className="mr-2" /> Go back to the <ServerView server={params.server} />
server list </div>
</Button> <Separator />
</Link> <br />
<TabServer server={params.server} tabDef="general" /> <AfterServerView server={params.server} />
<div className="pt-8"> </div>
<ServerView server={params.server} /> </ColorProvider>
</div> </main>
<Separator /> );
<br />
<AfterServerView server={params.server} />
</div>
</ColorProvider>
</main>
);
} }

@ -33,12 +33,11 @@ import { getCommunityServerFavorites, getCustomization } from "@/lib/api";
import { MHSF } from "@/lib/mhsf"; import { MHSF } from "@/lib/mhsf";
import { ServerResponse } from "@/lib/types/mh-server"; import { ServerResponse } from "@/lib/types/mh-server";
import { import {
MinehutIcon, MinehutIcon,
getIndexFromRarity, getIndexFromRarity,
getMinehutIcons, getMinehutIcons,
rarityIndex,
} from "@/lib/types/server-icon"; } from "@/lib/types/server-icon";
import { Copy, Info } from "lucide-react"; import { Copy, ExternalLink, Info } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FadeIn from "react-fade-in/lib/FadeIn"; import FadeIn from "react-fade-in/lib/FadeIn";
@ -48,441 +47,458 @@ import IconDisplay from "./IconDisplay";
import AchievementList from "./feat/AchievementList"; import AchievementList from "./feat/AchievementList";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "./ui/card"; } from "./ui/card";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "./ui/drawer";
import EmbedSelector from "./feat/EmbedSelector";
export default function AfterServerView({ server }: { server: string }) { export default function AfterServerView({ server }: { server: string }) {
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [discord, setDiscord] = useState(""); const [discord, setDiscord] = useState("");
const [mhsf, setMHSF] = useState(new MHSF()); const [mhsf, setMHSF] = useState(new MHSF());
const [icons, setIcons] = useState<MinehutIcon[]>(); const [icons, setIcons] = useState<MinehutIcon[]>();
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [view, setView] = useState( const [view, setView] = useState(
description !== "" || discord !== "" ? "desc" : "extra", description !== "" || discord !== "" ? "desc" : "extra"
); );
const [serverObject, setServerObject] = useState<ServerResponse | undefined>( const [serverObject, setServerObject] = useState<ServerResponse | undefined>(
undefined, undefined
); );
const [copied, setCopied] = useState(false); const [embedOpened, setEmbedOpened] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => { useEffect(() => {
getCustomization(server).then((b) => { getCustomization(server).then((b) => {
if (b != null) { if (b != null) {
setDescription(b.description == null ? "" : b.description); setDescription(b.description == null ? "" : b.description);
setDiscord(b.discord == null ? "" : b.discord); setDiscord(b.discord == null ? "" : b.discord);
mhsf.setCustomizations(b); mhsf.setCustomizations(b);
getCommunityServerFavorites(server).then((c) => { getCommunityServerFavorites(server).then((c) => {
mhsf.setFavorites(c); mhsf.setFavorites(c);
}); });
} }
fetch("https://api.minehut.com/server/" + server + "?byName=true").then( fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
(c) => c.json().then((n) => setServerObject(n.server)), (c) => c.json().then((n) => setServerObject(n.server))
); );
getMinehutIcons().then((i) => { getMinehutIcons().then((i) => {
setIcons(i); setIcons(i);
}); });
setLoading(false); setLoading(false);
}); });
}, []); }, []);
if (loading) return <></>; if (loading) return <></>;
return ( return (
<> <>
<FadeIn> <Drawer open={embedOpened} onOpenChange={setEmbedOpened}>
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4"> <DrawerContent className="max-w-md w-full mx-auto rounded-t-[10px]">
<div className="ml-5 mb-2 flex items-center sm:hidden"> <DrawerHeader>
{(description != "" || discord != "") && ( <DrawerTitle>Embed Creator</DrawerTitle>
<Button </DrawerHeader>
variant={view == "desc" ? undefined : "ghost"} <EmbedSelector server={server} />
onClick={() => setView("desc")} </DrawerContent>
> </Drawer>
Description <FadeIn>
</Button> <div className="grid sm:grid-cols-6 h-full pl-4 pr-4 ">
)} <div className="ml-5 mb-2 flex items-center sm:hidden overflow-auto w-[calc(100vw-5rem)]">
<Button {(description != "" || discord != "") && (
variant={view == "extra" ? undefined : "ghost"} <Button
onClick={() => setView("extra")} variant={view == "desc" ? undefined : "ghost"}
className="ml-2" onClick={() => setView("desc")}
> >
Server Information Description
</Button> </Button>
<Button )}
variant={view == "achievements" ? undefined : "ghost"} <Button
onClick={() => setView("achievements")} variant={view == "extra" ? undefined : "ghost"}
className="ml-2" onClick={() => setView("extra")}
> className="ml-2"
Achievements >
</Button> Server Information
<Button </Button>
variant={view == "icons" ? undefined : "ghost"} <Button
onClick={() => setView("icons")} variant={view == "achievements" ? undefined : "ghost"}
> onClick={() => setView("achievements")}
Purchased Icons className="ml-2"
</Button> >
</div> Achievements
<div className="max-sm:hidden"> </Button>
<div className="grid"> <Button
{(description != "" || discord != "") && ( variant={view == "icons" ? undefined : "ghost"}
<Button onClick={() => setView("icons")}
variant={view == "desc" ? undefined : "ghost"} >
onClick={() => setView("desc")} Purchased Icons
> </Button>
Description <Button variant="ghost" onClick={() => setEmbedOpened(true)}>
</Button> Embed Creator
)} <ExternalLink className="h-[1.2rem] w-[1.2rem] ml-1" />
<Button </Button>
variant={view == "extra" ? undefined : "ghost"} </div>
onClick={() => setView("extra")} <div className="max-sm:hidden">
> <div className="grid">
Server Information {(description != "" || discord != "") && (
</Button> <Button
<Button variant={view == "desc" ? undefined : "ghost"}
variant={view == "achievements" ? undefined : "ghost"} onClick={() => setView("desc")}
onClick={() => setView("achievements")} >
> Description
Achievements </Button>
</Button> )}
<Button <Button
variant={view == "icons" ? undefined : "ghost"} variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("icons")} onClick={() => setView("extra")}
> >
Purchased Icons Server Information
</Button> </Button>
</div> <Button
</div> variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
>
Achievements
</Button>
<Button
variant={view == "icons" ? undefined : "ghost"}
onClick={() => setView("icons")}
>
Purchased Icons
</Button>
<Button variant="ghost" onClick={() => setEmbedOpened(true)}>
Embed Creator
<ExternalLink className="h-[1.2rem] w-[1.2rem] ml-1" />
</Button>
</div>
</div>
<div className="grid lg:grid-cols-4 pl-4 pr-4 gap-3.5 col-span-5"> <div className="grid lg:grid-cols-4 pl-4 pr-4 gap-3.5 col-span-5">
{description != "" && view == "desc" && ( {description != "" && view == "desc" && (
<Card className="sm:col-span-3"> <Card className="sm:col-span-3">
<CardDescription className="p-4 prose dark:prose-invert"> <CardDescription className="p-4 prose dark:prose-invert">
<Markdown <Markdown
components={{ components={{
img(props) { img(props) {
// wsrv.nl caches images for us AND protects the IP's of users // wsrv.nl caches images for us AND protects the IP's of users
return ( return (
<img src={`//wsrv.nl/?url=${props.src}`} {...props} /> <img src={`//wsrv.nl/?url=${props.src}`} {...props} />
); );
}, },
}} }}
> >
{description} {description}
</Markdown> </Markdown>
</CardDescription> </CardDescription>
</Card> </Card>
)} )}
{discord != "" && view == "desc" && ( {discord != "" && view == "desc" && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Discord Server</CardTitle> <CardTitle>Discord Server</CardTitle>
<CardDescription className="p-4 prose dark:prose-invert"> <CardDescription className="p-4 prose dark:prose-invert">
<iframe <iframe
src={`https://discord.com/widget?id=${discord}&theme=${resolvedTheme}`} src={`https://discord.com/widget?id=${discord}&theme=${resolvedTheme}`}
height="500" height="500"
allowTransparency={true} allowTransparency={true}
className="rounded-lg max-sm:w-[100px] max-md:w-[250px]" className="rounded-lg max-sm:w-[100px] max-md:w-[250px]"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
/> />
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
)}{" "} )}{" "}
{view == "achievements" && ( {view == "achievements" && (
<div className="col-span-4"> <div className="col-span-4">
<AchievementList server={server} /> <AchievementList server={server} />
</div> </div>
)} )}
{view == "extra" && ( {view == "extra" && (
<div className="sm:grid sm:grid-cols-3 col-span-4 gap-4"> <div className="sm:grid sm:grid-cols-3 col-span-4 gap-4">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Plan Details</CardTitle> <CardTitle>Plan Details</CardTitle>
<CardDescription> <CardDescription>
Information about the plan being used by the server Information about the plan being used by the server
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
{(() => { {(() => {
console.log(serverObject); console.log(serverObject);
return true; return true;
})()} })()}
<CardContent> <CardContent>
{" "} {" "}
<table className="table-auto w-full"> <table className="table-auto w-full">
<tr> <tr>
<th className="border p-2">Server plan</th> <th className="border p-2">Server plan</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.expired == undefined ? ( {serverObject?.expired == undefined ? (
<div className="flex items-center"> <div className="flex items-center">
Free{" "} Free{" "}
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<div> <div>
<Info size={16} className="ml-2" /> <Info size={16} className="ml-2" />
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
The plan is really unknown, but in most The plan is really unknown, but in most
scenarios, the Minehut API returns{" "} scenarios, the Minehut API returns{" "}
<code>undefined</code> if the server is free. <code>undefined</code> if the server is free.
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
) : ( ) : (
<>{serverObject?.activeServerPlan}</> <>{serverObject?.activeServerPlan}</>
)} )}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Raw plan</th> <th className="border p-2">Raw plan</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.expired == undefined ? ( {serverObject?.expired == undefined ? (
"? (unknown)" "? (unknown)"
) : ( ) : (
<code>{serverObject?.rawPlan}</code> <code>{serverObject?.rawPlan}</code>
)} )}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Credits per day</th> <th className="border p-2">Credits per day</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.credits_per_day == undefined {serverObject?.credits_per_day == undefined
? "? (unknown)" ? "? (unknown)"
: Math.floor(serverObject?.credits_per_day)} : Math.floor(serverObject?.credits_per_day)}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server expired</th> <th className="border p-2">Server expired</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.expired == undefined {serverObject?.expired == undefined
? "? (unknown)" ? "? (unknown)"
: toJSX(serverObject?.expired)} : toJSX(serverObject?.expired)}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server external</th> <th className="border p-2">Server external</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.rawPlan == undefined {serverObject?.rawPlan == undefined
? "? (unknown)" ? "? (unknown)"
: toJSX(serverObject?.rawPlan == "EXTERNAL")} : toJSX(serverObject?.rawPlan == "EXTERNAL")}
</td> </td>
</tr> </tr>
</table>{" "} </table>{" "}
</CardContent> </CardContent>
</Card> </Card>
<br className="sm:hidden" /> <br className="sm:hidden" />
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Additional Info</CardTitle> <CardTitle>Additional Info</CardTitle>
<CardDescription> <CardDescription>
Additional info that could be useful{" "} Additional info that could be useful{" "}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<table className="table-auto w-full"> <table className="table-auto w-full">
<tr> <tr>
<th className="border p-2">Icon</th> <th className="border p-2">Icon</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.icon == undefined ? ( {serverObject?.icon == undefined ? (
<> <>
Default (<code>OAK_SIGN</code>) Default (<code>OAK_SIGN</code>)
</> </>
) : ( ) : (
<code>{serverObject?.icon}</code> <code>{serverObject?.icon}</code>
)} )}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">All-time joins</th> <th className="border p-2">All-time joins</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.joins == undefined {serverObject?.joins == undefined
? "? (unknown)" ? "? (unknown)"
: serverObject?.joins} : serverObject?.joins}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server Type</th> <th className="border p-2">Server Type</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.server_version_type == undefined ? ( {serverObject?.server_version_type == undefined ? (
"? (unknown)" "? (unknown)"
) : ( ) : (
<> <>
{serverObject?.server_version_type.toLowerCase()} {serverObject?.server_version_type.toLowerCase()}
</> </>
)} )}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server Platform</th> <th className="border p-2">Server Platform</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.platform == undefined {serverObject?.platform == undefined
? "? (unknown)" ? "? (unknown)"
: serverObject?.platform} : serverObject?.platform}
</td> </td>
</tr> </tr>
</table> </table>
</CardContent> </CardContent>
</Card> </Card>
<br className="sm:hidden" /> <br className="sm:hidden" />
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<div> <div>
<span>Technical Info</span> <span>Technical Info</span>
<Tooltip> <Tooltip>
<TooltipContent className="font-normal tracking-normal"> <TooltipContent className="font-normal tracking-normal">
Copy JSON data about the server Copy JSON data about the server
</TooltipContent> </TooltipContent>
<TooltipTrigger> <TooltipTrigger>
<Button <Button
className="justify-right ml-2" className="justify-right ml-2"
size="icon" size="icon"
variant="secondary" variant="secondary"
onClick={() => { onClick={() => {
setCopied(true); setCopied(true);
try { try {
navigator.clipboard.writeText( navigator.clipboard.writeText(
JSON.stringify({ JSON.stringify({
minehut: serverObject, minehut: serverObject,
mhsf: mhsf.getMHSF(), mhsf: mhsf.getMHSF(),
}), })
); );
} catch { } catch {
toast.error( toast.error(
"Clipboard is inaccessible. Cannot copy", "Clipboard is inaccessible. Cannot copy"
); );
} }
toast.success( toast.success(
<div className="block w-[300px]"> <div className="block w-[300px]">
Copied the following: <br />{" "} Copied the following: <br />{" "}
<code className="flex items-center"> <code className="flex items-center">
{JSON.stringify({ {JSON.stringify({
minehut: serverObject, minehut: serverObject,
mhsf, mhsf,
}).substring(0, 36)} }).substring(0, 36)}
... ...
</code> </code>
</div>, </div>
); );
setTimeout(() => setCopied(false), 1000); setTimeout(() => setCopied(false), 1000);
}} }}
> >
{!copied && <Copy size={16} />} {!copied && <Copy size={16} />}
{copied && <CheckmarkIcon />} {copied && <CheckmarkIcon />}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
</Tooltip> </Tooltip>
</div> </div>
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
Technical information about the server{" "} Technical information about the server{" "}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<table className="table-auto w-full"> <table className="table-auto w-full">
<tr> <tr>
<th className="border p-2">Visible</th> <th className="border p-2">Visible</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.visibility == undefined {serverObject?.visibility == undefined
? "? (unknown)" ? "? (unknown)"
: toJSX(serverObject?.visibility)} : toJSX(serverObject?.visibility)}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server Port</th> <th className="border p-2">Server Port</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.port == undefined ? ( {serverObject?.port == undefined ? (
"? (unknown)" "? (unknown)"
) : ( ) : (
<code>{serverObject?.port}</code> <code>{serverObject?.port}</code>
)} )}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Storage Node</th> <th className="border p-2">Storage Node</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?.storage_node == undefined {serverObject?.storage_node == undefined
? "? (unknown)" ? "? (unknown)"
: serverObject?.storage_node.toUpperCase()} : serverObject?.storage_node.toUpperCase()}
</td> </td>
</tr> </tr>
<tr> <tr>
<th className="border p-2">Server ID</th> <th className="border p-2">Server ID</th>
<td className="border p-2"> <td className="border p-2">
{serverObject?._id == undefined ? ( {serverObject?._id == undefined ? (
"? (unknown)" "? (unknown)"
) : ( ) : (
<code>{serverObject?._id}</code> <code>{serverObject?._id}</code>
)} )}
</td> </td>
</tr> </tr>
</table> </table>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
)} )}
{view == "icons" && ( {view == "icons" && (
<div className="col-span-4"> <div className="col-span-4">
<p> <p>
Purchased Icons are icons that are under the server's Purchased Icons are icons that are under the server's
ownership, they may or may not available at that certain ownership, they may or may not available at that certain
moment either. moment either.
</p> </p>
{serverObject?.purchased_icons.map((icon) => ( {serverObject?.purchased_icons.map((icon) => (
<Card key={icon} className="my-4"> <Card key={icon} className="my-4">
<CardContent <CardContent
className="pt-4" className="pt-4"
style={{ style={{
color: getIndexFromRarity( color: getIndexFromRarity(
icons icons?.find((c) => c._id === icon)?.rank.toLowerCase()
?.find((c) => c._id === icon) ).text,
?.rank.toLowerCase(), }}
).text, >
}} <IconDisplay
> server={{
<IconDisplay icon: icons?.find((c) => c._id === icon)?.icon_name,
server={{ }}
icon: icons?.find((c) => c._id === icon)?.icon_name, className="mr-2"
}} />
className="mr-2" {icons?.find((c) => c._id === icon)?.display_name}
/> <span
{icons?.find((c) => c._id === icon)?.display_name} className="mx-2 p-1 pr-2 rounded italic font-bold"
<span style={{
className="mx-2 p-1 pr-2 rounded italic font-bold" backgroundColor: getIndexFromRarity(
style={{ icons
backgroundColor: getIndexFromRarity( ?.find((c) => c._id === icon)
icons ?.rank.toLowerCase()
?.find((c) => c._id === icon) ).bg,
?.rank.toLowerCase(), }}
).bg, >
}} {icons
> ?.find((c) => c._id === icon)
{icons ?.rank.toLocaleUpperCase()}
?.find((c) => c._id === icon) </span>
?.rank.toLocaleUpperCase()} </CardContent>
</span> </Card>
</CardContent> ))}
</Card> </div>
))} )}
</div> </div>
)} </div>
</div> <br />
</div> <br />
<br /> </FadeIn>
<br /> </>
</FadeIn> );
</>
);
} }
function toJSX(boolean: boolean) { function toJSX(boolean: boolean) {
if (boolean) { if (boolean) {
return <div className="text-green-400">True</div>; return <div className="text-green-400">True</div>;
} }
return <div className="text-red-400">False</div>; return <div className="text-red-400">False</div>;
} }

@ -430,7 +430,6 @@ export function ServerCommandBar() {
)} )}
</span> </span>
</h2> </h2>
{owned && ( {owned && (
<h2 className="flex items-center text-muted-foreground"> <h2 className="flex items-center text-muted-foreground">
<CheckIcon /> <CheckIcon />

@ -30,7 +30,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Spinner } from "./ui/spinner";
import { import {
Card, Card,
CardContent, CardContent,
@ -108,7 +107,7 @@ export default function ServerView(props: { server: string }) {
} }
return ( return (
<> <>
{single.grabOnline() == undefined && ( {single.grabOnline() == undefined && !single.grabOffline()?.online && (
<div className="grid pl-4 pr-4"> <div className="grid pl-4 pr-4">
<div <div
className=" rounded p-2" className=" rounded p-2"
@ -130,7 +129,8 @@ export default function ServerView(props: { server: string }) {
<Card className="sm:col-span-2"> <Card className="sm:col-span-2">
<BetterHeader> <BetterHeader>
<CardTitle className="flex items-center"> <CardTitle className="flex items-center">
{single.grabOnline() == undefined ? ( {single.grabOnline() == undefined &&
!single.grabOffline()?.online ? (
<div <div
className="items-center mr-1" className="items-center mr-1"
style={{ style={{

@ -0,0 +1,168 @@
/*
* 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) 2024 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.
*/
"use client";
import IconDisplay from "@/components/IconDisplay";
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/spinner";
import type { ServerResponse } from "@/lib/types/mh-server";
import { Copy, ExternalLink, ServerCrash } from "lucide-react";
import { notFound, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { Button } from "../ui/button";
import { CheckmarkIcon } from "react-hot-toast";
import useClipboard from "@/lib/useClipboard";
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
export default function Embed({ params }: { params: { server: string } }) {
const [serverFound, setServerFound] = useState(true);
const [loading, setLoading] = useState(true);
const [copied, setCopied] = useState(false);
const [serverObject, setServerObject] = useState<ServerResponse | null>(null);
const searchParams = useSearchParams();
const staticMode = searchParams?.get("static") === "true";
const noShowBranding = searchParams?.get("branding") === "false";
const clipboard = useClipboard();
useEffect(() => {
(async () => {
const serverFoundResponse = await fetch(
"https://api.minehut.com/server/" + params.server + "?byName=true"
);
const stream = await serverFoundResponse.json();
if (stream.server == null) setServerFound(false);
else setServerObject(stream.server);
setLoading(false);
})();
}, [params]);
if (loading) {
return <Spinner />;
}
if (!serverFound) {
notFound();
}
return (
<div className="rounded w-[390px] h-[145px] bg-muted">
<div
className={
"flex items-center text-sm cursor-pointer border-b p-2" +
(staticMode ? "" : " group")
}
onClick={() =>
window.open("/server/" + params.server, "_blank")?.focus()
}
>
<ServerCrash
size={16}
className="group-hover:text-white p-[4px] group-hover:p-[3px] w-[24px] h-[24px] transition-all bg-gradient-to-r group-hover:from-blue-600 group-hover:to-purple-500 group-hover:rounded"
/>
<span className="transition-colors ml-2 group-hover:bg-clip-text group-hover:text-transparent bg-gradient-to-r group-hover:from-blue-600 group-hover:to-purple-500">
Powered by MHSF
</span>
</div>
<div className="px-4 pt-2 flex items-center group overflow-hidden">
<div className={staticMode ? "block" : "group-hover:block hidden"}>
<Tooltip>
<TooltipTrigger>
<Button
variant="outline"
size="sm"
className="mb-1"
onClick={() => {
setCopied(true);
clipboard.writeText(params.server + ".mhsf.minehut.gg");
setTimeout(() => setCopied(false), 1000);
}}
>
{copied ? <CheckmarkIcon /> : <Copy size={16} />}
</Button>
</TooltipTrigger>
<TooltipContent>Copy this server IP</TooltipContent>
</Tooltip>{" "}
<br />
<Button
variant="outline"
size="sm"
onClick={() => {
window.open("/server/" + params.server, "_blank")?.focus();
}}
>
<ExternalLink size={16} />
</Button>
</div>
<IconDisplay
server={serverObject}
className={
"flex items-center mr-2" +
(staticMode ? " mb-1 ml-1" : " group-hover:mb-1 group-hover:ml-1")
}
/>
<div className={"block" + (staticMode ? " mb-1" : " group-hover:mb-1")}>
<strong className="text-lg">{params.server}</strong>{" "}
{!noShowBranding && <Badge variant="blue">on Minehut</Badge>}
<br />
<span className="text-sm">Joined {serverObject?.joins} times</span>
<br />
{serverObject?.online && (
<span className="flex items-center">
{serverObject.playerCount === 0 ? (
<div
className="items-center border"
style={{
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
) : (
<div
className="items-center"
style={{
backgroundColor: "#0cce6b",
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
)}
<span className="text-sm ml-1">
{serverObject.playerCount} player(s) online
</span>
</span>
)}
</div>
</div>
</div>
);
}

@ -0,0 +1,306 @@
/*
* 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) 2024 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.
*/
"use client";
import { TabsContent } from "@radix-ui/react-tabs";
import { Button } from "../ui/button";
import { DrawerFooter, DrawerTrigger } from "../ui/drawer";
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
import { useEffect, useState } from "react";
import { codeToHtml } from "shiki";
import { useTheme } from "next-themes";
import { Asterisk, Copy } from "lucide-react";
import useClipboard from "@/lib/useClipboard";
import toast from "react-hot-toast";
import { Checkbox } from "../ui/checkbox";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
export default function EmbedSelector({ server }: { server: string }) {
const { theme, systemTheme, resolvedTheme } = useTheme();
const [embedTheme, setEmbedTheme] = useState("");
const [embedStatic, setEmbedStatic] = useState(false);
const [highlightedHtml, setHighlightedHtml] = useState("");
const [highlightedJsx, setHighlightedJsx] = useState("");
const [selectedCodeType, setSelectedCodeType] = useState("jsx");
const [noMinehutBranding, setNoMinehutBranding] = useState(false);
const clipboard = useClipboard();
const [url, setURL] = useState(`https://mhsf.app/embed/${server}?`);
const [jsxCode, setJsxCode] = useState(`<iframe
src="${url}"
width={390}
height={145}
style={{ borderRadius: 0.25 }}
allow="clipboard-write"
frameBorder={0}
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
/>`);
const [htmlCode, setHtmlCode] = useState(`<iframe
src="${url}"
width="390"
height="145"
style="border-radius: 0.25rem;"
allow="clipboard-write"
frameborder="0"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
></iframe>`);
useEffect(() => {
setHtmlCode(`<iframe
src="${url}"
width="390"
height="145"
style="border-radius: 0.25rem;"
allow="clipboard-write"
frameborder="0"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
></iframe>`);
setJsxCode(`<iframe
src="${url}"
width={390}
height={145}
style={{ borderRadius: 0.25 }}
allow="clipboard-write"
frameBorder={0}
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
/>`);
const currentTheme = theme === "system" ? systemTheme : theme;
const selectedTheme =
currentTheme === "dark" ? "vitesse-dark" : "vitesse-light";
async function highlightCode() {
const jsx = await codeToHtml(jsxCode, {
lang: "jsx",
theme: selectedTheme,
});
const html = await codeToHtml(htmlCode, {
lang: "html",
theme: selectedTheme,
});
setHighlightedHtml(html);
setHighlightedJsx(jsx);
}
highlightCode();
}, [
theme,
systemTheme,
jsxCode,
htmlCode,
embedStatic,
noMinehutBranding,
url,
]);
const renderCode = (code: string, highlighted: string) => {
if (highlighted) {
return (
<div
className="h-full overflow-auto bg-background font-mono text-xs [&>pre]:h-full [&>pre]:!bg-transparent [&>pre]:p-4 [&_code]:break-all"
dangerouslySetInnerHTML={{ __html: highlighted }}
/>
);
} else {
return (
<pre className="h-full overflow-auto break-all bg-background p-4 font-mono text-xs text-foreground">
{code}
</pre>
);
}
};
return (
<>
<div className="p-4">
<div className="px-2 pb-8">
<div className="items-top flex space-x-2">
<Checkbox
id="static"
checked={embedStatic}
onCheckedChange={(c) => {
setEmbedStatic(c == "indeterminate" ? true : c);
setURL(
`https://mhsf.app/embed/${server}?${c ? "&static=true" : ""}${
noMinehutBranding ? "&branding=false" : ""
}&theme=${embedTheme}`
);
}}
/>
<div className="grid gap-1.5 leading-none">
<label
htmlFor="static"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Make embed static
</label>
<p className="text-sm text-muted-foreground">
Interactions with the embed take less resources but will be less
interactive.
</p>
</div>
</div>
<br />
<div className="items-top flex space-x-2">
<Checkbox
id="static"
checked={noMinehutBranding}
onCheckedChange={(c) => {
setNoMinehutBranding(c == "indeterminate" ? true : c);
setURL(
`https://mhsf.app/embed/${server}?${embedStatic ? "&static=true" : ""}${
c ? "&branding=false" : ""
}&theme=${embedTheme}`
);
}}
/>
<div className="grid gap-1.5 leading-none">
<label
htmlFor="static"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Remove Minehut branding
</label>
<p className="text-sm text-muted-foreground">
Enabling this will remove the "on Minehut" tag on the embed.
</p>
</div>
</div>
<br />
<div>
<label
htmlFor="theme"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Theme
</label>
<Select
name="theme"
value={embedTheme}
onValueChange={(c) => {
setEmbedTheme(c);
setURL(
`https://mhsf.app/embed/${server}?${embedStatic ? "&static=true" : ""}${
noMinehutBranding ? "&branding=false" : ""
}&theme=${c}`
);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Light" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Tabs defaultValue="preview" className="relative mr-auto w-full">
<TabsList className="w-full justify-start rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="preview"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Preview
</TabsTrigger>
<TabsTrigger
value="code"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
Code
</TabsTrigger>
</TabsList>
<TabsContent value="preview">
<iframe
src={`/embed/${server}?${embedStatic ? "&static=true" : ""}${
noMinehutBranding ? "&branding=false" : ""
}&theme=${embedTheme}`}
width={390}
height={145}
className="justify-center m-1"
style={{ borderRadius: "0.25rem" }}
allow="clipboard-write"
frameBorder={0}
sandbox="allow-forms allow-scripts"
/>
</TabsContent>
<TabsContent value="code">
<div className="bg-secondary h-[43px] px-3 py-1 rounded-b">
<div className="w-[130px] grid grid-cols-2">
<Button
size="icon"
className="h-8 w-16 justify-end"
variant="ghost"
onClick={() => {
if (selectedCodeType === "jsx") setSelectedCodeType("html");
else setSelectedCodeType("jsx");
}}
>
<Asterisk size={16} className="mr-1" />
{selectedCodeType === "jsx" ? <>JSX</> : <>HTML</>}
</Button>
<Button
size="icon"
className="h-8 w-8 justify-end"
variant="ghost"
onClick={() => {
clipboard.writeText(
selectedCodeType === "jsx" ? jsxCode : htmlCode
);
toast.success("Copied!");
}}
>
<Copy size={16} />
</Button>
</div>
</div>
{renderCode(
selectedCodeType === "jsx" ? jsxCode : htmlCode,
selectedCodeType === "jsx" ? highlightedJsx : highlightedHtml
)}
</TabsContent>
</Tabs>
</div>
<DrawerFooter>
<DrawerTrigger asChild>
<Button>Close</Button>
</DrawerTrigger>
</DrawerFooter>
</>
);
}

@ -32,7 +32,7 @@
import { useState } from "react"; import { useState } from "react";
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
import { useRouter } from "@/lib/useRouter"; import { useRouter } from "@/lib/useRouter";
import { Database, Home, Paintbrush } from "lucide-react"; import { CornerDownLeft, Database, Home, Paintbrush } from "lucide-react";
export default function TabServer({ export default function TabServer({
server, server,
@ -45,7 +45,7 @@ export default function TabServer({
const router = useRouter(); const router = useRouter();
return ( return (
<div className="w-full flex justify-center"> <div className="w-full px-4">
<Tabs <Tabs
value={tab} value={tab}
onValueChange={(tac) => { onValueChange={(tac) => {
@ -53,23 +53,40 @@ export default function TabServer({
if (tac == "customize") router.push(`/server/${server}/customize`); if (tac == "customize") router.push(`/server/${server}/customize`);
if (tac == "statistics") router.push(`/server/${server}/statistics`); if (tac == "statistics") router.push(`/server/${server}/statistics`);
if (tac == "general") router.push(`/server/${server}`); if (tac == "general") router.push(`/server/${server}`);
if (tac == "server-list") router.push("/");
}} }}
className="sm:w-[500px] max-sm:w-[200px]"
> >
<TabsList className="grid w-full grid-cols-3 max-sm:min-h-[50px]"> <TabsList className="border-b bg-transparent p-0 rounded-none w-full justify-start">
<TabsTrigger value="general" className=""> <TabsTrigger
value="general"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
{" "} {" "}
<div className="max-sm:hidden">General Information</div> <div className="max-sm:hidden">General Information</div>
<Home className="sm:hidden" size={18} /> <Home className="sm:hidden" size={18} />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="statistics"> <TabsTrigger
value="statistics"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
<div className="max-sm:hidden">Statistics</div> <div className="max-sm:hidden">Statistics</div>
<Database className="sm:hidden" size={18} /> <Database className="sm:hidden" size={18} />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="customize"> <TabsTrigger
value="customize"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
<div className="max-sm:hidden">Customization</div> <div className="max-sm:hidden">Customization</div>
<Paintbrush className="sm:hidden" size={18} /> <Paintbrush className="sm:hidden" size={18} />
</TabsTrigger> </TabsTrigger>
<TabsTrigger
value="server-list"
className="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
>
<CornerDownLeft size={16} className="mr-2" />
<div className="max-sm:hidden">Back to server list</div>
</TabsTrigger>
</TabsList> </TabsList>
</Tabs> </Tabs>
</div> </div>

@ -0,0 +1,158 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@/lib/utils"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

@ -73,6 +73,7 @@ export const allFolders: DocsFolder[] = [
name: "Legal", name: "Legal",
docs: [ docs: [
{ title: "ECA Agreement", url: "/docs/legal/external-content-agreement" }, { title: "ECA Agreement", url: "/docs/legal/external-content-agreement" },
{ title: "Email List", url: "/docs/legal/email-list" },
], ],
}, },
]; ];

@ -33,356 +33,378 @@ import A from "@/components/misc/Link";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
const User = ({ user }: { user: string }) => ( const User = ({ user }: { user: string }) => (
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]"> <span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
{user} {user}
</span> </span>
); );
const FeatureList = ({ const FeatureList = ({
features, features,
title, title,
}: { features: (string | ReactNode)[]; title: ReactNode }) => { }: {
return ( features: (string | ReactNode)[];
<ul> title: ReactNode;
{title} }) => {
{features.map((feature, i) => ( return (
<li key={i}> {feature}</li> <ul>
))} {title}
</ul> {features.map((feature, i) => (
); <li key={i}> {feature}</li>
))}
</ul>
);
}; };
export const version = "1.4.0"; export const version = "1.4.0";
export const changelog: { name: string; id: string; changelog: ReactNode }[] = [ export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
{ {
id: "ywvhtcs4k9rqjfp57x", id: "r9swempc7kaqd2j84nutv5",
name: "v1.4.5", name: "v1.5.0",
changelog: ( changelog: (
<FeatureList <FeatureList
features={["Add server icons"]} features={[
title={ "New embeds",
<strong className="flex items-center"> "More mobile friendly elements",
Version 1.4.5 (November 6th 2024) "Better tabs in the server",
</strong> "Fixed issue where some servers due to their age were not loading",
} ]}
/> title={
), <strong className="flex items-center">
}, Version 1.5.0 (November 16th 2024)
{ </strong>
id: "amq4suhgcfwrb7y5j6", }
name: "v1.4.0", />
changelog: ( ),
<FeatureList },
features={[ {
"Revamped documentation", id: "ywvhtcs4k9rqjfp57x",
"Revamped changelog UI", name: "v1.4.5",
"New hover joins chart", changelog: (
]} <FeatureList
title={ features={["Add server icons"]}
<strong className="flex items-center"> title={
Version 1.4.0 (November 3rd 2024) <strong className="flex items-center">
</strong> Version 1.4.5 (November 6th 2024)
} </strong>
/> }
), />
}, ),
{ },
id: "jeh48p7w9bx2k3ad6f", {
name: "v1.3.2", id: "amq4suhgcfwrb7y5j6",
changelog: ( name: "v1.4.0",
<div> changelog: (
<strong className="flex items-center"> <FeatureList
Version 1.3.2 (October 4th 2024) features={[
</strong> "Revamped documentation",
<ul> "Revamped changelog UI",
<li> Minor backend changes</li> "New hover joins chart",
<li> ]}
{" "} title={
<A alt="Please check on GitHub for statuses about this project."> <strong className="flex items-center">
Special:GitHub/releases/tag/1.3.2 Version 1.4.0 (November 3rd 2024)
</A> </strong>
</li> }
</ul> />
</div> ),
), },
}, {
{ id: "jeh48p7w9bx2k3ad6f",
id: "wvg9x5dbpj76sn4yrz", name: "v1.3.2",
name: "v1.3.0", changelog: (
changelog: ( <div>
<div> <strong className="flex items-center">
<strong className="flex items-center"> Version 1.3.2 (October 4th 2024)
Version 1.3.0 (September 9th 2024) </strong>
</strong> <ul>
<ul> <li> Minor backend changes</li>
<li> <li>
<A alt="New documentation linking">Docs:Reading</A> {" "}
</li> <A alt="Please check on GitHub for statuses about this project.">
<li> Special:GitHub/releases/tag/1.3.2
Achievements are here! See more at{" "} </A>
<A alt="here">Docs:Advanced/Achievements</A> </li>
</li> </ul>
<li> Finally fixed Cron actions for the final time</li> </div>
<li> Overhauled account preferences</li> ),
</ul> },
</div> {
), id: "wvg9x5dbpj76sn4yrz",
}, name: "v1.3.0",
{ changelog: (
name: "v1.2.0", <div>
changelog: ( <strong className="flex items-center">
<div> Version 1.3.0 (September 9th 2024)
<strong className="flex items-center"> </strong>
Version 1.2.0 (September 3rd 2024) <ul>
</strong> <li>
<ul> <A alt="New documentation linking">Docs:Reading</A>
<li> Added documentation</li> </li>
<li> Brand new linking of padding and server options</li> <li>
<li> New system to ensure automatic Cron actions!</li> Achievements are here! See more at{" "}
<li> and alot more!</li> <A alt="here">Docs:Advanced/Achievements</A>
</ul> </li>
</div> <li> Finally fixed Cron actions for the final time</li>
), <li> Overhauled account preferences</li>
id: "e482y9k5hvjt73urfx", </ul>
}, </div>
{ ),
name: "v1.1.0", },
changelog: ( {
<div> name: "v1.2.0",
<strong className="flex items-center"> changelog: (
Version 1.1.0 (August 24rd 2024) <div>
</strong> <strong className="flex items-center">
<ul> Version 1.2.0 (September 3rd 2024)
<li> Brand new hero page</li> </strong>
<li> New padding option on server list</li> <ul>
<li> New help guide</li> <li> Added documentation</li>
</ul> <li> Brand new linking of padding and server options</li>
</div> <li> New system to ensure automatic Cron actions!</li>
), <li> and alot more!</li>
id: "hfn9p243765x8bwurj", </ul>
}, </div>
{ ),
name: "v1.0.0", id: "e482y9k5hvjt73urfx",
changelog: ( },
<div> {
<strong className="flex items-center"> name: "v1.1.0",
Version 1.0.0 (August 22nd 2024) changelog: (
</strong> <div>
<ul> <strong className="flex items-center">
<li> 1.0!</li> Version 1.1.0 (August 24rd 2024)
<li> New hover card on server title hover</li> </strong>
<li> Moving to self-hosted cron jobs</li> <ul>
<li> Fixing some mobile issues</li> <li> Brand new hero page</li>
</ul> <li> New padding option on server list</li>
</div> <li> New help guide</li>
), </ul>
id: "a8w4xvjbg3s7ynehu6", </div>
}, ),
{ id: "hfn9p243765x8bwurj",
name: "v0.10.7", },
changelog: ( {
<div> name: "v1.0.0",
<strong className="flex items-center"> changelog: (
Version b-0.10.7 (August 18th 2024) <div>
</strong> <strong className="flex items-center">
<ul> Version 1.0.0 (August 22nd 2024)
<li> New server information tab on server pages</li> </strong>
</ul> <ul>
</div> <li> 1.0!</li>
), <li> New hover card on server title hover</li>
id: "asbt64h9fdyu8neqmp", <li> Moving to self-hosted cron jobs</li>
}, <li> Fixing some mobile issues</li>
{ </ul>
name: "v0.10.2", </div>
changelog: ( ),
<div> id: "a8w4xvjbg3s7ynehu6",
<strong className="flex items-center"> },
Version b-0.10.2 (August 18th 2024) {
</strong> name: "v0.10.7",
<ul> changelog: (
<li> Content fades-in on load</li> <div>
<li> Instead of using spinners, now we are using Skeletons</li> <strong className="flex items-center">
</ul> Version b-0.10.7 (August 18th 2024)
</div> </strong>
), <ul>
id: "kct29adbp6zug5r3q8", <li> New server information tab on server pages</li>
}, </ul>
{ </div>
name: "v0.10.0", ),
changelog: ( id: "asbt64h9fdyu8neqmp",
<div> },
<strong className="flex items-center"> {
Version b-0.10.0 (August 17th 2024) name: "v0.10.2",
</strong> changelog: (
<ul> <div>
<li> Revamped server list button list</li> <strong className="flex items-center">
<li> Added welcome dialog when first launching</li> Version b-0.10.2 (August 18th 2024)
<li> </strong>
Fixed an issue where servers were still able to be favorited <ul>
client-side when logged out <li> Content fades-in on load</li>
</li> <li> Instead of using spinners, now we are using Skeletons</li>
<li> Improved MOTD engine</li> </ul>
</ul> </div>
<br /> ),
<i>👀</i> id: "kct29adbp6zug5r3q8",
{/** Ensure Tailwind pre-renders all grid column types */} },
<span className="grid-cols-6" /> {
<span className="grid-cols-5" /> name: "v0.10.0",
<span className="grid-cols-4" /> changelog: (
</div> <div>
), <strong className="flex items-center">
id: "ah6t7c8sfzyrkp3u52", Version b-0.10.0 (August 17th 2024)
}, </strong>
{ <ul>
name: "v0.9.0", <li> Revamped server list button list</li>
changelog: ( <li> Added welcome dialog when first launching</li>
<div> <li>
<strong className="flex items-center"> Fixed an issue where servers were still able to be favorited
Version b-0.9.0 (August 15th 2024) client-side when logged out
</strong> </li>
<ul> <li> Improved MOTD engine</li>
<li> Adding favorites sorting option</li> </ul>
<li> Fixed right-click context menu on the server list</li> <br />
<li> Fixed metadata bugs</li> <i>👀</i>
</ul> {/** Ensure Tailwind pre-renders all grid column types */}
<br /> <span className="grid-cols-6" />
<i> <span className="grid-cols-5" />
Hey! Update on statistics. Recently, we have figured out the Minehut <span className="grid-cols-4" />
API is blocked to Vercel servers (atleast the <code>/servers</code>{" "} </div>
endpoint). I'm actively trying to find a loop-hole so that statistics ),
works correctly. Thank you {":)"} id: "ah6t7c8sfzyrkp3u52",
</i> },
<br /> {
</div> name: "v0.9.0",
), changelog: (
id: "kjxnrfazc7hp9q4e82", <div>
}, <strong className="flex items-center">
{ Version b-0.9.0 (August 15th 2024)
name: "v0.8.0", </strong>
changelog: ( <ul>
<div> <li> Adding favorites sorting option</li>
<strong className="flex items-center"> <li> Fixed right-click context menu on the server list</li>
Version b-0.8.0 (August 11th 2024) <li> Fixed metadata bugs</li>
</strong> </ul>
<ul> <br />
<li> Fixing up command bar</li> <i>
<li> Renaming "Short Term" to "Statistics"</li> Hey! Update on statistics. Recently, we have figured out the Minehut
</ul> API is blocked to Vercel servers (atleast the <code>/servers</code>{" "}
</div> endpoint). I'm actively trying to find a loop-hole so that statistics
), works correctly. Thank you {":)"}
id: "f8rmhwzuxk3qyds542", </i>
}, <br />
{ </div>
name: "v0.7.2", ),
changelog: ( id: "kjxnrfazc7hp9q4e82",
<div> },
<strong className="flex items-center"> {
Version b-0.7.2 (August 7th 2024) name: "v0.8.0",
</strong> changelog: (
<ul> <div>
<li> Adding new spinners to pages that needed it</li> <strong className="flex items-center">
<li> Fixed lots of bugs</li> Version b-0.8.0 (August 11th 2024)
<li> Moved from Inngest to Vercel Cron</li> </strong>
</ul> <ul>
</div> <li> Fixing up command bar</li>
), <li> Renaming "Short Term" to "Statistics"</li>
id: "g2rhxfj6bu8wqk43n7", </ul>
}, </div>
{ ),
name: "v0.7.0", id: "f8rmhwzuxk3qyds542",
changelog: ( },
<div> {
<strong className="flex items-center"> name: "v0.7.2",
Version b-0.7.0 (August 7th 2024) changelog: (
</strong> <div>
<ul> <strong className="flex items-center">
<li> Added customization to servers</li> Version b-0.7.2 (August 7th 2024)
<li> New button focus effect</li> </strong>
<li> Lots of bugfixes</li> <ul>
</ul> <li> Adding new spinners to pages that needed it</li>
</div> <li> Fixed lots of bugs</li>
), <li> Moved from Inngest to Vercel Cron</li>
id: "a5xb97jv3surwmqn62", </ul>
}, </div>
{ ),
name: "v0.6.0", id: "g2rhxfj6bu8wqk43n7",
changelog: ( },
<div> {
<strong className="flex items-center"> name: "v0.7.0",
Version b-0.6.0 (August 3rd 2024) changelog: (
</strong> <div>
<ul> <strong className="flex items-center">
<li> Enhanced shortcuts</li> Version b-0.7.0 (August 7th 2024)
<li> Added gradient beam to player count</li> </strong>
<li> Updated loading animations</li> <ul>
<li> Lots of bugfixes</li> <li> Added customization to servers</li>
</ul> <li> New button focus effect</li>
</div> <li> Lots of bugfixes</li>
), </ul>
id: "u83r5mkea9x4p2fjnb", </div>
}, ),
{ id: "a5xb97jv3surwmqn62",
name: "v0.4.5", },
changelog: ( {
<div> name: "v0.6.0",
<strong className="flex items-center"> changelog: (
Version b-0.4.5 (July 26th 2024): <div>
</strong> <strong className="flex items-center">
<ul> Version b-0.6.0 (August 3rd 2024)
<li> Made charts better</li> </strong>
<li> Sorted API endpoints</li> <ul>
</ul> <li> Enhanced shortcuts</li>
</div> <li> Added gradient beam to player count</li>
), <li> Updated loading animations</li>
id: "vu3k2daqj4y68bnwsp", <li> Lots of bugfixes</li>
}, </ul>
{ </div>
name: "v0.4.0", ),
changelog: ( id: "u83r5mkea9x4p2fjnb",
<div> },
<strong className="flex items-center"> {
Version b-0.4 (July 25th 2024): name: "v0.4.5",
</strong> changelog: (
<ul> <div>
<li> Added Info button</li> <strong className="flex items-center">
<li> Fixed Clerk in production</li> Version b-0.4.5 (July 26th 2024):
<li> Added Turbo for faster builds</li> </strong>
<li> <ul>
<strong>Added historical data</strong> <li> Made charts better</li>
</li> <li> Sorted API endpoints</li>
</ul> </ul>
</div> </div>
), ),
id: "psr9tx5jah74d32vq6", id: "vu3k2daqj4y68bnwsp",
}, },
{ {
name: "v0.3.0", name: "v0.4.0",
changelog: ( changelog: (
<div> <div>
<strong className="flex items-center"> <strong className="flex items-center">
Version b-0.3 (July 23th 2024): Version b-0.4 (July 25th 2024):
</strong> </strong>
<ul> <ul>
<li> <li> Added Info button</li>
Fixed minor bugs described by <User user="@Tarna" /> <li> Fixed Clerk in production</li>
</li> <li> Added Turbo for faster builds</li>
</ul> <li>
</div> <strong>Added historical data</strong>
), </li>
id: "m2ngpd6fwtv7xh5zrk", </ul>
}, </div>
{ ),
name: "v0.2.0", id: "psr9tx5jah74d32vq6",
changelog: ( },
<div> {
<strong className="flex items-center"> name: "v0.3.0",
Version b-0.2 (July 23th 2024): changelog: (
</strong> <div>
<ul> <strong className="flex items-center">
<li> Inital release!</li> Version b-0.3 (July 23th 2024):
</ul> </strong>
</div> <ul>
), <li>
id: "xsfw2rcnv7m3kuhpbq", Fixed minor bugs described by <User user="@Tarna" />
}, </li>
</ul>
</div>
),
id: "m2ngpd6fwtv7xh5zrk",
},
{
name: "v0.2.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.2 (July 23th 2024):
</strong>
<ul>
<li> Inital release!</li>
</ul>
</div>
),
id: "xsfw2rcnv7m3kuhpbq",
},
]; ];

@ -60,6 +60,9 @@ export default class ServerSingle {
if (this.online == true && skipOnline != true) { if (this.online == true && skipOnline != true) {
fetch("https://api.minehut.com/servers").then((l) => fetch("https://api.minehut.com/servers").then((l) =>
l.json().then((o) => { l.json().then((o) => {
if (o.servers.find((j: OnlineServer) => j.name == this.name) == undefined) {
g(true);
}
o.servers.forEach((j: OnlineServer) => { o.servers.forEach((j: OnlineServer) => {
if (j.name == this.name) { if (j.name == this.name) {
this.onlineObj = j; this.onlineObj = j;

118
yarn.lock

@ -1747,6 +1747,33 @@
"@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-select@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.2.tgz#2346e118966db793940f6a866fd4cc5db2cc275e"
integrity sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==
dependencies:
"@radix-ui/number" "1.1.0"
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-collection" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-direction" "1.1.0"
"@radix-ui/react-dismissable-layer" "1.1.1"
"@radix-ui/react-focus-guards" "1.1.1"
"@radix-ui/react-focus-scope" "1.1.0"
"@radix-ui/react-id" "1.1.0"
"@radix-ui/react-popper" "1.2.0"
"@radix-ui/react-portal" "1.1.2"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-visually-hidden" "1.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "2.6.0"
"@radix-ui/react-separator@^1.1.0": "@radix-ui/react-separator@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e" resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e"
@ -1969,39 +1996,39 @@
resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a" resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a"
integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ== integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==
"@shikijs/core@1.22.2": "@shikijs/core@1.23.0":
version "1.22.2" version "1.23.0"
resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.22.2.tgz#9c22bd4cc8a4d6c062461cfd35e1faa6c617ca25" resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.23.0.tgz#6504882c7cafc1176b2443e87609b68fbdf10096"
integrity sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg== integrity sha512-J4Fo22oBlfRHAXec+1AEzcowv+Qdf4ZQkuP/X/UHYH9+KA9LvyFXSXyS+HxuBRFfon+u7bsmKdRBjoZlbDVRkQ==
dependencies: dependencies:
"@shikijs/engine-javascript" "1.22.2" "@shikijs/engine-javascript" "1.23.0"
"@shikijs/engine-oniguruma" "1.22.2" "@shikijs/engine-oniguruma" "1.23.0"
"@shikijs/types" "1.22.2" "@shikijs/types" "1.23.0"
"@shikijs/vscode-textmate" "^9.3.0" "@shikijs/vscode-textmate" "^9.3.0"
"@types/hast" "^3.0.4" "@types/hast" "^3.0.4"
hast-util-to-html "^9.0.3" hast-util-to-html "^9.0.3"
"@shikijs/engine-javascript@1.22.2": "@shikijs/engine-javascript@1.23.0":
version "1.22.2" version "1.23.0"
resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.22.2.tgz#62e90dbd2ed1d78b972ad7d0a1f8ffaaf5e43279" resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.23.0.tgz#51728dd9d68e4ddc123734f816874aded38a1f11"
integrity sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw== integrity sha512-CcrppseWShG+8Efp1iil9divltuXVdCaU4iu+CKvzTGZO5RmXyAiSx668M7VbX8+s/vt1ZKu75Vn/jWi8O3G/Q==
dependencies: dependencies:
"@shikijs/types" "1.22.2" "@shikijs/types" "1.23.0"
"@shikijs/vscode-textmate" "^9.3.0" "@shikijs/vscode-textmate" "^9.3.0"
oniguruma-to-js "0.4.3" oniguruma-to-es "0.1.2"
"@shikijs/engine-oniguruma@1.22.2": "@shikijs/engine-oniguruma@1.23.0":
version "1.22.2" version "1.23.0"
resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.2.tgz#b12a44e3faf486e19fbcf8952f4b56b9b9b8d9b8" resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.23.0.tgz#c32610bdfe0850e54b10d9e1f5a689f63e681c91"
integrity sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA== integrity sha512-gS8bZLqVvmZXX+E5JUMJICsBp+kx6gj79MH/UEpKHKIqnUzppgbmEn6zLa6mB5D+sHse2gFei3YYJxQe1EzZXQ==
dependencies: dependencies:
"@shikijs/types" "1.22.2" "@shikijs/types" "1.23.0"
"@shikijs/vscode-textmate" "^9.3.0" "@shikijs/vscode-textmate" "^9.3.0"
"@shikijs/types@1.22.2": "@shikijs/types@1.23.0":
version "1.22.2" version "1.23.0"
resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.22.2.tgz#695a283f19963fe0638fc2646862ba5cfc4623a8" resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.23.0.tgz#860635725176d8b0cd07cda418fb319fc7a068da"
integrity sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg== integrity sha512-HiwzsihRao+IbPk7FER/EQT/D0dEEK3n5LAtHDzL5iRT+JMblA7y9uitUnjEnHeLkKigNM+ZplrP7MuEyyc5kA==
dependencies: dependencies:
"@shikijs/vscode-textmate" "^9.3.0" "@shikijs/vscode-textmate" "^9.3.0"
"@types/hast" "^3.0.4" "@types/hast" "^3.0.4"
@ -3460,6 +3487,11 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
emoji-regex-xs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
@ -6506,12 +6538,14 @@ onetime@^6.0.0:
dependencies: dependencies:
mimic-fn "^4.0.0" mimic-fn "^4.0.0"
oniguruma-to-js@0.4.3: oniguruma-to-es@0.1.2:
version "0.4.3" version "0.1.2"
resolved "https://registry.yarnpkg.com/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz#8d899714c21f5c7d59a3c0008ca50e848086d740" resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-0.1.2.tgz#157a34f2c6a469c053a5deecf3065f4163279cd4"
integrity sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ== integrity sha512-sBYKVJlIMB0WPO+tSu/NNB1ytSFeHyyJZ3Ayxfx3f/QUuXu0lvZk0VB4K7npmdlHSC0ldqanzh/sUSlAbgCTfw==
dependencies: dependencies:
regex "^4.3.2" emoji-regex-xs "^1.0.0"
regex "^4.4.0"
regex-recursion "^4.1.0"
oo-ascii-tree@^1.84.0: oo-ascii-tree@^1.84.0:
version "1.102.0" version "1.102.0"
@ -7028,7 +7062,19 @@ regenerator-runtime@^0.14.0:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regex@^4.3.2: regex-recursion@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-4.2.1.tgz#024ee28593b8158e568307b99bf1b7a3d5ea31e9"
integrity sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==
dependencies:
regex-utilities "^2.3.0"
regex-utilities@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280"
integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==
regex@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/regex/-/regex-4.4.0.tgz#cb731e2819f230fad69089e1bd854fef7569e90a" resolved "https://registry.yarnpkg.com/regex/-/regex-4.4.0.tgz#cb731e2819f230fad69089e1bd854fef7569e90a"
integrity sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ== integrity sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==
@ -7314,15 +7360,15 @@ shebang-regex@^3.0.0:
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shiki@^1.22.2: shiki@^1.23.0:
version "1.22.2" version "1.23.0"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.22.2.tgz#ed109a3d0850504ad5a1edf8496470a2121c5b7b" resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.23.0.tgz#83e6a2b71d0faf9fa1679b045266ba5de711b4c6"
integrity sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA== integrity sha512-xfdu9DqPkIpExH29cmiTlgo0/jBki5la1Tkfhsv+Wu5TT3APLNHslR1acxuKJOCWqVdSc+pIbs/2ozjVRGppdg==
dependencies: dependencies:
"@shikijs/core" "1.22.2" "@shikijs/core" "1.23.0"
"@shikijs/engine-javascript" "1.22.2" "@shikijs/engine-javascript" "1.23.0"
"@shikijs/engine-oniguruma" "1.22.2" "@shikijs/engine-oniguruma" "1.23.0"
"@shikijs/types" "1.22.2" "@shikijs/types" "1.23.0"
"@shikijs/vscode-textmate" "^9.3.0" "@shikijs/vscode-textmate" "^9.3.0"
"@types/hast" "^3.0.4" "@types/hast" "^3.0.4"