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
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.
Using `Ctrl+Shift+K` opens a server viewer, and this may be faster then going through the general page.
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 <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
- **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](/)

@ -5,11 +5,11 @@ folder: "Guides"
# 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
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!
## 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",
"@linear/sdk": "^31.0.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-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.1.1",
@ -35,7 +37,7 @@
"inngest": "^3.21.2",
"input-otp": "^1.2.4",
"json-beautify": "^1.1.1",
"lucide-react": "^0.416.0",
"lucide-react": "^0.454.0",
"minimessage-2-html": "1.6.0",
"mongodb": "^6.8.0",
"next": "14.2.10",
@ -63,17 +65,17 @@
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.2.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-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-tooltip": "^1.1.3",
"@tailwindcss/typography": "^0.5.13",
"@types/canvas-confetti": "^1.6.4",
"@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.
*/
"use client";
import { Sidebar } from "@/components/docs/Sidebar";
import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { version } from "@/config/version";
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({
children,
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode;
}>) {
return (
<div className="border-b pt-[40px]">
<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">
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
<div className="bg-muted w-full rounded justify-center p-4 flex items-center">
MHSF Docs <small className="ml-2">Version {version}</small>
</div>
<br />
<Sidebar />
</ScrollArea>
</aside>
<br className="md:hidden" />
<div className="bg-muted w-full rounded justify-center p-4 flex items-center md:hidden">
MHSF Docs <small className="ml-2">Version {version}</small>
<Drawer>
<DrawerTrigger>
<Button className="ml-2">
<HamburgerMenuIcon />
</Button>
</DrawerTrigger>
<DrawerContent className="p-4">
<Sidebar />
</DrawerContent>
</Drawer>
</div>
{children}
</div>
</div>
);
const pathname = usePathname();
return (
<ClerkThemeProvider className="">
<div className="theme-zinc">
<NextTopLoader />
<SidebarProvider>
<Sidebar />
<SidebarInset>
<div className="fixed backdrop-blur w-full flex h-16 z-10 items-center gap-2 px-4 ">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-2 h-4" />
{
allDocs.find(
(c) =>
c._raw.flattenedPath ===
pathname
?.split("/")
.splice(2, pathname?.split("/").length)
.join("/"),
)?.title
}
</div>
<div className="px-[100px] pt-[50px]">{children}</div>
</SidebarInset>
</SidebarProvider>
</div>
</ClerkThemeProvider>
);
}

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

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

