feat: revamped documentation

This commit is contained in:
dvelo 2024-11-03 09:38:25 -06:00
parent dd9104f2f3
commit 3c1fd6cfc4
38 changed files with 3731 additions and 3237 deletions

@ -11,12 +11,12 @@ The command-bar has many mods and is a great tool for power-users to use as its
## Triggering the command-bar ## Triggering the command-bar
There are two ways to trigger the command bar, using `Ctrl+K` and `Ctrl+Shift+K`. Both put you in a command-bar, however when using `Ctrl+K`, you go into a general page with other settings. There are two ways to trigger the command bar, using `Ctrl+K` and <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd>. Both put you in a command-bar, however when using `Ctrl+K`, you go into a general page with other settings.
Using `Ctrl+Shift+K` opens a server viewer, and this may be faster then going through the general page. Using <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> opens a server viewer, and this may be faster then going through the general page.
## Functions using `Ctrl+K` ## Functions using Ctrl+K
- **Servers** opens a server list, same as `Ctrl+Shift+K` - **Servers** opens a server list, same as <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd>
- **Sort Servers** allows you to go into a sorted server list quickly - **Sort Servers** allows you to go into a sorted server list quickly
- **Links** shows links useful for MHSF - **Links** shows links useful for MHSF
- **Pick Random Server** picks a random server and shows the user what that server is, is similar to the one on [the server list](/) - **Pick Random Server** picks a random server and shows the user what that server is, is similar to the one on [the server list](/)

@ -5,11 +5,11 @@ folder: "Guides"
# Owning a server # Owning a server
Owning a server is quite simple and allows you to [customize your server](/docs/guides/customization) your server and make it stand out from other servers. Before owning your server, make sure you agree to the [ECA](Docs:legal/external-content-agreement). Owning a server is quite simple and allows you to [customize your server](/docs/guides/customization) and make it stand out from other servers. Before owning your server, make sure you agree to the [ECA](Docs:legal/external-content-agreement).
## Linking ## Linking
Find the server you would like to own (either by looking for it, or using the keyboard shortcut `Ctrl`+`Shift`+`K` and searching for it), and make sure your account has [already been linked with your Minecraft account](Docs:guides/linking). Go to the server, and hit the Customization tab. If the owner of the server, and the user your linked to match, you will gain access to the server. Find the server you would like to own (either by looking for it, or using the keyboard shortcut <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>K</kbd> and searching for it), and make sure your account has [already been linked with your Minecraft account](Docs:guides/linking). Go to the server, and hit the Customization tab. If the owner of the server, and the user your linked to match, you will gain access to the server.
If they match, you should see a button named Click to own. Press that button, and you should automagically own the server. Congratulations! If they match, you should see a button named Click to own. Press that button, and you should automagically own the server. Congratulations!
## I can't link my server, because my server doesn't have a author ## I can't link my server, because my server doesn't have a author

@ -19,6 +19,8 @@
"@emotion/is-prop-valid": "^1.3.0", "@emotion/is-prop-valid": "^1.3.0",
"@linear/sdk": "^31.0.0", "@linear/sdk": "^31.0.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-hover-card": "^1.1.1",
"@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",
@ -35,7 +37,7 @@
"inngest": "^3.21.2", "inngest": "^3.21.2",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"json-beautify": "^1.1.1", "json-beautify": "^1.1.1",
"lucide-react": "^0.416.0", "lucide-react": "^0.454.0",
"minimessage-2-html": "1.6.0", "minimessage-2-html": "1.6.0",
"mongodb": "^6.8.0", "mongodb": "^6.8.0",
"next": "14.2.10", "next": "14.2.10",
@ -63,17 +65,17 @@
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.1.5", "@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.1.3",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@types/canvas-confetti": "^1.6.4", "@types/canvas-confetti": "^1.6.4",
"@types/node": "^20", "@types/node": "^20",

@ -0,0 +1,129 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 TableOfContent from "@/components/docs/TOC";
import { ALegacy } from "@/components/misc/Link";
import { MDXElements } from "@/components/misc/MDXElements";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { allDocs } from "contentlayer/generated";
import { useMDXComponent } from "next-contentlayer/hooks";
import NextLink from "next/link";
import { notFound } from "next/navigation";
export const generateStaticParams = async () =>
allDocs.map((post) => ({ slug: [post._raw.flattenedPath] }));
export const generateMetadata = ({
params,
}: {
params: { slug: string[] };
}) => {
const post = allDocs.find(
(post) => post._raw.flattenedPath === params.slug.join("/"),
);
if (!post) notFound();
return { title: post.title + " | MHSF Docs", themeColor: "#000000" };
};
const PostLayout = ({ params }: { params: { slug: string[] } }) => {
const doc = allDocs.find(
(post) => post._raw.flattenedPath === params.slug.join("/"),
);
if (!doc) notFound();
console.log(doc);
const MDXContent = useMDXComponent(doc.body.code);
return (
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0">
<div className="pb-12 pt-8 prose dark:prose-invert">
{doc.folder && <span>{doc.folder}</span>}{" "}
{doc.lastUpdated && <span> - last updated {doc.lastUpdated}</span>}{" "}
<MDXContent
components={{
Separator,
a: (props) => <ALegacy {...props} />,
...MDXElements,
}}
/>
</div>
</div>
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12 space-y-2">
<p className="font-medium">On This Page</p>
{doc.toc.map(
(c: { level: number; text: string; slug: string }) => (
<TableOfContent toc={c} doc={doc} key={c.slug} />
),
)}
<br />
<div className="space-y-2">
<p className="font-medium">Contribute</p>
<ul className="m-0 list-none">
<li className="mt-0 pt-2">
<NextLink
href={
"https://github.com/DeveloLongScript/MHSF/edit/main/docs/" +
doc._raw.flattenedPath +
".mdx"
}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors no-underline"
>
<svg
viewBox="0 0 438.549 438.549"
fontSize={16}
className="mr-2 size-4"
>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
/>
</svg>
Edit page on GitHub
</NextLink>
</li>
</ul>
</div>
</div>
</ScrollArea>
</div>
</div>
)}
</main>
);
};
export default PostLayout;

@ -28,47 +28,75 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
"use client";
import { Sidebar } from "@/components/docs/Sidebar"; import { Sidebar } from "@/components/docs/Sidebar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { version } from "@/config/version"; import { version } from "@/config/version";
import { HamburgerMenuIcon } from "@radix-ui/react-icons"; import { HamburgerMenuIcon } from "@radix-ui/react-icons";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from "geist/font/sans";
import "../globals.css";
import "../../themes.css";
import { ThemeProvider } from "@/components/ThemeProvider";
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
import NextTopLoader from "@/lib/top-loader";
import { useRouter } from "@/lib/useRouter";
import { allDocs } from "contentlayer/generated";
import { GetServerSideProps } from "next";
import { usePathname } from "next/navigation";
interface Props {
pathname: string;
}
export default async function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( const pathname = usePathname();
<div className="border-b pt-[40px]"> return (
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10"> <ClerkThemeProvider className="">
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block"> <div className="theme-zinc">
<ScrollArea className="h-full py-6 pr-6 lg:py-8"> <NextTopLoader />
<div className="bg-muted w-full rounded justify-center p-4 flex items-center"> <SidebarProvider>
MHSF Docs <small className="ml-2">Version {version}</small> <Sidebar />
</div> <SidebarInset>
<br /> <div className="fixed backdrop-blur w-full flex h-16 z-10 items-center gap-2 px-4 ">
<Sidebar /> <SidebarTrigger />
</ScrollArea> <Separator orientation="vertical" className="mr-2 h-4" />
</aside> {
<br className="md:hidden" /> allDocs.find(
(c) =>
<div className="bg-muted w-full rounded justify-center p-4 flex items-center md:hidden"> c._raw.flattenedPath ===
MHSF Docs <small className="ml-2">Version {version}</small> pathname
<Drawer> ?.split("/")
<DrawerTrigger> .splice(2, pathname?.split("/").length)
<Button className="ml-2"> .join("/"),
<HamburgerMenuIcon /> )?.title
</Button> }
</DrawerTrigger> </div>
<DrawerContent className="p-4"> <div className="px-[100px] pt-[50px]">{children}</div>
<Sidebar /> </SidebarInset>
</DrawerContent> </SidebarProvider>
</Drawer> </div>
</div> </ClerkThemeProvider>
{children} );
</div>
</div>
);
} }

