mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 19:24:59 -05:00
bump: 0.9 - new sorting
This commit is contained in:
parent
0a347d3eac
commit
4e4dd9cfe6
@ -3,7 +3,10 @@ import { Metadata } from "next";
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "the MHSF project by dvelo",
|
title: "the MHSF project by dvelo",
|
||||||
description: `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA}) ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE} by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`,
|
description:
|
||||||
|
process.env.NEXT_PUBLIC_VERCEL_ENV != undefined
|
||||||
|
? `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${(process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA as string).substring(0, 7)}}) "${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE}" by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`
|
||||||
|
: "currently running in dev",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|||||||
11
src/app/sort/favorites/page.tsx
Normal file
11
src/app/sort/favorites/page.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import FavoriteSortView from "@/components/FavoritesSortView";
|
||||||
|
|
||||||
|
export default function FavoritesSort() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<div className="pt-[60px] p-4">
|
||||||
|
<FavoriteSortView />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -167,6 +167,7 @@ export function OfflineServerCB() {
|
|||||||
const [customized, setCustomized] = useState(false);
|
const [customized, setCustomized] = useState(false);
|
||||||
const [obj, setObj] = useState<ServerResponse | object>({});
|
const [obj, setObj] = useState<ServerResponse | object>({});
|
||||||
const [vb, setVB] = useState(false);
|
const [vb, setVB] = useState(false);
|
||||||
|
const { isSignedIn } = useUser();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [starred, setStarred] = useState(false);
|
const [starred, setStarred] = useState(false);
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
@ -257,9 +258,10 @@ export function OfflineServerCB() {
|
|||||||
)}
|
)}
|
||||||
<CommandGroup heading="Server Actions">
|
<CommandGroup heading="Server Actions">
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() =>
|
onSelect={() => {
|
||||||
router.push("/server/" + (obj as ServerResponse).name + "/")
|
router.push("/server/" + (obj as ServerResponse).name + "/");
|
||||||
}
|
setOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Server className="mr-2 h-4 w-4" />
|
<Server className="mr-2 h-4 w-4" />
|
||||||
Open Server Page
|
Open Server Page
|
||||||
@ -271,6 +273,7 @@ export function OfflineServerCB() {
|
|||||||
toast.success("Done!");
|
toast.success("Done!");
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
disabled={!isSignedIn}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
className="mr-2 h-4 w-4"
|
className="mr-2 h-4 w-4"
|
||||||
@ -285,11 +288,12 @@ export function OfflineServerCB() {
|
|||||||
{!starred ? "F" : "Unf"}avorite Server
|
{!starred ? "F" : "Unf"}avorite Server
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() =>
|
onSelect={() => {
|
||||||
router.push(
|
router.push(
|
||||||
"/server/" + (obj as ServerResponse).name + "/statistics"
|
"/server/" + (obj as ServerResponse).name + "/statistics"
|
||||||
)
|
);
|
||||||
}
|
setOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Database className="mr-2 h-4 w-4" />
|
<Database className="mr-2 h-4 w-4" />
|
||||||
See Statistics
|
See Statistics
|
||||||
@ -330,6 +334,7 @@ export function ServerCommandBar() {
|
|||||||
const [vb, setVB] = useState(false);
|
const [vb, setVB] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
const { isSignedIn } = useUser();
|
||||||
const [owned, setOwned] = useState(false);
|
const [owned, setOwned] = useState(false);
|
||||||
const [starred, setStarred] = useState(false);
|
const [starred, setStarred] = useState(false);
|
||||||
const [serverSingle, setSingle] = useState<ServerSingle>(
|
const [serverSingle, setSingle] = useState<ServerSingle>(
|
||||||
@ -406,7 +411,10 @@ export function ServerCommandBar() {
|
|||||||
|
|
||||||
<CommandGroup heading="Server Actions">
|
<CommandGroup heading="Server Actions">
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => router.push("/server/" + serverName + "/")}
|
onSelect={() => {
|
||||||
|
router.push("/server/" + serverName + "/");
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Server className="mr-2 h-4 w-4" />
|
<Server className="mr-2 h-4 w-4" />
|
||||||
Open Server Page
|
Open Server Page
|
||||||
@ -418,6 +426,7 @@ export function ServerCommandBar() {
|
|||||||
toast.success("Done!");
|
toast.success("Done!");
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
disabled={!isSignedIn}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
className="mr-2 h-4 w-4"
|
className="mr-2 h-4 w-4"
|
||||||
@ -432,9 +441,10 @@ export function ServerCommandBar() {
|
|||||||
{!starred ? "F" : "Unf"}avorite Server
|
{!starred ? "F" : "Unf"}avorite Server
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() =>
|
onSelect={() => {
|
||||||
router.push("/server/" + serverName + "/statistics")
|
router.push("/server/" + serverName + "/statistics");
|
||||||
}
|
setOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Database className="mr-2 h-4 w-4" />
|
<Database className="mr-2 h-4 w-4" />
|
||||||
See Statistics
|
See Statistics
|
||||||
@ -509,11 +519,14 @@ export function CommandBar() {
|
|||||||
+Shift+K
|
+Shift+K
|
||||||
</CommandShortcut>
|
</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem disabled>
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
events.emit("cmd-event-sort");
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ArrowDown01 className="mr-2 h-4 w-4" />
|
<ArrowDown01 className="mr-2 h-4 w-4" />
|
||||||
<span>
|
Sort Servers
|
||||||
Sort Servers - <i>coming soon</i>
|
|
||||||
</span>
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@ -672,6 +685,49 @@ export function RandomServerDialog() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SubSortCommandBar() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffectOnce(() => {
|
||||||
|
events.on("cmd-event-sort", () => {
|
||||||
|
setOpen(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<CommandInput placeholder="Type a command or search..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
|
||||||
|
<CommandGroup heading="Sorts">
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
router.push("/sort/favorites");
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Star className="mr-2 h-4 w-4" />
|
||||||
|
<span>Favorites</span>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandGroup heading="Hierarchy">
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
setOpen(false);
|
||||||
|
events.emit("cmd-event");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Go back
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</CommandDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function SubLinkCommandBar() {
|
export function SubLinkCommandBar() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
@ -771,6 +827,7 @@ export function FavoriteBar() {
|
|||||||
key={c}
|
key={c}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/server/" + c);
|
router.push("/server/" + c);
|
||||||
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{c}
|
{c}
|
||||||
@ -802,6 +859,7 @@ export function CommandBarer() {
|
|||||||
<CommandBar />
|
<CommandBar />
|
||||||
<SearchCommandBar />
|
<SearchCommandBar />
|
||||||
<ServerCommandBar />
|
<ServerCommandBar />
|
||||||
|
<SubSortCommandBar />
|
||||||
<OfflineServerCB />
|
<OfflineServerCB />
|
||||||
<RandomServerDialog />
|
<RandomServerDialog />
|
||||||
</>
|
</>
|
||||||
|
|||||||
267
src/components/FavoritesSortView.tsx
Normal file
267
src/components/FavoritesSortView.tsx
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Spinner } from "./ui/spinner";
|
||||||
|
import { sortedFavorites } from "@/lib/api";
|
||||||
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "./ui/radio-group";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
import { OnlineServer } from "@/lib/types/mh-server";
|
||||||
|
import ServerCard from "./ServerCard";
|
||||||
|
import { CircleUser, Copy, Info, Layers, Network } from "lucide-react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
|
import { BorderBeam } from "./effects/border-beam";
|
||||||
|
import Stat from "./Stat";
|
||||||
|
import { Separator } from "./ui/separator";
|
||||||
|
import { Card, CardDescription, CardHeader, CardTitle } from "./ui/card";
|
||||||
|
import Link from "next/link";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
export default function FavoriteSortView() {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [list, setList] = useState<{ server: string; favorites: number }[]>([]);
|
||||||
|
const [allItems, setAllItems] = useState<
|
||||||
|
Array<{ server: string; favorites: number }>
|
||||||
|
>([]);
|
||||||
|
const [upNumber, setUpNumber] = useState(0);
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
const [totalServers, setTotalServers] = useState(0);
|
||||||
|
const [totalPlayers, setTotalPlayers] = useState(0);
|
||||||
|
const [online, setOnline] = useState<any>({});
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sortedFavorites().then((c) => {
|
||||||
|
if (typeof c !== "boolean") {
|
||||||
|
const slicedArray = c.slice(0, 20);
|
||||||
|
setAllItems(slicedArray);
|
||||||
|
setList(c);
|
||||||
|
fetch("https://api.minehut.com/servers").then((b) =>
|
||||||
|
b.json().then((c) => {
|
||||||
|
c.servers.forEach((v: OnlineServer) =>
|
||||||
|
setOnline(setManipulate(online, v.name, v))
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
setTotalPlayers(c.total_players);
|
||||||
|
setTotalServers(c.total_servers);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner className="flex items-center" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="max-lg:grid-cols-2 grid grid-cols-2 gap-4 ">
|
||||||
|
<Stat
|
||||||
|
title="Players online"
|
||||||
|
desc={totalPlayers.toString()}
|
||||||
|
icon={CircleUser}
|
||||||
|
/>
|
||||||
|
<Stat
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
totalServers >= 3200
|
||||||
|
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Servers online{" "}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className="relative z-0"
|
||||||
|
desc={
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
totalServers >= 3200
|
||||||
|
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 "
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{totalServers.toString()}
|
||||||
|
</div>
|
||||||
|
{totalServers >= 3200 && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info size={16} className="ml-2" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="font-normal">
|
||||||
|
The server amount is over 3.2k, meaning that new servers
|
||||||
|
have to go into a queue before being able to be online.{" "}
|
||||||
|
<br />
|
||||||
|
(the server count isn't entirely accurate, so sometimes you
|
||||||
|
might not go into a queue even when the server count is at
|
||||||
|
3.2k)
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
icon={Network}
|
||||||
|
>
|
||||||
|
{totalServers >= 3200 && (
|
||||||
|
<BorderBeam
|
||||||
|
size={135}
|
||||||
|
duration={12}
|
||||||
|
delay={9}
|
||||||
|
colorFrom="rgb(6 182 212)"
|
||||||
|
colorTo="rgb(59 130 246)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stat>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<Separator />
|
||||||
|
<br />
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button className="ml-2" variant="secondary">
|
||||||
|
Sort
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<RadioGroup
|
||||||
|
defaultValue="option-two"
|
||||||
|
onValueChange={() => router.push("/")}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="option-one" id="option-one" />
|
||||||
|
<Label htmlFor="option-one">Online Players</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="option-two" id="option-two" />
|
||||||
|
<Label htmlFor="option-two">Favorites</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(true);
|
||||||
|
sortedFavorites().then((c) => {
|
||||||
|
if (typeof c !== "boolean") {
|
||||||
|
const slicedArray = c.slice(0, 20);
|
||||||
|
setAllItems(slicedArray);
|
||||||
|
setList(c);
|
||||||
|
fetch("https://api.minehut.com/servers").then((b) =>
|
||||||
|
b.json().then((c) => {
|
||||||
|
c.servers.forEach((v: OnlineServer) =>
|
||||||
|
setOnline(setManipulate(online, v.name, v))
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
setTotalPlayers(c.total_players);
|
||||||
|
setTotalServers(c.total_servers);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<InfiniteScroll
|
||||||
|
dataLength={list.length}
|
||||||
|
hasMore={hasMore}
|
||||||
|
next={() => {
|
||||||
|
const newUpNumber = upNumber + 1;
|
||||||
|
const slicedArray = list.slice(
|
||||||
|
newUpNumber * 20,
|
||||||
|
newUpNumber * 20 + 20
|
||||||
|
);
|
||||||
|
console.log(slicedArray.length);
|
||||||
|
|
||||||
|
setAllItems((prev) => [...prev, ...slicedArray]);
|
||||||
|
if (slicedArray.length !== 20) setHasMore(false);
|
||||||
|
setUpNumber(newUpNumber);
|
||||||
|
}}
|
||||||
|
loader={<br />}
|
||||||
|
endMessage={
|
||||||
|
<p style={{ textAlign: "center" }}>
|
||||||
|
<br />
|
||||||
|
<strong>You've seen it all</strong>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
style={{ overflow: "hidden", paddingLeft: 6 }}
|
||||||
|
>
|
||||||
|
<div className="grid sm:grid-cols-4 gap-4">
|
||||||
|
{allItems.map((v) => {
|
||||||
|
if (v.favorites == 0) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (online[v.server] != undefined)
|
||||||
|
return (
|
||||||
|
<ServerCard mini b={online[v.server]} favs={v.favorites} />
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<Card className="h-[226px]">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{v.server}</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{v.favorites} favorited
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="secondary"
|
||||||
|
className="min-w-[128px] max-w-[328px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
v.server + ".mshf.minehut.gg"
|
||||||
|
);
|
||||||
|
toast.success("Copied IP to clipboard");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Copy size={18} />
|
||||||
|
<code className="ml-2">{v.server}</code>
|
||||||
|
</Button>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Link href={"/server/" + v.server}>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="secondary"
|
||||||
|
className="w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||||
|
>
|
||||||
|
<Layers size={18} />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
Open up the server page to see more information about
|
||||||
|
the server
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</InfiniteScroll>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setManipulate(object: any, change: string, value: any) {
|
||||||
|
let newObject = object;
|
||||||
|
newObject[change] = value;
|
||||||
|
return newObject;
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ import {
|
|||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuSeparator,
|
ContextMenuSeparator,
|
||||||
} from "@/components/ui/context-menu";
|
} from "@/components/ui/context-menu";
|
||||||
import toast from "react-hot-toast";
|
import toast, { LoaderIcon } from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from "./ui/card";
|
} from "./ui/card";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import { TagShower } from "./ServerList";
|
import { TagShower } from "./ServerList";
|
||||||
import { Copy, EllipsisVertical, Layers } from "lucide-react";
|
import { Copy, EllipsisVertical, Layers, Star } from "lucide-react";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
@ -29,15 +29,35 @@ import { Tooltip } from "@radix-ui/react-tooltip";
|
|||||||
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
import { useRouter } from "@/lib/useRouter";
|
import { useRouter } from "@/lib/useRouter";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { favoriteServer, isFavorited } from "@/lib/api";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
export default function ServerCard({ b, motd }: any) {
|
export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [favoriteStar, setFavoriteStar] = useState(false);
|
||||||
|
const [favoriteLoading, setFavoriteLoading] = useState(true);
|
||||||
|
const { isSignedIn } = useUser();
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open && isSignedIn)
|
||||||
|
isFavorited(b.name).then((c) => {
|
||||||
|
setFavoriteStar(c);
|
||||||
|
setFavoriteLoading(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<Card
|
<Card
|
||||||
key={b.name}
|
key={b.name}
|
||||||
className="min-h-[450px] max-h-[450px] mb-4 flex items-start"
|
className={
|
||||||
|
(!mini ? "min-h-[450px] max-h-[450px]" : "") +
|
||||||
|
" mb-4 flex items-start"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="m-0">
|
<CardTitle className="m-0">
|
||||||
@ -115,7 +135,7 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
<span className="pl-1">
|
<span className="pl-1">
|
||||||
{b.playerData.playerCount}{" "}
|
{b.playerData.playerCount}{" "}
|
||||||
{b.playerData.playerCount == 1 ? "player" : "players"}{" "}
|
{b.playerData.playerCount == 1 ? "player" : "players"}{" "}
|
||||||
currently online
|
currently online {favs && <>• {favs} favorited</>}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -177,10 +197,12 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
{motd && (
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{ __html: motd }}
|
dangerouslySetInnerHTML={{ __html: motd }}
|
||||||
className="w-[30px] text-center break-all overflow-hidden"
|
className="w-[30px] text-center break-all overflow-hidden"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
@ -193,9 +215,6 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy server IP
|
Copy server IP
|
||||||
<div className="RightSlot">
|
|
||||||
<Copy size={18} />
|
|
||||||
</div>
|
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
@ -205,6 +224,40 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
router.push("/server/" + b.name + "/statistics");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open statistics page
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuSeparator />
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setFavoriteLoading(true);
|
||||||
|
favoriteServer(b.name).then(() => {
|
||||||
|
setFavoriteLoading(false);
|
||||||
|
setFavoriteStar(!favoriteStar);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isSignedIn || favoriteLoading}
|
||||||
|
>
|
||||||
|
{!favoriteLoading && (
|
||||||
|
<Star
|
||||||
|
size={16}
|
||||||
|
className="mr-2 text-white"
|
||||||
|
fill={
|
||||||
|
favoriteStar
|
||||||
|
? resolvedTheme == "dark"
|
||||||
|
? "white"
|
||||||
|
: "black"
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}{" "}
|
||||||
|
{favoriteLoading && <LoaderIcon className="mr-2" />}
|
||||||
|
{favoriteStar && isSignedIn ? "Unf" : "F"}avorite Server
|
||||||
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -37,6 +37,8 @@ import { useEffectOnce } from "@/lib/useEffectOnce";
|
|||||||
import ServerCard from "./ServerCard";
|
import ServerCard from "./ServerCard";
|
||||||
import events from "@/lib/commandEvent";
|
import events from "@/lib/commandEvent";
|
||||||
import { BorderBeam } from "@/components/effects/border-beam";
|
import { BorderBeam } from "@/components/effects/border-beam";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
|
||||||
export default function ServerList() {
|
export default function ServerList() {
|
||||||
const [loading, setLoading]: any = useState(true);
|
const [loading, setLoading]: any = useState(true);
|
||||||
@ -59,6 +61,7 @@ export default function ServerList() {
|
|||||||
const [nameFilters, setNameFilters] = useState<any>({});
|
const [nameFilters, setNameFilters] = useState<any>({});
|
||||||
const [inErrState, setErrState] = useState(false);
|
const [inErrState, setErrState] = useState(false);
|
||||||
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
||||||
|
const router = useRouter();
|
||||||
const [filters, setFilters] = useState<
|
const [filters, setFilters] = useState<
|
||||||
Array<(server: OnlineServer) => Promise<boolean>>
|
Array<(server: OnlineServer) => Promise<boolean>>
|
||||||
>([]);
|
>([]);
|
||||||
@ -125,7 +128,7 @@ export default function ServerList() {
|
|||||||
title="Players online"
|
title="Players online"
|
||||||
desc={serverList.getExtraData().total_players.toString()}
|
desc={serverList.getExtraData().total_players.toString()}
|
||||||
icon={CircleUser}
|
icon={CircleUser}
|
||||||
></Stat>
|
/>
|
||||||
<Stat
|
<Stat
|
||||||
title={
|
title={
|
||||||
<div
|
<div
|
||||||
@ -522,6 +525,28 @@ export default function ServerList() {
|
|||||||
))}
|
))}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button className="ml-2" variant="secondary">
|
||||||
|
Sort
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<RadioGroup
|
||||||
|
defaultValue="option-one"
|
||||||
|
onValueChange={() => router.push("/sort/favorites")}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="option-one" id="option-one" />
|
||||||
|
<Label htmlFor="option-one">Online Players</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="option-two" id="option-two" />
|
||||||
|
<Label htmlFor="option-two">Favorites</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="md:ml-3 "
|
className="md:ml-3 "
|
||||||
@ -629,7 +654,7 @@ export default function ServerList() {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<code className="border p-3 rounded">
|
<code className="border p-3 rounded">
|
||||||
{randomData.name}.mshf.minehut.gg{" "}
|
{randomData.name}.minehut.gg{" "}
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
className="ml-1 h-[20px]"
|
className="ml-1 h-[20px]"
|
||||||
|
|||||||
@ -84,6 +84,16 @@ export default function TextFromPathname() {
|
|||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{pathname == "/sort/favorites" && (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator className="max-sm:hidden" />
|
||||||
|
<BreadcrumbItem>Sort</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Favorites</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,27 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const ContextMenu = ContextMenuPrimitive.Root
|
const ContextMenu = ContextMenuPrimitive.Root;
|
||||||
|
|
||||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
||||||
|
|
||||||
const ContextMenuGroup = ContextMenuPrimitive.Group
|
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
||||||
|
|
||||||
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
||||||
|
|
||||||
const ContextMenuSub = ContextMenuPrimitive.Sub
|
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
||||||
|
|
||||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
||||||
|
|
||||||
const ContextMenuSubTrigger = React.forwardRef<
|
const ContextMenuSubTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, children, ...props }, ref) => (
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
<ContextMenuPrimitive.SubTrigger
|
<ContextMenuPrimitive.SubTrigger
|
||||||
@ -36,8 +36,8 @@ const ContextMenuSubTrigger = React.forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
<ChevronRight className="ml-auto h-4 w-4" />
|
<ChevronRight className="ml-auto h-4 w-4" />
|
||||||
</ContextMenuPrimitive.SubTrigger>
|
</ContextMenuPrimitive.SubTrigger>
|
||||||
))
|
));
|
||||||
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
||||||
|
|
||||||
const ContextMenuSubContent = React.forwardRef<
|
const ContextMenuSubContent = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||||
@ -51,8 +51,8 @@ const ContextMenuSubContent = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
||||||
|
|
||||||
const ContextMenuContent = React.forwardRef<
|
const ContextMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||||
@ -62,19 +62,19 @@ const ContextMenuContent = React.forwardRef<
|
|||||||
<ContextMenuPrimitive.Content
|
<ContextMenuPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</ContextMenuPrimitive.Portal>
|
</ContextMenuPrimitive.Portal>
|
||||||
))
|
));
|
||||||
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
||||||
|
|
||||||
const ContextMenuItem = React.forwardRef<
|
const ContextMenuItem = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, ...props }, ref) => (
|
>(({ className, inset, ...props }, ref) => (
|
||||||
<ContextMenuPrimitive.Item
|
<ContextMenuPrimitive.Item
|
||||||
@ -86,8 +86,8 @@ const ContextMenuItem = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
||||||
|
|
||||||
const ContextMenuCheckboxItem = React.forwardRef<
|
const ContextMenuCheckboxItem = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||||
@ -109,9 +109,9 @@ const ContextMenuCheckboxItem = React.forwardRef<
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.CheckboxItem>
|
</ContextMenuPrimitive.CheckboxItem>
|
||||||
))
|
));
|
||||||
ContextMenuCheckboxItem.displayName =
|
ContextMenuCheckboxItem.displayName =
|
||||||
ContextMenuPrimitive.CheckboxItem.displayName
|
ContextMenuPrimitive.CheckboxItem.displayName;
|
||||||
|
|
||||||
const ContextMenuRadioItem = React.forwardRef<
|
const ContextMenuRadioItem = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||||
@ -132,13 +132,13 @@ const ContextMenuRadioItem = React.forwardRef<
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</ContextMenuPrimitive.RadioItem>
|
</ContextMenuPrimitive.RadioItem>
|
||||||
))
|
));
|
||||||
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
||||||
|
|
||||||
const ContextMenuLabel = React.forwardRef<
|
const ContextMenuLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, ...props }, ref) => (
|
>(({ className, inset, ...props }, ref) => (
|
||||||
<ContextMenuPrimitive.Label
|
<ContextMenuPrimitive.Label
|
||||||
@ -150,8 +150,8 @@ const ContextMenuLabel = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
||||||
|
|
||||||
const ContextMenuSeparator = React.forwardRef<
|
const ContextMenuSeparator = React.forwardRef<
|
||||||
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||||
@ -162,8 +162,8 @@ const ContextMenuSeparator = React.forwardRef<
|
|||||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
||||||
|
|
||||||
const ContextMenuShortcut = ({
|
const ContextMenuShortcut = ({
|
||||||
className,
|
className,
|
||||||
@ -177,9 +177,9 @@ const ContextMenuShortcut = ({
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
@ -197,4 +197,4 @@ export {
|
|||||||
ContextMenuSubContent,
|
ContextMenuSubContent,
|
||||||
ContextMenuSubTrigger,
|
ContextMenuSubTrigger,
|
||||||
ContextMenuRadioGroup,
|
ContextMenuRadioGroup,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -30,12 +30,12 @@ export async function increaseNum(client: MongoClient, server: string) {
|
|||||||
const find = await collection.find({ server: server }).toArray();
|
const find = await collection.find({ server: server }).toArray();
|
||||||
|
|
||||||
if (find.length == 0) {
|
if (find.length == 0) {
|
||||||
collection.insertOne({ server: server, favorites: 1 });
|
collection.insertOne({ server: server, favorites: 1, date: new Date() });
|
||||||
} else {
|
} else {
|
||||||
const entry = find[0];
|
const entry = find[0];
|
||||||
collection.findOneAndReplace(
|
collection.findOneAndReplace(
|
||||||
{ server: server },
|
{ server: server },
|
||||||
{ server: server, favorites: entry.favorites + 1 }
|
{ server: server, favorites: entry.favorites + 1, date: new Date() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/pages/api/v0/sorting/favorites.ts
Normal file
20
src/pages/api/v0/sorting/favorites.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { MongoClient } from "mongodb";
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
const db = client.db("mhsf");
|
||||||
|
const collection = db.collection("meta");
|
||||||
|
|
||||||
|
const all = await collection.find().toArray();
|
||||||
|
const sorted = all.sort((a, b) => a.favorites - b.favorites);
|
||||||
|
sorted.reverse();
|
||||||
|
res.send({ results: sorted });
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export const version = "b-0.8.0";
|
export const version = "b-0.9.0";
|
||||||
|
|
||||||
const User = ({ user }: { user: string }) => (
|
const User = ({ user }: { user: string }) => (
|
||||||
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
||||||
@ -44,6 +44,25 @@ export const Changelog = () => (
|
|||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
<div>
|
||||||
|
<strong className="flex items-center">
|
||||||
|
Version b-0.9.0 (August 15th 2024)
|
||||||
|
</strong>
|
||||||
|
<ul>
|
||||||
|
<li>• Adding favorites sorting option</li>
|
||||||
|
<li>• Fixed right-click context menu on the server list</li>
|
||||||
|
<li>• Fixed metadata bugs</li>
|
||||||
|
</ul>
|
||||||
|
<br />
|
||||||
|
<i>
|
||||||
|
Hey! Update on statistics. Recently, we have figured out the Minehut API
|
||||||
|
is blocked to Vercel servers (atleast the <code>/servers</code>{" "}
|
||||||
|
endpoint). I'm actively trying to find a loop-hole so that statistics
|
||||||
|
works correctly. Thank you {":)"}
|
||||||
|
</i>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<strong className="flex items-center">
|
<strong className="flex items-center">
|
||||||
Version b-0.8.0 (August 11th 2024)
|
Version b-0.8.0 (August 11th 2024)
|
||||||
@ -135,7 +154,13 @@ export const Changelog = () => (
|
|||||||
<div>
|
<div>
|
||||||
<strong>All developers that helped out:</strong>
|
<strong>All developers that helped out:</strong>
|
||||||
<Link href="https://dvelo.vercel.app">
|
<Link href="https://dvelo.vercel.app">
|
||||||
<Image src="/imgs/badge1.png" alt="cool badge" width={88} height={31} />
|
<Image
|
||||||
|
src="/imgs/badge1.png"
|
||||||
|
alt="cool badge"
|
||||||
|
width={88}
|
||||||
|
height={31}
|
||||||
|
className="w-[88px] h-[31px]"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user