mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 15:05:02 -05:00
feat: new banner system
This commit is contained in:
parent
b144bafd03
commit
c8fe0a6051
1
.tool-versions
Normal file
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",
|
||||||
|
|||||||
48
src/app/(auth)/auth/sign-in/[[...sign-in]]/page.tsx
Normal file
48
src/app/(auth)/auth/sign-in/[[...sign-in]]/page.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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 SignUpPage from "@/components/clerk/SignInForm";
|
||||||
|
import { ServerCrash } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-[rgb(244,_244,_245)] p-6 md:p-10">
|
||||||
|
<div className="flex w-full max-w-sm flex-col gap-6">
|
||||||
|
<a href="/" className="flex items-center gap-2 self-center font-medium">
|
||||||
|
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||||
|
<ServerCrash className="size-4" />
|
||||||
|
</div>
|
||||||
|
MHSF
|
||||||
|
</a>
|
||||||
|
<SignUpPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/app/(auth)/layout.tsx
Normal file
22
src/app/(auth)/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ClerkProvider } from "@clerk/nextjs";
|
||||||
|
import "@/app/globals.css";
|
||||||
|
import { GeistSans } from "geist/font/sans";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "Next.js",
|
||||||
|
description: "Generated by Next.js",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={GeistSans.className}>
|
||||||
|
<body>
|
||||||
|
<ClerkProvider>{children}</ClerkProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 = {
|
||||||
@ -131,15 +130,15 @@ export default function ServerPage({ params }: { params: { server: string } }) {
|
|||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<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 };
|
||||||
@ -98,16 +99,19 @@ export default function ServerPage({ params }: { params: { server: string } }) {
|
|||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<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} />
|
||||||
<Separator />
|
</div>
|
||||||
<br />
|
|
||||||
<div className="p-4 gap-4">
|
<StickyTopbar scrollElevation={100} className="pt-4">
|
||||||
<NewChart server={params.server} />
|
<TabServer server={params.server} tabDef="statistics" />
|
||||||
</div>
|
</StickyTopbar>
|
||||||
|
<Separator />
|
||||||
|
<br />
|
||||||
|
<div className="p-4 gap-4">
|
||||||
|
<NewChart server={params.server} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ColorProvider>
|
</ColorProvider>
|
||||||
|
|||||||
@ -31,44 +31,54 @@
|
|||||||
"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) => {
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setBannerURL(c.banner == undefined ? "" : c.banner);
|
setBannerURL(c.banner == undefined ? "" : c.banner);
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [server]);
|
}, [server]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{bannerURL != "" && (
|
{bannerURL != "" && (
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
bannerURL.startsWith("https://i.imgur.com")
|
bannerURL.startsWith("https://i.imgur.com")
|
||||||
? 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 max-h-[400px] w-full object-fill"
|
||||||
alt="User-provided banner for this server."
|
alt="User-provided banner for this server."
|
||||||
/>
|
style={
|
||||||
)}
|
{
|
||||||
<br />
|
"-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 />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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]">
|
||||||
|
|||||||
311
src/components/clerk/SignInForm.tsx
Normal file
311
src/components/clerk/SignInForm.tsx
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
/*
|
||||||
|
* 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 * as Clerk from "@clerk/elements/common";
|
||||||
|
import * as SignUp from "@clerk/elements/sign-up";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { LoadingSpinner } from "../ui/loading-spinner";
|
||||||
|
import Discord from "../ui/discord";
|
||||||
|
import Github from "../ui/github";
|
||||||
|
|
||||||
|
export default function SignUpPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
<SignUp.Root
|
||||||
|
fallback={
|
||||||
|
<Card className="flex items-center justify-center float-center absolute left-[50%] translate-x-[-50%] size-[50px]">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</Card>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Clerk.Loading>
|
||||||
|
{(isGlobalLoading) => (
|
||||||
|
<>
|
||||||
|
{isGlobalLoading && (
|
||||||
|
<Card className="flex items-center justify-center float-center absolute left-[50%] translate-x-[-50%] size-[50px]">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
<SignUp.Step name="start">
|
||||||
|
<Card className="w-full sm:w-96 ">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<CardTitle>Create your account</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Welcome! Please fill in the details to get started.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-y-4">
|
||||||
|
<div className="grid gap-2 grid-cols-2">
|
||||||
|
<Clerk.Connection name="discord" asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
className="flex items-center"
|
||||||
|
disabled={isGlobalLoading}
|
||||||
|
>
|
||||||
|
<Clerk.Loading scope="provider:discord">
|
||||||
|
{(isLoading) =>
|
||||||
|
isLoading ? (
|
||||||
|
<LoadingSpinner className="size-4 " />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Discord className="mr-2 size-4" />
|
||||||
|
Discord
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</Button>
|
||||||
|
</Clerk.Connection>
|
||||||
|
<Clerk.Connection name="github" asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
className="flex items-center"
|
||||||
|
disabled={isGlobalLoading}
|
||||||
|
>
|
||||||
|
<Clerk.Loading scope="provider:github">
|
||||||
|
{(isLoading) =>
|
||||||
|
isLoading ? (
|
||||||
|
<LoadingSpinner className="size-4 " />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Github
|
||||||
|
className="mr-2 size-4"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
GitHub
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</Button>
|
||||||
|
</Clerk.Connection>
|
||||||
|
</div>
|
||||||
|
<p className="flex items-center gap-x-3 text-sm text-muted-foreground before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border">
|
||||||
|
or
|
||||||
|
</p>
|
||||||
|
<Clerk.Field name="emailAddress" className="space-y-2">
|
||||||
|
<Clerk.Label asChild>
|
||||||
|
<Label>Email address</Label>
|
||||||
|
</Clerk.Label>
|
||||||
|
<Clerk.Input type="email" required asChild>
|
||||||
|
<Input />
|
||||||
|
</Clerk.Input>
|
||||||
|
<Clerk.FieldError className="block text-sm text-destructive" />
|
||||||
|
</Clerk.Field>
|
||||||
|
<Clerk.Field name="password" className="space-y-2">
|
||||||
|
<Clerk.Label asChild>
|
||||||
|
<Label>Password</Label>
|
||||||
|
</Clerk.Label>
|
||||||
|
<Clerk.Input type="password" required asChild>
|
||||||
|
<Input />
|
||||||
|
</Clerk.Input>
|
||||||
|
<Clerk.FieldError className="block text-sm text-destructive" />
|
||||||
|
</Clerk.Field>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<div className="grid w-full gap-y-4">
|
||||||
|
<SignUp.Captcha className="empty:hidden" />
|
||||||
|
<SignUp.Action submit asChild>
|
||||||
|
<Button
|
||||||
|
disabled={isGlobalLoading}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Clerk.Loading>
|
||||||
|
{(isLoading) => {
|
||||||
|
return isLoading ? (
|
||||||
|
<LoadingSpinner className="size-4 " />
|
||||||
|
) : (
|
||||||
|
"Continue"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</Button>
|
||||||
|
</SignUp.Action>
|
||||||
|
<Button variant="link" size="sm" asChild>
|
||||||
|
<Clerk.Link navigate="sign-in">
|
||||||
|
Already have an account? Sign in
|
||||||
|
</Clerk.Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</SignUp.Step>
|
||||||
|
|
||||||
|
<SignUp.Step name="continue">
|
||||||
|
<Card className="w-full sm:w-96 ">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Continue registration</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Clerk.Field name="username" className="space-y-2">
|
||||||
|
<Clerk.Label>
|
||||||
|
<Label>Username</Label>
|
||||||
|
</Clerk.Label>
|
||||||
|
<Clerk.Input type="text" required asChild>
|
||||||
|
<Input />
|
||||||
|
</Clerk.Input>
|
||||||
|
<Clerk.FieldError className="block text-sm text-destructive" />
|
||||||
|
</Clerk.Field>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<div className="grid w-full gap-y-4">
|
||||||
|
<SignUp.Action submit asChild>
|
||||||
|
<Button
|
||||||
|
disabled={isGlobalLoading}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Clerk.Loading>
|
||||||
|
{(isLoading) => {
|
||||||
|
return isLoading ? (
|
||||||
|
<LoadingSpinner className="size-4 " />
|
||||||
|
) : (
|
||||||
|
"Continue"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</Button>
|
||||||
|
</SignUp.Action>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</SignUp.Step>
|
||||||
|
|
||||||
|
<SignUp.Step name="verifications">
|
||||||
|
<SignUp.Strategy name="email_code">
|
||||||
|
<Card className="w-full sm:w-96 ">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Verify your email</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Use the verification link sent to your email address
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-y-4">
|
||||||
|
<div className="grid items-center justify-center gap-y-2">
|
||||||
|
<Clerk.Field name="code" className="space-y-2">
|
||||||
|
<Clerk.Label className="sr-only">
|
||||||
|
Email address
|
||||||
|
</Clerk.Label>
|
||||||
|
<div className="flex justify-center text-center">
|
||||||
|
<Clerk.Input
|
||||||
|
type="otp"
|
||||||
|
className="flex justify-center has-[:disabled]:opacity-50"
|
||||||
|
autoSubmit
|
||||||
|
render={({ value, status }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-status={status}
|
||||||
|
className={cn(
|
||||||
|
"relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
||||||
|
{
|
||||||
|
"z-10 ring-2 ring-ring ring-offset-background":
|
||||||
|
status === "cursor" ||
|
||||||
|
status === "selected",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
{status === "cursor" && (
|
||||||
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Clerk.FieldError className="block text-center text-sm text-destructive" />
|
||||||
|
</Clerk.Field>
|
||||||
|
<SignUp.Action
|
||||||
|
asChild
|
||||||
|
resend
|
||||||
|
className="text-muted-foreground"
|
||||||
|
fallback={({ resendableAfter }) => (
|
||||||
|
<Button variant="link" size="sm" disabled>
|
||||||
|
Didn't receive a code? Resend (
|
||||||
|
<span className="tabular-nums">
|
||||||
|
{resendableAfter}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button type="button" variant="link" size="sm">
|
||||||
|
Didn't receive a code? Resend
|
||||||
|
</Button>
|
||||||
|
</SignUp.Action>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<div className="grid w-full gap-y-4">
|
||||||
|
<SignUp.Action submit asChild>
|
||||||
|
<Button
|
||||||
|
disabled={isGlobalLoading}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Clerk.Loading>
|
||||||
|
{(isLoading) => {
|
||||||
|
return isLoading ? (
|
||||||
|
<LoadingSpinner className="size-4 " />
|
||||||
|
) : (
|
||||||
|
"Continue"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</Button>
|
||||||
|
</SignUp.Action>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</SignUp.Strategy>
|
||||||
|
</SignUp.Step>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Clerk.Loading>
|
||||||
|
</SignUp.Root>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/components/effects/gradient-banner.tsx
Normal file
73
src/components/effects/gradient-banner.tsx
Normal file
@ -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! */}</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
51
src/components/feat/BannerContainer.tsx
Normal file
51
src/components/feat/BannerContainer.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
src/components/feat/LayoutPart.tsx
Normal file
55
src/components/feat/LayoutPart.tsx
Normal file
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/components/feat/MainBanner.tsx
Normal file
50
src/components/feat/MainBanner.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src/components/misc/AffilatePopup.tsx
Normal file
29
src/components/misc/AffilatePopup.tsx
Normal file
@ -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.
|
||||||
|
*/
|
||||||
83
src/components/misc/KnockNotifications.tsx
Normal file
83
src/components/misc/KnockNotifications.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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 { SignedIn } from "@clerk/clerk-react";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import {
|
||||||
|
KnockProvider,
|
||||||
|
KnockFeedProvider,
|
||||||
|
NotificationIconButton,
|
||||||
|
NotificationFeedPopover,
|
||||||
|
NotificationCell,
|
||||||
|
} from "@knocklabs/react";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
|
import "@knocklabs/react/dist/index.css";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Bell } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
|
export default function KnockNotification() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const notifButtonRef = useRef(null);
|
||||||
|
const { user } = useUser();
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SignedIn>
|
||||||
|
<KnockProvider
|
||||||
|
apiKey={process.env.NEXT_PUBLIC_KNOCK_KEY as string}
|
||||||
|
userId={user?.id as string}
|
||||||
|
>
|
||||||
|
<KnockFeedProvider
|
||||||
|
feedId={process.env.NEXT_PUBLIC_CHANNEL_ID as string}
|
||||||
|
colorMode={resolvedTheme}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
className="mb-1"
|
||||||
|
ref={notifButtonRef}
|
||||||
|
onClick={() => setIsVisible(!isVisible)}
|
||||||
|
>
|
||||||
|
<Bell className="h-[1.2rem] w-[1.2rem]" />
|
||||||
|
</Button>
|
||||||
|
<NotificationFeedPopover
|
||||||
|
buttonRef={notifButtonRef}
|
||||||
|
isVisible={isVisible}
|
||||||
|
onClose={() => setIsVisible(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
</KnockFeedProvider>
|
||||||
|
</KnockProvider>
|
||||||
|
</SignedIn>
|
||||||
|
);
|
||||||
|
}
|
||||||
99
src/components/misc/ServerListNav.tsx
Normal file
99
src/components/misc/ServerListNav.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "../ui/dropdown-menu";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
|
export function ServerListNav() {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-6xl mx-auto p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-xl font-semibold">Links</h1>
|
||||||
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center sm:justify-between gap-2 sm:gap-8">
|
||||||
|
<div className="grid grid-cols-2 sm:flex sm:flex-row items-stretch sm:items-center gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="w-full gap-2">
|
||||||
|
Filter
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>All links</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Active links</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Archived links</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="w-full gap-2">
|
||||||
|
Display
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>List view</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Grid view</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Compact view</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 w-full sm:w-auto">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input type="search" placeholder="Search..." className="pl-8" />
|
||||||
|
</div>
|
||||||
|
<Button className="shrink-0 gap-2">
|
||||||
|
Create link
|
||||||
|
<kbd className="pointer-events-none hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
|
||||||
|
C
|
||||||
|
</kbd>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="icon" className="shrink-0">
|
||||||
|
<MoreVertical className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More options</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
7
src/components/ui/aspect-ratio.tsx
Normal file
7
src/components/ui/aspect-ratio.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
||||||
|
|
||||||
|
const AspectRatio = AspectRatioPrimitive.Root
|
||||||
|
|
||||||
|
export { AspectRatio }
|
||||||
17
src/components/ui/discord.tsx
Normal file
17
src/components/ui/discord.tsx
Normal file
@ -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;
|
||||||
15
src/components/ui/github.tsx
Normal file
15
src/components/ui/github.tsx
Normal file
@ -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
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;
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -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(
|
||||||
body: JSON.stringify({ motd: list }),
|
process.env.NEXT_PUBLIC_ALTERNATE_MOTD_ENDPOINT ??
|
||||||
method: "POST",
|
connector("/motd", { version: 1 }),
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
body: JSON.stringify({ motd: list }),
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
let json = await result.json();
|
let json = await result.json();
|
||||||
return json.result;
|
return json.result;
|
||||||
|
|||||||
57
src/lib/hooks/use-banners.tsx
Normal file
57
src/lib/hooks/use-banners.tsx
Normal file
@ -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 };
|
||||||
|
}
|
||||||
61
src/lib/hooks/use-total-banner-size.tsx
Normal file
61
src/lib/hooks/use-total-banner-size.tsx
Normal file
@ -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
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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user