@ -28,104 +28,104 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { GeistSans } from "geist/font/sans";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { Analytics } from "@vercel/analytics/react"; import { Analytics } from "@vercel/analytics/react";
import "./globals.css"; import { SpeedInsights } from "@vercel/speed-insights/next";
import { TooltipProvider } from "@/components/ui/tooltip"; import { GeistSans } from "geist/font/sans";
import "../globals.css";
import ClientFadeIn from "@/components/ClientFadeIn";
import { CommandBarer } from "@/components/CommandBar";
import { BrandingGenericIcon } from "@/components/Icon";
import TextFromPathname from "@/components/TextFromPathname";
import { ThemeProvider } from "@/components/ThemeProvider"; import { ThemeProvider } from "@/components/ThemeProvider";
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider"; import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
import NextTopLoader from "@/lib/top-loader";
import { banner } from "@/config/banner";
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import Link from "next/link";
import TopBar from "@/components/clerk/Topbar"; import TopBar from "@/components/clerk/Topbar";
import TextFromPathname from "@/components/TextFromPathname";
import { Inter as interFont } from "next/font/google";
import { CommandBarer } from "@/components/CommandBar";
import ThemedToaster from "@/components/misc/ThemedToaster"; import ThemedToaster from "@/components/misc/ThemedToaster";
import UnofficalDialog from "@/components/misc/UnofficalDialog"; import UnofficalDialog from "@/components/misc/UnofficalDialog";
import ClientFadeIn from "@/components/ClientFadeIn"; import {
import { BrandingGenericIcon } from "@/components/Icon"; Breadcrumb,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { TooltipProvider } from "@/components/ui/tooltip";
import { banner } from "@/config/banner";
import NextTopLoader from "@/lib/top-loader";
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Inter as interFont } from "next/font/google";
import Link from "next/link";
export const extraMetadata = { export const extraMetadata = {
twitter: { twitter: {
images: [ images: [
{ {
url: "/public/imgs/icon-cf.png", url: "/imgs/icon-cf.png",
}, },
], ],
}, },
themeColor: "#000000", themeColor: "#000000",
openGraph: { openGraph: {
images: [ images: [
{ {
url: "/public/imgs/icon-cf.png", url: "/imgs/icon-cf.png",
}, },
], ],
}, },
} satisfies Metadata; } satisfies Metadata;
export const viewport: Viewport = { export const viewport: Viewport = {
themeColor: "black", themeColor: "black",
colorScheme: "dark", colorScheme: "dark",
}; };
const inter = interFont({ variable: "--font-inter", subsets: ["latin"] }); const inter = interFont({ variable: "--font-inter", subsets: ["latin"] });
export default async function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<ClerkThemeProvider className={GeistSans.className}> <ClerkThemeProvider className={GeistSans.className}>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="system" defaultTheme="system"
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
<TooltipProvider> <TooltipProvider>
{banner.isBanner && ( {banner.isBanner && (
<div className="bg-orange-600 z-10 w-screen h-8 border-b fixed text-black flex items-center text-center font-medium pl-2"> <div className="bg-orange-600 z-10 w-screen h-8 border-b fixed text-black flex items-center text-center font-medium pl-2">
{banner.bannerText} {banner.bannerText}
</div> </div>
)} )}
<div <div
className={ className={
"w-screen h-[3rem] border-b fixed backdrop-blur flex z-10 " + "w-screen h-[3rem] border-b fixed backdrop-blur flex z-10 " +
(banner.isBanner == true ? "mt-8" : "") (banner.isBanner == true ? "mt-8" : "")
} }
> >
<div className="items-center me-auto mt-2 pl-7 max-sm:mt-3"> <div className="items-center me-auto mt-2 pl-7 max-sm:mt-3">
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
<Link href="/"> <Link href="/">
<BreadcrumbPage className="max-sm:hidden"> <BreadcrumbPage className="max-sm:hidden">
<BrandingGenericIcon className="max-w-[32px] max-h-[32px] " /> <BrandingGenericIcon className="max-w-[32px] max-h-[32px] " />
</BreadcrumbPage> </BreadcrumbPage>
</Link> </Link>
<TextFromPathname /> <TextFromPathname />
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
</div> </div>
<TopBar inter={inter.className} /> <TopBar inter={inter.className} />
</div> </div>
<div className={banner.isBanner ? "pt-8" : undefined}> <div className={banner.isBanner ? "pt-8" : undefined}>
<NextTopLoader /> <NextTopLoader />
<ClientFadeIn>{children}</ClientFadeIn> <ClientFadeIn>{children}</ClientFadeIn>
</div>{" "} </div>{" "}
<ThemedToaster /> <ThemedToaster />
<CommandBarer /> <CommandBarer />
<SpeedInsights /> <SpeedInsights />
<Analytics /> <Analytics />
<UnofficalDialog /> <UnofficalDialog />
</TooltipProvider> </TooltipProvider>
</ThemeProvider> </ThemeProvider>
</ClerkThemeProvider> </ClerkThemeProvider>
); );
} }

@ -31,16 +31,16 @@
import Link from "next/link"; import Link from "next/link";
export default function NotFound() { export default function NotFound() {
return ( return (
<main> <main>
<div className="pt-[60px] p-4"> <div className="pt-[60px] p-4">
<strong>404 - Page not found</strong> <strong>404 - Page not found</strong>
<br /> <br />
<p> <p>
We couldn't find the page you were looking for.{" "} We couldn't find the page you were looking for.{" "}
<Link href="/">Go home</Link> <Link href="/public">Go home</Link>
</p> </p>
</div> </div>
</main> </main>
); );
} }

@ -32,34 +32,34 @@ import ServerList from "@/components/ServerList";
import { Metadata } from "next"; import { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "the MHSF project by dvelo", title: "the MHSF project by dvelo",
description: description:
process.env.NEXT_PUBLIC_VERCEL_ENV != undefined process.env.NEXT_PUBLIC_VERCEL_ENV != undefined
? `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${(process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA as string).substring(0, 7)}}) "${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE}" by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}` ? `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${(process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA as string).substring(0, 7)}}) "${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE}" by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`
: "currently running in dev", : "currently running in dev",
twitter: { twitter: {
images: [ images: [
{ {
url: "/public/imgs/icon-cf.png", url: "/imgs/icon-cf.png",
}, },
], ],
}, },
themeColor: "#000000", themeColor: "#000000",
openGraph: { openGraph: {
images: [ images: [
{ {
url: "/public/imgs/icon-cf.png", url: "/imgs/icon-cf.png",
}, },
], ],
}, },
}; };
export default function Home() { export default function Home() {
return ( return (
<main> <main>
<div className="pt-[60px]"> <div className="pt-[60px]">
<ServerList /> <ServerList />
</div> </div>
</main> </main>
); );
} }

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

@ -0,0 +1,116 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 Banner from "@/components/Banner";
import ColorProvider from "@/components/ColorProvider";
import { NewChart } from "@/components/NewChart";
import ServerView from "@/components/ServerView";
import TabServer from "@/components/misc/TabServer";
import { Separator } from "@/components/ui/separator";
import type { Metadata, ResolvingMetadata } from "next";
type Props = {
params: { server: string };
};
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata,
): Promise<Metadata> {
// read route params
const { server } = params;
const json = await (
await fetch("https://api.minehut.com/server/" + server + "?byName=true")
).json();
return {
title:
json.server == null
? "Server doesn't exist | MHSF"
: json.server.name +
", " +
(json.server.online
? json.server.playerCount +
(json.server.maxPlayers != 10
? "/" + json.server.maxPlayers
: "") +
" online"
: "Offline") +
" | MHSF",
description:
json.server == null
? `The server ${server} doesn't exist.`
: `View ${server} on Minehut Server Finder!`,
authors: json.server == null ? undefined : { name: json.server.owner },
applicationName: "MHSF (Minehut Server Finder)",
icons:
json.server == null
? undefined
: "https://mcapi.marveldc.me/item/" +
(json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) +
"?width=64&height=64",
openGraph: {
type: "profile",
siteName: "MHSF (Minehut Server Finder)",
images: [
{
url:
"https://mcapi.marveldc.me/item/" +
json.server.icon +
"?width=64&height=64",
},
{
url: "/favicon.ico",
},
],
},
};
}
export default function ServerPage({ params }: { params: { server: string } }) {
return (
<main>
<ColorProvider server={params.server}>
<div className={"pt-16"}>
<Banner server={params.server} />
<TabServer server={params.server} tabDef="statistics" />
<div className="pt-8">
<ServerView server={params.server} />
<Separator />
<br />
<div className="p-4 gap-4">
<NewChart server={params.server} />
</div>
</div>
</div>
</ColorProvider>
</main>
);
}

@ -1,129 +0,0 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 TableOfContent from "@/components/docs/TOC";
import { ScrollArea } from "@/components/ui/scroll-area";
import { allDocs } from "contentlayer/generated";
import { useMDXComponent } from "next-contentlayer/hooks";
import NextLink from "next/link";
import { notFound } from "next/navigation";
import { Separator } from "@/components/ui/separator";
import { ALegacy } from "@/components/misc/Link";
import { MDXElements } from "@/components/misc/MDXElements";
export const generateStaticParams = async () =>
allDocs.map((post) => ({ slug: [post._raw.flattenedPath] }));
export const generateMetadata = ({
params,
}: {
params: { slug: string[] };
}) => {
const post = allDocs.find(
(post) => post._raw.flattenedPath === params.slug.join("/")
);
if (!post) notFound();
return { title: post.title + " | MHSF Docs", themeColor: "#000000" };
};
const PostLayout = ({ params }: { params: { slug: string[] } }) => {
const doc = allDocs.find(
(post) => post._raw.flattenedPath === params.slug.join("/")
);
if (!doc) notFound();
console.log(doc);
const MDXContent = useMDXComponent(doc.body.code);
return (
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0">
<div className="pb-12 pt-8 prose dark:prose-invert">
{doc.folder && <span>{doc.folder}</span>}{" "}
{doc.lastUpdated && <span> - last updated {doc.lastUpdated}</span>}{" "}
<MDXContent
components={{
Separator,
a: (props) => <ALegacy {...props} />,
...MDXElements,
}}
/>
</div>
</div>
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12 space-y-2">
<p className="font-medium">On This Page</p>
{doc.toc.map(
(c: { level: number; text: string; slug: string }) => (
<TableOfContent toc={c} doc={doc} key={c.slug} />
)
)}
<br />
<div className="space-y-2">
<p className="font-medium">Contribute</p>
<ul className="m-0 list-none">
<li className="mt-0 pt-2">
<NextLink
href={
"https://github.com/DeveloLongScript/MHSF/edit/main/docs/" +
doc._raw.flattenedPath +
".mdx"
}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors no-underline"
>
<svg
viewBox="0 0 438.549 438.549"
fontSize={16}
className="mr-2 size-4"
>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
/>
</svg>
Edit page on GitHub
</NextLink>
</li>
</ul>
</div>
</div>
</ScrollArea>
</div>
</div>
)}
</main>
);
};
export default PostLayout;

