push changes to main

push changes to main
This commit is contained in:
dvelo 2024-12-21 20:11:38 -06:00 committed by GitHub
commit 7cc8671448
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 649 additions and 183 deletions

1
.tool-versions Normal file

@ -0,0 +1 @@
nodejs 23.3.0

@ -20,6 +20,7 @@
"@emotion/is-prop-valid": "^1.3.0", "@emotion/is-prop-valid": "^1.3.0",
"@linear/sdk": "^31.0.0", "@linear/sdk": "^31.0.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-hover-card": "^1.1.1",

@ -32,27 +32,17 @@ import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next"; import { SpeedInsights } from "@vercel/speed-insights/next";
import { GeistSans } from "geist/font/sans"; import { GeistSans } from "geist/font/sans";
import "../globals.css"; import "../globals.css";
import ClientFadeIn from "@/components/ClientFadeIn";
import { CommandBarer } from "@/components/CommandBar"; import { CommandBarer } from "@/components/CommandBar";
import { BrandingGenericIcon } from "@/components/Icon";
import TextFromPathname from "@/components/TextFromPathname";
import { ThemeProvider } from "@/components/ThemeProvider"; import { ThemeProvider } from "@/components/ThemeProvider";
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider"; import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
import TopBar from "@/components/clerk/Topbar";
import NewDomainDialog from "@/components/misc/NewDomainDialog"; import NewDomainDialog from "@/components/misc/NewDomainDialog";
import ThemedToaster from "@/components/misc/ThemedToaster"; import ThemedToaster from "@/components/misc/ThemedToaster";
import UnofficalDialog from "@/components/misc/UnofficalDialog"; import UnofficalDialog from "@/components/misc/UnofficalDialog";
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { banner } from "@/config/banner";
import NextTopLoader from "@/lib/top-loader";
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Inter as interFont } from "next/font/google"; import { Inter as interFont } from "next/font/google";
import Link from "next/link"; import LayoutPart from "@/components/feat/LayoutPart";
import AllBanners from "@/components/feat/AllBanners";
export const extraMetadata = { export const extraMetadata = {
twitter: { twitter: {
@ -86,35 +76,8 @@ export default async function RootLayout({
<ClerkThemeProvider className={GeistSans.className}> <ClerkThemeProvider className={GeistSans.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem> <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<TooltipProvider> <TooltipProvider>
{banner.isBanner && ( <AllBanners />
<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"> <LayoutPart>{children}</LayoutPart>
{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 /> <ThemedToaster />
<CommandBarer /> <CommandBarer />
<SpeedInsights /> <SpeedInsights />

@ -34,7 +34,6 @@ import ColorProvider from "@/components/ColorProvider";
import ServerView from "@/components/ServerView"; import ServerView from "@/components/ServerView";
import StickyTopbar from "@/components/misc/StickyTopbar"; import StickyTopbar from "@/components/misc/StickyTopbar";
import TabServer from "@/components/misc/TabServer"; import TabServer from "@/components/misc/TabServer";
import { Separator } from "@/components/ui/separator";
import type { Metadata, ResolvingMetadata } from "next"; import type { Metadata, ResolvingMetadata } from "next";
type Props = { type Props = {
@ -129,17 +128,17 @@ export async function generateMetadata(
export default function ServerPage({ params }: { params: { server: string } }) { export default function ServerPage({ params }: { params: { server: string } }) {
return ( return (
<main> <main style={{ "color-scheme": "dark" } as React.CSSProperties}>
<ColorProvider server={params.server}> <ColorProvider server={params.server}>
<div className={"pt-16 xl:px-[100px]"}> <div className={"pt-[300px] xl:px-[100px]"}>
<Banner server={params.server} /> <Banner server={params.server} />
<div className="pt-8 z-10 relative">
<ServerView server={params.server} />
</div>
<StickyTopbar scrollElevation={100} className="pt-4"> <StickyTopbar scrollElevation={100} className="pt-4">
<TabServer server={params.server} tabDef="general" /> <TabServer server={params.server} tabDef="general" />
</StickyTopbar> </StickyTopbar>
<div className="pt-8">
<ServerView server={params.server} />
</div>
<Separator />
<br /> <br />
<AfterServerView server={params.server} /> <AfterServerView server={params.server} />
</div> </div>

@ -35,6 +35,7 @@ import ServerView from "@/components/ServerView";
import TabServer from "@/components/misc/TabServer"; import TabServer from "@/components/misc/TabServer";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import type { Metadata, ResolvingMetadata } from "next"; import type { Metadata, ResolvingMetadata } from "next";
import StickyTopbar from "@/components/misc/StickyTopbar";
type Props = { type Props = {
params: { server: string }; params: { server: string };
@ -96,20 +97,23 @@ export async function generateMetadata(
export default function ServerPage({ params }: { params: { server: string } }) { export default function ServerPage({ params }: { params: { server: string } }) {
return ( return (
<main> <main style={{ "color-scheme": "dark" } as React.CSSProperties}>
<ColorProvider server={params.server}> <ColorProvider server={params.server}>
<div className={"pt-16 xl:px-[100px]"}> <div className={"pt-[300px] xl:px-[100px]"}>
<Banner server={params.server} /> <Banner server={params.server} />
<TabServer server={params.server} tabDef="statistics" /> <div className="pt-8 z-10 relative">
<div className="pt-8">
<ServerView server={params.server} /> <ServerView server={params.server} />
</div>
<StickyTopbar scrollElevation={100} className="pt-4">
<TabServer server={params.server} tabDef="statistics" />
</StickyTopbar>
<Separator /> <Separator />
<br /> <br />
<div className="p-4 gap-4"> <div className="p-4 gap-4">
<NewChart server={params.server} /> <NewChart server={params.server} />
</div> </div>
</div> </div>
</div>
</ColorProvider> </ColorProvider>
</main> </main>
); );

@ -117,7 +117,7 @@ export default function AfterServerView({ server }: { server: string }) {
<QRCodeGenerator server={server} /> <QRCodeGenerator server={server} />
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
<FadeIn> <FadeIn className="relative z-10">
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4 "> <div className="grid sm:grid-cols-6 h-full pl-4 pr-4 ">
<div className="ml-5 mb-2 flex items-center sm:hidden overflow-auto w-[calc(100vw-5rem)]"> <div className="ml-5 mb-2 flex items-center sm:hidden overflow-auto w-[calc(100vw-5rem)]">
{(description != "" || discord != "") && ( {(description != "" || discord != "") && (

@ -31,10 +31,12 @@
"use client"; "use client";
import { getCustomization } from "@/lib/api"; import { getCustomization } from "@/lib/api";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useTotalBannerSize from "@/lib/hooks/use-total-banner-size";
export default function Banner({ server }: { server: string }) { export default function Banner({ server }: { server: string }) {
const [bannerURL, setBannerURL] = useState(""); const [bannerURL, setBannerURL] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { bannerSize } = useTotalBannerSize();
useEffect(() => { useEffect(() => {
getCustomization(server).then((c) => { getCustomization(server).then((c) => {
@ -64,8 +66,16 @@ export default function Banner({ server }: { server: string }) {
? bannerURL ? bannerURL
: "wsrv.nl/?url=" + encodeURIComponent(bannerURL) + "?n=-1" : "wsrv.nl/?url=" + encodeURIComponent(bannerURL) + "?n=-1"
} }
className="rounded align-middle block ml-auto mr-auto w-[50%] max-h-[150px]" className="rounded align-middle block ml-auto mr-auto absolute left-0 z-0 w-full object-fill"
alt="User-provided banner for this server." alt="User-provided banner for this server."
style={
{
"-webkit-mask-image":
"linear-gradient(to top, transparent, black)",
maskImage: "linear-gradient(to top, transparent, black)",
top: `${bannerSize * 32 + 36}px`,
} as React.CSSProperties
}
/> />
)} )}
<br /> <br />

@ -30,7 +30,6 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import Banner from "./Banner";
import ServerCustomize from "./ServerCustomize"; import ServerCustomize from "./ServerCustomize";
import TabServer from "./misc/TabServer"; import TabServer from "./misc/TabServer";
@ -42,7 +41,6 @@ export default function CustomizeRoot({
const [color, setColor] = useState(""); const [color, setColor] = useState("");
return ( return (
<div className={"pt-16 xl:px-[100px] theme-" + color}> <div className={"pt-16 xl:px-[100px] theme-" + color}>
<Banner server={params.server} />
<TabServer server={params.server} tabDef="customize" /> <TabServer server={params.server} tabDef="customize" />
<br /> <br />
<div className="pl-[40px] pr-[40px]"> <div className="pl-[40px] pr-[40px]">

@ -33,6 +33,7 @@
import * as React from "react"; import * as React from "react";
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes"; import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
import { type ThemeProviderProps } from "next-themes"; import { type ThemeProviderProps } from "next-themes";
import { usePathname } from "next/navigation";
declare global { declare global {
interface Document { interface Document {
@ -48,6 +49,7 @@ declare global {
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const [mounted, setMounted] = React.useState(false); const [mounted, setMounted] = React.useState(false);
const pathname = usePathname();
React.useEffect(() => { React.useEffect(() => {
setMounted(true); setMounted(true);
@ -55,7 +57,14 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
if (!mounted) return null; if (!mounted) return null;
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return (
<NextThemesProvider
forcedTheme={pathname?.startsWith("/server") ? "dark" : undefined}
{...props}
>
{children}
</NextThemesProvider>
);
} }
interface UseThemeTransitionResult { interface UseThemeTransitionResult {

@ -39,9 +39,11 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useThemeTransition } from "./ThemeProvider"; import { useThemeTransition } from "./ThemeProvider";
import { usePathname } from "next/navigation";
export function ModeToggle() { export function ModeToggle() {
const { changeTheme } = useThemeTransition(); const { changeTheme } = useThemeTransition();
const pathname = usePathname();
return ( return (
<DropdownMenu> <DropdownMenu>
@ -53,13 +55,28 @@ export function ModeToggle() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => changeTheme("light")}> {pathname?.startsWith("/server") && (
<div className="text-sm p-4">
For compatibility reasons, <br /> server pages are forced to dark
mode
</div>
)}
<DropdownMenuItem
onClick={() => changeTheme("light")}
disabled={pathname?.startsWith("/server")}
>
Light Light
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => changeTheme("dark")}> <DropdownMenuItem
onClick={() => changeTheme("dark")}
disabled={pathname?.startsWith("/server")}
>
Dark Dark
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => changeTheme("system")}> <DropdownMenuItem
onClick={() => changeTheme("system")}
disabled={pathname?.startsWith("/server")}
>
System System
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

@ -0,0 +1,73 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
"use client";
import React, { useEffect, useState } from "react";
import { Gradient } from "stripe-gradient";
export default function GradientBanner({
children,
}: {
children?: React.ReactNode;
}) {
const [gradientId, setGradientId] = useState("gradient-banner");
useEffect(() => {
setGradientId("gradient-banner");
const gradient = new Gradient();
gradient.initGradient("#" + gradientId);
}, [gradientId]);
return (
<div className="fixed top-0 left-0 backdrop-blur">
<canvas
id={gradientId}
data-js-darken-top
className="w-screen blur-sm h-[4rem] border-b z-1"
style={
{
"--gradient-color-1": "#6ec3f4",
"--gradient-color-2": "#3a3aff",
"--gradient-color-3": "#ff61ab",
"--gradient-color-4": "#E63946",
webKitMaskImage: "linear-gradient(to top, transparent, black)",
maskImage: "linear-gradient(to top, transparent, black)",
} as React.CSSProperties
}
height="64"
width={window.screen.width}
/>{" "}
<div className="fixed top-0 left-0 z-2 p-2 text-left text-black dark:text-white">
{children}
</div>
</div>
);
}

@ -1,7 +1,7 @@
/* /*
* MHSF, Minehut Server List * MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement * All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/docs/legal/external-content-agreement * located here: https://mhsf.app/docs/legal/external-content-agreement
* *
* All code under MHSF is licensed under the MIT License * All code under MHSF is licensed under the MIT License
* by open source contributors * by open source contributors
@ -28,26 +28,16 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { Button } from "@/components/ui/button"; "use client";
import Link from "next/link";
/** used when there is a outage */ import { useBanners } from "@/lib/hooks/use-banners";
export const banner = {
isBanner: export default function AllBanners() {
process.env.NEXT_PUBLIC_VERCEL_ENV !== "production" const { banners } = useBanners();
? true
: /** Set this to true when outage --->*/ false, return (
bannerText: <div className="fixed grid grid-cols-1 z-10">
process.env.NEXT_PUBLIC_VERCEL_ENV !== "production" ? ( {banners.map((banner) => banner.bannerContent)}
<> </div>
Your not in production!{" "} );
<Link href="https://list.mlnehut.com"> }
<Button variant="link" className="dark:text-black">
Go to production
</Button>
</Link>
</>
) : (
<>{/** Set this to an explanation! */}</>
),
};

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

@ -0,0 +1,55 @@
"use client";
import ClientFadeIn from "@/components/ClientFadeIn";
import { BrandingGenericIcon } from "@/components/Icon";
import TextFromPathname from "@/components/TextFromPathname";
import TopBar from "@/components/clerk/Topbar";
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import NextTopLoader from "@/lib/top-loader";
import Link from "next/link";
import BannerContainer from "@/components/feat/BannerContainer";
import { Inter } from "next/font/google";
const inter = Inter({ variable: "--font-inter", subsets: ["latin"] });
export default function LayoutPart({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<BannerContainer
className={"w-screen h-[3rem] border-b fixed backdrop-blur flex z-10"}
style={(size: number) => ({
marginTop: `${2 * size}rem`,
})}
>
<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} />
</BannerContainer>
<BannerContainer
style={(size: number) => ({
paddingTop: `${2 * size}rem`,
})}
>
<NextTopLoader />
<ClientFadeIn>{children}</ClientFadeIn>
</BannerContainer>
</>
);
}

@ -0,0 +1,50 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
export default function MainBanner({
className,
size = 1,
children,
}: {
className?: string;
size?: number;
children?: React.ReactNode;
}) {
return (
<div
className={`w-screen border-b text-black flex items-center text-center font-medium pl-2 ${className}`}
style={{
height: `${size * 2}rem`,
}}
>
{children}
</div>
);
}

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

@ -29,8 +29,7 @@
*/ */
"use client"; "use client";
import useTotalBannerSize from "@/lib/hooks/use-total-banner-size";
import { banner } from "@/config/banner";
import { useEffect, useState, ReactNode } from "react"; import { useEffect, useState, ReactNode } from "react";
export default function StickyTopbar({ export default function StickyTopbar({
@ -43,6 +42,7 @@ export default function StickyTopbar({
className?: string; className?: string;
}) { }) {
const [isSticky, setIsSticky] = useState(false); const [isSticky, setIsSticky] = useState(false);
const { bannerSize } = useTotalBannerSize();
const handleScroll = () => { const handleScroll = () => {
if (window.scrollY > scrollElevation) { if (window.scrollY > scrollElevation) {
@ -61,7 +61,10 @@ export default function StickyTopbar({
return ( return (
<div <div
className={`transition-all duration-300 ${isSticky ? "fixed left-0 w-full backdrop-blur shadow-lg " + (banner.isBanner == true ? "top-[70px] " : "top-[38px] ") + className : "block w-full bg-transparent"}`} className={`transition-all duration-300 ${isSticky ? "fixed left-0 w-full backdrop-blur shadow-lg " + className : "block w-full bg-transparent"}`}
style={{
top: isSticky ? `${bannerSize * 32 + 38}px` : undefined,
}}
> >
{children} {children}
</div> </div>

@ -0,0 +1,7 @@
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root
export { AspectRatio }

@ -0,0 +1,17 @@
import type { SVGProps } from "react";
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 default Discord;

@ -0,0 +1,15 @@
import type { SVGProps } from "react";
const Github = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 256 250"
width="1em"
height="1em"
fill="#fff"
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>
);
export default Github;

41
src/config/banners.tsx Normal file

@ -0,0 +1,41 @@
export const defaultBanners: {
bannerSpace: number;
bannerContent: React.ReactNode;
}[] = [
// The sponsor banner ALWAYS has to be first.
// {
// bannerSpace: 2,
// bannerContent: (
// <MainBanner size={2} className="max-h-[4rem] border-0">
// {" "}
// <GradientBanner>
// <strong>???</strong> — <i>an official affiliate of MHSF</i>{" "}
// <br />
// Lorem ipsum odor amet, consectetuer adipiscing elit. — check it out
// </GradientBanner>
// </MainBanner>
// ),
// },
];
export const bannerHooks: (() =>
| { bannerSpace: number; bannerContent: React.ReactNode }
| undefined)[] = [
() => {
// if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production")
// return {
// bannerSpace: 1,
// bannerContent: (
// <MainBanner className="bg-orange-600">
// Your not in production!{" "}
// <Link href="https://mhsf.app">
// <Button variant="link" className="dark:text-black">
// Go to production
// </Button>
// </Link>
// </MainBanner>
// ),
// };
return undefined;
},
];

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

@ -65,7 +65,7 @@ export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
features={[ features={[
"New MOTD engine that is over 3,000% faster, runs client-side, and doesn't need any requests to run.", "New MOTD engine that is over 3,000% faster, runs client-side, and doesn't need any requests to run.",
"Fixed issue where GitHub link was broken if you were signed-out", "Fixed issue where GitHub link was broken if you were signed-out",
"", "Adding snowfall finally (better late then ever)",
]} ]}
title={ title={
<strong className="flex items-center"> <strong className="flex items-center">

@ -62,13 +62,17 @@ async function apiConstructor<K>(
export async function getMOTDFromServer( export async function getMOTDFromServer(
list: Array<{ server: string; motd: string }>, list: Array<{ server: string; motd: string }>,
): Promise<Array<{ server: string; motd: string }>> { ): Promise<Array<{ server: string; motd: string }>> {
const result = await fetch(connector("/motd", { version: 1 }), { const result = await fetch(
process.env.NEXT_PUBLIC_ALTERNATE_MOTD_ENDPOINT ??
connector("/motd", { version: 1 }),
{
body: JSON.stringify({ motd: list }), body: JSON.stringify({ motd: list }),
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); },
);
let json = await result.json(); let json = await result.json();
return json.result; return json.result;

@ -0,0 +1,57 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { bannerHooks, defaultBanners } from "@/config/banners";
import { useEffect, useState } from "react";
import { useIsMobile } from "./use-mobile";
export function useBanners() {
const [banners, setBanners] = useState<
{
bannerSpace: number;
bannerContent: React.ReactNode;
}[]
>(defaultBanners);
const isOnMobile = useIsMobile();
useEffect(() => {
if (isOnMobile) {
setBanners([]);
return;
}
setBanners(defaultBanners);
bannerHooks.forEach((hook) => {
const run = hook();
if (run !== undefined) setBanners((oldBanners) => [...oldBanners, run]);
});
}, [isOnMobile]);
return { banners };
}

@ -0,0 +1,61 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { bannerHooks, defaultBanners } from "@/config/banners";
import { useEffect, useState } from "react";
import { useIsMobile } from "./use-mobile";
export default function useTotalBannerSize() {
const [bannerSize, setBannerSize] = useState(0);
const isOnMobile = useIsMobile();
useEffect(() => {
setBannerSize(0);
if (isOnMobile) return;
const allBanners = [];
// First push the default banners
allBanners.push(...defaultBanners);
// Then push the banner hooks
bannerHooks.forEach((hook) => {
allBanners.push(hook());
});
setBannerSize(
allBanners.reduce(
(acc, banner) => acc + (banner ?? { bannerSpace: 0 }).bannerSpace,
0
) ?? 0
);
}, [isOnMobile]);
return { bannerSize };
}

5
src/lib/types/stripe-gradient.d.ts vendored Normal file

@ -0,0 +1,5 @@
declare module "stripe-gradient" {
declare class Gradient {
initGradient(id: string): void;
};
}

@ -1178,6 +1178,13 @@
dependencies: dependencies:
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-aspect-ratio@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.1.tgz#95d7692e61bab5eb7fec91f241ea993899593313"
integrity sha512-kNU4FIpcFMBLkOUcgeIteH06/8JLBcYY6Le1iKenDGCYNYFX3TQqCZjzkOsz37h7r94/99GTb7YhEr98ZBJibw==
dependencies:
"@radix-ui/react-primitive" "2.0.1"
"@radix-ui/react-avatar@^1.1.1": "@radix-ui/react-avatar@^1.1.1":
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.1.tgz#5848d2ed5f34d18b36fc7e2d227c41fca8600ea1" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.1.tgz#5848d2ed5f34d18b36fc7e2d227c41fca8600ea1"