mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 18:14:58 -05:00
feat: progress (2/16)
This commit is contained in:
parent
5478276fd7
commit
e0095ca890
64
apps/www/src/components/feat/navbar/menu-dropdown.tsx
Normal file
64
apps/www/src/components/feat/navbar/menu-dropdown.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { SignedIn, SignedOut, useClerk } from "@clerk/nextjs";
|
||||
import { LogIn, Settings, Ship, User, UserCog } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function MenuDropdown() {
|
||||
const clerk = useClerk();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuSeparator>Profile</DropdownMenuSeparator>
|
||||
<SignedOut>
|
||||
<DropdownMenuItem
|
||||
onClick={() => clerk.openSignIn()}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<LogIn size={16} />
|
||||
Login
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => clerk.openSignUp()}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<User size={16} />
|
||||
Sign up
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
router.push(process.env.NEXT_PUBLIC_CLERK_SWITCH_DOMAIN as string)
|
||||
}
|
||||
className="hover:block group hover:py-1"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Ship size={16} />
|
||||
Migrate your account
|
||||
</span>
|
||||
<small className="hidden group-hover:block break-words text-xs text-muted-foreground">
|
||||
If you created your account before Jan. 29th 2025, you'll need to
|
||||
migrate your account
|
||||
</small>
|
||||
</DropdownMenuItem>
|
||||
</SignedOut>
|
||||
<SignedIn>
|
||||
<DropdownMenuItem onClick={() => clerk.openUserProfile()}>
|
||||
<span className="flex items-center gap-2">
|
||||
<UserCog size={16} />
|
||||
User Settings
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</SignedIn>
|
||||
<DropdownMenuSeparator>App</DropdownMenuSeparator>
|
||||
<DropdownMenuItem>
|
||||
<span className="flex items-center gap-2">
|
||||
<Settings size={16} />
|
||||
Settings
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { BrandingGenericIcon } from "@/components/feat/icons/branding-icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@ -7,14 +8,19 @@ import {
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import Github from "@/components/ui/github";
|
||||
import { ALegacy } from "@/components/util/link";
|
||||
import { ModeToggle } from "@/components/util/mode-toggle";
|
||||
import { version } from "@/config/version";
|
||||
import { useScroll } from "@/lib/hooks/use-scroll";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ServerCrash } from "lucide-react";
|
||||
import { Menu, ServerCrash } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { MenuDropdown } from "./menu-dropdown";
|
||||
|
||||
export function NavBar() {
|
||||
const showBorder = useScroll(40);
|
||||
@ -56,11 +62,31 @@ export function NavBar() {
|
||||
</span>
|
||||
</ContextMenuItem>
|
||||
</ALegacy>
|
||||
<ALegacy href="Special:GitHub/releases">
|
||||
<ContextMenuItem>
|
||||
<span className="pl-2 flex gap-2 items-center">
|
||||
<Github /> Open GitHub Releases
|
||||
</span>
|
||||
</ContextMenuItem>
|
||||
</ALegacy>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<ModeToggle />
|
||||
<span className="mr-3 flex items-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Button
|
||||
className="rounded-full flex items-center"
|
||||
size="square-lg"
|
||||
variant="secondary"
|
||||
>
|
||||
<Menu size={16} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="max-w-[280px] w-[280px] mt-2 mr-2">
|
||||
<MenuDropdown />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,14 +1,61 @@
|
||||
import type { OnlineServer } from "@/lib/types/mh-server";
|
||||
import IconDisplay from "../icons/minecraft-icon-display";
|
||||
import { Material } from "@/components/ui/material";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export default function ServerCard({ server }: { server: OnlineServer }) {
|
||||
return (
|
||||
<Material key={server.name}>
|
||||
<span className="flex gap-2 items-center">
|
||||
<IconDisplay server={server} />
|
||||
{server.name}
|
||||
<strong className="text-lg">{server.name}</strong>
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span className="text-muted-foreground">
|
||||
by {server.author || "Nobody"}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="max-w-[390px] break-words">
|
||||
{server.author ? (
|
||||
<span>
|
||||
{server.name} is owned by{" "}
|
||||
<RankColoring author={server.author} rank={server.authorRank} />
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
This server doesn't have a recorded owner because the server owner
|
||||
never linked their Minecraft account to their Minehut account.
|
||||
</span>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Material>
|
||||
);
|
||||
}
|
||||
|
||||
function RankColoring({ rank, author }: { rank: string; author: string }) {
|
||||
switch (rank.toLocaleLowerCase()) {
|
||||
case "default":
|
||||
return <span className="text-muted-foreground">{author}</span>;
|
||||
case "vip":
|
||||
return <span className="text-green-700">[VIP] {author}</span>;
|
||||
case "vip_plus":
|
||||
return <span className="text-lime-500">[VIP+] {author}</span>;
|
||||
case "pro":
|
||||
return <span className="text-cyan-500">[PRO] {author}</span>;
|
||||
case "legend":
|
||||
return <span className="text-yellow-500">[LEGEND] {author}</span>;
|
||||
case "patron":
|
||||
return <span className="text-[#f355ff]">[PATRON] {author}</span>;
|
||||
case "mod":
|
||||
return <span className="text-yellow-200">[MOD] {author}</span>;
|
||||
default:
|
||||
return <span className="text-red-500">[STAFF] {author}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,13 @@ import { useServers } from "@/lib/hooks/use-servers";
|
||||
import ServerCard from "./server-card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Statistics } from "./statistics";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling";
|
||||
|
||||
export function ServerList() {
|
||||
const { servers, loading, serverCount, playerCount } = useServers();
|
||||
const { itemsLength, fetchMoreData, hasMoreData, data } =
|
||||
useInfiniteScrolling(servers);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
@ -29,11 +33,18 @@ export function ServerList() {
|
||||
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl">
|
||||
Servers
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mt-3">
|
||||
{servers.map((c) => (
|
||||
<ServerCard server={c} key={c.name} />
|
||||
))}
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
dataLength={itemsLength}
|
||||
next={fetchMoreData}
|
||||
hasMore={hasMoreData}
|
||||
loader={<>Loading...</>}
|
||||
>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mt-3">
|
||||
{data.map((c) => (
|
||||
<ServerCard server={c} key={c.name} />
|
||||
))}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"backdrop-blur-xl w-max z-[100] max-w-[280px] origin-top-left max-h-[32rem] overflow-auto list-none shadow-xl rounded-xl",
|
||||
"backdrop-blur-xl w-max z-[100] max-w-[280px] origin-top-left max-h-[32rem] overflow-auto list-none shadow-xl rounded-xl select-none outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -96,7 +96,7 @@ const DropdownMenuItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"w-full px-2 rounded-lg z-[100] min-h-[36px] font-normal flex items-center cursor-pointer hover:dark:bg-zinc-800/70",
|
||||
"w-full px-2 rounded-lg z-[100] outline-none min-h-[36px] font-normal flex items-center cursor-pointer hover:dark:bg-zinc-800/70",
|
||||
props.disabled ? "opacity-70 pointer-events-none cursor-not-allowed" : "",
|
||||
"duration-100 border border-transparent bg-transparent hover:bg-slate-100 dark:text-zinc-200",
|
||||
className
|
||||
@ -176,9 +176,15 @@ const DropdownMenuSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
className={cn(
|
||||
"text-slate-800 dark:text-zinc-200 text-xs font-medium py-2 px-2 flex items-center gap-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
<div>{props.children}</div>
|
||||
<hr className="flex-shrink flex-1 border-slate-200 dark:border-zinc-800 border-opacity-70" />
|
||||
</DropdownMenuPrimitive.Separator>
|
||||
));
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||
|
||||
|
||||
@ -1,7 +1,33 @@
|
||||
import { OnlineServer } from "../types/mh-server";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { OnlineServer } from "../types/mh-server";
|
||||
|
||||
export function useInfiniteScrolling({
|
||||
servers,
|
||||
}: {
|
||||
servers: OnlineServer[];
|
||||
}) {}
|
||||
const itemsPerScroll = 40;
|
||||
|
||||
export function useInfiniteScrolling(servers: OnlineServer[]) {
|
||||
const [currentOffset, setCurrentOffset] = useState(0);
|
||||
const [data, setData] = useState<OnlineServer[]>([]);
|
||||
const [hasMoreData, setHasMoreData] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Start at the first `itemsPerScroll` servers (duh)
|
||||
setData(servers.slice(0, itemsPerScroll));
|
||||
}, [servers]);
|
||||
|
||||
return {
|
||||
itemsLength: currentOffset + itemsPerScroll,
|
||||
fetchMoreData: () => {
|
||||
setCurrentOffset(currentOffset + itemsPerScroll);
|
||||
const currentData = data;
|
||||
const dataSlice = servers.slice(
|
||||
currentOffset,
|
||||
currentOffset + itemsPerScroll
|
||||
);
|
||||
const newDataArray = [...currentData, ...dataSlice];
|
||||
|
||||
setData(newDataArray);
|
||||
if (dataSlice.length !== itemsPerScroll) setHasMoreData(false);
|
||||
},
|
||||
hasMoreData,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user