feat: progress (2/16)

This commit is contained in:
dvelo 2025-02-16 21:40:17 -06:00
parent 5478276fd7
commit e0095ca890
6 changed files with 200 additions and 20 deletions

@ -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>
<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">
{servers.map((c) => (
{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,
};
}