@ -32,34 +32,34 @@ import ServerList from "@/components/ServerList";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "the MHSF project by dvelo",
description:
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 dev",
twitter: {
images: [
{
url: "/public/imgs/icon-cf.png",
},
],
},
themeColor: "#000000",
openGraph: {
images: [
{
url: "/public/imgs/icon-cf.png",
},
],
},
title: "the MHSF project by dvelo",
description:
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 dev",
twitter: {
images: [
{
url: "/imgs/icon-cf.png",
},
],
},
themeColor: "#000000",
openGraph: {
images: [
{
url: "/imgs/icon-cf.png",
},
],
},
};
export default function Home() {
return (
<main>
<div className="pt-[60px]">
<ServerList />
</div>
</main>
);
return (
<main>
<div className="pt-[60px]">
<ServerList />
</div>
</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-two: #72afd3;
--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 {
@ -112,6 +121,15 @@
--chart-3: 216 92% 60%;
--chart-4: 210 98% 78%;
--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;
}
}
@layer base {
a {
@apply underline text-blue-400;
}
}
/*@layer base {*/
/* a {*/
/* @apply underline text-blue-400;*/
/* }*/
/*}*/
.backdrop-blur {
-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";
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 { 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 { 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 }) {
const [description, setDescription] = useState("");
const [discord, setDiscord] = useState("");
const [mhsf, setMHSF] = useState(new MHSF());
const { resolvedTheme } = useTheme();
const [loading, setLoading] = useState(true);
const [view, setView] = useState(
description !== "" || discord !== "" ? "desc" : "extra"
);
const [serverObject, setServerObject] = useState<ServerResponse | undefined>(
undefined
);
const [copied, setCopied] = useState(false);
const [description, setDescription] = useState("");
const [discord, setDiscord] = useState("");
const [mhsf, setMHSF] = useState(new MHSF());
const [icons, setIcons] = useState<MinehutIcon[]>();
const { resolvedTheme } = useTheme();
const [loading, setLoading] = useState(true);
const [view, setView] = useState(
description !== "" || discord !== "" ? "desc" : "extra",
);
const [serverObject, setServerObject] = useState<ServerResponse | undefined>(
undefined,
);
const [copied, setCopied] = useState(false);
useEffect(() => {
getCustomization(server).then((b) => {
if (b != null) {
setDescription(b.description == null ? "" : b.description);
setDiscord(b.discord == null ? "" : b.discord);
mhsf.setCustomizations(b);
getCommunityServerFavorites(server).then((c) => {
mhsf.setFavorites(c);
});
}
fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
(c) => c.json().then((n) => setServerObject(n.server))
);
setLoading(false);
});
}, []);
if (loading) return <></>;
useEffect(() => {
getCustomization(server).then((b) => {
if (b != null) {
setDescription(b.description == null ? "" : b.description);
setDiscord(b.discord == null ? "" : b.discord);
mhsf.setCustomizations(b);
getCommunityServerFavorites(server).then((c) => {
mhsf.setFavorites(c);
});
getMinehutIcons().then((i) => {
setIcons(i);
});
}
fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
(c) => c.json().then((n) => setServerObject(n.server)),
);
setLoading(false);
});
}, []);
if (loading) return <></>;
return (
<>
<FadeIn>
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4">
<div className="ml-5 mb-2 flex items-center sm:hidden">
{(description != "" || discord != "") && (
<Button
variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")}
>
Description
</Button>
)}
<Button
variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")}
className="ml-2"
>
Server Information
</Button>
<Button
variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
className="ml-2"
>
Achievements
</Button>
</div>
<div className="max-sm:hidden">
<div className="grid">
{(description != "" || discord != "") && (
<Button
variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")}
>
Description
</Button>
)}
<Button
variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")}
>
Server Information
</Button>
<Button
variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
>
Achievements
</Button>
</div>
</div>
return (
<>
<FadeIn>
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4">
<div className="ml-5 mb-2 flex items-center sm:hidden">
{(description != "" || discord != "") && (
<Button
variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")}
>
Description
</Button>
)}
<Button
variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")}
className="ml-2"
>
Server Information
</Button>
<Button
variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
className="ml-2"
>
Achievements
</Button>
<Button
variant={view == "icons" ? undefined : "ghost"}
onClick={() => setView("icons")}
>
Purchased Icons
</Button>
</div>
<div className="max-sm:hidden">
<div className="grid">
{(description != "" || discord != "") && (
<Button
variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")}
>
Description
</Button>
)}
<Button
variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")}
>
Server Information
</Button>
<Button
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">
{description != "" && view == "desc" && (
<Card className="sm:col-span-3">
<CardDescription className="p-4 prose dark:prose-invert">
<Markdown disallowedElements={["img"]}>
{description}
</Markdown>
</CardDescription>
</Card>
)}
{discord != "" && view == "desc" && (
<Card>
<CardHeader>
<CardTitle>Discord Server</CardTitle>
<CardDescription className="p-4 prose dark:prose-invert">
<iframe
src={`https://discord.com/widget?id=${discord}&theme=${resolvedTheme}`}
height="500"
allowTransparency={true}
className="rounded-lg max-sm:w-[100px] max-md:w-[250px]"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
/>
</CardDescription>
</CardHeader>
</Card>
)}{" "}
{view == "achievements" && (
<div className="col-span-4">
<AchievementList server={server} />
</div>
)}
{view == "extra" && (
<div className="sm:grid sm:grid-cols-3 col-span-4 gap-4">
<Card>
<CardHeader>
<CardTitle>Plan Details</CardTitle>
<CardDescription>
Information about the plan being used by the server
</CardDescription>
</CardHeader>
{(() => {
console.log(serverObject);
return true;
})()}
<CardContent>
{" "}
<table className="table-auto w-full">
<tr>
<th className="border p-2">Server plan</th>
<td className="border p-2">
{serverObject?.expired == undefined ? (
<div className="flex items-center">
Free{" "}
<Tooltip>
<TooltipTrigger>
<div>
<Info size={16} className="ml-2" />
</div>
</TooltipTrigger>
<TooltipContent>
The plan is really unknown, but in most
scenarios, the Minehut API returns{" "}
<code>undefined</code> if the server is free.
</TooltipContent>
</Tooltip>
</div>
) : (
<>{serverObject?.activeServerPlan}</>
)}
</td>
</tr>
<tr>
<th className="border p-2">Raw plan</th>
<td className="border p-2">
{serverObject?.expired == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?.rawPlan}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">Credits per day</th>
<td className="border p-2">
{serverObject?.credits_per_day == undefined
? "? (unknown)"
: Math.floor(serverObject?.credits_per_day)}
</td>
</tr>
<tr>
<th className="border p-2">Server expired</th>
<td className="border p-2">
{serverObject?.expired == undefined
? "? (unknown)"
: toJSX(serverObject?.expired)}
</td>
</tr>
<tr>
<th className="border p-2">Server external</th>
<td className="border p-2">
{serverObject?.rawPlan == undefined
? "? (unknown)"
: toJSX(serverObject?.rawPlan == "EXTERNAL")}
</td>
</tr>
</table>{" "}
</CardContent>
</Card>
<br className="sm:hidden" />
<Card>
<CardHeader>
<CardTitle>Additional Info</CardTitle>
<CardDescription>
Additional info that could be useful{" "}
</CardDescription>
</CardHeader>
<CardContent>
<table className="table-auto w-full">
<tr>
<th className="border p-2">Icon</th>
<td className="border p-2">
{serverObject?.icon == undefined ? (
<>
Default (<code>OAK_SIGN</code>)
</>
) : (
<code>{serverObject?.icon}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">All-time joins</th>
<td className="border p-2">
{serverObject?.joins == undefined
? "? (unknown)"
: serverObject?.joins}
</td>
</tr>
<tr>
<th className="border p-2">Server Type</th>
<td className="border p-2">
{serverObject?.server_version_type == undefined ? (
"? (unknown)"
) : (
<>
{serverObject?.server_version_type.toLowerCase()}
</>
)}
</td>
</tr>
<tr>
<th className="border p-2">Server Platform</th>
<td className="border p-2">
{serverObject?.platform == undefined
? "? (unknown)"
: serverObject?.platform}
</td>
</tr>
</table>
</CardContent>
</Card>
<br className="sm:hidden" />
<Card>
<CardHeader>
<CardTitle>
<div>
<span>Technical Info</span>
<Tooltip>
<TooltipContent className="font-normal tracking-normal">
Copy JSON data about the server
</TooltipContent>
<TooltipTrigger>
<Button
className="justify-right ml-2"
size="icon"
variant="secondary"
onClick={() => {
setCopied(true);
try {
navigator.clipboard.writeText(
JSON.stringify({
minehut: serverObject,
mhsf: mhsf.getMHSF(),
})
);
} catch {
toast.error(
"Clipboard is inaccessible. Cannot copy"
);
}
toast.success(
<div className="block w-[300px]">
Copied the following: <br />{" "}
<code className="flex items-center">
{JSON.stringify({
minehut: serverObject,
mhsf,
}).substring(0, 36)}
...
</code>
</div>
);
setTimeout(() => setCopied(false), 1000);
}}
>
{!copied && <Copy size={16} />}
{copied && <CheckmarkIcon />}
</Button>
</TooltipTrigger>
</Tooltip>
</div>
</CardTitle>
<CardDescription>
Technical information about the server{" "}
</CardDescription>
</CardHeader>
<CardContent>
<table className="table-auto w-full">
<tr>
<th className="border p-2">Visible</th>
<td className="border p-2">
{serverObject?.visibility == undefined
? "? (unknown)"
: toJSX(serverObject?.visibility)}
</td>
</tr>
<tr>
<th className="border p-2">Server Port</th>
<td className="border p-2">
{serverObject?.port == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?.port}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">Storage Node</th>
<td className="border p-2">
{serverObject?.storage_node == undefined
? "? (unknown)"
: serverObject?.storage_node.toUpperCase()}
</td>
</tr>
<tr>
<th className="border p-2">Server ID</th>
<td className="border p-2">
{serverObject?._id == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?._id}</code>
)}
</td>
</tr>
</table>
</CardContent>
</Card>
</div>
)}
</div>
</div>
<br />
<br />
</FadeIn>
</>
);
<div className="grid lg:grid-cols-4 pl-4 pr-4 gap-3.5 col-span-5">
{description != "" && view == "desc" && (
<Card className="sm:col-span-3">
<CardDescription className="p-4 prose dark:prose-invert">
<Markdown disallowedElements={["img"]}>
{description}
</Markdown>
</CardDescription>
</Card>
)}
{discord != "" && view == "desc" && (
<Card>
<CardHeader>
<CardTitle>Discord Server</CardTitle>
<CardDescription className="p-4 prose dark:prose-invert">
<iframe
src={`https://discord.com/widget?id=${discord}&theme=${resolvedTheme}`}
height="500"
allowTransparency={true}
className="rounded-lg max-sm:w-[100px] max-md:w-[250px]"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
/>
</CardDescription>
</CardHeader>
</Card>
)}{" "}
{view == "achievements" && (
<div className="col-span-4">
<AchievementList server={server} />
</div>
)}
{view == "extra" && (
<div className="sm:grid sm:grid-cols-3 col-span-4 gap-4">
<Card>
<CardHeader>
<CardTitle>Plan Details</CardTitle>
<CardDescription>
Information about the plan being used by the server
</CardDescription>
</CardHeader>
{(() => {
console.log(serverObject);
return true;
})()}
<CardContent>
{" "}
<table className="table-auto w-full">
<tr>
<th className="border p-2">Server plan</th>
<td className="border p-2">
{serverObject?.expired == undefined ? (
<div className="flex items-center">
Free{" "}
<Tooltip>
<TooltipTrigger>
<div>
<Info size={16} className="ml-2" />
</div>
</TooltipTrigger>
<TooltipContent>
The plan is really unknown, but in most
scenarios, the Minehut API returns{" "}
<code>undefined</code> if the server is free.
</TooltipContent>
</Tooltip>
</div>
) : (
<>{serverObject?.activeServerPlan}</>
)}
</td>
</tr>
<tr>
<th className="border p-2">Raw plan</th>
<td className="border p-2">
{serverObject?.expired == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?.rawPlan}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">Credits per day</th>
<td className="border p-2">
{serverObject?.credits_per_day == undefined
? "? (unknown)"
: Math.floor(serverObject?.credits_per_day)}
</td>
</tr>
<tr>
<th className="border p-2">Server expired</th>
<td className="border p-2">
{serverObject?.expired == undefined
? "? (unknown)"
: toJSX(serverObject?.expired)}
</td>
</tr>
<tr>
<th className="border p-2">Server external</th>
<td className="border p-2">
{serverObject?.rawPlan == undefined
? "? (unknown)"
: toJSX(serverObject?.rawPlan == "EXTERNAL")}
</td>
</tr>
</table>{" "}
</CardContent>
</Card>
<br className="sm:hidden" />
<Card>
<CardHeader>
<CardTitle>Additional Info</CardTitle>
<CardDescription>
Additional info that could be useful{" "}
</CardDescription>
</CardHeader>
<CardContent>
<table className="table-auto w-full">
<tr>
<th className="border p-2">Icon</th>
<td className="border p-2">
{serverObject?.icon == undefined ? (
<>
Default (<code>OAK_SIGN</code>)
</>
) : (
<code>{serverObject?.icon}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">All-time joins</th>
<td className="border p-2">
{serverObject?.joins == undefined
? "? (unknown)"
: serverObject?.joins}
</td>
</tr>
<tr>
<th className="border p-2">Server Type</th>
<td className="border p-2">
{serverObject?.server_version_type == undefined ? (
"? (unknown)"
) : (
<>
{serverObject?.server_version_type.toLowerCase()}
</>
)}
</td>
</tr>
<tr>
<th className="border p-2">Server Platform</th>
<td className="border p-2">
{serverObject?.platform == undefined
? "? (unknown)"
: serverObject?.platform}
</td>
</tr>
</table>
</CardContent>
</Card>
<br className="sm:hidden" />
<Card>
<CardHeader>
<CardTitle>
<div>
<span>Technical Info</span>
<Tooltip>
<TooltipContent className="font-normal tracking-normal">
Copy JSON data about the server
</TooltipContent>
<TooltipTrigger>
<Button
className="justify-right ml-2"
size="icon"
variant="secondary"
onClick={() => {
setCopied(true);
try {
navigator.clipboard.writeText(
JSON.stringify({
minehut: serverObject,
mhsf: mhsf.getMHSF(),
}),
);
} catch {
toast.error(
"Clipboard is inaccessible. Cannot copy",
);
}
toast.success(
<div className="block w-[300px]">
Copied the following: <br />{" "}
<code className="flex items-center">
{JSON.stringify({
minehut: serverObject,
mhsf,
}).substring(0, 36)}
...
</code>
</div>,
);
setTimeout(() => setCopied(false), 1000);
}}
>
{!copied && <Copy size={16} />}
{copied && <CheckmarkIcon />}
</Button>
</TooltipTrigger>
</Tooltip>
</div>
</CardTitle>
<CardDescription>
Technical information about the server{" "}
</CardDescription>
</CardHeader>
<CardContent>
<table className="table-auto w-full">
<tr>
<th className="border p-2">Visible</th>
<td className="border p-2">
{serverObject?.visibility == undefined
? "? (unknown)"
: toJSX(serverObject?.visibility)}
</td>
</tr>
<tr>
<th className="border p-2">Server Port</th>
<td className="border p-2">
{serverObject?.port == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?.port}</code>
)}
</td>
</tr>
<tr>
<th className="border p-2">Storage Node</th>
<td className="border p-2">
{serverObject?.storage_node == undefined
? "? (unknown)"
: serverObject?.storage_node.toUpperCase()}
</td>
</tr>
<tr>
<th className="border p-2">Server ID</th>
<td className="border p-2">
{serverObject?._id == undefined ? (
"? (unknown)"
) : (
<code>{serverObject?._id}</code>
)}
</td>
</tr>
</table>
</CardContent>
</Card>
</div>
)}
{view == "icons" && (
<div>
<p>
Purchased Icons are icons that are under the server's
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) {
if (boolean) {
return <div className="text-green-400">True</div>;
}
if (boolean) {
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";
import { Separator } from "@/components/ui/separator";
import { useState } from "react";
import TabServer from "./misc/TabServer";
import ServerCustomize from "./ServerCustomize";
import Banner from "./Banner";
import ServerCustomize from "./ServerCustomize";
import TabServer from "./misc/TabServer";
export default function CustomizeRoot({
params,
params,
}: {
params: { server: string };
params: { server: string };
}) {
const [color, setColor] = useState("");
return (
<div className={"pt-16 theme-" + color}>
<Banner server={params.server} />
<TabServer server={params.server} tabDef="customize" />
<br />
<div className="pl-[40px] pr-[40px]">
<ServerCustomize server={params.server} cs={color} setCS={setColor} />
</div>
</div>
);
const [color, setColor] = useState("");
return (
<div className={"pt-16 theme-" + color}>
<Banner server={params.server} />
<TabServer server={params.server} tabDef="customize" />
<Separator />
<br />
<div className="pl-[40px] pr-[40px]">
<ServerCustomize server={params.server} cs={color} setCS={setColor} />
</div>
</div>
);
}

@ -28,108 +28,120 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { useClerk } from "@clerk/nextjs";
import { Cog, ExternalLink, KeyRound, Link, UserPen } from "lucide-react";
import NextLink from "next/link";
import { Button } from "./ui/button";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "./ui/resizable";
import NextLink from "next/link";
import { useClerk } from "@clerk/nextjs";
export function Sidebar({
children,
curPage,
children,
curPage,
}: {
children: React.ReactNode;
curPage: string;
children: React.ReactNode;
curPage: string;
}) {
const clerk = useClerk();
const clerk = useClerk();
return (
<ResizablePanelGroup
direction="horizontal"
className="min-h-[calc(100vh-70px)] pt-[70px]"
>
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
<div className="w-[300px] ml-[10px]">
<NextLink href="/account/settings" className="text-inherit">
<Button
className="mb-[2px] w-[250px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink href="/account/settings/options" className="text-inherit">
<Button
className="mb-[2px] w-[250px] "
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
</ResizablePanel>
<ResizableHandle className="max-md:hidden" />
<ResizablePanel>
<div className="md:hidden ml-2">
<NextLink href="/account/settings" className="text-inherit">
<Button
className="mr-[2px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink href="/account/settings/options" className="text-inherit">
<Button
className="mr-[2px]"
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mr-[2px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</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>
);
return (
<ResizablePanelGroup
direction="horizontal"
className="min-h-[calc(100vh-70px)] pt-[70px]"
>
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
<div className="w-[300px] ml-[10px]">
<NextLink
href="/src/app/(main)/account/settings"
className="text-inherit"
>
<Button
className="mb-[2px] w-[250px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink
href="/src/app/(main)/account/settings/options"
className="text-inherit"
>
<Button
className="mb-[2px] w-[250px] "
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
</ResizablePanel>
<ResizableHandle className="max-md:hidden" />
<ResizablePanel>
<div className="md:hidden ml-2">
<NextLink
href="/src/app/(main)/account/settings"
className="text-inherit"
>
<Button
className="mr-[2px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink
href="/src/app/(main)/account/settings/options"
className="text-inherit"
>
<Button
className="mr-[2px]"
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mr-[2px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</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.
*/
import { NewChart } from "@/components/NewChart";
import { MiniJoinsChart } from "@/components/misc/MiniJoinsChart";
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} 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 {
Drawer,
DrawerContent,
@ -62,20 +45,39 @@ import {
DrawerTitle,
DrawerTrigger,
} 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 {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { favoriteServer, isFavorited } from "@/lib/api";
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) {
const router = useRouter();
@ -130,6 +132,13 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
/>
)}
</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">
<span className="text-xs text-muted-foreground flex items-center">
<ArrowRight size={16} className="mr-2" />
@ -272,7 +281,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
</div>
</ContextMenuItem>
<ContextMenuSeparator />
<Link href={"/server/" + b.name}>
<Link href={"/src/app/(main)/server/" + b.name}>
<ContextMenuItem>Open server page</ContextMenuItem>
</Link>
</ContextMenuContent>

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

File diff suppressed because it is too large Load Diff

@ -30,50 +30,50 @@
"use client";
import { ClerkProvider } from "@clerk/nextjs";
import { useTheme } from "next-themes";
import { dark } from "@clerk/themes";
import { useTheme } from "next-themes";
import { type ReactNode, useEffect, useState } from "react";
import { ThemeProvider } from "../ThemeProvider";
import { useEffect, useState } from "react";
export function ClerkThemeProvider({
children,
className,
children,
className,
}: {
children: JSX.Element;
className: string;
children: ReactNode | ReactNode[];
className: string | undefined;
}) {
const [theme, setTheme] = useState<string | undefined>("");
const [theme, setTheme] = useState<string | undefined>("");
return (
<ClerkProvider
appearance={{ baseTheme: theme == "dark" ? dark : undefined }}
>
<html lang="en" className={className}>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
{/** This *has* to be implemented in component form for the `useTheme` to load at the appropriate time. */}
<ThemeElement setTheme={setTheme} />
</ThemeProvider>
</body>
</html>
</ClerkProvider>
);
return (
<ClerkProvider
appearance={{ baseTheme: theme == "dark" ? dark : undefined }}
>
<html lang="en" className={className}>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
{/** This *has* to be implemented in component form for the `useTheme` to load at the appropriate time. */}
<ThemeElement setTheme={setTheme} />
</ThemeProvider>
</body>
</html>
</ClerkProvider>
);
}
function ThemeElement({
setTheme,
setTheme,
}: {
setTheme: (update: string | undefined) => void;
setTheme: (update: string | undefined) => void;
}) {
const theme = useTheme();
useEffect(() => {
setTheme(theme.resolvedTheme);
}, [theme.resolvedTheme, setTheme]);
return <></>;
const theme = useTheme();
useEffect(() => {
setTheme(theme.resolvedTheme);
}, [theme.resolvedTheme, setTheme]);
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 @@
/*
* 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 * as React from "react";
"use client";
import { allFolders, Docs, DocsFolder } from "@/config/docs";
import { usePathname } from "next/navigation";
import { Button } from "../ui/button";
import { useState } from "react";
import { ChevronRight } from "lucide-react";
import { useRouter } from "@/lib/useRouter";
import { AnimatePresence, motion } from "framer-motion";
import { NavUser } from "@/components/docs/NavUser";
import { VersionSwitcher } from "@/components/docs/VersionSwitcher";
import {
Sidebar as ShadSidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar";
import { allFolders } from "@/config/docs";
export function Sidebar() {
return (
<>
{allFolders.map((docs) => (
<Folder docs={docs} key={"url" in docs ? docs.title : docs.name} />
))}
</>
);
}
function Folder({ docs }: { docs: any }) {
const [folderOpen, setOpen] = useState(false);
const router = useRouter();
const pathname = usePathname();
return (
<div key={"url" in docs ? docs.title : docs.name}>
<Button
size="sm"
className="w-full font-normal tracking-normal mt-1"
noJustify
variant={
"url" in docs
? pathname === docs.url
? "default"
: "ghost"
: "ghost"
}
onClick={() => {
if ("docs" in docs) {
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} />
</>
);
})}
</>
);
return (
<ShadSidebar>
<SidebarHeader>
<VersionSwitcher />
</SidebarHeader>
<SidebarContent>
{/* We create a SidebarGroup for each parent. */}
{allFolders.map((item) => (
<SidebarGroup key={item.name}>
<SidebarGroupLabel>{item.name}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{item.docs.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>{item.title}</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarFooter>
<NavUser />
</SidebarFooter>
<SidebarRail />
</ShadSidebar>
);
}

@ -28,59 +28,51 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { Button } from "../ui/button";
import { Book, Calendar, Star, TerminalIcon } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import { useState } from "react";
import { Changelog, version } from "@/config/version";
import { Changelog } from "@/components/changelog";
import { changelog, version } from "@/config/version";
import events from "@/lib/commandEvent";
import { ScrollArea } from "../ui/scroll-area";
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() {
const [changeLog, setChangelog] = useState(false);
const router = useRouter();
const router = useRouter();
return (
<div className="grid w-full">
<strong className="text-center">The future of Minehut lists</strong>
<small className="text-center mb-3">
Use filters, intuitive keyboard shortcuts and other features for
completely free, and *open-source. <br /> Currently on version{" "}
<code>{version}</code>.<br />{" "}
<small>* Licensed under the MIT License</small>
</small>
return (
<div className="grid w-full">
<strong className="text-center">The future of Minehut lists</strong>
<small className="text-center mb-3">
Use filters, intuitive keyboard shortcuts and other features for
completely free, and *open-source. <br /> Currently on version{" "}
<code>{version}</code>.<br />{" "}
<small>* Licensed under the MIT License</small>
</small>
<Button variant={"ghost"} onClick={() => setChangelog(true)}>
<Calendar size={18} className="mr-2" /> Changelog
</Button>
<Dialog open={changeLog} onOpenChange={setChangelog}>
<DialogContent>
<ScrollArea className="max-h-[500px]">
<DialogHeader>
<DialogTitle>Changelog</DialogTitle>
<Changelog />
</DialogHeader>
</ScrollArea>
</DialogContent>
</Dialog>
<Changelog items={changelog}>
<Button variant={"ghost"}>
<Calendar size={18} className="mr-2" /> Changelog
</Button>
</Changelog>
<Button
variant={"ghost"}
onClick={() =>
window
.open("https://github.com/DeveloLongScript/MHSF", "_blank")
?.focus()
}
>
<Star size={18} className="mr-2" /> Star on GitHub
</Button>
<Button variant={"ghost"} onClick={() => router.push("/docs")}>
<Book size={18} className="mr-2" /> See the docs
</Button>
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
<TerminalIcon size={18} className="mr-2" /> Open commands
</Button>
</div>
);
<Button
variant={"ghost"}
onClick={() =>
window
.open("https://github.com/DeveloLongScript/MHSF", "_blank")
?.focus()
}
>
<Star size={18} className="mr-2" /> Star on GitHub
</Button>
<Button variant={"ghost"} onClick={() => router.push("/docs")}>
<Book size={18} className="mr-2" /> See the docs
</Button>
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
<TerminalIcon size={18} className="mr-2" /> Open commands
</Button>
</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.
*/
"use client"
"use client";
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
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<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
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",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
<DropdownMenuPrimitive.SubTrigger
ref={ref}
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",
inset && "pl-8",
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
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",
className
)}
{...props}
/>
))
<DropdownMenuPrimitive.SubContent
ref={ref}
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",
className,
)}
{...props}
/>
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
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",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
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",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
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",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"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",
className,
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
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",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
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",
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
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",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
<DropdownMenuPrimitive.RadioItem
ref={ref}
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 [&>svg]:size-4 [&>svg]:shrink-0 gap-3",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
...props
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

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

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

@ -29,407 +29,346 @@
*/
"use client";
import Image from "next/image";
import Link from "next/link";
import { Separator } from "../components/ui/separator";
import { Button } from "../components/ui/button";
import confetti from "canvas-confetti";
export const version = "1.3.2";
import A from "@/components/misc/Link";
import type { ReactNode } from "react";
const User = ({ user }: { user: string }) => (
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
{user}
</span>
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
{user}
</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) =>
Math.random() * (max - min) + min;
const interval = window.setInterval(() => {
const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
return clearInterval(interval);
}
const particleCount = 50 * (timeLeft / duration);
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>
</>
);
const FeatureList = ({
features,
title,
}: { features: (string | ReactNode)[]; title: ReactNode }) => {
return (
<ul>
{title}
{features.map((feature, i) => (
<li key={i}> {feature}</li>
))}
</ul>
);
};
const Github = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 256 250"
width="1em"
height="1em"
fill="#24292f"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
{...props}
>
<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" />
</svg>
);
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";
const Discord = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 256 199"
width="1em"
height="1em"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
{...props}
>
<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"
fill="#5865F2"
/>
</svg>
);
export const version = "1.4.0";
export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
{
id: "amq4suhgcfwrb7y5j6",
name: "v1.4.0",
changelog: (
<FeatureList
features={[
"Revamped documentation",
"Revamped changelog UI",
"New hover joins chart",
]}
title={
<strong className="flex items-center">
Version 1.4.0 (November 3rd 2024)
</strong>
}
/>
),
},
{
id: "jeh48p7w9bx2k3ad6f",
name: "v1.3.2",
changelog: (
<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>
),
},
{
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";
export async function createReportIssue(server: string, reportDescription: string, userId: string) {
const linearClient = new LinearClient({
apiKey: process.env.LINEAR
})
export async function createReportIssue(
server: string,
reportDescription: string,
userId: string,
) {
const linearClient = new LinearClient({
apiKey: process.env.LINEAR,
});
const allTeams = await linearClient.teams();
// Always grabs the first issue category.
const team = allTeams.nodes[0];
const allTeams = await linearClient.teams();
// Always grabs the first issue category.
const team = allTeams.nodes[0];
// Ensure there *actually* is a team there
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 })
}
// Ensure there *actually* is a team there
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,
});
}
}
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.*
`
`;

