mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-08 05:15:00 -05:00
feat: server list
This commit is contained in:
parent
f438d03399
commit
0f254e09f6
@ -39,6 +39,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { ClerkProvider } from "@clerk/nextjs";
|
import { ClerkProvider } from "@clerk/nextjs";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { NavBar } from "@/components/feat/navbar/navbar";
|
import { NavBar } from "@/components/feat/navbar/navbar";
|
||||||
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@ -54,23 +55,25 @@ export default function RootLayout({
|
|||||||
<ClerkProvider>
|
<ClerkProvider>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<noscript>
|
<TooltipProvider>
|
||||||
<main className="flex justify-center items-center text-center min-h-screen h-max">
|
<noscript>
|
||||||
<Placeholder
|
<main className="flex justify-center items-center text-center min-h-screen h-max">
|
||||||
icon={<X />}
|
<Placeholder
|
||||||
title="JavaScript is required for MHSF"
|
icon={<X />}
|
||||||
description="MHSF cannot grab servers or do other external requests without JavaScript."
|
title="JavaScript is required for MHSF"
|
||||||
>
|
description="MHSF cannot grab servers or do other external requests without JavaScript."
|
||||||
<Link href="https://www.enable-javascript.com/">
|
>
|
||||||
<Button>Here's how</Button>
|
<Link href="https://www.enable-javascript.com/">
|
||||||
</Link>
|
<Button>Here's how</Button>
|
||||||
</Placeholder>
|
</Link>
|
||||||
</main>
|
</Placeholder>
|
||||||
</noscript>
|
</main>
|
||||||
<IsScript>
|
</noscript>
|
||||||
<NavBar />
|
<IsScript>
|
||||||
<div className="pt-16">{children}</div>
|
<NavBar />
|
||||||
</IsScript>
|
<div className="pt-16">{children}</div>
|
||||||
|
</IsScript>
|
||||||
|
</TooltipProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
</ClerkProvider>
|
</ClerkProvider>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -20,8 +20,7 @@ export default function IconDisplay(props: {
|
|||||||
<i
|
<i
|
||||||
className={cn(
|
className={cn(
|
||||||
props.server.icon != null
|
props.server.icon != null
|
||||||
? `icon-minecraft icon-minecraft-
|
? `icon-minecraft icon-minecraft-${props.server.icon.replaceAll("_", "-").toLowerCase()}`
|
||||||
${props.server.icon.replaceAll("_", "-").toLowerCase()}`
|
|
||||||
: "icon-minecraft icon-minecraft-oak-sign",
|
: "icon-minecraft icon-minecraft-oak-sign",
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
@ -62,8 +61,7 @@ export function IconDisplayClient(props: { server: string }) {
|
|||||||
<i
|
<i
|
||||||
className={cn(
|
className={cn(
|
||||||
icon != null
|
icon != null
|
||||||
? `icon-minecraft icon-minecraft-
|
? `icon-minecraft icon-minecraft-${icon.replaceAll("_", "-").toLowerCase()}`
|
||||||
${icon.replaceAll("_", "-").toLowerCase()}`
|
|
||||||
: "icon-minecraft icon-minecraft-oak-sign",
|
: "icon-minecraft icon-minecraft-oak-sign",
|
||||||
"w-[16px] h-[16px]"
|
"w-[16px] h-[16px]"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,24 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { BrandingGenericIcon } from "@/components/icon";
|
||||||
import { BrandingColorfulIcon } from "@/components/Icon";
|
|
||||||
import { version } from "@/config/version";
|
import { version } from "@/config/version";
|
||||||
|
import { useScroll } from "@/lib/hooks/use-scroll";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export function NavBar() {
|
export function NavBar() {
|
||||||
|
const showBorder = useScroll(40);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-screen h-[3rem] border-b grid-cols-3 fixed backdrop-blur z-10",
|
"w-screen h-[3rem] grid-cols-3 fixed backdrop-blur-xl z-10 flex",
|
||||||
"items-center justify-self-start me-auto mt-2 pl-4 max-sm:mt-3 flex-1"
|
"items-center justify-self-start me-auto pl-4 flex-1 transition-all",
|
||||||
|
showBorder ? "border-b" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="gap-3 flex items-center">
|
<Link className="gap-5 flex items-center " href="/">
|
||||||
<BrandingColorfulIcon className="max-w-[32px] max-h-[32px] mt-0.5" />
|
<BrandingGenericIcon className="max-w-[32px] max-h-[32px] mt-0.5" />
|
||||||
<span className="gap-2 flex">
|
<span className="gap-2 flex group hover:text-blue-500 hover:underline transition-all">
|
||||||
<strong>MHSF</strong>
|
<strong className="">MHSF</strong>
|
||||||
<span className="text-muted-foreground">v{version}</span>
|
<span className="text-muted-foreground group-hover:text-blue-500 transition-all">
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
14
apps/www/src/components/feat/server-list/server-card.tsx
Normal file
14
apps/www/src/components/feat/server-list/server-card.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { OnlineServer } from "@/lib/types/mh-server";
|
||||||
|
import IconDisplay from "../icons/minecraft-icon-display";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
|
||||||
|
export default function ServerCard({ server }: { server: OnlineServer }) {
|
||||||
|
return (
|
||||||
|
<Material key={server.name}>
|
||||||
|
<span className="flex gap-2 items-center">
|
||||||
|
<IconDisplay server={server} />
|
||||||
|
{server.name}
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,39 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { useServers } from "@/lib/hooks/use-servers";
|
||||||
|
import ServerCard from "./server-card";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Statistics } from "./statistics";
|
||||||
|
|
||||||
export function ServerList() {
|
export function ServerList() {
|
||||||
return <main>Server List </main>;
|
const { servers, loading, serverCount, playerCount } = useServers();
|
||||||
|
|
||||||
|
if (loading)
|
||||||
|
return (
|
||||||
|
<div className="absolute top-[50%] left-[50%]">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="px-3 lg:px-16">
|
||||||
|
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl mb-3">
|
||||||
|
Statistics
|
||||||
|
</h1>
|
||||||
|
<Statistics
|
||||||
|
totalServers={serverCount}
|
||||||
|
totalPlayers={playerCount}
|
||||||
|
topServer={servers[0]}
|
||||||
|
/>
|
||||||
|
<Separator className="my-3" />
|
||||||
|
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl">
|
||||||
|
Servers
|
||||||
|
</h1>
|
||||||
|
<div className="grid grid-cols-4 gap-2 mt-3">
|
||||||
|
{servers.map((c) => (
|
||||||
|
<ServerCard server={c} key={c.name} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
29
apps/www/src/components/feat/server-list/statistics.tsx
Normal file
29
apps/www/src/components/feat/server-list/statistics.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import type { OnlineServer } from "@/lib/types/mh-server";
|
||||||
|
|
||||||
|
export function Statistics({
|
||||||
|
totalPlayers,
|
||||||
|
totalServers,
|
||||||
|
topServer,
|
||||||
|
}: {
|
||||||
|
totalPlayers: number;
|
||||||
|
totalServers: number;
|
||||||
|
topServer: OnlineServer;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
<Material className="gap-2">
|
||||||
|
<strong>Total Players</strong> <br />
|
||||||
|
<span className="text-lg">{totalPlayers}</span>
|
||||||
|
</Material>
|
||||||
|
<Material className="gap-2">
|
||||||
|
<strong>Total Servers</strong> <br />
|
||||||
|
<span className="text-lg">{totalServers}</span>
|
||||||
|
</Material>
|
||||||
|
<Material className="gap-2">
|
||||||
|
<strong>Top Server</strong> <br />
|
||||||
|
<span className="text-lg">{topServer.name}</span>
|
||||||
|
</Material>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -189,7 +189,7 @@ export function BrandingPrideIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
export function BrandingGenericIcon(props: SVGProps<SVGSVGElement>) {
|
export function BrandingGenericIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
if (resolvedTheme == "dark") {
|
if (resolvedTheme === "dark") {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="265"
|
width="265"
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
const Separator = React.forwardRef<
|
||||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
@ -18,14 +18,14 @@ const Separator = React.forwardRef<
|
|||||||
decorative={decorative}
|
decorative={decorative}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 bg-border",
|
"shrink-0 bg-slate-200",
|
||||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||||
|
|
||||||
export { Separator }
|
export { Separator };
|
||||||
|
|||||||
7
apps/www/src/lib/hooks/use-infinite-scrolling.tsx
Normal file
7
apps/www/src/lib/hooks/use-infinite-scrolling.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { OnlineServer } from "../types/mh-server";
|
||||||
|
|
||||||
|
export function useInfiniteScrolling({
|
||||||
|
servers,
|
||||||
|
}: {
|
||||||
|
servers: OnlineServer[];
|
||||||
|
}) {}
|
||||||
21
apps/www/src/lib/hooks/use-scroll.tsx
Normal file
21
apps/www/src/lib/hooks/use-scroll.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useScroll(threshold: number) {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
|
const onScroll = useCallback(() => {
|
||||||
|
setScrolled(window.scrollY > threshold);
|
||||||
|
}, [threshold]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("scroll", onScroll);
|
||||||
|
return () => window.removeEventListener("scroll", onScroll);
|
||||||
|
}, [onScroll]);
|
||||||
|
|
||||||
|
// also check on first load
|
||||||
|
useEffect(() => {
|
||||||
|
onScroll();
|
||||||
|
}, [onScroll]);
|
||||||
|
|
||||||
|
return scrolled;
|
||||||
|
}
|
||||||
43
apps/www/src/lib/hooks/use-servers.tsx
Normal file
43
apps/www/src/lib/hooks/use-servers.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { OnlineServer } from "../types/mh-server";
|
||||||
|
|
||||||
|
export function useServers() {
|
||||||
|
const [servers, setServers] = useState<OnlineServer[]>([]);
|
||||||
|
const [serverCount, setServerCount] = useState<number>(0);
|
||||||
|
const [playerCount, setPlayerCount] = useState<number>(0);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
(async () => {
|
||||||
|
const serversFetch = await fetch("https://api.minehut.com/servers");
|
||||||
|
const serversJson: ServersAPIResponse = await serversFetch.json();
|
||||||
|
|
||||||
|
setPlayerCount(serversJson.total_players);
|
||||||
|
setServerCount(serversJson.total_servers);
|
||||||
|
setServers(serversJson.servers);
|
||||||
|
setLoading(false);
|
||||||
|
})();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
servers,
|
||||||
|
playerCount,
|
||||||
|
serverCount,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServersAPIResponse = {
|
||||||
|
servers: OnlineServer[];
|
||||||
|
// stats
|
||||||
|
total_players: number;
|
||||||
|
total_search_results: number;
|
||||||
|
total_servers: number;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user