@ -73,6 +73,15 @@
--color-one: #37ecba; --color-one: #37ecba;
--color-two: #72afd3; --color-two: #72afd3;
--color-three: #ff2e63; --color-three: #ff2e63;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-mhsf: 240 1% 92%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
} }
.dark { .dark {
@ -112,6 +121,15 @@
--chart-3: 216 92% 60%; --chart-3: 216 92% 60%;
--chart-4: 210 98% 78%; --chart-4: 210 98% 78%;
--chart-5: 212 97% 87%; --chart-5: 212 97% 87%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-mhsf: 240 0% 13%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
} }
} }
@ -123,11 +141,11 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }
@layer base { /*@layer base {*/
a { /* a {*/
@apply underline text-blue-400; /* @apply underline text-blue-400;*/
} /* }*/
} /*}*/
.backdrop-blur { .backdrop-blur {
-webkit-backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important;

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

@ -1,113 +0,0 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 ServerView from "@/components/ServerView";
import type { Metadata, ResolvingMetadata } from "next";
import TabServer from "@/components/misc/TabServer";
import { NewChart } from "@/components/NewChart";
import ColorProvider from "@/components/ColorProvider";
import Banner from "@/components/Banner";
type Props = {
params: { server: string };
};
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// read route params
const { server } = params;
const json = await (
await fetch("https://api.minehut.com/server/" + server + "?byName=true")
).json();
return {
title:
json.server == null
? "Server doesn't exist | MHSF"
: json.server.name +
", " +
(json.server.online
? json.server.playerCount +
(json.server.maxPlayers != 10
? "/" + json.server.maxPlayers
: "") +
" online"
: "Offline") +
" | MHSF",
description:
json.server == null
? `The server ${server} doesn't exist.`
: `View ${server} on Minehut Server Finder!`,
authors: json.server == null ? undefined : { name: json.server.owner },
applicationName: "MHSF (Minehut Server Finder)",
icons:
json.server == null
? undefined
: "https://mcapi.marveldc.me/item/" +
(json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) +
"?width=64&height=64",
openGraph: {
type: "profile",
siteName: "MHSF (Minehut Server Finder)",
images: [
{
url:
"https://mcapi.marveldc.me/item/" +
json.server.icon +
"?width=64&height=64",
},
{
url: "/favicon.ico",
},
],
},
};
}
export default function ServerPage({ params }: { params: { server: string } }) {
return (
<main>
<ColorProvider server={params.server}>
<div className={"pt-16"}>
<Banner server={params.server} />
<TabServer server={params.server} tabDef="statistics" />
<div className="pt-8">
<ServerView server={params.server} />
<div className="p-4 gap-4">
<NewChart server={params.server} />
</div>
</div>
</div>
</ColorProvider>
</main>
);
}

@ -30,382 +30,413 @@
"use client"; "use client";
import { getCommunityServerFavorites, getCustomization } from "@/lib/api"; import { getCommunityServerFavorites, getCustomization } from "@/lib/api";
import { useEffect, useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "./ui/card";
import Markdown from "react-markdown";
import { useTheme } from "next-themes";
import FadeIn from "react-fade-in/lib/FadeIn";
import { Button } from "./ui/button";
import { ServerResponse } from "@/lib/types/mh-server";
import { Copy, Info } from "lucide-react";
import toast, { CheckmarkIcon } from "react-hot-toast";
import { MHSF } from "@/lib/mhsf"; import { MHSF } from "@/lib/mhsf";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { ServerResponse } from "@/lib/types/mh-server";
import { MinehutIcon, getMinehutIcons } from "@/lib/types/server-icon";
import { Copy, Info } from "lucide-react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import FadeIn from "react-fade-in/lib/FadeIn";
import toast, { CheckmarkIcon } from "react-hot-toast";
import Markdown from "react-markdown";
import AchievementList from "./feat/AchievementList"; import AchievementList from "./feat/AchievementList";
import { Button } from "./ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "./ui/card";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
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 { resolvedTheme } = useTheme(); const [icons, setIcons] = useState<MinehutIcon[]>();
const [loading, setLoading] = useState(true); const { resolvedTheme } = useTheme();
const [view, setView] = useState( const [loading, setLoading] = useState(true);
description !== "" || discord !== "" ? "desc" : "extra" const [view, setView] = useState(
); description !== "" || discord !== "" ? "desc" : "extra",
const [serverObject, setServerObject] = useState<ServerResponse | undefined>( );
undefined const [serverObject, setServerObject] = useState<ServerResponse | undefined>(
); undefined,
const [copied, setCopied] = 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);
}); });
} getMinehutIcons().then((i) => {
fetch("https://api.minehut.com/server/" + server + "?byName=true").then( setIcons(i);
(c) => c.json().then((n) => setServerObject(n.server)) });
); }
setLoading(false); fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
}); (c) => c.json().then((n) => setServerObject(n.server)),
}, []); );
if (loading) return <></>; setLoading(false);
});
}, []);
if (loading) return <></>;
return ( return (
<> <>
<FadeIn> <FadeIn>
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4"> <div className="grid sm:grid-cols-6 h-full pl-4 pr-4">
<div className="ml-5 mb-2 flex items-center sm:hidden"> <div className="ml-5 mb-2 flex items-center sm:hidden">
{(description != "" || discord != "") && ( {(description != "" || discord != "") && (
<Button <Button
variant={view == "desc" ? undefined : "ghost"} variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")} onClick={() => setView("desc")}
> >
Description Description
</Button> </Button>
)} )}
<Button <Button
variant={view == "extra" ? undefined : "ghost"} variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")} onClick={() => setView("extra")}
className="ml-2" className="ml-2"
> >
Server Information Server Information
</Button> </Button>
<Button <Button
variant={view == "achievements" ? undefined : "ghost"} variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")} onClick={() => setView("achievements")}
className="ml-2" className="ml-2"
> >
Achievements Achievements
</Button> </Button>
</div> <Button
<div className="max-sm:hidden"> variant={view == "icons" ? undefined : "ghost"}
<div className="grid"> onClick={() => setView("icons")}
{(description != "" || discord != "") && ( >
<Button Purchased Icons
variant={view == "desc" ? undefined : "ghost"} </Button>
onClick={() => setView("desc")} </div>
> <div className="max-sm:hidden">
Description <div className="grid">
</Button> {(description != "" || discord != "") && (
)} <Button
<Button variant={view == "desc" ? undefined : "ghost"}
variant={view == "extra" ? undefined : "ghost"} onClick={() => setView("desc")}
onClick={() => setView("extra")} >
> Description
Server Information </Button>
</Button> )}
<Button <Button
variant={view == "achievements" ? undefined : "ghost"} variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("achievements")} onClick={() => setView("extra")}
> >
Achievements 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>
</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 disallowedElements={["img"]}> <Markdown disallowedElements={["img"]}>
{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>
)} )}
</div> {view == "icons" && (
</div> <div>
<br /> <p>
<br /> Purchased Icons are icons that are under the server's
</FadeIn> ownership, they may or may not available at that certain
</> moment either.
); </p>
{serverObject?.purchased_icons.map((icon) => (
<div key={icon}>
{icons?.find((c) => c._id === icon)?.icon_name}
</div>
))}
</div>
)}
</div>
</div>
<br />
<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>;
} }

@ -29,25 +29,27 @@
*/ */
"use client"; "use client";
import { Separator } from "@/components/ui/separator";
import { useState } from "react"; import { useState } from "react";
import TabServer from "./misc/TabServer";
import ServerCustomize from "./ServerCustomize";
import Banner from "./Banner"; import Banner from "./Banner";
import ServerCustomize from "./ServerCustomize";
import TabServer from "./misc/TabServer";
export default function CustomizeRoot({ export default function CustomizeRoot({
params, params,
}: { }: {
params: { server: string }; params: { server: string };
}) { }) {
const [color, setColor] = useState(""); const [color, setColor] = useState("");
return ( return (
<div className={"pt-16 theme-" + color}> <div className={"pt-16 theme-" + color}>
<Banner server={params.server} /> <Banner server={params.server} />
<TabServer server={params.server} tabDef="customize" /> <TabServer server={params.server} tabDef="customize" />
<br /> <Separator />
<div className="pl-[40px] pr-[40px]"> <br />
<ServerCustomize server={params.server} cs={color} setCS={setColor} /> <div className="pl-[40px] pr-[40px]">
</div> <ServerCustomize server={params.server} cs={color} setCS={setColor} />
</div> </div>
); </div>
);
} }

@ -28,108 +28,120 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { useClerk } from "@clerk/nextjs";
import { Cog, ExternalLink, KeyRound, Link, UserPen } from "lucide-react"; import { Cog, ExternalLink, KeyRound, Link, UserPen } from "lucide-react";
import NextLink from "next/link";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { import {
ResizableHandle, ResizableHandle,
ResizablePanel, ResizablePanel,
ResizablePanelGroup, ResizablePanelGroup,
} from "./ui/resizable"; } from "./ui/resizable";
import NextLink from "next/link";
import { useClerk } from "@clerk/nextjs";
export function Sidebar({ export function Sidebar({
children, children,
curPage, curPage,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
curPage: string; curPage: string;
}) { }) {
const clerk = useClerk(); const clerk = useClerk();
return ( return (
<ResizablePanelGroup <ResizablePanelGroup
direction="horizontal" direction="horizontal"
className="min-h-[calc(100vh-70px)] pt-[70px]" className="min-h-[calc(100vh-70px)] pt-[70px]"
> >
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]"> <ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
<div className="w-[300px] ml-[10px]"> <div className="w-[300px] ml-[10px]">
<NextLink href="/account/settings" className="text-inherit"> <NextLink
<Button href="/src/app/(main)/account/settings"
className="mb-[2px] w-[250px]" className="text-inherit"
variant={curPage !== "/account/settings" ? "ghost" : "default"} >
> <Button
<Link size={16} className="mr-2" /> Linking className="mb-[2px] w-[250px]"
</Button> variant={curPage !== "/account/settings" ? "ghost" : "default"}
</NextLink> >
<NextLink href="/account/settings/options" className="text-inherit"> <Link size={16} className="mr-2" /> Linking
<Button </Button>
className="mb-[2px] w-[250px] " </NextLink>
variant={ <NextLink
curPage !== "/account/settings/options" ? "ghost" : "default" href="/src/app/(main)/account/settings/options"
} className="text-inherit"
> >
<Cog size={16} className="mr-2" /> Options <Button
</Button> className="mb-[2px] w-[250px] "
</NextLink> variant={
<Button curPage !== "/account/settings/options" ? "ghost" : "default"
className="mb-[2px] w-[250px]" }
variant="ghost" >
onClick={() => clerk.openUserProfile({})} <Cog size={16} className="mr-2" /> Options
> </Button>
<UserPen size={16} className="mr-2" /> Profile{" "} </NextLink>
<ExternalLink size={16} className="ml-2" /> <Button
</Button> className="mb-[2px] w-[250px]"
<Button variant="ghost"
className="mb-[2px] w-[250px]" onClick={() => clerk.openUserProfile({})}
variant="ghost" >
onClick={() => clerk.openUserProfile({})} <UserPen size={16} className="mr-2" /> Profile{" "}
> <ExternalLink size={16} className="ml-2" />
<KeyRound size={16} className="mr-2" /> Security{" "} </Button>
<ExternalLink size={16} className="ml-2" /> <Button
</Button> className="mb-[2px] w-[250px]"
</div> variant="ghost"
</ResizablePanel> onClick={() => clerk.openUserProfile({})}
<ResizableHandle className="max-md:hidden" /> >
<ResizablePanel> <KeyRound size={16} className="mr-2" /> Security{" "}
<div className="md:hidden ml-2"> <ExternalLink size={16} className="ml-2" />
<NextLink href="/account/settings" className="text-inherit"> </Button>
<Button </div>
className="mr-[2px]" </ResizablePanel>
variant={curPage !== "/account/settings" ? "ghost" : "default"} <ResizableHandle className="max-md:hidden" />
> <ResizablePanel>
<Link size={16} className="mr-2" /> Linking <div className="md:hidden ml-2">
</Button> <NextLink
</NextLink> href="/src/app/(main)/account/settings"
<NextLink href="/account/settings/options" className="text-inherit"> className="text-inherit"
<Button >
className="mr-[2px]" <Button
variant={ className="mr-[2px]"
curPage !== "/account/settings/options" ? "ghost" : "default" variant={curPage !== "/account/settings" ? "ghost" : "default"}
} >
> <Link size={16} className="mr-2" /> Linking
<Cog size={16} className="mr-2" /> Options </Button>
</Button> </NextLink>
</NextLink> <NextLink
<Button href="/src/app/(main)/account/settings/options"
className="mr-[2px]" className="text-inherit"
variant="ghost" >
onClick={() => clerk.openUserProfile({})} <Button
> className="mr-[2px]"
<UserPen size={16} className="mr-2" /> Profile{" "} variant={
<ExternalLink size={16} className="ml-2" /> curPage !== "/account/settings/options" ? "ghost" : "default"
</Button> }
<Button >
className="mr-[2px] mb-[30px]" <Cog size={16} className="mr-2" /> Options
variant="ghost" </Button>
onClick={() => clerk.openUserProfile({})} </NextLink>
> <Button
<KeyRound size={16} className="mr-2" /> Security{" "} className="mr-[2px]"
<ExternalLink size={16} className="ml-2" /> variant="ghost"
</Button> onClick={() => clerk.openUserProfile({})}
</div> >
{children}{" "} <UserPen size={16} className="mr-2" /> Profile{" "}
</ResizablePanel> <ExternalLink size={16} className="ml-2" />
</ResizablePanelGroup> </Button>
); <Button
className="mr-[2px] mb-[30px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
{children}{" "}
</ResizablePanel>
</ResizablePanelGroup>
);
} }