@ -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";
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx,json}",
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx,json}",
],
prefix: "",
theme: {
container: {
center: 'true',
padding: '2rem',
screens: {
'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: {
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")],
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
} satisfies Config;
export default config;

193
yarn.lock

@ -1157,6 +1157,16 @@
dependencies:
"@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":
version "1.1.1"
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-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":
version "1.0.3"
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"
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":
version "1.0.5"
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"
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"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
@ -1270,6 +1299,26 @@
aria-hidden "^1.1.1"
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":
version "1.0.1"
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-escape-keydown" "1.1.0"
"@radix-ui/react-dropdown-menu@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz#3dc578488688250dbbe109d9ff2ca28a9bca27ec"
integrity sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==
"@radix-ui/react-dismissable-layer@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396"
integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==
dependencies:
"@radix-ui/primitive" "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-menu" "2.1.1"
"@radix-ui/react-menu" "2.1.2"
"@radix-ui/react-primitive" "2.0.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"
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":
version "1.0.4"
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"
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":
version "1.1.1"
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-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":
version "1.0.1"
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-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":
version "1.0.3"
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-layout-effect" "1.1.0"
"@radix-ui/react-separator@^1.0.3":
version "1.0.3"
resolved "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz"
integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==
"@radix-ui/react-separator@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e"
integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot@1.0.2":
version "1.0.2"
@ -1692,24 +1796,23 @@
"@radix-ui/react-roving-focus" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-tooltip@^1.0.7":
version "1.0.7"
resolved "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz"
integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==
"@radix-ui/react-tooltip@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz#4250b14723f2d8477e7a3d0526c169f91d1f2f74"
integrity sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-dismissable-layer" "1.0.5"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-popper" "1.1.3"
"@radix-ui/react-portal" "1.0.4"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-visually-hidden" "1.0.3"
"@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-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-slot" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-visually-hidden" "1.1.0"
"@radix-ui/react-use-callback-ref@1.0.1":
version "1.0.1"
@ -1815,6 +1918,13 @@
"@babel/runtime" "^7.13.10"
"@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":
version "1.0.1"
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:
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==
dependencies:
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"
integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
lucide-react@^0.416.0:
version "0.416.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.416.0.tgz#657da248f9b862703d7d80aafb912e79ad886313"
integrity sha512-wPWxTzdss1CTz2aqcNWNlbh4YSnH9neJWP3RaeXepxpLCTW+pmu7WcT/wxJe+Q7Y7DqGOxAqakJv0pIK3431Ag==
lucide-react@^0.454.0:
version "0.454.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.454.0.tgz#a81b9c482018720f07ead0503ae502d94d528444"
integrity sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==
luxon@~3.4.0:
version "3.4.4"
@ -6708,7 +6818,7 @@ react-markdown@^9.0.1:
unist-util-visit "^5.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"
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz"
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
@ -6738,6 +6848,17 @@ react-remove-scroll@2.5.7:
use-callback-ref "^1.3.0"
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:
version "2.0.23"
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.0.23.tgz#7a4296f23028c32ffcbe8086aa918cd934e7e87f"