mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-15 12:08:02 -05:00
Compare commits
1 Commits
5773e1da62
...
d6f3ef7816
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6f3ef7816 |
@ -44,7 +44,7 @@
|
|||||||
"next": "14.2.10",
|
"next": "14.2.10",
|
||||||
"next-contentlayer": "^0.3.4",
|
"next-contentlayer": "^0.3.4",
|
||||||
"next-css-obfuscator": "^2.2.16",
|
"next-css-obfuscator": "^2.2.16",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.3.0",
|
||||||
"nextjs-toploader": "^1.6.12",
|
"nextjs-toploader": "^1.6.12",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"postcss-obfuscator": "^1.6.1",
|
"postcss-obfuscator": "^1.6.1",
|
||||||
@ -53,11 +53,8 @@
|
|||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-fade-in": "^2.0.1",
|
"react-fade-in": "^2.0.1",
|
||||||
"react-fast-marquee": "^1.6.5",
|
"react-fast-marquee": "^1.6.5",
|
||||||
"react-hot-toast": "^2.4.1",
|
|
||||||
"react-qr-code": "^2.0.15",
|
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"sonner": "^1.7.0",
|
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tailwindcss-patch": "^4.0.0",
|
"tailwindcss-patch": "^4.0.0",
|
||||||
@ -103,6 +100,7 @@
|
|||||||
"mangle-css-class-webpack-plugin": "^5.1.0",
|
"mangle-css-class-webpack-plugin": "^5.1.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"react-hook-form": "^7.52.2",
|
"react-hook-form": "^7.52.2",
|
||||||
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useClerk, useUser } from "@clerk/nextjs";
|
import { useClerk, useUser } from "@clerk/nextjs";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { unlinkMCAccount } from "@/lib/api";
|
import { unlinkMCAccount } from "@/lib/api";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Dialog } from "@/components/ui/dialog";
|
import { Dialog } from "@/components/ui/dialog";
|
||||||
|
|||||||
@ -84,7 +84,12 @@ export default async function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<ClerkThemeProvider className={GeistSans.className}>
|
<ClerkThemeProvider className={GeistSans.className}>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
{banner.isBanner && (
|
{banner.isBanner && (
|
||||||
<div className="bg-orange-600 z-10 w-screen h-8 border-b fixed text-black flex items-center text-center font-medium pl-2">
|
<div className="bg-orange-600 z-10 w-screen h-8 border-b fixed text-black flex items-center text-center font-medium pl-2">
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import AfterServerView from "@/components/AfterServerView";
|
|||||||
import Banner from "@/components/Banner";
|
import Banner from "@/components/Banner";
|
||||||
import ColorProvider from "@/components/ColorProvider";
|
import ColorProvider from "@/components/ColorProvider";
|
||||||
import ServerView from "@/components/ServerView";
|
import ServerView from "@/components/ServerView";
|
||||||
import StickyTopbar from "@/components/misc/StickyTopbar";
|
|
||||||
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";
|
||||||
@ -131,11 +130,9 @@ 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-16"}>
|
||||||
<Banner server={params.server} />
|
<Banner server={params.server} />
|
||||||
<StickyTopbar scrollElevation={100} className="pt-4">
|
|
||||||
<TabServer server={params.server} tabDef="general" />
|
<TabServer server={params.server} tabDef="general" />
|
||||||
</StickyTopbar>
|
|
||||||
<div className="pt-8">
|
<div className="pt-8">
|
||||||
<ServerView server={params.server} />
|
<ServerView server={params.server} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -42,7 +42,7 @@ type Props = {
|
|||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: Props,
|
{ params }: Props,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata,
|
||||||
): Promise<Metadata> {
|
): Promise<Metadata> {
|
||||||
// read route params
|
// read route params
|
||||||
const { server } = params;
|
const { server } = params;
|
||||||
@ -98,7 +98,7 @@ 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-16"}>
|
||||||
<Banner server={params.server} />
|
<Banner server={params.server} />
|
||||||
<TabServer server={params.server} tabDef="statistics" />
|
<TabServer server={params.server} tabDef="statistics" />
|
||||||
<div className="pt-8">
|
<div className="pt-8">
|
||||||
|
|||||||
@ -147,37 +147,6 @@
|
|||||||
/* }*/
|
/* }*/
|
||||||
/*}*/
|
/*}*/
|
||||||
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
::view-transition-old(root),
|
|
||||||
::view-transition-new(root) {
|
|
||||||
animation: none;
|
|
||||||
mix-blend-mode: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
::view-transition-old(root) {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
::view-transition-new(root) {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes clip-down {
|
|
||||||
from {
|
|
||||||
clip-path: inset(0 0 100% 0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
clip-path: inset(0 0 0 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark::view-transition-new(root),
|
|
||||||
.light::view-transition-new(root) {
|
|
||||||
animation: 0.7s clip-down;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.backdrop-blur {
|
.backdrop-blur {
|
||||||
-webkit-backdrop-filter: blur(8px) !important;
|
-webkit-backdrop-filter: blur(8px) !important;
|
||||||
backdrop-filter: blur(8px) !important;
|
backdrop-filter: blur(8px) !important;
|
||||||
|
|||||||
@ -37,12 +37,11 @@ import {
|
|||||||
getIndexFromRarity,
|
getIndexFromRarity,
|
||||||
getMinehutIcons,
|
getMinehutIcons,
|
||||||
} from "@/lib/types/server-icon";
|
} from "@/lib/types/server-icon";
|
||||||
import { Copy, Info, QrCode, Share2 } from "lucide-react";
|
import { Copy, ExternalLink, Info } from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import FadeIn from "react-fade-in/lib/FadeIn";
|
import FadeIn from "react-fade-in/lib/FadeIn";
|
||||||
import { CheckmarkIcon } from "react-hot-toast";
|
import toast, { CheckmarkIcon } from "react-hot-toast";
|
||||||
import { toast } from "sonner";
|
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import AchievementList from "./feat/AchievementList";
|
import AchievementList from "./feat/AchievementList";
|
||||||
@ -57,9 +56,6 @@ import {
|
|||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "./ui/drawer";
|
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "./ui/drawer";
|
||||||
import EmbedSelector from "./feat/EmbedSelector";
|
import EmbedSelector from "./feat/EmbedSelector";
|
||||||
import { Separator } from "./ui/separator";
|
|
||||||
import QRCodeGenerator from "./feat/QRCodeGen";
|
|
||||||
import NoItems from "./misc/NoItems";
|
|
||||||
|
|
||||||
export default function AfterServerView({ server }: { server: string }) {
|
export default function AfterServerView({ server }: { server: string }) {
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
@ -68,7 +64,6 @@ export default function AfterServerView({ server }: { server: string }) {
|
|||||||
const [icons, setIcons] = useState<MinehutIcon[]>();
|
const [icons, setIcons] = useState<MinehutIcon[]>();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [qrCodeOpen, setQrCodeOpen] = useState(false);
|
|
||||||
const [view, setView] = useState(
|
const [view, setView] = useState(
|
||||||
description !== "" || discord !== "" ? "desc" : "extra"
|
description !== "" || discord !== "" ? "desc" : "extra"
|
||||||
);
|
);
|
||||||
@ -109,14 +104,6 @@ export default function AfterServerView({ server }: { server: string }) {
|
|||||||
<EmbedSelector server={server} />
|
<EmbedSelector server={server} />
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Drawer open={qrCodeOpen} onOpenChange={setQrCodeOpen}>
|
|
||||||
<DrawerContent className="max-w-md w-full mx-auto rounded-t-[10px]">
|
|
||||||
<DrawerHeader>
|
|
||||||
<DrawerTitle>QR Code generator</DrawerTitle>
|
|
||||||
</DrawerHeader>
|
|
||||||
<QRCodeGenerator server={server} />
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
<FadeIn>
|
<FadeIn>
|
||||||
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4 ">
|
<div className="grid sm:grid-cols-6 h-full pl-4 pr-4 ">
|
||||||
<div className="ml-5 mb-2 flex items-center sm:hidden overflow-auto w-[calc(100vw-5rem)]">
|
<div className="ml-5 mb-2 flex items-center sm:hidden overflow-auto w-[calc(100vw-5rem)]">
|
||||||
@ -148,10 +135,9 @@ export default function AfterServerView({ server }: { server: string }) {
|
|||||||
>
|
>
|
||||||
Purchased Icons
|
Purchased Icons
|
||||||
</Button>
|
</Button>
|
||||||
<Separator orientation="vertical" />
|
|
||||||
<Button variant="ghost" onClick={() => setEmbedOpened(true)}>
|
<Button variant="ghost" onClick={() => setEmbedOpened(true)}>
|
||||||
<Share2 className="h-[1rem] w-[1rem] mr-2" />
|
Embed Creator
|
||||||
Embeds
|
<ExternalLink className="h-[1.2rem] w-[1.2rem] ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-sm:hidden">
|
<div className="max-sm:hidden">
|
||||||
@ -182,16 +168,9 @@ export default function AfterServerView({ server }: { server: string }) {
|
|||||||
>
|
>
|
||||||
Purchased Icons
|
Purchased Icons
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
|
||||||
<Separator />
|
|
||||||
<br />
|
|
||||||
<Button variant="ghost" onClick={() => setEmbedOpened(true)}>
|
<Button variant="ghost" onClick={() => setEmbedOpened(true)}>
|
||||||
<Share2 className="h-[1rem] w-[1rem] mr-2" />
|
Embed Creator
|
||||||
Embeds
|
<ExternalLink className="h-[1.2rem] w-[1.2rem] ml-1" />
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" onClick={() => setQrCodeOpen(true)}>
|
|
||||||
<QrCode className="h-[1rem] w-[1rem] mr-2" />
|
|
||||||
QR Code
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -471,7 +450,6 @@ export default function AfterServerView({ server }: { server: string }) {
|
|||||||
ownership, they may or may not available at that certain
|
ownership, they may or may not available at that certain
|
||||||
moment either.
|
moment either.
|
||||||
</p>
|
</p>
|
||||||
{serverObject?.purchased_icons.length == 0 && <NoItems />}
|
|
||||||
{serverObject?.purchased_icons.map((icon) => (
|
{serverObject?.purchased_icons.map((icon) => (
|
||||||
<Card key={icon} className="my-4">
|
<Card key={icon} className="my-4">
|
||||||
<CardContent
|
<CardContent
|
||||||
|
|||||||
@ -68,7 +68,7 @@ import {
|
|||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import ServerSingle from "@/lib/single";
|
import ServerSingle from "@/lib/single";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { ServerResponse, OnlineServer } from "@/lib/types/mh-server";
|
import { ServerResponse, OnlineServer } from "@/lib/types/mh-server";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -80,7 +80,6 @@ import {
|
|||||||
import { TagShower } from "./ServerList";
|
import { TagShower } from "./ServerList";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { CheckmarkIcon } from "react-hot-toast";
|
|
||||||
|
|
||||||
export function SearchCommandBar() {
|
export function SearchCommandBar() {
|
||||||
const [serverList, setServerList] = useState<OnlineServer[]>([]);
|
const [serverList, setServerList] = useState<OnlineServer[]>([]);
|
||||||
@ -279,7 +278,7 @@ export function OfflineServerCB() {
|
|||||||
|
|
||||||
{customized && (
|
{customized && (
|
||||||
<h2 className="flex items-center text-muted-foreground">
|
<h2 className="flex items-center text-muted-foreground">
|
||||||
<CheckmarkIcon />
|
<CheckIcon />
|
||||||
<span className="pl-1.5 text-[16px]">
|
<span className="pl-1.5 text-[16px]">
|
||||||
Is customized by a MHSF User
|
Is customized by a MHSF User
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Banner from "./Banner";
|
import Banner from "./Banner";
|
||||||
import ServerCustomize from "./ServerCustomize";
|
import ServerCustomize from "./ServerCustomize";
|
||||||
@ -41,9 +42,10 @@ 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 theme-" + color}>
|
||||||
<Banner server={params.server} />
|
<Banner server={params.server} />
|
||||||
<TabServer server={params.server} tabDef="customize" />
|
<TabServer server={params.server} tabDef="customize" />
|
||||||
|
<Separator />
|
||||||
<br />
|
<br />
|
||||||
<div className="pl-[40px] pr-[40px]">
|
<div className="pl-[40px] pr-[40px]">
|
||||||
<ServerCustomize server={params.server} cs={color} setCS={setColor} />
|
<ServerCustomize server={params.server} cs={color} setCS={setColor} />
|
||||||
|
|||||||
@ -30,12 +30,13 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Spinner } from "./ui/spinner";
|
||||||
import { Card, CardHeader, CardTitle } from "./ui/card";
|
import { Card, CardHeader, CardTitle } from "./ui/card";
|
||||||
import type { ServerResponse } from "@/lib/types/mh-server";
|
import type { ServerResponse } from "@/lib/types/mh-server";
|
||||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Copy, Layers, XIcon } from "lucide-react";
|
import { Copy, Layers, XIcon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
import { getAccountFavorites } from "@/lib/api";
|
import { getAccountFavorites } from "@/lib/api";
|
||||||
import { useRouter } from "@/lib/useRouter";
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
|||||||
@ -304,19 +304,3 @@ export function BrandingGenericIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export 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>
|
|
||||||
);
|
|
||||||
|
|||||||
@ -196,7 +196,7 @@ export function NewChart({ server }: { server: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convert(value: number) {
|
function convert(value: number) {
|
||||||
var result: string = value.toString();
|
var result: string = value.toString();
|
||||||
if (value >= 1000000) {
|
if (value >= 1000000) {
|
||||||
result = Math.floor(value / 1000000) + "m";
|
result = Math.floor(value / 1000000) + "m";
|
||||||
|
|||||||
@ -45,7 +45,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "./ui/switch";
|
import { Switch } from "./ui/switch";
|
||||||
import { setAccountSL } from "@/lib/api";
|
import { setAccountSL } from "@/lib/api";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
|
||||||
export function SLCustomize() {
|
export function SLCustomize() {
|
||||||
|
|||||||
@ -1,3 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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 { NewChart } from "@/components/NewChart";
|
||||||
import { MiniJoinsChart } from "@/components/misc/MiniJoinsChart";
|
import { MiniJoinsChart } from "@/components/misc/MiniJoinsChart";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
@ -35,8 +66,7 @@ import {
|
|||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { LoaderIcon } from "react-hot-toast";
|
import toast, { LoaderIcon } from "react-hot-toast";
|
||||||
import { toast } from "sonner";
|
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import { TagShower } from "./ServerList";
|
import { TagShower } from "./ServerList";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
|
|||||||
@ -45,7 +45,7 @@ import { CheckIcon, X } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import {
|
||||||
@ -82,7 +82,7 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./ui/dialog";
|
} from "./ui/dialog";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { LoadingSpinner } from "./ui/loading-spinner";
|
import { Spinner } from "./ui/spinner";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
description: z
|
description: z
|
||||||
@ -150,7 +150,7 @@ export default function ServerCustomize({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingSpinner className="flex items-center" />
|
<Spinner className="flex items-center" />
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -233,7 +233,7 @@ export default function ServerCustomize({
|
|||||||
success: "Report sent!",
|
success: "Report sent!",
|
||||||
loading: "Sending report...",
|
loading: "Sending report...",
|
||||||
error: "Error while sending report",
|
error: "Error while sending report",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -261,7 +261,7 @@ export default function ServerCustomize({
|
|||||||
success: "Owned server!",
|
success: "Owned server!",
|
||||||
loading: "Owning server...",
|
loading: "Owning server...",
|
||||||
error: "Error while owning server",
|
error: "Error while owning server",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
setMinehutOwned(true);
|
setMinehutOwned(true);
|
||||||
|
|||||||
@ -85,7 +85,7 @@ import { CommandIcon } from "lucide-react";
|
|||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import ClientFadeIn from "./ClientFadeIn";
|
import ClientFadeIn from "./ClientFadeIn";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
@ -100,9 +100,7 @@ import { pageFind } from "./misc/Link";
|
|||||||
import { Badge } from "./ui/badge";
|
import { Badge } from "./ui/badge";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { Skeleton } from "./ui/skeleton";
|
import { Skeleton } from "./ui/skeleton";
|
||||||
import { affiliates } from "@/config/affiliates";
|
import { Spinner } from "./ui/spinner";
|
||||||
import { LoadingSpinner } from "./ui/loading-spinner";
|
|
||||||
import StickyTopbar from "./misc/StickyTopbar";
|
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
@ -169,7 +167,7 @@ export default function ServerList() {
|
|||||||
Array<(server: OnlineServer) => Promise<boolean>>
|
Array<(server: OnlineServer) => Promise<boolean>>
|
||||||
>([]);
|
>([]);
|
||||||
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
||||||
undefined
|
undefined,
|
||||||
);
|
);
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const [color, setColor] = useState("#ffffff");
|
const [color, setColor] = useState("#ffffff");
|
||||||
@ -351,7 +349,7 @@ export default function ServerList() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
|
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
|
||||||
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
|
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
|
||||||
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
|
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]",
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(pageFind(`Server:${server.name}`))
|
router.push(pageFind(`Server:${server.name}`))
|
||||||
@ -385,7 +383,7 @@ export default function ServerList() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
|
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
|
||||||
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
|
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
|
||||||
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
|
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]",
|
||||||
)}
|
)}
|
||||||
onClick={() => router.push(`/server/${server.name}`)}
|
onClick={() => router.push(`/server/${server.name}`)}
|
||||||
>
|
>
|
||||||
@ -521,8 +519,7 @@ export default function ServerList() {
|
|||||||
<br id="serverlist" className="pb-14" />
|
<br id="serverlist" className="pb-14" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<ClientFadeIn delay={100}>
|
<ClientFadeIn delay={100}>
|
||||||
<StickyTopbar scrollElevation={250} className="p-2">
|
<Menubar className="mt-3 ml-2 border rounded p-2 shadow">
|
||||||
<Menubar className="mt-3 border rounded shadow">
|
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>Servers</MenubarTrigger>
|
<MenubarTrigger>Servers</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
@ -561,10 +558,7 @@ export default function ServerList() {
|
|||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
|
|
||||||
serverList.currentServers.forEach((b) => {
|
serverList.currentServers.forEach((b) => {
|
||||||
stringList.push({
|
stringList.push({ motd: b.motd, server: b.name });
|
||||||
motd: b.motd,
|
|
||||||
server: b.name,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serverList.getMOTDs(stringList).then((c) => {
|
serverList.getMOTDs(stringList).then((c) => {
|
||||||
@ -572,7 +566,7 @@ export default function ServerList() {
|
|||||||
c.forEach(
|
c.forEach(
|
||||||
(b: { server: string; motd: string }) => {
|
(b: { server: string; motd: string }) => {
|
||||||
updatedSL[b.server] = b.motd;
|
updatedSL[b.server] = b.motd;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setMotdList(updatedSL);
|
setMotdList(updatedSL);
|
||||||
setServers(serverList.currentServers);
|
setServers(serverList.currentServers);
|
||||||
@ -588,7 +582,7 @@ export default function ServerList() {
|
|||||||
success: "Succesfully refreshed servers",
|
success: "Succesfully refreshed servers",
|
||||||
loading: "Refreshing...",
|
loading: "Refreshing...",
|
||||||
error: "Error while refreshing",
|
error: "Error while refreshing",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -627,10 +621,7 @@ export default function ServerList() {
|
|||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
|
|
||||||
serverList.currentServers.forEach((b) => {
|
serverList.currentServers.forEach((b) => {
|
||||||
stringList.push({
|
stringList.push({ motd: b.motd, server: b.name });
|
||||||
motd: b.motd,
|
|
||||||
server: b.name,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serverList.getMOTDs(stringList).then((c) => {
|
serverList.getMOTDs(stringList).then((c) => {
|
||||||
@ -638,7 +629,7 @@ export default function ServerList() {
|
|||||||
c.forEach(
|
c.forEach(
|
||||||
(b: { server: string; motd: string }) => {
|
(b: { server: string; motd: string }) => {
|
||||||
updatedSL[b.server] = b.motd;
|
updatedSL[b.server] = b.motd;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setMotdList(updatedSL);
|
setMotdList(updatedSL);
|
||||||
setServers(serverList.currentServers);
|
setServers(serverList.currentServers);
|
||||||
@ -669,10 +660,7 @@ export default function ServerList() {
|
|||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
|
|
||||||
serverList.currentServers.forEach((b) => {
|
serverList.currentServers.forEach((b) => {
|
||||||
stringList.push({
|
stringList.push({ motd: b.motd, server: b.name });
|
||||||
motd: b.motd,
|
|
||||||
server: b.name,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serverList.getMOTDs(stringList).then((c) => {
|
serverList.getMOTDs(stringList).then((c) => {
|
||||||
@ -680,7 +668,7 @@ export default function ServerList() {
|
|||||||
c.forEach(
|
c.forEach(
|
||||||
(b: { server: string; motd: string }) => {
|
(b: { server: string; motd: string }) => {
|
||||||
updatedSL[b.server] = b.motd;
|
updatedSL[b.server] = b.motd;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setMotdList(updatedSL);
|
setMotdList(updatedSL);
|
||||||
setServers(serverList.currentServers);
|
setServers(serverList.currentServers);
|
||||||
@ -711,10 +699,7 @@ export default function ServerList() {
|
|||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
|
|
||||||
serverList.currentServers.forEach((b) => {
|
serverList.currentServers.forEach((b) => {
|
||||||
stringList.push({
|
stringList.push({ motd: b.motd, server: b.name });
|
||||||
motd: b.motd,
|
|
||||||
server: b.name,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serverList.getMOTDs(stringList).then((c) => {
|
serverList.getMOTDs(stringList).then((c) => {
|
||||||
@ -722,7 +707,7 @@ export default function ServerList() {
|
|||||||
c.forEach(
|
c.forEach(
|
||||||
(b: { server: string; motd: string }) => {
|
(b: { server: string; motd: string }) => {
|
||||||
updatedSL[b.server] = b.motd;
|
updatedSL[b.server] = b.motd;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setMotdList(updatedSL);
|
setMotdList(updatedSL);
|
||||||
setServers(serverList.currentServers);
|
setServers(serverList.currentServers);
|
||||||
@ -735,7 +720,7 @@ export default function ServerList() {
|
|||||||
error: "Error while changing filters",
|
error: "Error while changing filters",
|
||||||
loading: "Changing filters...",
|
loading: "Changing filters...",
|
||||||
success: "Changed filters!",
|
success: "Changed filters!",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
value={(() => {
|
value={(() => {
|
||||||
@ -753,8 +738,8 @@ export default function ServerList() {
|
|||||||
Only allow smaller servers
|
Only allow smaller servers
|
||||||
<br />
|
<br />
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Only allow servers that have the player range 7-15,
|
Only allow servers that have the player range 7-15, and
|
||||||
and cannot <br />
|
cannot <br />
|
||||||
be Always Online.
|
be Always Online.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -811,10 +796,7 @@ export default function ServerList() {
|
|||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
|
|
||||||
serverList.currentServers.forEach((b) => {
|
serverList.currentServers.forEach((b) => {
|
||||||
stringList.push({
|
stringList.push({ motd: b.motd, server: b.name });
|
||||||
motd: b.motd,
|
|
||||||
server: b.name,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serverList.getMOTDs(stringList).then((c) => {
|
serverList.getMOTDs(stringList).then((c) => {
|
||||||
@ -822,7 +804,7 @@ export default function ServerList() {
|
|||||||
c.forEach(
|
c.forEach(
|
||||||
(b: { server: string; motd: string }) => {
|
(b: { server: string; motd: string }) => {
|
||||||
updatedSL[b.server] = b.motd;
|
updatedSL[b.server] = b.motd;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setMotdList(updatedSL);
|
setMotdList(updatedSL);
|
||||||
setServers(serverList.currentServers);
|
setServers(serverList.currentServers);
|
||||||
@ -905,15 +887,21 @@ export default function ServerList() {
|
|||||||
value={ipr}
|
value={ipr}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
if (am)
|
if (am)
|
||||||
toast.warning(
|
toast(
|
||||||
"These settings will not change over reloads because you have account specific options enabled",
|
<span>
|
||||||
{
|
These settings will not change over reloads
|
||||||
action: {
|
because you have account specific options enabled
|
||||||
label: "Check settings",
|
<Button
|
||||||
onClick: () =>
|
variant="link"
|
||||||
router.push("/account/settings/options"),
|
className="p-0 m-0"
|
||||||
},
|
onClick={() =>
|
||||||
|
router.push("/account/settings/options")
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
Change your preferences
|
||||||
|
</Button>
|
||||||
|
</span>,
|
||||||
|
{ icon: "!" },
|
||||||
);
|
);
|
||||||
setIPR(v);
|
setIPR(v);
|
||||||
}}
|
}}
|
||||||
@ -937,15 +925,21 @@ export default function ServerList() {
|
|||||||
value={padding.toString()}
|
value={padding.toString()}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
if (am)
|
if (am)
|
||||||
toast.warning(
|
toast(
|
||||||
"These settings will not change over reloads because you have account specific options enabled",
|
<span>
|
||||||
{
|
These settings will not change over reloads
|
||||||
action: {
|
because you have account specific options enabled
|
||||||
label: "Check settings",
|
<Button
|
||||||
onClick: () =>
|
variant="link"
|
||||||
router.push("/account/settings/options"),
|
className="p-0 m-0"
|
||||||
},
|
onClick={() =>
|
||||||
|
router.push("/account/settings/options")
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
Change your preferences
|
||||||
|
</Button>
|
||||||
|
</span>,
|
||||||
|
{ icon: "!" },
|
||||||
);
|
);
|
||||||
setPadding(v);
|
setPadding(v);
|
||||||
}}
|
}}
|
||||||
@ -960,10 +954,7 @@ export default function ServerList() {
|
|||||||
<MenubarRadioItem value="200">200px</MenubarRadioItem>
|
<MenubarRadioItem value="200">200px</MenubarRadioItem>
|
||||||
</MenubarRadioGroup>
|
</MenubarRadioGroup>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarCheckboxItem
|
<MenubarCheckboxItem checked={pOS} onCheckedChange={setpOS}>
|
||||||
checked={pOS}
|
|
||||||
onCheckedChange={setpOS}
|
|
||||||
>
|
|
||||||
Only use padding on servers
|
Only use padding on servers
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarSubContent>
|
</MenubarSubContent>
|
||||||
@ -988,10 +979,7 @@ export default function ServerList() {
|
|||||||
</MenubarSub>
|
</MenubarSub>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
<MenubarCheckboxItem
|
<MenubarCheckboxItem checked={hero} onCheckedChange={setHero}>
|
||||||
checked={hero}
|
|
||||||
onCheckedChange={setHero}
|
|
||||||
>
|
|
||||||
Show Hero
|
Show Hero
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
</SignedIn>
|
</SignedIn>
|
||||||
@ -1014,7 +1002,6 @@ export default function ServerList() {
|
|||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
</StickyTopbar>
|
|
||||||
</ClientFadeIn>
|
</ClientFadeIn>
|
||||||
|
|
||||||
<Dialog open={random} onOpenChange={setRandom}>
|
<Dialog open={random} onOpenChange={setRandom}>
|
||||||
@ -1076,7 +1063,7 @@ export default function ServerList() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTextCopied(true);
|
setTextCopied(true);
|
||||||
clipboard.writeText(
|
clipboard.writeText(
|
||||||
randomData.name + ".mshf.minehut.gg"
|
randomData.name + ".mshf.minehut.gg",
|
||||||
);
|
);
|
||||||
toast.success("Copied!");
|
toast.success("Copied!");
|
||||||
setTimeout(() => setTextCopied(false), 1000);
|
setTimeout(() => setTextCopied(false), 1000);
|
||||||
@ -1116,7 +1103,7 @@ export default function ServerList() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
loader={<LoadingSpinner className="flex items-center" />}
|
loader={<Spinner className="flex items-center" />}
|
||||||
endMessage={
|
endMessage={
|
||||||
<p
|
<p
|
||||||
style={{ textAlign: "center" }}
|
style={{ textAlign: "center" }}
|
||||||
@ -1147,45 +1134,8 @@ export default function ServerList() {
|
|||||||
" gap-4 sm:grid-cols-2"
|
" gap-4 sm:grid-cols-2"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{servers.map((b: any, i: number) => (
|
{servers.map((b: any) => (
|
||||||
<>
|
<>
|
||||||
{i === Number(ipr) && affiliates.length != 0 && (
|
|
||||||
<div
|
|
||||||
className="border rounded h-[450px] shadow p-10 max-w-full dark:prose-invert prose grid grid-cols-3 max-xl:hidden"
|
|
||||||
style={{
|
|
||||||
gridColumn: `span ${ipr} / span ${ipr}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="dark:text-white text-black max-w-[300px] overflow-auto">
|
|
||||||
<h2 className="bg-gradient-to-bl from-yellow-300 via-yellow-500 to-yellow-100 bg-clip-text text-transparent">
|
|
||||||
Affiliates
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
We have been able to partner with some servers that we
|
|
||||||
think are high-effort servers that need to be
|
|
||||||
recognized.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Please take some interest in a server you find
|
|
||||||
interesting and give them some much needed support.
|
|
||||||
</p>
|
|
||||||
<small>
|
|
||||||
These servers have absolutely no financial affiliation
|
|
||||||
with MHSF.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
{affiliates
|
|
||||||
.filter((a) => a.mode.includes("server-list"))
|
|
||||||
.map((a) => (
|
|
||||||
<div
|
|
||||||
className="border rounded p-4 col-span-2"
|
|
||||||
key={a.name}
|
|
||||||
>
|
|
||||||
{a.name}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ServerCard b={b} motd={motdList[b.name]} />
|
<ServerCard b={b} motd={motdList[b.name]} />
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -31,11 +31,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
BetterHeader,
|
BetterHeader,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@ -43,23 +47,14 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Badge } from "./ui/badge";
|
import { Badge } from "./ui/badge";
|
||||||
import ServerSingle from "@/lib/single";
|
import ServerSingle from "@/lib/single";
|
||||||
import { motion } from "framer-motion";
|
import { SignedIn, SignedOut } from "@clerk/nextjs";
|
||||||
import { Cake, Check, Heart, Star, Users, X } from "lucide-react";
|
import SignInPopoverButton from "./clerk/SignInPopoverButton";
|
||||||
import {
|
import { Star, X } from "lucide-react";
|
||||||
favoriteServer,
|
import { favoriteServer, isFavorited } from "@/lib/api";
|
||||||
getCommunityServerFavorites,
|
import { LoadingButton } from "./ui/loading-button";
|
||||||
isFavorited,
|
|
||||||
} from "@/lib/api";
|
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Skeleton } from "./ui/skeleton";
|
import { Skeleton } from "./ui/skeleton";
|
||||||
import FadeIn from "react-fade-in/lib/FadeIn";
|
import FadeIn from "react-fade-in/lib/FadeIn";
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import IconDisplay from "./IconDisplay";
|
|
||||||
import { useClerk, useUser } from "@clerk/nextjs";
|
|
||||||
import { LoaderIcon } from "react-hot-toast";
|
|
||||||
import { Separator } from "./ui/separator";
|
|
||||||
import { convert } from "@/components/NewChart";
|
|
||||||
import { LoadingSpinner } from "./ui/loading-spinner";
|
|
||||||
|
|
||||||
export default function ServerView(props: { server: string }) {
|
export default function ServerView(props: { server: string }) {
|
||||||
const [single, setSingle] = useState(new ServerSingle(props.server));
|
const [single, setSingle] = useState(new ServerSingle(props.server));
|
||||||
@ -69,9 +64,6 @@ export default function ServerView(props: { server: string }) {
|
|||||||
const [loadingFavorite, setLoadingFavorite] = useState(false);
|
const [loadingFavorite, setLoadingFavorite] = useState(false);
|
||||||
const [randomText, setRandomText] = useState("");
|
const [randomText, setRandomText] = useState("");
|
||||||
const [lastOnline, setLastOnline] = useState(0);
|
const [lastOnline, setLastOnline] = useState(0);
|
||||||
const { isSignedIn } = useUser();
|
|
||||||
const [communityFavorited, setCommunityFavorited] = useState(0);
|
|
||||||
const clerk = useClerk();
|
|
||||||
const [format, setFormat] = useState("");
|
const [format, setFormat] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const allText = [""];
|
const allText = [""];
|
||||||
@ -99,19 +91,16 @@ export default function ServerView(props: { server: string }) {
|
|||||||
setLastOnline(online);
|
setLastOnline(online);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getCommunityServerFavorites(single.grabOffline()?.name as string).then(
|
|
||||||
(b) => {
|
|
||||||
setCommunityFavorited(b);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-4">
|
<div className="grid p-4 sm:grid-cols-3 gap-4">
|
||||||
<Skeleton className="sm:col-span-2 h-[155px]" />
|
<Skeleton className="sm:col-span-2 h-[245px]" />
|
||||||
|
|
||||||
|
<Skeleton className="h-[245px]" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -136,14 +125,8 @@ export default function ServerView(props: { server: string }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FadeIn>
|
<FadeIn>
|
||||||
<div className="flex items-center">
|
<div className="grid p-4 sm:grid-cols-3 gap-4">
|
||||||
<div className="bg-secondary p-4 rounded-lg ml-4">
|
<Card className="sm:col-span-2">
|
||||||
<IconDisplay
|
|
||||||
server={single.grabOffline()}
|
|
||||||
className="flex items-center"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="block">
|
|
||||||
<BetterHeader>
|
<BetterHeader>
|
||||||
<CardTitle className="flex items-center">
|
<CardTitle className="flex items-center">
|
||||||
{single.grabOnline() == undefined &&
|
{single.grabOnline() == undefined &&
|
||||||
@ -190,116 +173,103 @@ export default function ServerView(props: { server: string }) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{single.getAuthor() != undefined ? (
|
{single.getAuthor() != undefined && (
|
||||||
<p className="text-lg flex items-center">
|
<p>by {single.getAuthor()}</p>
|
||||||
by {single.getAuthor()}{" "}
|
|
||||||
<Button
|
|
||||||
className="h-7 ml-2"
|
|
||||||
variant={favorited ? "outline" : "favorite"}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isSignedIn) {
|
|
||||||
clerk.openSignUp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoadingFavorite(true);
|
|
||||||
favoriteServer(
|
|
||||||
single.grabOffline()?.name as string
|
|
||||||
).then(() => {
|
|
||||||
setLoadingFavorite(false);
|
|
||||||
setFavorited(!favorited);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={loadingFavorite}
|
|
||||||
>
|
|
||||||
{loadingFavorite && (
|
|
||||||
<LoadingSpinner className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
{!favorited && !loadingFavorite && (
|
|
||||||
<motion.div
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
initial={{ opacity: 0, scale: 0.3 }}
|
|
||||||
transition={{ duration: 0.25, ease: "linear" }}
|
|
||||||
>
|
|
||||||
<Star size={16} className="mr-2" />
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
{favorited && !loadingFavorite && (
|
|
||||||
<motion.div
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
initial={{ opacity: 0, scale: 0.3 }}
|
|
||||||
transition={{ duration: 0.25, ease: "linear" }}
|
|
||||||
>
|
|
||||||
<Check size={16} className="mr-2" />
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
Favorite{favorited && "d"}
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-lg flex items-center">
|
|
||||||
by Anonymous{" "}
|
|
||||||
<Button
|
|
||||||
className="h-7 ml-2"
|
|
||||||
variant={favorited ? "outline" : "favorite"}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isSignedIn) {
|
|
||||||
clerk.openSignUp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoadingFavorite(true);
|
|
||||||
favoriteServer(
|
|
||||||
single.grabOffline()?.name as string
|
|
||||||
).then(() => {
|
|
||||||
setLoadingFavorite(false);
|
|
||||||
setFavorited(!favorited);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={loadingFavorite}
|
|
||||||
>
|
|
||||||
{loadingFavorite && <LoaderIcon className="mr-2" />}
|
|
||||||
{!favorited && !loadingFavorite && (
|
|
||||||
<motion.div
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
initial={{ opacity: 0, scale: 0.3 }}
|
|
||||||
transition={{ duration: 0.25, ease: "linear" }}
|
|
||||||
>
|
|
||||||
<Star size={16} className="mr-2" />
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
{favorited && !loadingFavorite && (
|
|
||||||
<motion.div
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
initial={{ opacity: 0, scale: 0.3 }}
|
|
||||||
transition={{ duration: 0.25, ease: "linear" }}
|
|
||||||
>
|
|
||||||
<Check size={16} className="mr-2" />
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
Favorite{favorited && "d"}
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</BetterHeader>
|
</BetterHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-md font-semibold text-muted-foreground flex items-center">
|
<p>
|
||||||
<>
|
<strong>Time:</strong>
|
||||||
<Heart className="mr-2" size={24} />
|
<br />
|
||||||
{convert(communityFavorited)}
|
<i>Last online</i>{" "}
|
||||||
<Separator orientation="vertical" className="ml-4 h-[30px]" />
|
<Tooltip>
|
||||||
</>
|
<TooltipTrigger>
|
||||||
<>
|
<code>
|
||||||
<Users className="mr-2 ml-4" size={24} />{" "}
|
{timeConverter(single.grabOffline()?.last_online)}
|
||||||
{convert(single.grabOffline()?.joins as number)}
|
</code>
|
||||||
<Separator orientation="vertical" className="ml-4 h-[30px]" />
|
</TooltipTrigger>
|
||||||
</>
|
<TooltipContent>
|
||||||
<>
|
<code>{single.grabOffline()?.last_online}</code> in Unix
|
||||||
<Cake className="mr-2 ml-4" size={24} />{" "}
|
time
|
||||||
{timeConverter(single.grabOffline()?.creation)}
|
</TooltipContent>
|
||||||
</>
|
</Tooltip>{" "}
|
||||||
|
<br />
|
||||||
|
<i>Created on</i>{" "}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<code>{timeConverter(single.grabOffline()?.creation)}</code>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<code>{single.grabOffline()?.creation}</code> in Unix time
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Favorite the server?</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
By favoriting the server, you can see it later.{" "}
|
||||||
|
<SignedOut>
|
||||||
|
<strong>You need to sign in to favorite a server.</strong>
|
||||||
|
</SignedOut>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<SignedOut>
|
||||||
|
<SignInPopoverButton />
|
||||||
|
</SignedOut>
|
||||||
|
<SignedIn>
|
||||||
|
<LoadingButton
|
||||||
|
variant={resolvedTheme == "dark" ? "outline" : "default"}
|
||||||
|
loading={loadingFavorite}
|
||||||
|
onClick={() => {
|
||||||
|
setLoadingFavorite(true);
|
||||||
|
favoriteServer(single.grabOffline()?.name as string).then(
|
||||||
|
() => {
|
||||||
|
setFavorited(!favorited);
|
||||||
|
setLoadingFavorite(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{favorited && (
|
||||||
|
<motion.div
|
||||||
|
animate={{ color: "yellow", fill: "yellow" }}
|
||||||
|
transition={{ duration: 2 }}
|
||||||
|
>
|
||||||
|
<Star
|
||||||
|
className="mr-2"
|
||||||
|
size="16"
|
||||||
|
color="yellow"
|
||||||
|
fill="yellow"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{!favorited && (
|
||||||
|
<motion.div
|
||||||
|
transition={{ duration: 1 }}
|
||||||
|
animate={{ color: "yellow", fill: "yellow" }}
|
||||||
|
>
|
||||||
|
<Star className="mr-2" size="16" />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{favorited && "Unf"}
|
||||||
|
{!favorited && "F"}avorite Server
|
||||||
|
</LoadingButton>
|
||||||
|
</SignedIn>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<small>
|
||||||
|
This is unlike voting. The{" "}
|
||||||
|
<i>amount of people who favorited are public</i>, but the server
|
||||||
|
doesn{"'"}t know who favorited, as Favorites are completely
|
||||||
|
anonymous.
|
||||||
|
</small>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</FadeIn>
|
</FadeIn>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -31,21 +31,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Document {
|
|
||||||
startViewTransition(updateCallback: () => void):
|
|
||||||
| {
|
|
||||||
finished: Promise<void>;
|
|
||||||
ready: Promise<void>;
|
|
||||||
updateCallbackDone: Promise<void>;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
const [mounted, setMounted] = React.useState(false);
|
const [mounted, setMounted] = React.useState(false);
|
||||||
|
|
||||||
@ -57,57 +45,3 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|||||||
|
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseThemeTransitionResult {
|
|
||||||
theme: string | undefined;
|
|
||||||
changeTheme: (changeTheme: string) => void;
|
|
||||||
mounted: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useThemeTransition(): UseThemeTransitionResult {
|
|
||||||
const { theme, setTheme, systemTheme } = useTheme();
|
|
||||||
const [mounted, setMounted] = React.useState<boolean>(false);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const changeTheme = (changeTheme: string) => {
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
const resolvedTheme = theme === "system" ? systemTheme : changeTheme;
|
|
||||||
|
|
||||||
if (document.startViewTransition) {
|
|
||||||
document.startViewTransition(() => {
|
|
||||||
const root = document.documentElement;
|
|
||||||
root.style.setProperty(
|
|
||||||
"--current-background",
|
|
||||||
`var(--${resolvedTheme}-background)`
|
|
||||||
);
|
|
||||||
root.style.setProperty(
|
|
||||||
"--current-foreground",
|
|
||||||
`var(--${resolvedTheme}-foreground)`
|
|
||||||
);
|
|
||||||
setTheme(changeTheme);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setTheme(changeTheme);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (mounted && theme) {
|
|
||||||
const root = document.documentElement;
|
|
||||||
root.style.setProperty(
|
|
||||||
"--current-background",
|
|
||||||
`var(--${theme}-background)`
|
|
||||||
);
|
|
||||||
root.style.setProperty(
|
|
||||||
"--current-foreground",
|
|
||||||
`var(--${theme}-foreground)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [mounted, theme]);
|
|
||||||
|
|
||||||
return { theme, changeTheme, mounted };
|
|
||||||
}
|
|
||||||
|
|||||||
@ -29,7 +29,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -38,28 +41,27 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useThemeTransition } from "./ThemeProvider";
|
|
||||||
|
|
||||||
export function ModeToggle() {
|
export function ModeToggle() {
|
||||||
const { changeTheme } = useThemeTransition();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon" className="mr-3">
|
||||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={() => changeTheme("light")}>
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
Light
|
Light
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => changeTheme("dark")}>
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
Dark
|
Dark
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => changeTheme("system")}>
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
System
|
System
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@ -36,7 +36,6 @@ import { Card, CardContent } from "../ui/card";
|
|||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
import A from "../misc/Link";
|
import A from "../misc/Link";
|
||||||
import { formalNames } from "@/config/achievements";
|
import { formalNames } from "@/config/achievements";
|
||||||
import NoItems from "../misc/NoItems";
|
|
||||||
|
|
||||||
export default function AchievementList({ server }: { server: string }) {
|
export default function AchievementList({ server }: { server: string }) {
|
||||||
const [achievements, setAchievements] = useState<
|
const [achievements, setAchievements] = useState<
|
||||||
@ -71,7 +70,6 @@ export default function AchievementList({ server }: { server: string }) {
|
|||||||
Achievements are earned automatically when the server is online. See{" "}
|
Achievements are earned automatically when the server is online. See{" "}
|
||||||
<A alt="Achievement collection">Docs:Advanced/Achievements</A>
|
<A alt="Achievement collection">Docs:Advanced/Achievements</A>
|
||||||
</span>
|
</span>
|
||||||
{achievements.length === 0 && <NoItems />}
|
|
||||||
{achievements
|
{achievements
|
||||||
.filter(
|
.filter(
|
||||||
(value, index) => listify(achievements).indexOf(value.type) === index
|
(value, index) => listify(achievements).indexOf(value.type) === index
|
||||||
@ -111,6 +109,8 @@ export default function AchievementList({ server }: { server: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type WithInterval<K> = K & {
|
type WithInterval<K> = K & {
|
||||||
interval: number;
|
interval: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import IconDisplay from "@/components/IconDisplay";
|
import IconDisplay from "@/components/IconDisplay";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import type { ServerResponse } from "@/lib/types/mh-server";
|
import type { ServerResponse } from "@/lib/types/mh-server";
|
||||||
import { Copy, ExternalLink, ServerCrash } from "lucide-react";
|
import { Copy, ExternalLink, ServerCrash } from "lucide-react";
|
||||||
import { notFound, useSearchParams } from "next/navigation";
|
import { notFound, useSearchParams } from "next/navigation";
|
||||||
@ -39,7 +40,6 @@ import { Button } from "../ui/button";
|
|||||||
import { CheckmarkIcon } from "react-hot-toast";
|
import { CheckmarkIcon } from "react-hot-toast";
|
||||||
import useClipboard from "@/lib/useClipboard";
|
import useClipboard from "@/lib/useClipboard";
|
||||||
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
|
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
|
||||||
import { LoadingSpinner } from "../ui/loading-spinner";
|
|
||||||
|
|
||||||
export default function Embed({ params }: { params: { server: string } }) {
|
export default function Embed({ params }: { params: { server: string } }) {
|
||||||
const [serverFound, setServerFound] = useState(true);
|
const [serverFound, setServerFound] = useState(true);
|
||||||
@ -65,7 +65,7 @@ export default function Embed({ params }: { params: { server: string } }) {
|
|||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingSpinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverFound) {
|
if (!serverFound) {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import { codeToHtml } from "shiki";
|
|||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Asterisk, Copy } from "lucide-react";
|
import { Asterisk, Copy } from "lucide-react";
|
||||||
import useClipboard from "@/lib/useClipboard";
|
import useClipboard from "@/lib/useClipboard";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { Checkbox } from "../ui/checkbox";
|
import { Checkbox } from "../ui/checkbox";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -64,7 +64,7 @@ export default function EmbedSelector({ server }: { server: string }) {
|
|||||||
src="${url}"
|
src="${url}"
|
||||||
width={390}
|
width={390}
|
||||||
height={145}
|
height={145}
|
||||||
style={{ borderRadius: "0.25rem" }}
|
style={{ borderRadius: 0.25 }}
|
||||||
allow="clipboard-write"
|
allow="clipboard-write"
|
||||||
frameBorder={0}
|
frameBorder={0}
|
||||||
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
|
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
|
||||||
@ -93,7 +93,7 @@ export default function EmbedSelector({ server }: { server: string }) {
|
|||||||
src="${url}"
|
src="${url}"
|
||||||
width={390}
|
width={390}
|
||||||
height={145}
|
height={145}
|
||||||
style={{ borderRadius: "0.25rem" }}
|
style={{ borderRadius: 0.25 }}
|
||||||
allow="clipboard-write"
|
allow="clipboard-write"
|
||||||
frameBorder={0}
|
frameBorder={0}
|
||||||
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
|
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 QRCode from "react-qr-code";
|
|
||||||
import { DrawerFooter, DrawerTrigger } from "../ui/drawer";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
|
|
||||||
export default function QRCodeGenerator({ server }: { server: string }) {
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<QRCode
|
|
||||||
value={"https://mhsf.app/server/" + server + "?source=qrCode"}
|
|
||||||
className="flex flex-col items-center w-full py-4"
|
|
||||||
style={{
|
|
||||||
backgroundColor: resolvedTheme === "dark" ? "#fff" : undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<DrawerFooter>
|
|
||||||
<DrawerTrigger asChild>
|
|
||||||
<Button>Close</Button>
|
|
||||||
</DrawerTrigger>
|
|
||||||
</DrawerFooter>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
"use client";;
|
"use client";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@ -14,7 +15,8 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { setCustomization } from "@/lib/api";
|
import { setCustomization } from "@/lib/api";
|
||||||
import { toast } from "sonner";
|
import { useEffect } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
import ColorProvider from "../ColorProvider";
|
import ColorProvider from "../ColorProvider";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
|
|||||||
@ -47,7 +47,7 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { setCustomization } from "@/lib/api";
|
import { setCustomization } from "@/lib/api";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ColorProvider from "../ColorProvider";
|
import ColorProvider from "../ColorProvider";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
id: z.string().min(2, {
|
id: z.string().min(2, {
|
||||||
|
|||||||
@ -49,7 +49,7 @@ import {
|
|||||||
InputOTPSlot,
|
InputOTPSlot,
|
||||||
} from "@/components/ui/input-otp";
|
} from "@/components/ui/input-otp";
|
||||||
import { linkMCAccount } from "@/lib/api";
|
import { linkMCAccount } from "@/lib/api";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { DatabaseZap } from "lucide-react";
|
|
||||||
|
|
||||||
export default function NoItems() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col items-center justify-center p-4 pt-10">
|
|
||||||
<DatabaseZap
|
|
||||||
className="text-2xl font-semibold text-gray-600"
|
|
||||||
size={32}
|
|
||||||
/>
|
|
||||||
<p className="text-xl text-gray-600 mt-2">
|
|
||||||
Huh, we tried to find something, but nothing was found.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { useEffect, useState, ReactNode } from "react";
|
|
||||||
|
|
||||||
export default function StickyTopbar({
|
|
||||||
children,
|
|
||||||
scrollElevation,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
scrollElevation: number;
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
const [isSticky, setIsSticky] = useState(false);
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (window.scrollY > scrollElevation) {
|
|
||||||
setIsSticky(true);
|
|
||||||
} else {
|
|
||||||
setIsSticky(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("scroll", handleScroll);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("scroll", handleScroll);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`transition-all duration-300 ${isSticky ? "fixed top-[70px] left-0 w-full backdrop-blur shadow-lg " + className : "block w-full bg-transparent"}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -31,7 +31,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { Check } from "lucide-react";
|
import { Check } from "lucide-react";
|
||||||
import useClipboard from "@/lib/useClipboard";
|
import useClipboard from "@/lib/useClipboard";
|
||||||
|
|
||||||
|
|||||||
@ -31,10 +31,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster } from "../ui/sonner";
|
import { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
export default function ThemedToaster() {
|
export default function ThemedToaster() {
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
return <Toaster position="bottom-center" richColors />;
|
return (
|
||||||
|
<Toaster
|
||||||
|
position="bottom-center"
|
||||||
|
reverseOrder={false}
|
||||||
|
toastOptions={
|
||||||
|
resolvedTheme == "dark"
|
||||||
|
? { style: { background: "#333", color: "#fff" } }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,8 +50,6 @@ const buttonVariants = cva(
|
|||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground focus:ring-4 focus:ring-neutral-100 focus:ring-offset-current dark:focus:ring-neutral-900 duration-150 ease-in-out transition-all",
|
"hover:bg-accent hover:text-accent-foreground focus:ring-4 focus:ring-neutral-100 focus:ring-offset-current dark:focus:ring-neutral-900 duration-150 ease-in-out transition-all",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
favorite:
|
|
||||||
"text-black rounded-lg hover:bg-primary/90 focus:ring-4 focus:ring-yellow-400/60 focus:ring-offset-current dark:focus:ring-yellow-400/60 duration-150 ease-in-out transition-all bg-gradient-to-bl from-yellow-300 via-yellow-500 to-yellow-100",
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
export function LoadingSpinner({ className }: { className?: string }) {
|
|
||||||
return (
|
|
||||||
<div className={cn("h-5 w-5", className)}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "relative",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
}}
|
|
||||||
className={cn("loading-spinner", "h-5 w-5", className)}
|
|
||||||
>
|
|
||||||
{[...Array(12)].map((_, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
style={{
|
|
||||||
animationDelay: `${-1.2 + 0.1 * i}s`,
|
|
||||||
background: "gray",
|
|
||||||
position: "absolute",
|
|
||||||
borderRadius: "1rem",
|
|
||||||
width: "30%",
|
|
||||||
height: "8%",
|
|
||||||
left: "-10%",
|
|
||||||
top: "-4%",
|
|
||||||
transform: `rotate(${30 * i}deg) translate(120%)`,
|
|
||||||
}}
|
|
||||||
className="animate-spinner"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
import { Toaster as Sonner } from "sonner"
|
|
||||||
|
|
||||||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
|
||||||
const { theme = "system" } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sonner
|
|
||||||
theme={theme as ToasterProps["theme"]}
|
|
||||||
className="toaster group"
|
|
||||||
toastOptions={{
|
|
||||||
classNames: {
|
|
||||||
toast:
|
|
||||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
|
||||||
description: "group-[.toast]:text-muted-foreground",
|
|
||||||
actionButton:
|
|
||||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
|
||||||
cancelButton:
|
|
||||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Toaster }
|
|
||||||
@ -59,7 +59,7 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { SVGProps } from "react";
|
|
||||||
|
|
||||||
export const affiliates: {
|
|
||||||
name: string;
|
|
||||||
shop: string;
|
|
||||||
line: string;
|
|
||||||
mode: string[];
|
|
||||||
otherLinks: {
|
|
||||||
name: string;
|
|
||||||
icon: (props: SVGProps<SVGSVGElement>) => JSX.Element;
|
|
||||||
}[];
|
|
||||||
}[] = [];
|
|
||||||
@ -55,30 +55,8 @@ const FeatureList = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const version = "1.6.0";
|
export const version = "1.4.0";
|
||||||
export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
|
export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
|
||||||
{
|
|
||||||
id: "h9jr2cbxn7qwfvt5uypsdg",
|
|
||||||
name: "v1.6.0",
|
|
||||||
changelog: (
|
|
||||||
<FeatureList
|
|
||||||
features={[
|
|
||||||
"Completely redid top of server view",
|
|
||||||
"Favorite counts are now prominent on the server view",
|
|
||||||
"New theme transition (smooth)",
|
|
||||||
"New favorite button",
|
|
||||||
"Added more padding in the server view",
|
|
||||||
"Separated the tabs on the side for sharing actions",
|
|
||||||
"Added new QR code generator",
|
|
||||||
]}
|
|
||||||
title={
|
|
||||||
<strong className="flex items-center">
|
|
||||||
Version 1.6.0 (November 17th 2024)
|
|
||||||
</strong>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "r9swempc7kaqd2j84nutv5",
|
id: "r9swempc7kaqd2j84nutv5",
|
||||||
name: "v1.5.0",
|
name: "v1.5.0",
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 async function getMinecraftHead(username: string) {
|
|
||||||
const uuidRequest = await fetch("https://api.mojang.com/users/profiles/minecraft/" + username);
|
|
||||||
const uuid = (await uuidRequest.json()).id;
|
|
||||||
|
|
||||||
return `https://crafatar.com/avatars/${uuid}`;
|
|
||||||
}
|
|
||||||
@ -29,7 +29,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { OnlineServer } from "./types/mh-server";
|
import { OnlineServer } from "./types/mh-server";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
import { getMOTDFromServer } from "./api";
|
import { getMOTDFromServer } from "./api";
|
||||||
|
|
||||||
var numberOfItemsInView = 20;
|
var numberOfItemsInView = 20;
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
import { serverOwned } from "./api";
|
import { serverOwned } from "./api";
|
||||||
import { OnlineServer, ServerResponse } from "./types/mh-server";
|
import { OnlineServer, ServerResponse } from "./types/mh-server";
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export default class ServerSingle {
|
export default class ServerSingle {
|
||||||
private name = "";
|
private name = "";
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { toast } from "sonner";
|
import toast from "react-hot-toast"
|
||||||
|
|
||||||
/** A hook to properly write text to the clipboard without triggering a client-side error
|
/** A hook to properly write text to the clipboard without triggering a client-side error
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
|
|||||||
@ -178,14 +178,6 @@ const config = {
|
|||||||
transform: "none",
|
transform: "none",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
spinner: {
|
|
||||||
"0%": {
|
|
||||||
opacity: "1",
|
|
||||||
},
|
|
||||||
"100%": {
|
|
||||||
opacity: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shimmer: {
|
shimmer: {
|
||||||
"0%, 90%, 100%": {
|
"0%, 90%, 100%": {
|
||||||
"background-position": "calc(-100% - var(--shimmer-width)) 0",
|
"background-position": "calc(-100% - var(--shimmer-width)) 0",
|
||||||
@ -202,7 +194,6 @@ const config = {
|
|||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
"image-glow": "image-glow 4100ms 600ms ease-out forwards",
|
"image-glow": "image-glow 4100ms 600ms ease-out forwards",
|
||||||
shimmer: "shimmer 8s infinite",
|
shimmer: "shimmer 8s infinite",
|
||||||
spinner: "spinner 1.2s linear infinite",
|
|
||||||
"fade-in": "fade-in 1000ms var(--animation-delay, 0ms) ease forwards",
|
"fade-in": "fade-in 1000ms var(--animation-delay, 0ms) ease forwards",
|
||||||
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
||||||
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
|
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
|
||||||
|
|||||||
34
yarn.lock
34
yarn.lock
@ -4322,9 +4322,9 @@ globby@^11.1.0:
|
|||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
goober@^2.1.10:
|
goober@^2.1.10:
|
||||||
version "2.1.16"
|
version "2.1.14"
|
||||||
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.16.tgz#7d548eb9b83ff0988d102be71f271ca8f9c82a95"
|
resolved "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz"
|
||||||
integrity sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==
|
integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==
|
||||||
|
|
||||||
gopd@^1.0.1:
|
gopd@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
@ -6344,10 +6344,10 @@ next-css-obfuscator@^2.2.16:
|
|||||||
recoverable-random "^1.0.5"
|
recoverable-random "^1.0.5"
|
||||||
yargs "^17.7.2"
|
yargs "^17.7.2"
|
||||||
|
|
||||||
next-themes@^0.4.3:
|
next-themes@^0.3.0:
|
||||||
version "0.4.3"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.3.tgz#ea54552d5986936d177eed393ea50b658ae44800"
|
resolved "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz"
|
||||||
integrity sha512-nG84VPkTdUHR2YeD89YchvV4I9RbiMAql3GiLEQlPvq1ioaqPaIReK+yMRdg/zgiXws620qS1rU30TiWmmG9lA==
|
integrity sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==
|
||||||
|
|
||||||
next@14.2.10:
|
next@14.2.10:
|
||||||
version "14.2.10"
|
version "14.2.10"
|
||||||
@ -6847,11 +6847,6 @@ punycode@^2.1.0, punycode@^2.3.0:
|
|||||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
|
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
|
||||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||||
|
|
||||||
qr.js@0.0.0:
|
|
||||||
version "0.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
|
|
||||||
integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==
|
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
queue-microtask@^1.2.2:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||||
@ -6890,7 +6885,7 @@ react-hook-form@^7.52.2:
|
|||||||
|
|
||||||
react-hot-toast@^2.4.1:
|
react-hot-toast@^2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"
|
resolved "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz"
|
||||||
integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==
|
integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
goober "^2.1.10"
|
goober "^2.1.10"
|
||||||
@ -6928,14 +6923,6 @@ react-markdown@^9.0.1:
|
|||||||
unist-util-visit "^5.0.0"
|
unist-util-visit "^5.0.0"
|
||||||
vfile "^6.0.0"
|
vfile "^6.0.0"
|
||||||
|
|
||||||
react-qr-code@^2.0.15:
|
|
||||||
version "2.0.15"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.15.tgz#fbfc12952c504bcd64275647e9d1ea63251742ce"
|
|
||||||
integrity sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==
|
|
||||||
dependencies:
|
|
||||||
prop-types "^15.8.1"
|
|
||||||
qr.js "0.0.0"
|
|
||||||
|
|
||||||
react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4, react-remove-scroll-bar@^2.3.6:
|
react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4, react-remove-scroll-bar@^2.3.6:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz"
|
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz"
|
||||||
@ -7422,11 +7409,6 @@ snakecase-keys@5.4.4:
|
|||||||
snake-case "^3.0.4"
|
snake-case "^3.0.4"
|
||||||
type-fest "^2.5.2"
|
type-fest "^2.5.2"
|
||||||
|
|
||||||
sonner@^1.7.0:
|
|
||||||
version "1.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.7.0.tgz#f59a2a70e049a179b6fbd1bb1bf2619d5ced07c0"
|
|
||||||
integrity sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==
|
|
||||||
|
|
||||||
source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1:
|
source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user