@ -28,32 +28,15 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { NewChart } from "@/components/NewChart";
import { MiniJoinsChart } from "@/components/misc/MiniJoinsChart";
import { import {
ContextMenu, ContextMenu,
ContextMenuTrigger,
ContextMenuItem,
ContextMenuContent, ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator, ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu"; } from "@/components/ui/context-menu";
import toast, { LoaderIcon } from "react-hot-toast";
import {
CardHeader,
CardTitle,
CardDescription,
Card,
CardContent,
} from "./ui/card";
import IconDisplay from "./IconDisplay";
import { TagShower } from "./ServerList";
import {
ArrowRight,
ChartArea,
Copy,
EllipsisVertical,
Layers,
Star,
} from "lucide-react";
import { Button } from "./ui/button";
import { import {
Drawer, Drawer,
DrawerContent, DrawerContent,
@ -62,20 +45,39 @@ import {
DrawerTitle, DrawerTitle,
DrawerTrigger, DrawerTrigger,
} from "@/components/ui/drawer"; } from "@/components/ui/drawer";
import { Tooltip } from "@radix-ui/react-tooltip";
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { useRouter } from "@/lib/useRouter";
import Link from "next/link";
import { useState } from "react";
import { favoriteServer, isFavorited } from "@/lib/api";
import { useUser } from "@clerk/nextjs";
import { useTheme } from "next-themes";
import { import {
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
HoverCardTrigger, HoverCardTrigger,
} from "@/components/ui/hover-card"; } from "@/components/ui/hover-card";
import { favoriteServer, isFavorited } from "@/lib/api";
import useClipboard from "@/lib/useClipboard"; import useClipboard from "@/lib/useClipboard";
import { useRouter } from "@/lib/useRouter";
import { useUser } from "@clerk/nextjs";
import { Tooltip } from "@radix-ui/react-tooltip";
import {
ArrowRight,
ChartArea,
Copy,
EllipsisVertical,
Layers,
Star,
} from "lucide-react";
import { useTheme } from "next-themes";
import Link from "next/link";
import { useState } from "react";
import toast, { LoaderIcon } from "react-hot-toast";
import IconDisplay from "./IconDisplay";
import { TagShower } from "./ServerList";
import { Button } from "./ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "./ui/card";
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
export default function ServerCard({ b, motd, mini, favs }: any) { export default function ServerCard({ b, motd, mini, favs }: any) {
const router = useRouter(); const router = useRouter();
@ -130,6 +132,13 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
/> />
)} )}
</p> </p>
<br />
<br />
<strong className="text-sm font-semibold text-center">
Joins Chart
</strong>
<MiniJoinsChart server={b.name} />
<div className="flex items-center pt-2"> <div className="flex items-center pt-2">
<span className="text-xs text-muted-foreground flex items-center"> <span className="text-xs text-muted-foreground flex items-center">
<ArrowRight size={16} className="mr-2" /> <ArrowRight size={16} className="mr-2" />
@ -272,7 +281,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
</div> </div>
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<Link href={"/server/" + b.name}> <Link href={"/src/app/(main)/server/" + b.name}>
<ContextMenuItem>Open server page</ContextMenuItem> <ContextMenuItem>Open server page</ContextMenuItem>
</Link> </Link>
</ContextMenuContent> </ContextMenuContent>

@ -181,7 +181,7 @@ export default function ServerCustomize({
Is this server in violation of the ECA? Is this server in violation of the ECA?
</div> </div>
Is this server in violation of the{" "} Is this server in violation of the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/src/app/(main)/docs/legal/external-content-agreement">
External Content Agreement (aka ECA) External Content Agreement (aka ECA)
</Link> </Link>
? You can report the server to remove the customizations from the ? You can report the server to remove the customizations from the
@ -196,7 +196,7 @@ export default function ServerCustomize({
<DialogDescription> <DialogDescription>
This will send a notification to MHSF maintainers. This This will send a notification to MHSF maintainers. This
server must be in violation of the{" "} server must be in violation of the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/src/app/(main)/docs/legal/external-content-agreement">
ECA ECA
</Link>{" "} </Link>{" "}
to be a valid report. Typical response times include 1 hour to be a valid report. Typical response times include 1 hour
@ -329,7 +329,7 @@ export default function ServerCustomize({
Minehuts Terms of Service Minehuts Terms of Service
</Link>{" "} </Link>{" "}
& the{" "} & the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/src/app/(main)/docs/legal/external-content-agreement">
External Content Agreement External Content Agreement
</Link> </Link>
. .
@ -436,7 +436,7 @@ export default function ServerCustomize({
Imgurs Terms of Service Imgurs Terms of Service
</Link>{" "} </Link>{" "}
& the{" "} & the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/src/app/(main)/docs/legal/external-content-agreement">
External Content Agreement External Content Agreement
</Link> </Link>
. .
@ -468,7 +468,7 @@ export default function ServerCustomize({
Discords Terms of Service Discords Terms of Service
</Link>{" "} </Link>{" "}
& the{" "} & the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/src/app/(main)/docs/legal/external-content-agreement">
External Content Agreement External Content Agreement
</Link> </Link>
. .

File diff suppressed because it is too large Load Diff

@ -30,50 +30,50 @@
"use client"; "use client";
import { ClerkProvider } from "@clerk/nextjs"; import { ClerkProvider } from "@clerk/nextjs";
import { useTheme } from "next-themes";
import { dark } from "@clerk/themes"; import { dark } from "@clerk/themes";
import { useTheme } from "next-themes";
import { type ReactNode, useEffect, useState } from "react";
import { ThemeProvider } from "../ThemeProvider"; import { ThemeProvider } from "../ThemeProvider";
import { useEffect, useState } from "react";
export function ClerkThemeProvider({ export function ClerkThemeProvider({
children, children,
className, className,
}: { }: {
children: JSX.Element; children: ReactNode | ReactNode[];
className: string; className: string | undefined;
}) { }) {
const [theme, setTheme] = useState<string | undefined>(""); const [theme, setTheme] = useState<string | undefined>("");
return ( return (
<ClerkProvider <ClerkProvider
appearance={{ baseTheme: theme == "dark" ? dark : undefined }} appearance={{ baseTheme: theme == "dark" ? dark : undefined }}
> >
<html lang="en" className={className}> <html lang="en" className={className}>
<body> <body>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="system" defaultTheme="system"
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
{children} {children}
{/** This *has* to be implemented in component form for the `useTheme` to load at the appropriate time. */} {/** This *has* to be implemented in component form for the `useTheme` to load at the appropriate time. */}
<ThemeElement setTheme={setTheme} /> <ThemeElement setTheme={setTheme} />
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>
</ClerkProvider> </ClerkProvider>
); );
} }
function ThemeElement({ function ThemeElement({
setTheme, setTheme,
}: { }: {
setTheme: (update: string | undefined) => void; setTheme: (update: string | undefined) => void;
}) { }) {
const theme = useTheme(); const theme = useTheme();
useEffect(() => { useEffect(() => {
setTheme(theme.resolvedTheme); setTheme(theme.resolvedTheme);
}, [theme.resolvedTheme, setTheme]); }, [theme.resolvedTheme, setTheme]);
return <></>; return <></>;
} }

@ -0,0 +1,199 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 {
BadgeCheck,
Bell,
ChevronsUpDown,
Computer,
CreditCard,
LogIn,
LogOut,
Moon,
SettingsIcon,
Sparkles,
Sun,
} from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import { useClerk, useUser } from "@clerk/nextjs";
import { useTheme } from "next-themes";
export function NavUser() {
const { isMobile } = useSidebar();
const { user, isSignedIn } = useUser();
const clerk = useClerk();
const { setTheme, theme } = useTheme();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
{isSignedIn && (
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage
src={
user?.imageUrl == undefined
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
: user?.imageUrl
}
alt={user?.username || "?"}
/>
<AvatarFallback className="rounded-lg">?</AvatarFallback>
</Avatar>
)}
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{isSignedIn ? user?.username : "Not logged in"}
</span>
<span className="truncate text-xs">
{user?.primaryEmailAddress?.emailAddress}
</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
{isSignedIn && (
<>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage
src={
user?.imageUrl == undefined
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
: user?.imageUrl
}
alt={user?.username || "?"}
/>
<AvatarFallback className="rounded-lg">?</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user?.username}
</span>
<span className="truncate text-xs">
{user?.primaryEmailAddress?.emailAddress}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuGroup>
<DropdownMenuRadioGroup
onValueChange={(c) => setTheme(c)}
value={theme}
>
<DropdownMenuRadioItem value="dark">
<Moon /> Dark
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="light">
<Sun /> Light
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">
<Computer /> System
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
{isSignedIn && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => clerk.openUserProfile()}>
<SettingsIcon />
Account Settings
</DropdownMenuItem>
<DropdownMenuItem
className="text-red-500"
onSelect={() => clerk.signOut()}
>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)}
{!isSignedIn && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => clerk.openSignIn()}>
<LogIn />
Sign in
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}

@ -1,134 +1,51 @@
/* import * as React from "react";
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 { NavUser } from "@/components/docs/NavUser";
import { allFolders, Docs, DocsFolder } from "@/config/docs"; import { VersionSwitcher } from "@/components/docs/VersionSwitcher";
import { usePathname } from "next/navigation"; import {
import { Button } from "../ui/button"; Sidebar as ShadSidebar,
import { useState } from "react"; SidebarContent,
import { ChevronRight } from "lucide-react"; SidebarFooter,
import { useRouter } from "@/lib/useRouter"; SidebarGroup,
import { AnimatePresence, motion } from "framer-motion"; SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar";
import { allFolders } from "@/config/docs";
export function Sidebar() { export function Sidebar() {
return ( return (
<> <ShadSidebar>
{allFolders.map((docs) => ( <SidebarHeader>
<Folder docs={docs} key={"url" in docs ? docs.title : docs.name} /> <VersionSwitcher />
))} </SidebarHeader>
</> <SidebarContent>
); {/* We create a SidebarGroup for each parent. */}
} {allFolders.map((item) => (
<SidebarGroup key={item.name}>
function Folder({ docs }: { docs: any }) { <SidebarGroupLabel>{item.name}</SidebarGroupLabel>
const [folderOpen, setOpen] = useState(false); <SidebarGroupContent>
const router = useRouter(); <SidebarMenu>
const pathname = usePathname(); {item.docs.map((item) => (
<SidebarMenuItem key={item.title}>
return ( <SidebarMenuButton asChild>
<div key={"url" in docs ? docs.title : docs.name}> <a href={item.url}>{item.title}</a>
<Button </SidebarMenuButton>
size="sm" </SidebarMenuItem>
className="w-full font-normal tracking-normal mt-1" ))}
noJustify </SidebarMenu>
variant={ </SidebarGroupContent>
"url" in docs </SidebarGroup>
? pathname === docs.url ))}
? "default" </SidebarContent>
: "ghost" <SidebarFooter>
: "ghost" <NavUser />
} </SidebarFooter>
onClick={() => { <SidebarRail />
if ("docs" in docs) { </ShadSidebar>
setOpen(!folderOpen); );
} else {
router.push(docs.url);
}
}}
>
{"url" in docs ? docs.title : docs.name}
<div className="flex items-center ml-auto text-muted-foreground">
<AnimatePresence>
{"docs" in docs && folderOpen && (
<motion.div initial={{ rotate: 90 }} animate={{ rotate: 0 }}>
<ChevronRight size={18} />
</motion.div>
)}
{"docs" in docs && !folderOpen && (
<motion.div initial={{ rotate: 0 }} animate={{ rotate: 90 }}>
<ChevronRight size={18} />
</motion.div>
)}
</AnimatePresence>
</div>
</Button>
<div className="ml-2">
{folderOpen && <Subdocs docs={"docs" in docs ? docs.docs : []} />}
</div>
</div>
);
}
function Subdocs({ docs }: { docs: (Docs | DocsFolder)[] }) {
const pathname = usePathname();
const router = useRouter();
return (
<>
{docs.map((doc) => {
if ("docs" in doc) {
return <Subdocs docs={doc.docs} key={doc.name} />;
}
return (
<>
<Button
size="sm"
className="w-full font-normal tracking-normal mt-1"
noJustify
onClick={() => {
router.push(doc.url);
}}
key={doc.title}
variant={
"url" in doc
? pathname == doc.url
? "default"
: "ghost"
: "ghost"
}
>
{doc.title}
</Button>
<br key={doc.url} />
</>
);
})}
</>
);
} }

@ -28,59 +28,51 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { Button } from "../ui/button"; import { Changelog } from "@/components/changelog";
import { Book, Calendar, Star, TerminalIcon } from "lucide-react"; import { changelog, version } from "@/config/version";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import { useState } from "react";
import { Changelog, version } from "@/config/version";
import events from "@/lib/commandEvent"; import events from "@/lib/commandEvent";
import { ScrollArea } from "../ui/scroll-area";
import { useRouter } from "@/lib/useRouter"; import { useRouter } from "@/lib/useRouter";
import { Book, Calendar, Star, TerminalIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "../ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import { ScrollArea } from "../ui/scroll-area";
export default function InfoPopover() { export default function InfoPopover() {
const [changeLog, setChangelog] = useState(false); const router = useRouter();
const router = useRouter();
return ( return (
<div className="grid w-full"> <div className="grid w-full">
<strong className="text-center">The future of Minehut lists</strong> <strong className="text-center">The future of Minehut lists</strong>
<small className="text-center mb-3"> <small className="text-center mb-3">
Use filters, intuitive keyboard shortcuts and other features for Use filters, intuitive keyboard shortcuts and other features for
completely free, and *open-source. <br /> Currently on version{" "} completely free, and *open-source. <br /> Currently on version{" "}
<code>{version}</code>.<br />{" "} <code>{version}</code>.<br />{" "}
<small>* Licensed under the MIT License</small> <small>* Licensed under the MIT License</small>
</small> </small>
<Button variant={"ghost"} onClick={() => setChangelog(true)}> <Changelog items={changelog}>
<Calendar size={18} className="mr-2" /> Changelog <Button variant={"ghost"}>
</Button> <Calendar size={18} className="mr-2" /> Changelog
<Dialog open={changeLog} onOpenChange={setChangelog}> </Button>
<DialogContent> </Changelog>
<ScrollArea className="max-h-[500px]">
<DialogHeader>
<DialogTitle>Changelog</DialogTitle>
<Changelog />
</DialogHeader>
</ScrollArea>
</DialogContent>
</Dialog>
<Button <Button
variant={"ghost"} variant={"ghost"}
onClick={() => onClick={() =>
window window
.open("https://github.com/DeveloLongScript/MHSF", "_blank") .open("https://github.com/DeveloLongScript/MHSF", "_blank")
?.focus() ?.focus()
} }
> >
<Star size={18} className="mr-2" /> Star on GitHub <Star size={18} className="mr-2" /> Star on GitHub
</Button> </Button>
<Button variant={"ghost"} onClick={() => router.push("/docs")}> <Button variant={"ghost"} onClick={() => router.push("/docs")}>
<Book size={18} className="mr-2" /> See the docs <Book size={18} className="mr-2" /> See the docs
</Button> </Button>
<Button variant="ghost" onClick={() => events.emit("cmd-event")}> <Button variant="ghost" onClick={() => events.emit("cmd-event")}>
<TerminalIcon size={18} className="mr-2" /> Open commands <TerminalIcon size={18} className="mr-2" /> Open commands
</Button> </Button>
</div> </div>
); );
} }

@ -0,0 +1,111 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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 {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { getShortTermData } from "@/lib/api";
import { useEffectOnce } from "@/lib/useEffectOnce";
import * as React from "react";
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
const chartConfig = {
player_count: {
label: "Joins",
color: "hsl(var(--chart-1))",
},
} satisfies ChartConfig;
export function MiniJoinsChart({ server }: { server: string }) {
const [chartData, setChartData] = React.useState<any>([]);
const [loading, setLoading] = React.useState(true);
useEffectOnce(() => {
getShortTermData(server, ["player_count", "date"]).then((result) => {
setChartData(result.slice(-20));
setLoading(false);
});
});
return (
<div className="max-h-[160px] w-full">
<ChartContainer config={chartConfig}>
<LineChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<YAxis
dataKey={"player_count"}
tickLine={false}
axisLine={false}
tickFormatter={(value) => {
return value;
}}
/>
<XAxis
dataKey="date"
className="hidden"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
tickFormatter={(value) => {
return new Date(value).toLocaleTimeString("en-US", {
timeStyle: "short",
});
}}
/>
<ChartTooltip
content={
<ChartTooltipContent
className="w-[150px]"
nameKey={"player_count"}
indicator="line"
labelFormatter={(value) => {
return new Date(value).toLocaleTimeString("en-US", {
timeStyle: "short",
});
}}
/>
}
/>
<Line
dataKey={"player_count"}
type="monotone"
stroke={"var(--color-player_count)"}
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
</div>
);
}

@ -28,203 +28,203 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
"use client" "use client";
import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react";
import { Check, ChevronRight, Circle } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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",
className className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => ( >(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0 gap-3",
className className,
)} )}
{...props} {...props}
> >
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" /> <Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuPortal, DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

@ -28,60 +28,61 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
export const allFolders: (DocsFolder | Docs)[] = [ export const allFolders: DocsFolder[] = [
{ {
title: "Getting Started", name: "General",
url: "/docs/getting-started", docs: [
}, {
{ title: "Getting Started",
title: "Server List", url: "/docs/getting-started",
url: "/", },
}, {
{ title: "Reading",
title: "Reading", url: "/docs/reading",
url: "/docs/reading", },
}, ],
{ },
name: "Guides", {
docs: [ name: "Guides",
{ docs: [
title: "Linking", {
url: "/docs/guides/linking", title: "Linking",
}, url: "/docs/guides/linking",
{ },
title: "Owning a Server", {
url: "/docs/guides/owning-a-server", title: "Owning a Server",
}, url: "/docs/guides/owning-a-server",
{ },
title: "Server Customization", {
url: "/docs/guides/customization", title: "Server Customization",
}, url: "/docs/guides/customization",
{ title: "Reporting a server", url: "/docs/guides/reporting-server" }, },
], { title: "Reporting a server", url: "/docs/guides/reporting-server" },
}, ],
{ },
name: "Advanced", {
docs: [ name: "Advanced",
{ title: "Tech Stack", url: "/docs/advanced/tech-stack" }, docs: [
{ title: "Using the Command-bar", url: "/docs/advanced/command-bar" }, { title: "Tech Stack", url: "/docs/advanced/tech-stack" },
{ title: "Tips with external servers", url: "/docs/advanced/external" }, { title: "Using the Command-bar", url: "/docs/advanced/command-bar" },
{ title: "Achievements", url: "/docs/advanced/achievements" }, { title: "Tips with external servers", url: "/docs/advanced/external" },
], { title: "Achievements", url: "/docs/advanced/achievements" },
}, ],
{ },
name: "Legal", {
docs: [ name: "Legal",
{ title: "ECA Agreement", url: "/docs/legal/external-content-agreement" }, docs: [
], { title: "ECA Agreement", url: "/docs/legal/external-content-agreement" },
}, ],
},
]; ];
export type Docs = { export type Docs = {
title: string; title: string;
url: string; url: string;
}; };
export type DocsFolder = { export type DocsFolder = {
name: string; name: string;
docs: Array<Docs>; docs: Array<Docs>;
}; };

@ -278,7 +278,7 @@ export const allCategories: Array<{
]; ];
async function requestServer(s: OnlineServer): Promise<ServerResponse> { async function requestServer(s: OnlineServer): Promise<ServerResponse> {
if (serverCache[s.name] == undefined) { if (serverCache[s.name] === undefined) {
const re = await fetch( const re = await fetch(
"https://api.minehut.com/server/" + s.name + "?byName=true", "https://api.minehut.com/server/" + s.name + "?byName=true",
); );

@ -29,407 +29,346 @@
*/ */
"use client"; "use client";
import Image from "next/image"; import A from "@/components/misc/Link";
import Link from "next/link"; import type { ReactNode } from "react";
import { Separator } from "../components/ui/separator";
import { Button } from "../components/ui/button";
import confetti from "canvas-confetti";
export const version = "1.3.2";
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 handleClick = () => {
const duration = 5 * 1000;
const animationEnd = Date.now() + duration;
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
const randomInRange = (min: number, max: number) => const FeatureList = ({
Math.random() * (max - min) + min; features,
title,
const interval = window.setInterval(() => { }: { features: (string | ReactNode)[]; title: ReactNode }) => {
const timeLeft = animationEnd - Date.now(); return (
<ul>
if (timeLeft <= 0) { {title}
return clearInterval(interval); {features.map((feature, i) => (
} <li key={i}> {feature}</li>
))}
const particleCount = 50 * (timeLeft / duration); </ul>
confetti({ );
...defaults,
particleCount,
zIndex: 60,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
});
confetti({
...defaults,
particleCount,
zIndex: 60,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
});
}, 250);
};
export const Changelog = () => {
const router = useRouter();
return (
<>
<div>
Running on commit{" "}
<code>
<a
href={`https://github.com/DeveloLongScript/mhsf/commit/${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA}`}
>
{(
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || "unknown"
).substring(0, 7)}
</a>{" "}
{process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID != undefined &&
process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID != "" && (
<>
{" "}
| on PR{" "}
<a
href={`https://github.com/DeveloLongScript/MHSF/pull/${process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID}`}
>
{process.env.NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID}
</a>{" "}
by{" "}
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`}
>
{process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}
</a>
</>
)}{" "}
{process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE != undefined &&
`| ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE.substring(0, 24)}`}
</code>
</div>
<div className="md:grid md:grid-cols-3 gap-1.5">
<Button
className="text-sm hover:h-[60px] animate-all group block max-md:w-full max-md:mt-2"
onClick={() =>
window.open("https://discord.com/invite/cCyEeUs", "_blank")
}
>
<span className="group-hover:underline flex items-center">
<Discord className="mr-2"/> Discord
</span>
<Marquee
className="hidden group-hover:flex font-normal"
style={{"--duration": "15s"}}
>
Join the offical Minehut Discord server! Talk to people that like
MHSF too!
</Marquee>
</Button>
<Button
className="text-sm max-md:w-full max-md:mt-2 hover:h-[60px] animate-all group block "
onClick={() =>
window.open("https://github.com/DeveloLongScript/MHSF", "_blank")
}
>
<span className="group-hover:underline flex items-center">
<Github className="mr-2" fill={useDepTheme()}/> Star on GitHub
</span>
<Marquee
className="hidden group-hover:flex font-normal"
style={{"--duration": "10s"}}
>
Support the development of MHSF by starring it on GitHub!
</Marquee>
</Button>
<Button
className="text-sm max-md:w-full max-md:mt-2 hover:h-[60px] animate-all group block "
onClick={() => window.open("/docs", "_blank")}
>
<span className="group-hover:underline flex items-center">
<BookIcon className="mr-2" size={16}/> See the docs
</span>
<Marquee
className="hidden group-hover:flex font-normal"
style={{"--duration": "10s"}}
>
See more information about MHSF and how to use it
</Marquee>
</Button>
</div>
<br/>
<div>
<strong className="flex items-center">
Version 1.3.2 (October 4th 2024)
</strong>
<ul>
<li>
Minor backend changes
</li>
<li>
<A alt="Please check on GitHub for statuses about this project.">Special:GitHub/releases/tag/1.3.2</A>
</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version 1.3.0 (September 9th 2024)
</strong>
<ul>
<li>
<A alt="New documentation linking">Docs:Reading</A>
</li>
<li>
Achievements are here! See more at{" "}
<A alt="here">Docs:Advanced/Achievements</A>
</li>
<li> Finally fixed Cron actions for the final time</li>
<li> Overhauled account preferences</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version 1.2.0 (September 3rd 2024)
</strong>
<ul>
<li> Added documentation</li>
<li> Brand new linking of padding and server options</li>
<li> New system to ensure automatic Cron actions!</li>
<li> and alot more!</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version 1.1.0 (August 24rd 2024)
</strong>
<ul>
<li> Brand new hero page</li>
<li> New padding option on server list</li>
<li> New help guide</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version 1.0.0 (August 22nd 2024)
</strong>
<ul>
<li>
1.0!{" "}
<Button className="h-[25px] w-[50px] ml-2" onClick={handleClick}>
woah!
</Button>
</li>
<li> New hover card on server title hover</li>
<li> Moving to self-hosted cron jobs</li>
<li> Fixing some mobile issues</li>
</ul>
</div>
<br/>
<Separator/>
<br/>
<div>
<strong className="flex items-center">
Version b-0.10.7 (August 18th 2024)
</strong>
<ul>
<li> New server information tab on server pages</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.10.2 (August 18th 2024)
</strong>
<ul>
<li> Content fades-in on load</li>
<li> Instead of using spinners, now we are using Skeletons</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.10.0 (August 17th 2024)
</strong>
<ul>
<li> Revamped server list button list</li>
<li> Added welcome dialog when first launching</li>
<li>
Fixed an issue where servers were still able to be favorited
client-side when logged out
</li>
<li> Improved MOTD engine</li>
</ul>
<br/>
<i>👀</i>
{/** Ensure Tailwind pre-renders all grid column types */}
<span className="grid-cols-6"/>
<span className="grid-cols-5"/>
<span className="grid-cols-4"/>
</div>
<br/>
<br/>
<div>
<strong className="flex items-center">
Version b-0.9.0 (August 15th 2024)
</strong>
<ul>
<li> Adding favorites sorting option</li>
<li> Fixed right-click context menu on the server list</li>
<li> Fixed metadata bugs</li>
</ul>
<br/>
<i>
Hey! Update on statistics. Recently, we have figured out the Minehut
API is blocked to Vercel servers (atleast the <code>/servers</code>{" "}
endpoint). I'm actively trying to find a loop-hole so that statistics
works correctly. Thank you {":)"}
</i>
<br/>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.8.0 (August 11th 2024)
</strong>
<ul>
<li> Fixing up command bar</li>
<li> Renaming "Short Term" to "Statistics"</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.7.2 (August 7th 2024)
</strong>
<ul>
<li> Adding new spinners to pages that needed it</li>
<li> Fixed lots of bugs</li>
<li> Moved from Inngest to Vercel Cron</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.7.0 (August 7th 2024)
</strong>
<ul>
<li> Added customization to servers</li>
<li> New button focus effect</li>
<li> Lots of bugfixes</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.6.0 (August 3rd 2024)
</strong>
<ul>
<li> Enhanced shortcuts</li>
<li> Added gradient beam to player count</li>
<li> Updated loading animations</li>
<li> Lots of bugfixes</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.4.5 (July 26th 2024):
</strong>
<ul>
<li> Made charts better</li>
<li> Sorted API endpoints</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.4 (July 25th 2024):
</strong>
<ul>
<li> Added Info button</li>
<li> Fixed Clerk in production</li>
<li> Added Turbo for faster builds</li>
<li>
<strong>Added historical data</strong>
</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.3 (July 23th 2024):
</strong>
<ul>
<li>
Fixed minor bugs described by <User user="@Tarna"/>
</li>
</ul>
</div>
<br/>
<div>
<strong className="flex items-center">
Version b-0.2 (July 23th 2024):
</strong>
<ul>
<li> Inital release!</li>
</ul>
</div>
<br/>
<div>
<strong>All developers that helped out:</strong>
<Link href="https://dvelo.vercel.app">
<Image
src="/imgs/badge1.gif"
alt="cool badge"
width={88}
height={31}
className="w-[88px] h-[31px]"
/>
</Link>
</div>
</>
);
}; };
export const version = "1.4.0";
const Github = (props: SVGProps<SVGSVGElement>) => ( export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
<svg {
viewBox="0 0 256 250" id: "amq4suhgcfwrb7y5j6",
width="1em" name: "v1.4.0",
height="1em" changelog: (
fill="#24292f" <FeatureList
xmlns="http://www.w3.org/2000/svg" features={[
preserveAspectRatio="xMidYMid" "Revamped documentation",
{...props} "Revamped changelog UI",
> "New hover joins chart",
<path ]}
d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z" /> title={
</svg> <strong className="flex items-center">
); Version 1.4.0 (November 3rd 2024)
</strong>
import type { SVGProps } from "react"; }
import Marquee from "@/components/effects/marquee"; />
import { useRouter } from "@/lib/useRouter"; ),
import { BookIcon } from "lucide-react"; },
import A from "@/components/misc/Link"; {
import { useDepTheme } from "@/lib/getDependentTheming"; id: "jeh48p7w9bx2k3ad6f",
const Discord = (props: SVGProps<SVGSVGElement>) => ( name: "v1.3.2",
<svg changelog: (
viewBox="0 0 256 199" <div>
width="1em" <strong className="flex items-center">
height="1em" Version 1.3.2 (October 4th 2024)
xmlns="http://www.w3.org/2000/svg" </strong>
preserveAspectRatio="xMidYMid" <ul>
{...props} <li> Minor backend changes</li>
> <li>
<path {" "}
d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" <A alt="Please check on GitHub for statuses about this project.">
fill="#5865F2" Special:GitHub/releases/tag/1.3.2
/> </A>
</svg> </li>
); </ul>
</div>
),
},
{
id: "wvg9x5dbpj76sn4yrz",
name: "v1.3.0",
changelog: (
<div>
<strong className="flex items-center">
Version 1.3.0 (September 9th 2024)
</strong>
<ul>
<li>
<A alt="New documentation linking">Docs:Reading</A>
</li>
<li>
Achievements are here! See more at{" "}
<A alt="here">Docs:Advanced/Achievements</A>
</li>
<li> Finally fixed Cron actions for the final time</li>
<li> Overhauled account preferences</li>
</ul>
</div>
),
},
{
name: "v1.2.0",
changelog: (
<div>
<strong className="flex items-center">
Version 1.2.0 (September 3rd 2024)
</strong>
<ul>
<li> Added documentation</li>
<li> Brand new linking of padding and server options</li>
<li> New system to ensure automatic Cron actions!</li>
<li> and alot more!</li>
</ul>
</div>
),
id: "e482y9k5hvjt73urfx",
},
{
name: "v1.1.0",
changelog: (
<div>
<strong className="flex items-center">
Version 1.1.0 (August 24rd 2024)
</strong>
<ul>
<li> Brand new hero page</li>
<li> New padding option on server list</li>
<li> New help guide</li>
</ul>
</div>
),
id: "hfn9p243765x8bwurj",
},
{
name: "v1.0.0",
changelog: (
<div>
<strong className="flex items-center">
Version 1.0.0 (August 22nd 2024)
</strong>
<ul>
<li> 1.0!</li>
<li> New hover card on server title hover</li>
<li> Moving to self-hosted cron jobs</li>
<li> Fixing some mobile issues</li>
</ul>
</div>
),
id: "a8w4xvjbg3s7ynehu6",
},
{
name: "v0.10.7",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.10.7 (August 18th 2024)
</strong>
<ul>
<li> New server information tab on server pages</li>
</ul>
</div>
),
id: "asbt64h9fdyu8neqmp",
},
{
name: "v0.10.2",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.10.2 (August 18th 2024)
</strong>
<ul>
<li> Content fades-in on load</li>
<li> Instead of using spinners, now we are using Skeletons</li>
</ul>
</div>
),
id: "kct29adbp6zug5r3q8",
},
{
name: "v0.10.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.10.0 (August 17th 2024)
</strong>
<ul>
<li> Revamped server list button list</li>
<li> Added welcome dialog when first launching</li>
<li>
Fixed an issue where servers were still able to be favorited
client-side when logged out
</li>
<li> Improved MOTD engine</li>
</ul>
<br />
<i>👀</i>
{/** Ensure Tailwind pre-renders all grid column types */}
<span className="grid-cols-6" />
<span className="grid-cols-5" />
<span className="grid-cols-4" />
</div>
),
id: "ah6t7c8sfzyrkp3u52",
},
{
name: "v0.9.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.9.0 (August 15th 2024)
</strong>
<ul>
<li> Adding favorites sorting option</li>
<li> Fixed right-click context menu on the server list</li>
<li> Fixed metadata bugs</li>
</ul>
<br />
<i>
Hey! Update on statistics. Recently, we have figured out the Minehut
API is blocked to Vercel servers (atleast the <code>/servers</code>{" "}
endpoint). I'm actively trying to find a loop-hole so that statistics
works correctly. Thank you {":)"}
</i>
<br />
</div>
),
id: "kjxnrfazc7hp9q4e82",
},
{
name: "v0.8.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.8.0 (August 11th 2024)
</strong>
<ul>
<li> Fixing up command bar</li>
<li> Renaming "Short Term" to "Statistics"</li>
</ul>
</div>
),
id: "f8rmhwzuxk3qyds542",
},
{
name: "v0.7.2",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.7.2 (August 7th 2024)
</strong>
<ul>
<li> Adding new spinners to pages that needed it</li>
<li> Fixed lots of bugs</li>
<li> Moved from Inngest to Vercel Cron</li>
</ul>
</div>
),
id: "g2rhxfj6bu8wqk43n7",
},
{
name: "v0.7.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.7.0 (August 7th 2024)
</strong>
<ul>
<li> Added customization to servers</li>
<li> New button focus effect</li>
<li> Lots of bugfixes</li>
</ul>
</div>
),
id: "a5xb97jv3surwmqn62",
},
{
name: "v0.6.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.6.0 (August 3rd 2024)
</strong>
<ul>
<li> Enhanced shortcuts</li>
<li> Added gradient beam to player count</li>
<li> Updated loading animations</li>
<li> Lots of bugfixes</li>
</ul>
</div>
),
id: "u83r5mkea9x4p2fjnb",
},
{
name: "v0.4.5",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.4.5 (July 26th 2024):
</strong>
<ul>
<li> Made charts better</li>
<li> Sorted API endpoints</li>
</ul>
</div>
),
id: "vu3k2daqj4y68bnwsp",
},
{
name: "v0.4.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.4 (July 25th 2024):
</strong>
<ul>
<li> Added Info button</li>
<li> Fixed Clerk in production</li>
<li> Added Turbo for faster builds</li>
<li>
<strong>Added historical data</strong>
</li>
</ul>
</div>
),
id: "psr9tx5jah74d32vq6",
},
{
name: "v0.3.0",
changelog: (
<div>
<strong className="flex items-center">
Version b-0.3 (July 23th 2024):
</strong>
<ul>
<li>
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",
},
];

@ -30,19 +30,28 @@
import { LinearClient, LinearFetch, User } from "@linear/sdk"; import { LinearClient, LinearFetch, User } from "@linear/sdk";
export async function createReportIssue(server: string, reportDescription: string, userId: string) { export async function createReportIssue(
const linearClient = new LinearClient({ server: string,
apiKey: process.env.LINEAR reportDescription: string,
}) userId: string,
) {
const linearClient = new LinearClient({
apiKey: process.env.LINEAR,
});
const allTeams = await linearClient.teams(); const allTeams = await linearClient.teams();
// Always grabs the first issue category. // Always grabs the first issue category.
const team = allTeams.nodes[0]; const team = allTeams.nodes[0];
// Ensure there *actually* is a team there // Ensure there *actually* is a team there
if (team.id) { if (team.id) {
await linearClient.createIssue({teamId: team.id, title: `Issue against server \`${server}\``, description: desc(userId, server, reportDescription), assigneeId: (await team.members()).nodes[0].id }) await linearClient.createIssue({
} teamId: team.id,
title: `Issue against server \`${server}\``,
description: desc(userId, server, reportDescription),
assigneeId: (await team.members()).nodes[0].id,
});
}
} }
const desc = (user: string, server: string, reason: string) => `There was a report against the server, submitted by a user. const desc = (user: string, server: string, reason: string) => `There was a report against the server, submitted by a user.
@ -56,4 +65,4 @@ ${reason}
*This was an automatically added issue by the report bot. Add the canceled status to remove the issue from the active issues, along with the labels Not Controllable & Spam for their respective values.* *This was an automatically added issue by the report bot. Add the canceled status to remove the issue from the active issues, along with the labels Not Controllable & Spam for their respective values.*
` `;

@ -0,0 +1,58 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/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.
*/
export async function getMinehutIcons(): Promise<MinehutIcon[] | undefined> {
const icons = await fetch("https://api.minehut.com/servers/icons");
console.log(icons);
if (!icons.ok) return undefined;
return await icons.json();
}
export type MinehutIcon = {
_id: string;
display_name: string;
icon_name: string;
price: number;
rank: string;
available: boolean;
disabled: boolean;
created: number;
last_updated: number;
__v: number;
salePrice: any;
};
export const rarityIndex = {
common: { bg: "#40464d", text: "#b7bfc5" },
uncommon: { bg: "#184f02", text: "#61bf01" },
rare: { bg: "#15448a", text: "#41afff" },
epic: { bg: "#4c1a7b", text: "#ce59ff" },
legendary: { bg: "#de6e0d", text: "#fce8cf" },
};

@ -31,138 +31,177 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
const config = { const config = {
darkMode: ["class"], darkMode: ["class"],
content: [ content: [
"./pages/**/*.{ts,tsx}", "./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx,json}", "./src/**/*.{ts,tsx,json}",
], ],
prefix: "", prefix: "",
theme: { theme: {
container: { container: {
center: true, center: 'true',
padding: "2rem", padding: '2rem',
screens: { screens: {
"2xl": "1400px", '2xl': '1400px'
}, }
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))',
mhsf: 'hsl(var(--sidebar-mhsf))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'image-glow': {
'0%': {
opacity: '0',
'animation-timing-function': 'cubic-bezier(0.74, 0.25, 0.76, 1)'
},
'10%': {
opacity: '0.7',
'animation-timing-function': 'cubic-bezier(0.12, 0.01, 0.08, 0.99)'
},
'100%': {
opacity: '0.4'
}
},
'border-beam': {
'100%': {
'offset-distance': '100%'
}
},
'caret-blink': {
'0%,70%,100%': {
opacity: '1'
},
'20%,50%': {
opacity: '0'
}
},
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
},
'fade-in': {
from: {
opacity: '0',
transform: 'translateY(-10px)'
},
to: {
opacity: '1',
transform: 'none'
}
},
marquee: {
from: {
transform: 'translateX(0)'
},
to: {
transform: 'translateX(calc(-100% - var(--gap)))'
}
},
'marquee-vertical': {
from: {
transform: 'translateY(0)'
},
to: {
transform: 'translateY(calc(-100% - var(--gap)))'
}
},
'fade-up': {
from: {
opacity: '0',
transform: 'translateY(20px)'
},
to: {
opacity: '1',
transform: 'none'
}
},
shimmer: {
'0%, 90%, 100%': {
'background-position': 'calc(-100% - var(--shimmer-width)) 0'
},
'30%, 60%': {
'background-position': 'calc(100% + var(--shimmer-width)) 0'
}
}
},
animation: {
marquee: 'marquee var(--duration) linear infinite',
'marquee-vertical': 'marquee-vertical var(--duration) linear infinite',
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'image-glow': 'image-glow 4100ms 600ms ease-out forwards',
shimmer: 'shimmer 8s infinite',
'fade-in': 'fade-in 1000ms var(--animation-delay, 0ms) ease forwards',
'caret-blink': 'caret-blink 1.25s ease-out infinite',
'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
'fade-up': 'fade-up 1000ms var(--animation-delay, 0ms) ease forwards'
}
}
}, },
extend: { plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
'image-glow': {
'0%': {
'opacity': '0',
'animation-timing-function': 'cubic-bezier(0.74, 0.25, 0.76, 1)',
},
'10%': {
'opacity': '0.7',
'animation-timing-function': 'cubic-bezier(0.12, 0.01, 0.08, 0.99)',
},
'100%': {
opacity: '0.4',
},
},
"border-beam": {
"100%": {
"offset-distance": "100%",
},
},
"caret-blink": {
"0%,70%,100%": { opacity: "1" },
"20%,50%": { opacity: "0" },
},
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"fade-in": {
from: { opacity: "0", transform: "translateY(-10px)" },
to: { opacity: "1", transform: "none" },
},
marquee: {
from: { transform: "translateX(0)" },
to: { transform: "translateX(calc(-100% - var(--gap)))" },
},
"marquee-vertical": {
from: { transform: "translateY(0)" },
to: { transform: "translateY(calc(-100% - var(--gap)))" },
},
"fade-up": {
from: { opacity: "0", transform: "translateY(20px)" },
to: { opacity: "1", transform: "none" },
},
'shimmer': {
'0%, 90%, 100%': {
'background-position': 'calc(-100% - var(--shimmer-width)) 0',
},
'30%, 60%': {
'background-position': 'calc(100% + var(--shimmer-width)) 0',
},
},
},
animation: {
marquee: "marquee var(--duration) linear infinite",
"marquee-vertical": "marquee-vertical var(--duration) linear infinite",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
'image-glow': 'image-glow 4100ms 600ms ease-out forwards',
'shimmer': 'shimmer 8s infinite',
"fade-in": "fade-in 1000ms var(--animation-delay, 0ms) ease forwards",
"caret-blink": "caret-blink 1.25s ease-out infinite",
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
"fade-up": "fade-up 1000ms var(--animation-delay, 0ms) ease forwards",
},
},
},
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
} satisfies Config; } satisfies Config;
export default config; export default config;

193
yarn.lock

@ -1157,6 +1157,16 @@
dependencies: dependencies:
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-avatar@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.1.tgz#5848d2ed5f34d18b36fc7e2d227c41fca8600ea1"
integrity sha512-eoOtThOmxeoizxpX6RiEsQZ2wj5r4+zoeqAwO0cBaFQGjJwIH3dIX0OCxNrCyrrdxG+vBweMETh3VziQG7c1kw==
dependencies:
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-checkbox@^1.1.1": "@radix-ui/react-checkbox@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6" resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6"
@ -1171,6 +1181,20 @@
"@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-use-size" "1.1.0" "@radix-ui/react-use-size" "1.1.0"
"@radix-ui/react-collapsible@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.1.tgz#1382cc9ec48f8b473c14f3779d317f0cdf6da5e9"
integrity sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-id" "1.1.0"
"@radix-ui/react-presence" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-collection@1.0.3": "@radix-ui/react-collection@1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz" resolved "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz"
@ -1229,6 +1253,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
"@radix-ui/react-context@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
"@radix-ui/react-dialog@1.0.5": "@radix-ui/react-dialog@1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
@ -1250,7 +1279,7 @@
aria-hidden "^1.1.1" aria-hidden "^1.1.1"
react-remove-scroll "2.5.5" react-remove-scroll "2.5.5"
"@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.1.1": "@radix-ui/react-dialog@^1.0.4":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg== integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
@ -1270,6 +1299,26 @@
aria-hidden "^1.1.1" aria-hidden "^1.1.1"
react-remove-scroll "2.5.7" react-remove-scroll "2.5.7"
"@radix-ui/react-dialog@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c"
integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@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-portal" "1.1.2"
"@radix-ui/react-presence" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "2.6.0"
"@radix-ui/react-direction@1.0.1": "@radix-ui/react-direction@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz" resolved "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz"
@ -1305,16 +1354,27 @@
"@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-escape-keydown" "1.1.0" "@radix-ui/react-use-escape-keydown" "1.1.0"
"@radix-ui/react-dropdown-menu@^2.1.1": "@radix-ui/react-dismissable-layer@1.1.1":
version "2.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz#3dc578488688250dbbe109d9ff2ca28a9bca27ec" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396"
integrity sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ== integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==
dependencies: dependencies:
"@radix-ui/primitive" "1.1.0" "@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-escape-keydown" "1.1.0"
"@radix-ui/react-dropdown-menu@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz#acc49577130e3c875ef0133bd1e271ea3392d924"
integrity sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-id" "1.1.0" "@radix-ui/react-id" "1.1.0"
"@radix-ui/react-menu" "2.1.1" "@radix-ui/react-menu" "2.1.2"
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0"
@ -1330,6 +1390,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13"
integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw== integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==
"@radix-ui/react-focus-guards@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe"
integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==
"@radix-ui/react-focus-scope@1.0.4": "@radix-ui/react-focus-scope@1.0.4":
version "1.0.4" version "1.0.4"
resolved "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz" resolved "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz"
@ -1440,6 +1505,30 @@
aria-hidden "^1.1.1" aria-hidden "^1.1.1"
react-remove-scroll "2.5.7" react-remove-scroll "2.5.7"
"@radix-ui/react-menu@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.2.tgz#91f6815845a4298dde775563ed2d80b7ad667899"
integrity sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==
dependencies:
"@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-presence" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-roving-focus" "1.1.0"
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-use-callback-ref" "1.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "2.6.0"
"@radix-ui/react-menubar@^1.1.1": "@radix-ui/react-menubar@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menubar/-/react-menubar-1.1.1.tgz#e126514cb1c46e0a4f9fba7d016e578cc4e41f22" resolved "https://registry.yarnpkg.com/@radix-ui/react-menubar/-/react-menubar-1.1.1.tgz#e126514cb1c46e0a4f9fba7d016e578cc4e41f22"
@ -1548,6 +1637,14 @@
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-portal@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e"
integrity sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==
dependencies:
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-presence@1.0.1": "@radix-ui/react-presence@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz" resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz"
@ -1565,6 +1662,14 @@
"@radix-ui/react-compose-refs" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-presence@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1"
integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==
dependencies:
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-primitive@1.0.3": "@radix-ui/react-primitive@1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz" resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz"
@ -1642,13 +1747,12 @@
"@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-separator@^1.0.3": "@radix-ui/react-separator@^1.1.0":
version "1.0.3" version "1.1.0"
resolved "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz" resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e"
integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw== integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-slot@1.0.2": "@radix-ui/react-slot@1.0.2":
version "1.0.2" version "1.0.2"
@ -1692,24 +1796,23 @@
"@radix-ui/react-roving-focus" "1.1.0" "@radix-ui/react-roving-focus" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-tooltip@^1.0.7": "@radix-ui/react-tooltip@^1.1.3":
version "1.0.7" version "1.1.3"
resolved "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz" resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz#4250b14723f2d8477e7a3d0526c169f91d1f2f74"
integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== integrity sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@radix-ui/primitive" "1.1.0"
"@radix-ui/primitive" "1.0.1" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-context" "1.1.1"
"@radix-ui/react-context" "1.0.1" "@radix-ui/react-dismissable-layer" "1.1.1"
"@radix-ui/react-dismissable-layer" "1.0.5" "@radix-ui/react-id" "1.1.0"
"@radix-ui/react-id" "1.0.1" "@radix-ui/react-popper" "1.2.0"
"@radix-ui/react-popper" "1.1.3" "@radix-ui/react-portal" "1.1.2"
"@radix-ui/react-portal" "1.0.4" "@radix-ui/react-presence" "1.1.1"
"@radix-ui/react-presence" "1.0.1" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-slot" "1.0.2" "@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.0.1" "@radix-ui/react-visually-hidden" "1.1.0"
"@radix-ui/react-visually-hidden" "1.0.3"
"@radix-ui/react-use-callback-ref@1.0.1": "@radix-ui/react-use-callback-ref@1.0.1":
version "1.0.1" version "1.0.1"
@ -1815,6 +1918,13 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-visually-hidden@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2"
integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==
dependencies:
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/rect@1.0.1": "@radix-ui/rect@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz" resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz"
@ -2809,7 +2919,7 @@ citty@^0.1.6:
class-variance-authority@^0.7.0: class-variance-authority@^0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz" resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522"
integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==
dependencies: dependencies:
clsx "2.0.0" clsx "2.0.0"
@ -5010,10 +5120,10 @@ lru-cache@^10.2.0:
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz"
integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
lucide-react@^0.416.0: lucide-react@^0.454.0:
version "0.416.0" version "0.454.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.416.0.tgz#657da248f9b862703d7d80aafb912e79ad886313" resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.454.0.tgz#a81b9c482018720f07ead0503ae502d94d528444"
integrity sha512-wPWxTzdss1CTz2aqcNWNlbh4YSnH9neJWP3RaeXepxpLCTW+pmu7WcT/wxJe+Q7Y7DqGOxAqakJv0pIK3431Ag== integrity sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==
luxon@~3.4.0: luxon@~3.4.0:
version "3.4.4" version "3.4.4"
@ -6708,7 +6818,7 @@ react-markdown@^9.0.1:
unist-util-visit "^5.0.0" unist-util-visit "^5.0.0"
vfile "^6.0.0" vfile "^6.0.0"
react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4: react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4, react-remove-scroll-bar@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz" resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz"
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
@ -6738,6 +6848,17 @@ react-remove-scroll@2.5.7:
use-callback-ref "^1.3.0" use-callback-ref "^1.3.0"
use-sidecar "^1.1.2" use-sidecar "^1.1.2"
react-remove-scroll@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07"
integrity sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==
dependencies:
react-remove-scroll-bar "^2.3.6"
react-style-singleton "^2.2.1"
tslib "^2.1.0"
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-resizable-panels@^2.0.23: react-resizable-panels@^2.0.23:
version "2.0.23" version "2.0.23"
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.0.23.tgz#7a4296f23028c32ffcbe8086aa918cd934e7e87f" resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.0.23.tgz#7a4296f23028c32ffcbe8086aa918cd934e7e87f"