mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 22:25:00 -05:00
new release: 0.6
This commit is contained in:
parent
473332cf69
commit
724c301a70
@ -25,7 +25,7 @@ Clone the repo!
|
||||
|
||||
First, you must supply the following services with API keys:
|
||||
|
||||
- [Clerk](https://clerk.com): Create an app and put the respective keys in `.env.local`. Also, add `IS_AUTH=true`.
|
||||
- [Clerk](https://clerk.com): Create an app and put the respective keys in `.env.local`
|
||||
- MongoDB: Create a database, can be anywhere, and put the location to connect in `.env.local` for the key `MONGO_DB` (this isn't required by any means, but if you want to store any short term or historical data, use this.)
|
||||
- Inngest: Inngest is a smaller library, but runs the `cron` jobs which will make servers automaticly get added to the database.
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/react-twemoji": "^0.4.3",
|
||||
"@unocss/eslint-plugin": "^0.61.5",
|
||||
"@unocss/postcss": "^0.61.5",
|
||||
@ -47,6 +48,8 @@
|
||||
"next": "14.2.3",
|
||||
"next-css-obfuscator": "^2.2.16",
|
||||
"next-themes": "^0.3.0",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss-obfuscator": "^1.6.1",
|
||||
"prettier": "^3.3.1",
|
||||
"react": "^18",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { OnlineServer, ServerResponse } from "./components/ServerView";
|
||||
import { OnlineServer, ServerResponse } from "./lib/types/server";
|
||||
|
||||
const serverCache: any = {};
|
||||
|
||||
@ -262,7 +262,7 @@ export var allCategories: Array<{
|
||||
async function requestServer(s: OnlineServer): Promise<ServerResponse> {
|
||||
if (serverCache[s.name] == undefined) {
|
||||
const re = await fetch(
|
||||
"https://api.minehut.com/server/" + s.name + "?byName=true",
|
||||
"https://api.minehut.com/server/" + s.name + "?byName=true"
|
||||
);
|
||||
const json = await re.json();
|
||||
serverCache[s.name] = json.server;
|
||||
|
||||
@ -95,6 +95,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.backdrop-blur {
|
||||
-webkit-backdrop-filter: blur(8px)!important;
|
||||
backdrop-filter: blur(8px)!important;
|
||||
}
|
||||
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { GeistSans } from "geist/font/sans";
|
||||
import { Github, CodeXml, Server } from "lucide-react";
|
||||
import { Github, CodeXml, Server, Command } from "lucide-react";
|
||||
|
||||
import "./globals.css";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
@ -12,6 +12,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
import Image from "next/image";
|
||||
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
|
||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||
import NextTopLoader from '@/lib/top-loader';
|
||||
import { banner } from "@/banner";
|
||||
import {
|
||||
Breadcrumb,
|
||||
@ -24,6 +25,12 @@ import Link from "next/link";
|
||||
import TopBar from "@/components/clerk/Topbar";
|
||||
import TextFromPathname from "@/components/TextFromPathname";
|
||||
import { Inter as interFont } from "next/font/google";
|
||||
import {
|
||||
CommandBar,
|
||||
CommandBarer,
|
||||
SearchCommandBar,
|
||||
SubLinkCommandBar,
|
||||
} from "@/components/CommandBar";
|
||||
|
||||
const inter = interFont({ variable: "--font-inter", subsets: ["latin"] });
|
||||
export default async function RootLayout({
|
||||
@ -40,6 +47,7 @@ export default async function RootLayout({
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<TooltipProvider>
|
||||
|
||||
{banner.isBanner && (
|
||||
<div className="bg-orange-600 w-screen h-8 border-b fixed text-black flex items-center text-center font-medium pl-2">
|
||||
{banner.bannerText}
|
||||
@ -65,8 +73,9 @@ export default async function RootLayout({
|
||||
</div>
|
||||
<TopBar inter={inter.className} />
|
||||
</div>
|
||||
<div>{children}</div>{" "}
|
||||
<div><NextTopLoader/>{children}</div>{" "}
|
||||
<Toaster position="bottom-center" reverseOrder={false} />
|
||||
<CommandBarer />
|
||||
</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
</ClerkThemeProvider>
|
||||
|
||||
335
src/components/CommandBar.tsx
Normal file
335
src/components/CommandBar.tsx
Normal file
@ -0,0 +1,335 @@
|
||||
"use client";
|
||||
import {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
CommandShortcut,
|
||||
} from "@/components/ui/command";
|
||||
import { TagShower } from "./ServerList";
|
||||
import { useEffect, useState } from "react";
|
||||
import { OnlineServer } from "@/lib/types/server";
|
||||
import events from "@/lib/commandEvent";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowDown01,
|
||||
ArrowLeft,
|
||||
CommandIcon,
|
||||
LinkIcon,
|
||||
Server,
|
||||
Settings,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||
import { useClerk, useUser } from "@clerk/nextjs";
|
||||
import { useRouter } from '@/lib/useRouter'
|
||||
|
||||
export function SearchCommandBar() {
|
||||
const [serverList, setServerList] = useState<OnlineServer[]>([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [backEnabled, setBackEnabled] = useState(false);
|
||||
const [searchRes, setSearchRes] = useState<any>(undefined);
|
||||
const router = useRouter()
|
||||
useHotkeys("mod+shift+k", () => setOpen(true), []);
|
||||
|
||||
useEffectOnce(() => {
|
||||
events.on("search-request-event", () => {
|
||||
setOpen(true);
|
||||
});
|
||||
events.on("search-request-event-back", () => {
|
||||
setOpen(true);
|
||||
setBackEnabled(true);
|
||||
});
|
||||
|
||||
fetch("https://api.minehut.com/servers").then((c) =>
|
||||
c.json().then((b: { servers: OnlineServer[] }) => {
|
||||
setServerList(b.servers.slice(0, 20));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput
|
||||
placeholder="Search for a server (offline or online)"
|
||||
onValueChange={(c) => {
|
||||
fetch("https://api.minehut.com/server/" + c + "?byName=true").then(
|
||||
(l) => {
|
||||
if (l.ok) {
|
||||
console.log("found!");
|
||||
l.json().then((m: any) => {
|
||||
setSearchRes(m.server);
|
||||
console.log(searchRes);
|
||||
});
|
||||
} else {
|
||||
setSearchRes(undefined);
|
||||
}
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandGroup heading="">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
if (backEnabled) events.emit("cmd-event");
|
||||
setBackEnabled(false);
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
<span>Go back</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandEmpty>
|
||||
No results found. (Minehut deleted legacy servers)
|
||||
</CommandEmpty>
|
||||
{searchRes == undefined ? (
|
||||
""
|
||||
) : (
|
||||
<CommandGroup heading="Search Results">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
router.push("/server/" + searchRes.name);
|
||||
}}
|
||||
>
|
||||
<div className="block">
|
||||
<span className="font-medium">{searchRes.name}</span> <br />
|
||||
<code className="text-gray-500 text-[14px]">
|
||||
{searchRes.joins} total joins •{" "}
|
||||
{searchRes.online ? "Online" : "Offline"}
|
||||
</code>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Popular Servers">
|
||||
{serverList.map((b: OnlineServer) => (
|
||||
<CommandItem
|
||||
key={b.name}
|
||||
onSelect={() => {
|
||||
router.push("/server/" + b.name);
|
||||
}}
|
||||
>
|
||||
<div className="block">
|
||||
<span className="font-medium">{b.name}</span> <br />
|
||||
<code className="text-gray-500 text-[14px]">
|
||||
<TagShower server={b} />
|
||||
</code>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommandBar() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const clerk = useClerk();
|
||||
const { user } = useUser();
|
||||
useHotkeys("mod+k", () => setOpen(true), []);
|
||||
|
||||
useEffectOnce(() => {
|
||||
events.on("cmd-event", () => {
|
||||
setOpen(true);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
events.emit("search-request-event-back");
|
||||
}}
|
||||
>
|
||||
<Server className="mr-2 h-4 w-4" />
|
||||
<span>Servers</span>
|
||||
<CommandShortcut className="flex items-center">
|
||||
<CommandIcon size={10} />
|
||||
+Shift+K
|
||||
</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem disabled>
|
||||
<ArrowDown01 className="mr-2 h-4 w-4" />
|
||||
<span>
|
||||
Sort Servers - <i>coming soon</i>
|
||||
</span>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
events.emit("cmd-event-link");
|
||||
}}
|
||||
>
|
||||
<LinkIcon className="mr-2 h-4 w-4" />
|
||||
<span>Links</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Profile">
|
||||
<CommandItem onSelect={() => events.emit("cmd-event-favorites")}>
|
||||
<Star className="mr-2 h-4 w-4" />
|
||||
<span>Favorites</span>
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
try {
|
||||
clerk.openUserProfile();
|
||||
} catch {
|
||||
clerk.openSignIn();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>User Settings</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubLinkCommandBar() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffectOnce(() => {
|
||||
events.on("cmd-event-link", () => {
|
||||
setOpen(true);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
events.emit("cmd-event");
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
<span>Go back</span>
|
||||
</CommandItem>
|
||||
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
window
|
||||
.open("https://github.com/DeveloLongScript/MHSF", "_blank")
|
||||
?.focus();
|
||||
}}
|
||||
>
|
||||
<Github className="mr-2 h-4 w-4" />
|
||||
<span>GitHub</span>
|
||||
</CommandItem>
|
||||
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
window.open("https://mhsf.betteruptime.com", "_blank")?.focus();
|
||||
}}
|
||||
>
|
||||
<ArrowDown01 className="mr-2 h-4 w-4" />
|
||||
<span>Status Page</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
import * as React from "react";
|
||||
import type { SVGProps } from "react";
|
||||
import { getAccountFavorites } from "@/lib/api";
|
||||
const Github = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
viewBox="0 0 256 250"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="#24292f"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<path d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z" />
|
||||
</svg>
|
||||
);
|
||||
export function FavoriteBar() {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [favorites, setFavorites] = useState<Array<string> | undefined>(
|
||||
undefined
|
||||
);
|
||||
const clerk = useClerk();
|
||||
const router = useRouter()
|
||||
|
||||
useEffectOnce(() => {
|
||||
events.on("cmd-event-favorites", () => setOpen(true));
|
||||
getAccountFavorites().then((c) => setFavorites(c));
|
||||
});
|
||||
|
||||
return (
|
||||
<CommandDialog open={isOpen} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
events.emit("cmd-event");
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Go back
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
<CommandGroup heading="Favorites">
|
||||
{favorites == undefined && (
|
||||
<CommandItem onSelect={() => clerk.openSignIn()}>
|
||||
Login to see favorites
|
||||
</CommandItem>
|
||||
)}
|
||||
{favorites != undefined && (
|
||||
<>
|
||||
{favorites.map((c) => (
|
||||
<CommandItem
|
||||
key={c}
|
||||
onSelect={() => {
|
||||
router.push("/server/" + c);
|
||||
}}
|
||||
>
|
||||
{c}
|
||||
</CommandItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
export function CommandBarer() {
|
||||
return (
|
||||
<>
|
||||
<FavoriteBar />
|
||||
<SubLinkCommandBar />
|
||||
<CommandBar />
|
||||
<SearchCommandBar />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -2,37 +2,38 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Spinner } from "./ui/spinner";
|
||||
import { Card, CardHeader, CardTitle } from "./ui/card";
|
||||
import { ServerResponse } from "./ServerView";
|
||||
import { ServerResponse } from "@/lib/types/server";
|
||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||
import { Button } from "./ui/button";
|
||||
import { Copy, Layers, X, XIcon } from "lucide-react";
|
||||
import { Copy, Layers, XIcon } from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
import { getAccountFavorites } from "@/lib/api";
|
||||
import { useRouter } from '@/lib/useRouter'
|
||||
|
||||
export default function FavoritesView() {
|
||||
const [apiFavorites, setApiFavorites] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter()
|
||||
|
||||
useEffectOnce(() => {
|
||||
fetch("/api/favorites/getAllFavorites").then((c) => {
|
||||
c.json().then((d) => {
|
||||
let num = 0;
|
||||
d.result.forEach((a: any, i: number) => {
|
||||
fetch("https://api.minehut.com/server/" + a + "?byName=true").then(
|
||||
(b) =>
|
||||
b.json().then((c) => {
|
||||
num++;
|
||||
var apiClone = apiFavorites;
|
||||
apiClone.push(c.server);
|
||||
setApiFavorites(apiClone);
|
||||
if (num == d.result.length) {
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
if (d.result.length == 0) setLoading(false);
|
||||
getAccountFavorites().then((d) => {
|
||||
let num = 0;
|
||||
d.forEach((a: any, i: number) => {
|
||||
fetch("https://api.minehut.com/server/" + a + "?byName=true").then(
|
||||
(b) =>
|
||||
b.json().then((c) => {
|
||||
num++;
|
||||
var apiClone = apiFavorites;
|
||||
apiClone.push(c.server);
|
||||
setApiFavorites(apiClone);
|
||||
if (num == d.length) {
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
if (d.length == 0) setLoading(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,7 +81,7 @@ export default function FavoritesView() {
|
||||
variant="secondary"
|
||||
className=" w-[32px] h-[32px] mb-2 ml-2 max-md:hidden"
|
||||
onClick={() => {
|
||||
window.location.href = "/server/" + server.name;
|
||||
router.push("/server/" + server.name);
|
||||
}}
|
||||
>
|
||||
<Layers size={18} />
|
||||
|
||||
@ -17,7 +17,8 @@ import {
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||
import { ServerResponse } from "./ServerView";
|
||||
import { ServerResponse } from "@/lib/types/server";
|
||||
import { getCommunityServerFavorites, getShortTermData } from "@/lib/api";
|
||||
|
||||
const chartConfig = {
|
||||
player_count: {
|
||||
@ -40,25 +41,10 @@ export function NewChart({ server }: { server: string }) {
|
||||
|
||||
const allNums = { player_count: joins, favorites };
|
||||
useEffectOnce(() => {
|
||||
fetch("/api/history/getShortTermData", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
scopes: ["player_count", "favorites", "time"],
|
||||
server,
|
||||
}),
|
||||
method: "POST",
|
||||
}).then((c) => {
|
||||
c.json().then((b) => {
|
||||
setChartData(b.data);
|
||||
fetch("/api/favorites/getCommunityNum", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ server }),
|
||||
method: "POST",
|
||||
}).then((b) =>
|
||||
b.json().then((f) => {
|
||||
setFavorites(f.result);
|
||||
})
|
||||
);
|
||||
getShortTermData(server, ["player_count", "favorites", "time"]).then(
|
||||
(c) => {
|
||||
setChartData(c);
|
||||
getCommunityServerFavorites(server).then((b) => setFavorites(b));
|
||||
fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
|
||||
(k) => {
|
||||
k.json().then((p: { server: ServerResponse }) => {
|
||||
@ -66,8 +52,8 @@ export function NewChart({ server }: { server: string }) {
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from "./ui/card";
|
||||
import IconDisplay from "./IconDisplay";
|
||||
import { TagShower } from "./ServerList";
|
||||
import { Copy, EllipsisVertical, Layers, MoveRight } from "lucide-react";
|
||||
import { Copy, EllipsisVertical, Layers, MoveRight, Router } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
Drawer,
|
||||
@ -30,8 +30,11 @@ import {
|
||||
import { useEffect, useState } from "react";
|
||||
import { Tooltip } from "@radix-ui/react-tooltip";
|
||||
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
import { useRouter } from '@/lib/useRouter'
|
||||
import Link from "next/link";
|
||||
|
||||
export default function ServerCard({ b, motd }: any) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
@ -72,7 +75,7 @@ export default function ServerCard({ b, motd }: any) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
window.location.href = "/server/" + b.name;
|
||||
router.push("/server/" + b.name);
|
||||
}}
|
||||
>
|
||||
Open server page
|
||||
@ -138,16 +141,15 @@ export default function ServerCard({ b, motd }: any) {
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Link href={"/server/" + b.name}>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
className=" w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||
onClick={() => {
|
||||
window.location.href = "/server/" + b.name;
|
||||
}}
|
||||
>
|
||||
<Layers size={18} />
|
||||
</Button>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Open up the server page to see more information about
|
||||
@ -171,20 +173,19 @@ export default function ServerCard({ b, motd }: any) {
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<Link href={"/server/" + b.name}>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
window.location.href = "/server/" + b.name;
|
||||
}}
|
||||
|
||||
>
|
||||
Open server page
|
||||
</ContextMenuItem>
|
||||
</ContextMenuItem></Link>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</CardDescription>
|
||||
<CardContent>
|
||||
{b.name != "Skylegendz" && (
|
||||
<span dangerouslySetInnerHTML={{ __html: motd }} />
|
||||
)}
|
||||
|
||||
<span dangerouslySetInnerHTML={{ __html: motd }} className="max-w-[12px] text-center"/>
|
||||
|
||||
</CardContent>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
@ -204,7 +205,7 @@ export default function ServerCard({ b, motd }: any) {
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
window.location.href = "/server/" + b.name;
|
||||
router.push("/server/" + b.name);
|
||||
}}
|
||||
>
|
||||
Open server page
|
||||
|
||||
@ -79,24 +79,25 @@ import {
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Spinner } from "./ui/spinner";
|
||||
import { CommandIcon } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { OnlineServer, ServerResponse } from "./ServerView";
|
||||
import { OnlineServer, ServerResponse } from "@/lib/types/server";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||
import ServerCard from "./ServerCard";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import events from "@/lib/commandEvent";
|
||||
import { BorderBeam } from "@/components/effects/border-beam";
|
||||
|
||||
export default function ServerList() {
|
||||
const [loading, setLoading]: any = useState(true);
|
||||
const [command, setCommand] = useState(false);
|
||||
const [randomText, setRandomText] = useState("");
|
||||
const [motdList, setMotdList] = useState<any>({});
|
||||
const allText = [""];
|
||||
const getRandomText = () => {
|
||||
return allText[Math.floor(Math.random() * allText.length)];
|
||||
};
|
||||
const [searchRes, setSearchRes] = useState<any>(undefined);
|
||||
const [templateFilter, setTemplateFilter] = useState(false);
|
||||
const [random, setRandom] = useState(false);
|
||||
const [serverList, setServerList] = useState(new ServersList([]));
|
||||
@ -108,14 +109,13 @@ export default function ServerList() {
|
||||
server.playerData.playerCount < 15 &&
|
||||
server.playerData.playerCount > 7;
|
||||
const [nameFilters, setNameFilters] = useState<any>({});
|
||||
useHotkeys("ctrl+k", () => setCommand(true), []);
|
||||
const [inErrState, setErrState] = useState(false);
|
||||
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
||||
const [filters, setFilters] = useState<
|
||||
Array<(server: OnlineServer) => Promise<boolean>>
|
||||
>([]);
|
||||
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
useEffectOnce(() => {
|
||||
@ -175,9 +175,10 @@ export default function ServerList() {
|
||||
<div className=" max-lg:grid-cols-2 grid grid-cols-3 gap-4 ">
|
||||
<Stat
|
||||
title="Players online"
|
||||
className="relative"
|
||||
desc={serverList.getExtraData().total_players.toString()}
|
||||
icon={CircleUser}
|
||||
/>
|
||||
><BorderBeam size={135} duration={12} delay={9}/></Stat>
|
||||
<Stat
|
||||
title="Servers online"
|
||||
desc={serverList.getExtraData().total_servers.toString()}
|
||||
@ -203,11 +204,15 @@ export default function ServerList() {
|
||||
<Separator />
|
||||
<div className=" mt-3 ml-3">
|
||||
<Button
|
||||
onClick={() => setCommand(true)}
|
||||
onClick={() => events.emit("search-request-event")}
|
||||
variant="secondary"
|
||||
className=" max-lg:mb-3"
|
||||
>
|
||||
Search <code className="ml-2">Ctrl+K</code>
|
||||
Search{" "}
|
||||
<code className="ml-2 flex items-center">
|
||||
<CommandIcon size={14}/>
|
||||
+Shift+K
|
||||
</code>
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
@ -338,7 +343,7 @@ export default function ServerList() {
|
||||
error: "Error while changing filters",
|
||||
loading: "Changing filters...",
|
||||
success: "Changed filters!",
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
defaultValue={(() => {
|
||||
@ -571,7 +576,7 @@ export default function ServerList() {
|
||||
success: "Succesfully refreshed servers",
|
||||
loading: "Refreshing...",
|
||||
error: "Error while refreshing",
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
@ -636,7 +641,7 @@ export default function ServerList() {
|
||||
onClick={() => {
|
||||
setTextCopied(true);
|
||||
navigator.clipboard.writeText(
|
||||
randomData.name + ".mshf.minehut.gg",
|
||||
randomData.name + ".mshf.minehut.gg"
|
||||
);
|
||||
toast.success("Copied!");
|
||||
setTimeout(() => setTextCopied(false), 1000);
|
||||
@ -655,68 +660,6 @@ export default function ServerList() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<CommandDialog open={command} onOpenChange={setCommand}>
|
||||
<CommandInput
|
||||
placeholder="Search for a server (offline or online)"
|
||||
onValueChange={(c) => {
|
||||
fetch("https://api.minehut.com/server/" + c + "?byName=true").then(
|
||||
(l) => {
|
||||
if (l.ok) {
|
||||
console.log("found!");
|
||||
l.json().then((m: any) => {
|
||||
setSearchRes(m.server);
|
||||
console.log(searchRes);
|
||||
});
|
||||
} else {
|
||||
setSearchRes(undefined);
|
||||
}
|
||||
},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
No results found. (Minehut deleted legacy servers)
|
||||
</CommandEmpty>
|
||||
{searchRes == undefined ? (
|
||||
""
|
||||
) : (
|
||||
<CommandGroup heading="Search Results">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
window.location.replace("/server/" + searchRes.name);
|
||||
}}
|
||||
>
|
||||
<div className="block">
|
||||
<span className="font-medium">{searchRes.name}</span> <br />
|
||||
<code className="text-gray-500 text-[14px]">
|
||||
{searchRes.joins} total joins •{" "}
|
||||
{searchRes.online ? "Online" : "Offline"}
|
||||
</code>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
<CommandGroup heading="Popular Servers">
|
||||
{serverList.currentServers.map((b: OnlineServer) => (
|
||||
<CommandItem
|
||||
key={b.name}
|
||||
onSelect={() => {
|
||||
window.location.replace("/server/" + b.name);
|
||||
}}
|
||||
>
|
||||
<div className="block">
|
||||
<span className="font-medium">{b.name}</span> <br />
|
||||
<code className="text-gray-500 text-[14px]">
|
||||
<TagShower server={b} />
|
||||
</code>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
<br />
|
||||
<InfiniteScroll
|
||||
dataLength={serverList.currentServers.length}
|
||||
|
||||
@ -38,11 +38,14 @@ import toast from "react-hot-toast";
|
||||
import { SignedIn, SignedOut, SignInButton } from "@clerk/nextjs";
|
||||
import SignInPopoverButton from "./clerk/SignInPopoverButton";
|
||||
import { Sparkle, Star, X } from "lucide-react";
|
||||
import { favoriteServer, isFavorited } from "@/lib/api";
|
||||
import { LoadingButton } from "./ui/loading-button";
|
||||
|
||||
export default function ServerView(props: { server: string }) {
|
||||
const [single, setSingle] = useState(new ServerSingle(props.server));
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [favorited, setFavorited] = useState(false);
|
||||
const [loadingFavorite, setLoadingFavorite] = useState(false);
|
||||
const [randomText, setRandomText] = useState("");
|
||||
const [lastOnline, setLastOnline] = useState(0);
|
||||
const [format, setFormat] = useState("");
|
||||
@ -54,22 +57,14 @@ export default function ServerView(props: { server: string }) {
|
||||
useEffect(() => {
|
||||
setRandomText(getRandomText());
|
||||
single.init().then(() => {
|
||||
fetch("/api/favorites/isFavorited", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
server: single.grabOffline()?.name,
|
||||
}),
|
||||
})
|
||||
isFavorited(single.grabOffline()?.name as string)
|
||||
.then((b) => {
|
||||
b.json().then((c) => {
|
||||
setFavorited(c.result);
|
||||
setLoading(false);
|
||||
var online = single.grabOffline()?.last_online;
|
||||
if (online != undefined) {
|
||||
setLastOnline(online);
|
||||
}
|
||||
});
|
||||
setFavorited(b);
|
||||
setLoading(false);
|
||||
var online = single.grabOffline()?.last_online;
|
||||
if (online != undefined) {
|
||||
setLastOnline(online);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
@ -208,42 +203,46 @@ export default function ServerView(props: { server: string }) {
|
||||
<SignInPopoverButton />
|
||||
</SignedOut>
|
||||
<SignedIn>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
fetch("/api/favorites/favoriteServer", {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
server: single.grabOffline()?.name,
|
||||
}),
|
||||
method: "POST",
|
||||
}).then(() => {});
|
||||
setFavorited(!favorited);
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
Favorite Server
|
||||
</Button>
|
||||
{loadingFavorite && (
|
||||
<LoadingButton variant="outline">Favorite Server</LoadingButton>
|
||||
)}
|
||||
{!loadingFavorite && (
|
||||
<Button
|
||||
variant="outline"
|
||||
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
|
||||
</Button>
|
||||
)}
|
||||
</SignedIn>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
@ -269,85 +268,3 @@ function timeConverter(UNIX_timestamp: any) {
|
||||
var time = month + "/" + date + "/" + year;
|
||||
return time;
|
||||
}
|
||||
|
||||
export interface ServerResponse {
|
||||
__unix?: string;
|
||||
deletion?: Deletion;
|
||||
_id: string;
|
||||
categories: string[];
|
||||
inheritedCategories: any[];
|
||||
purchased_icons: string[];
|
||||
backup_slots: number;
|
||||
suspended: boolean;
|
||||
server_version_type: string;
|
||||
proxy: boolean;
|
||||
connectedServers: any[];
|
||||
motd: string;
|
||||
visibility: boolean;
|
||||
server_plan: string;
|
||||
storage_node: string;
|
||||
default_banner_image: string;
|
||||
default_banner_tint: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
name_lower: string;
|
||||
creation: number;
|
||||
platform: string;
|
||||
credits_per_day: number;
|
||||
in_game: boolean;
|
||||
using_cosmetics: boolean;
|
||||
__v: number;
|
||||
port: number;
|
||||
last_online: number;
|
||||
joins: number;
|
||||
active_icon: string;
|
||||
expired: boolean;
|
||||
icon: string;
|
||||
online: boolean;
|
||||
maxPlayers: number;
|
||||
playerCount: number;
|
||||
rawPlan: string;
|
||||
activeServerPlan: string;
|
||||
}
|
||||
|
||||
export interface Deletion {
|
||||
started: boolean;
|
||||
started_at: number;
|
||||
reason: string;
|
||||
completed: boolean;
|
||||
completed_at: number;
|
||||
storage_completed: boolean;
|
||||
storage_completed_at: number;
|
||||
}
|
||||
|
||||
export interface OnlineServer {
|
||||
staticInfo: StaticInfo;
|
||||
maxPlayers: number;
|
||||
name: string;
|
||||
motd: string;
|
||||
icon: string;
|
||||
playerData: PlayerData;
|
||||
connectable: boolean;
|
||||
visibility: boolean;
|
||||
allCategories: string[];
|
||||
usingCosmetics: boolean;
|
||||
author?: string;
|
||||
authorRank: string;
|
||||
}
|
||||
|
||||
export interface StaticInfo {
|
||||
_id: string;
|
||||
serverPlan: string;
|
||||
serviceStartDate: number;
|
||||
platform: string;
|
||||
planMaxPlayers: number;
|
||||
planRam: number;
|
||||
alwaysOnline: boolean;
|
||||
rawPlan: string;
|
||||
connectedServers: any[];
|
||||
}
|
||||
|
||||
export interface PlayerData {
|
||||
playerCount: number;
|
||||
timeNoPlayers: number;
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { DollarSign } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Children } from "react";
|
||||
|
||||
export default function Component(props: {
|
||||
title: string;
|
||||
desc: string | JSX.Element;
|
||||
icon: any;
|
||||
className?: string;
|
||||
children?: any;
|
||||
}) {
|
||||
return (
|
||||
<Card className={props.className}>
|
||||
{props.children}
|
||||
<CardHeader className=" flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className=" text-sm font-medium m-0">
|
||||
{props.title}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useClerk, useUser } from "@clerk/nextjs";
|
||||
import { Button } from "../ui/button";
|
||||
import { Star, UserCog, X } from "lucide-react";
|
||||
import { useRouter } from '@/lib/useRouter'
|
||||
|
||||
export default function LoggedInPopover() {
|
||||
const clerk = useClerk();
|
||||
const router = useRouter()
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
@ -19,7 +21,7 @@ export default function LoggedInPopover() {
|
||||
</Button>
|
||||
<Button
|
||||
variant={"ghost"}
|
||||
onClick={() => window.location.replace("/account/favorites")}
|
||||
onClick={() => router.push("/account/favorites")}
|
||||
>
|
||||
<Star size={18} className=" mr-2" /> Favorites
|
||||
</Button>
|
||||
|
||||
@ -11,15 +11,17 @@ import { SignIn, useClerk } from "@clerk/nextjs";
|
||||
|
||||
export default function SignInPopoverButton({
|
||||
className,
|
||||
variant
|
||||
}: {
|
||||
className?: string;
|
||||
variant?: "default" | "destructive" | "secondary" | "outline" | "ghost" | "link";
|
||||
}) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button className={className}>Sign In</Button>
|
||||
<Button className={className} variant={variant}>Sign In</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full">
|
||||
<div className=" grid w-[200px]">
|
||||
|
||||
@ -14,28 +14,24 @@ import InfoPopover from "../misc/InfoPopover";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function TopBar({ inter }: { inter: string }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAuthenticating, setAuthenticating] = useState(false);
|
||||
const clerk = useClerk();
|
||||
const { user } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/isAuthenticating").then((b) => {
|
||||
b.json().then((m) => {
|
||||
setAuthenticating(m.message);
|
||||
setLoading(false);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SignedOut>
|
||||
<div className=" mt-1 gap-1 grid grid-cols-5">
|
||||
{isAuthenticating && <SignInPopoverButton className="col-span-2" />}
|
||||
<Button size="icon" variant="ghost">
|
||||
<InfoIcon size={18} />
|
||||
</Button>
|
||||
<SignInPopoverButton className="col-span-2" variant="outline"/>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<Button size="icon" variant="ghost">
|
||||
<InfoIcon size={18} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<InfoPopover />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button variant="ghost" size="icon">
|
||||
<svg
|
||||
viewBox="0 0 438.549 438.549"
|
||||
@ -54,21 +50,19 @@ export default function TopBar({ inter }: { inter: string }) {
|
||||
<div className="mt-1 grid grid-cols-4 gap-1">
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
{isAuthenticating && !loading && (
|
||||
<Button size="icon" variant="ghost" className="mb-1">
|
||||
<Image
|
||||
alt="Clerk Image"
|
||||
src={
|
||||
user?.imageUrl == undefined
|
||||
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
|
||||
: user?.imageUrl
|
||||
}
|
||||
width={26}
|
||||
height={26}
|
||||
className="rounded-full"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button size="icon" variant="ghost" className="mb-1">
|
||||
<Image
|
||||
alt="Clerk Image"
|
||||
src={
|
||||
user?.imageUrl == undefined
|
||||
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
|
||||
: user?.imageUrl
|
||||
}
|
||||
width={26}
|
||||
height={26}
|
||||
className="rounded-full"
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<LoggedInPopover />
|
||||
|
||||
49
src/components/effects/border-beam.tsx
Normal file
49
src/components/effects/border-beam.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface BorderBeamProps {
|
||||
className?: string;
|
||||
size?: number;
|
||||
duration?: number;
|
||||
borderWidth?: number;
|
||||
anchor?: number;
|
||||
colorFrom?: string;
|
||||
colorTo?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export const BorderBeam = ({
|
||||
className,
|
||||
size = 200,
|
||||
duration = 15,
|
||||
anchor = 90,
|
||||
borderWidth = 1.5,
|
||||
colorFrom = "#ffaa40",
|
||||
colorTo = "#9c40ff",
|
||||
delay = 0,
|
||||
}: BorderBeamProps) => {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--size": size,
|
||||
"--duration": duration,
|
||||
"--anchor": anchor,
|
||||
"--border-width": borderWidth,
|
||||
"--color-from": colorFrom,
|
||||
"--color-to": colorTo,
|
||||
"--delay": `-${delay}s`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]",
|
||||
|
||||
// mask styles
|
||||
"![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]",
|
||||
|
||||
// pseudo styles
|
||||
"after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { Button } from "../ui/button";
|
||||
import { Activity, Calendar, Star } from "lucide-react";
|
||||
import { Activity, Calendar, Star, TerminalIcon } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from "../ui/dialog";
|
||||
import { useState } from "react";
|
||||
import { Changelog, version } from "@/version";
|
||||
import events from "@/lib/commandEvent"
|
||||
|
||||
export default function InfoPopover() {
|
||||
const [changeLog, setChangelog] = useState(false);
|
||||
@ -48,13 +49,8 @@ export default function InfoPopover() {
|
||||
>
|
||||
<Star size={18} className="mr-2" /> Star on GitHub
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
window.open("https://mhsf.betteruptime.com/", "_blank")?.focus()
|
||||
}
|
||||
>
|
||||
<Activity size={18} className="mr-2" /> View status
|
||||
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
|
||||
<TerminalIcon size={18} className="mr-2" /> Open commands
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import { Spinner } from "../ui/spinner";
|
||||
import { useRouter } from '@/lib/useRouter'
|
||||
|
||||
export default function TabServer({
|
||||
server,
|
||||
@ -13,6 +14,7 @@ export default function TabServer({
|
||||
}) {
|
||||
const [tab, setTab] = useState(tabDef);
|
||||
const [tabLoading, setTabLoading] = useState(false);
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-center">
|
||||
@ -22,8 +24,8 @@ export default function TabServer({
|
||||
setTab(tac);
|
||||
setTabLoading(true);
|
||||
if (tac == "historical")
|
||||
window.location.replace(`/server/${server}/short-term`);
|
||||
if (tac == "general") window.location.replace(`/server/${server}`);
|
||||
router.push(`/server/${server}/short-term`);
|
||||
if (tac == "general") router.push(`/server/${server}`);
|
||||
}}
|
||||
className="w-[300px]"
|
||||
>
|
||||
|
||||
81
src/components/ui/loading-button.tsx
Normal file
81
src/components/ui/loading-button.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const LoadingButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, loading, children, ...props }, ref) => {
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot ref={ref} {...props}>
|
||||
<>
|
||||
{React.Children.map(children as React.ReactElement, (child: React.ReactElement) => {
|
||||
return React.cloneElement(child, {
|
||||
className: cn(buttonVariants({ variant, size }), className),
|
||||
children: (
|
||||
<>
|
||||
{loading && (
|
||||
<Loader2 className={cn('h-4 w-4 animate-spin', children && 'mr-2')} />
|
||||
)}
|
||||
{child.props.children}
|
||||
</>
|
||||
),
|
||||
});
|
||||
})}
|
||||
</>
|
||||
</Slot>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
disabled={loading}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<>
|
||||
<Loader2 className={cn('h-4 w-4 animate-spin', children && 'mr-2')}/>
|
||||
{children}
|
||||
</>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
LoadingButton.displayName = 'LoadingButton';
|
||||
|
||||
export { LoadingButton, buttonVariants };
|
||||
175
src/lib/api.ts
Normal file
175
src/lib/api.ts
Normal file
@ -0,0 +1,175 @@
|
||||
/**
|
||||
* New API file for easier API access
|
||||
* Could be used for a JavaScript library :eyes:
|
||||
* @author DeveloLongScript
|
||||
*/
|
||||
//
|
||||
|
||||
const connector = (
|
||||
endpoint: string,
|
||||
options: { version: number; starting?: string }
|
||||
) =>
|
||||
`${options.starting == undefined ? "/" : `${options.starting}/`}api/v${options.version}${endpoint}`;
|
||||
|
||||
export async function getMOTDFromServer(
|
||||
list: Array<{ server: string; motd: string }>
|
||||
): Promise<Array<{ server: string; motd: string }>> {
|
||||
const result = await fetch(connector("/motd", { version: 1 }), {
|
||||
body: JSON.stringify({ motd: list }),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
let json = await result.json();
|
||||
return json.result;
|
||||
}
|
||||
|
||||
export async function getCommunityServerFavorites(
|
||||
server: string
|
||||
): Promise<number> {
|
||||
const result = await fetch(
|
||||
connector(`/favorites/${server}/community-favorites`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
let json = await result.json();
|
||||
return json.result;
|
||||
}
|
||||
|
||||
/** requires authentication */
|
||||
export async function favoriteServer(server: string) {
|
||||
try {
|
||||
await fetch(
|
||||
connector(`/favorites/${server}/favorite-server`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
throw Error("Not authenticated with a user.");
|
||||
}
|
||||
}
|
||||
|
||||
/** requires authentication */
|
||||
export async function isFavorited(server: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
connector(`/favorites/${server}/favorited`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (await response.json()).data;
|
||||
} catch {
|
||||
throw Error("Not authenticated with a user.");
|
||||
}
|
||||
}
|
||||
|
||||
/** requires authentication */
|
||||
export async function getAccountFavorites(): Promise<Array<string>> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
connector(`/favorites/account-favorites`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (await response.json()).result;
|
||||
} catch {
|
||||
throw Error("Not authenticated with a user.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* currently not used in frontend yet
|
||||
*/
|
||||
export async function getHistoricalData(
|
||||
server: string,
|
||||
scopes: Array<"player_count" | "favorites" | "server" | "time">
|
||||
): Promise<
|
||||
Array<{
|
||||
player_count?: number;
|
||||
favorites?: number;
|
||||
server?: string;
|
||||
time?: number;
|
||||
}>
|
||||
> {
|
||||
const response = await fetch(
|
||||
connector(`/history/${server}/get-historical-data`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ scopes }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (await response.json()).data;
|
||||
}
|
||||
|
||||
export async function getShortTermData(
|
||||
server: string,
|
||||
scopes: Array<"player_count" | "favorites" | "server" | "time">
|
||||
): Promise<
|
||||
Array<{
|
||||
player_count?: number;
|
||||
favorites?: number;
|
||||
server?: string;
|
||||
time?: number;
|
||||
}>
|
||||
> {
|
||||
const response = await fetch(
|
||||
connector(`/history/${server}/get-short-term-data`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ scopes }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (await response.json()).data;
|
||||
}
|
||||
|
||||
export async function getMetaShortTerm(
|
||||
scopes: Array<"total_players" | "total_servers" | "unix">
|
||||
): Promise<
|
||||
Array<{
|
||||
total_players?: number;
|
||||
total_servers?: number;
|
||||
unix?: number;
|
||||
}>
|
||||
> {
|
||||
const response = await fetch(
|
||||
connector(`/history/meta-short-term-data`, { version: 0 }),
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ scopes }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (await response.json()).data;
|
||||
}
|
||||
23
src/lib/commandEvent.ts
Normal file
23
src/lib/commandEvent.ts
Normal file
@ -0,0 +1,23 @@
|
||||
class CommandEvents {
|
||||
eventTarget;
|
||||
|
||||
constructor() {
|
||||
this.eventTarget = new EventTarget();
|
||||
}
|
||||
|
||||
// Method to emit events
|
||||
emit(eventName: string) {
|
||||
const event = new CustomEvent(eventName);
|
||||
this.eventTarget.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Method to listen for events
|
||||
on(eventName: string, callback: () => void) {
|
||||
this.eventTarget.addEventListener(eventName, () => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const events = new CommandEvents();
|
||||
export default events;
|
||||
@ -1,5 +1,6 @@
|
||||
import { OnlineServer } from "@/components/ServerView";
|
||||
import { OnlineServer } from "./types/server";
|
||||
import toast from "react-hot-toast";
|
||||
import { getMOTDFromServer } from "./api";
|
||||
|
||||
var numberOfItemsInView = 20;
|
||||
|
||||
@ -28,7 +29,7 @@ export default class ServersList {
|
||||
console.log(
|
||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
||||
"font-weight: bold",
|
||||
b,
|
||||
b
|
||||
);
|
||||
toast.error(`
|
||||
Error while grabbing servers from API.
|
||||
@ -82,7 +83,7 @@ export default class ServersList {
|
||||
console.log(
|
||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
||||
"font-weight: bold",
|
||||
b,
|
||||
b
|
||||
);
|
||||
bc();
|
||||
});
|
||||
@ -92,14 +93,14 @@ export default class ServersList {
|
||||
moveListDown() {
|
||||
const slicedArray = this.servers.slice(
|
||||
this.it * numberOfItemsInView,
|
||||
this.it * numberOfItemsInView + numberOfItemsInView,
|
||||
this.it * numberOfItemsInView + numberOfItemsInView
|
||||
);
|
||||
this.currentServers = this.currentServers.concat(slicedArray);
|
||||
this.it++;
|
||||
console.log(
|
||||
"%c[MHSF] Moved list down! Updated entries: ",
|
||||
"font-weight: bold",
|
||||
slicedArray,
|
||||
slicedArray
|
||||
);
|
||||
if (slicedArray.length != numberOfItemsInView) {
|
||||
this.hasMore = false;
|
||||
@ -114,16 +115,10 @@ export default class ServersList {
|
||||
this.hasMore = true;
|
||||
}
|
||||
|
||||
async getMOTDs(list: Array<{ server: string; motd: string }>) {
|
||||
let response = await fetch("/api/getMOTD", {
|
||||
body: JSON.stringify({ motd: list }),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
let json = await response.json();
|
||||
return json.result;
|
||||
async getMOTDs(
|
||||
list: Array<{ server: string; motd: string }>
|
||||
): Promise<Array<{ server: string; motd: string }>> {
|
||||
return await getMOTDFromServer(list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { twi } from "tw-to-css";
|
||||
// rendering engine for MOTDs (aka Minehut)
|
||||
|
||||
const divList: any = {
|
||||
black: "000000",
|
||||
dark_blue: "002bff",
|
||||
@ -147,7 +148,7 @@ function createHTML(
|
||||
tag: string,
|
||||
className: string,
|
||||
contents: string,
|
||||
tw?: boolean,
|
||||
tw?: boolean
|
||||
) {
|
||||
if (className == undefined) className = "";
|
||||
if (contents == undefined) contents = "";
|
||||
@ -1,4 +1,4 @@
|
||||
import { OnlineServer, ServerResponse } from "@/components/ServerView";
|
||||
import { OnlineServer, ServerResponse } from "./types/server";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default class ServerSingle {
|
||||
@ -27,7 +27,7 @@ export default class ServerSingle {
|
||||
g(true);
|
||||
}
|
||||
});
|
||||
}),
|
||||
})
|
||||
);
|
||||
} else g(true);
|
||||
});
|
||||
@ -35,7 +35,7 @@ export default class ServerSingle {
|
||||
console.log(
|
||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
||||
"font-weight: bold",
|
||||
d,
|
||||
d
|
||||
);
|
||||
toast.error(`
|
||||
Error while grabbing servers from API.
|
||||
@ -52,7 +52,7 @@ export default class ServerSingle {
|
||||
console.log(
|
||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
||||
"font-weight: bold",
|
||||
b,
|
||||
b
|
||||
);
|
||||
bc();
|
||||
});
|
||||
|
||||
21
src/lib/top-loader.tsx
Normal file
21
src/lib/top-loader.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
// NextTopLoader.tsx
|
||||
'use client';
|
||||
|
||||
import Loader from 'nextjs-toploader';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {useEffect} from "react"
|
||||
import * as NProgress from "nprogress";
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
export default function NextTopLoader() {
|
||||
const pathname = usePathname();
|
||||
const theme = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
NProgress.done();
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<Loader color={theme.resolvedTheme == "dark" ? "white" : "black"} shadow={false}/>
|
||||
)
|
||||
}
|
||||
81
src/lib/types/server.ts
Normal file
81
src/lib/types/server.ts
Normal file
@ -0,0 +1,81 @@
|
||||
export interface ServerResponse {
|
||||
__unix?: string;
|
||||
deletion?: Deletion;
|
||||
_id: string;
|
||||
categories: string[];
|
||||
inheritedCategories: any[];
|
||||
purchased_icons: string[];
|
||||
backup_slots: number;
|
||||
suspended: boolean;
|
||||
server_version_type: string;
|
||||
proxy: boolean;
|
||||
connectedServers: any[];
|
||||
motd: string;
|
||||
visibility: boolean;
|
||||
server_plan: string;
|
||||
storage_node: string;
|
||||
default_banner_image: string;
|
||||
default_banner_tint: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
name_lower: string;
|
||||
creation: number;
|
||||
platform: string;
|
||||
credits_per_day: number;
|
||||
in_game: boolean;
|
||||
using_cosmetics: boolean;
|
||||
__v: number;
|
||||
port: number;
|
||||
last_online: number;
|
||||
joins: number;
|
||||
active_icon: string;
|
||||
expired: boolean;
|
||||
icon: string;
|
||||
online: boolean;
|
||||
maxPlayers: number;
|
||||
playerCount: number;
|
||||
rawPlan: string;
|
||||
activeServerPlan: string;
|
||||
}
|
||||
|
||||
export interface Deletion {
|
||||
started: boolean;
|
||||
started_at: number;
|
||||
reason: string;
|
||||
completed: boolean;
|
||||
completed_at: number;
|
||||
storage_completed: boolean;
|
||||
storage_completed_at: number;
|
||||
}
|
||||
|
||||
export interface OnlineServer {
|
||||
staticInfo: StaticInfo;
|
||||
maxPlayers: number;
|
||||
name: string;
|
||||
motd: string;
|
||||
icon: string;
|
||||
playerData: PlayerData;
|
||||
connectable: boolean;
|
||||
visibility: boolean;
|
||||
allCategories: string[];
|
||||
usingCosmetics: boolean;
|
||||
author?: string;
|
||||
authorRank: string;
|
||||
}
|
||||
|
||||
export interface StaticInfo {
|
||||
_id: string;
|
||||
serverPlan: string;
|
||||
serviceStartDate: number;
|
||||
platform: string;
|
||||
planMaxPlayers: number;
|
||||
planRam: number;
|
||||
alwaysOnline: boolean;
|
||||
rawPlan: string;
|
||||
connectedServers: any[];
|
||||
}
|
||||
|
||||
export interface PlayerData {
|
||||
playerCount: number;
|
||||
timeNoPlayers: number;
|
||||
}
|
||||
32
src/lib/useRouter.tsx
Normal file
32
src/lib/useRouter.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
// useRouter.ts
|
||||
import { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime';
|
||||
import { useRouter as useNextRouter, usePathname } from 'next/navigation';
|
||||
import { useCallback } from 'react';
|
||||
import NProgress from 'nprogress';
|
||||
|
||||
export const useRouter = () => {
|
||||
const router = useNextRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const replace = useCallback(
|
||||
(href: string, options?: NavigateOptions) => {
|
||||
href !== pathname && NProgress.start();
|
||||
router.replace(href, options);
|
||||
},
|
||||
[router, pathname],
|
||||
);
|
||||
|
||||
const push = useCallback(
|
||||
(href: string, options?: NavigateOptions) => {
|
||||
href !== pathname && NProgress.start();
|
||||
router.push(href, options);
|
||||
},
|
||||
[router, pathname],
|
||||
);
|
||||
|
||||
return {
|
||||
...router,
|
||||
replace,
|
||||
push,
|
||||
};
|
||||
};
|
||||
@ -2,7 +2,7 @@
|
||||
// its fully automatic
|
||||
|
||||
import Favorites from "@/app/account/favorites/page";
|
||||
import { OnlineServer } from "@/components/ServerView";
|
||||
import { OnlineServer } from "@/lib/types/server";
|
||||
import { Inngest } from "inngest";
|
||||
import { serve } from "inngest/next";
|
||||
import { MongoClient } from "mongodb";
|
||||
@ -16,8 +16,8 @@ export default serve({
|
||||
client: inngest,
|
||||
functions: [
|
||||
inngest.createFunction(
|
||||
{ id: "every-60-min" },
|
||||
[{ cron: "*/30 * * * *" }],
|
||||
{ id: "every-30-min" },
|
||||
[{ cron: "*/30 * * * *" }, { event: "test/30-min" }],
|
||||
async ({ event, step }) => {
|
||||
const mongo = new MongoClient(process.env.MONGO_DB as string);
|
||||
try {
|
||||
@ -79,7 +79,7 @@ export default serve({
|
||||
mongo.close();
|
||||
return { event, body: "Cloudflare.. aborting " + e };
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
inngest.createFunction(
|
||||
{ id: "every-two-months" },
|
||||
@ -118,7 +118,7 @@ export default serve({
|
||||
event,
|
||||
body: "Dropped database. ",
|
||||
};
|
||||
},
|
||||
}
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
res.send({ message: process.env.IS_AUTH == "true" });
|
||||
}
|
||||
@ -6,7 +6,7 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const server = checkForInfoOrLeave(res, req.body.server);
|
||||
const { server } = req.query;
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
|
||||
await client.connect();
|
||||
@ -25,12 +25,6 @@ export default async function handler(
|
||||
client.close();
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return info;
|
||||
}
|
||||
|
||||
export async function increaseNum(client: MongoClient, server: string) {
|
||||
const db = client.db("mhsf");
|
||||
const collection = db.collection("meta");
|
||||
10
src/pages/api/favorites/favoriteServer.ts → src/pages/api/v0/favorites/[server]/favorite-server.ts
10
src/pages/api/favorites/favoriteServer.ts → src/pages/api/v0/favorites/[server]/favorite-server.ts
@ -1,7 +1,7 @@
|
||||
import type { NextApiResponse, NextApiRequest } from "next";
|
||||
import { MongoClient, ObjectId } from "mongodb";
|
||||
import { getAuth } from "@clerk/nextjs/server";
|
||||
import { decreaseNum, increaseNum } from "./getCommunityNum";
|
||||
import { decreaseNum, increaseNum } from "./community-favorites";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
@ -12,7 +12,7 @@ export default async function handler(
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
const server = checkForInfoOrLeave(res, req.body.server);
|
||||
const server = req.query.server as string;
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
await client.connect();
|
||||
|
||||
@ -58,9 +58,3 @@ export default async function handler(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return info;
|
||||
}
|
||||
@ -4,14 +4,14 @@ import { getAuth } from "@clerk/nextjs/server";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { userId } = getAuth(req);
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
const server = checkForInfoOrLeave(res, req.body.server);
|
||||
const server = req.query.server as string;
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
await client.connect();
|
||||
|
||||
@ -24,9 +24,3 @@ export default async function handler(
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return info;
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { getAuth } from "@clerk/nextjs/server";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { userId } = getAuth(req);
|
||||
|
||||
@ -25,9 +25,3 @@ export default async function handler(
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return info;
|
||||
}
|
||||
@ -3,11 +3,11 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const db = client.db("mhsf").collection("historical");
|
||||
const server = checkForInfoOrLeave(res, req.body.server);
|
||||
const server = req.query.server as string;
|
||||
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||
|
||||
const allData = await db.find({ server }).toArray();
|
||||
4
src/pages/api/history/getShortTermData.ts → src/pages/api/v0/history/[server]/get-short-term-data.ts
4
src/pages/api/history/getShortTermData.ts → src/pages/api/v0/history/[server]/get-short-term-data.ts
@ -3,11 +3,11 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const db = client.db("mhsf").collection("history");
|
||||
const server = checkForInfoOrLeave(res, req.body.server);
|
||||
const server = req.query.server as string;
|
||||
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||
|
||||
const allData = await db.find({ server }).toArray();
|
||||
@ -1,11 +1,11 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import parseToHTML from "@/lib/miniMessage2HTML";
|
||||
import parseToHTML from "@/lib/motdEngine";
|
||||
|
||||
let num = 0;
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
num++;
|
||||
var body: Array<{ server: string; motd: string }> = req.body.motd;
|
||||
@ -1,4 +1,4 @@
|
||||
export const version = "b-0.4.5";
|
||||
export const version = "b-0.6.0";
|
||||
|
||||
const User = ({ user }: { user: string }) => (
|
||||
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
||||
@ -8,6 +8,18 @@ const User = ({ user }: { user: string }) => (
|
||||
|
||||
export const Changelog = () => (
|
||||
<>
|
||||
<div>
|
||||
<strong className="flex items-center">
|
||||
Version b-0.6.0 (August 3rd 2024)
|
||||
</strong>
|
||||
<ul>
|
||||
<li>• Enhanced shortcuts</li>
|
||||
<li>• Added gradient beam to player count</li>
|
||||
<li>• Updated loading animations</li>
|
||||
<li>• Lots of bugfixes</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<strong className="flex items-center">
|
||||
Version b-0.4.5 (July 26th 2024):
|
||||
|
||||
@ -59,6 +59,12 @@ const config = {
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"border-beam": {
|
||||
"100%": {
|
||||
"offset-distance": "100%",
|
||||
},
|
||||
},
|
||||
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
@ -71,6 +77,8 @@ const config = {
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
|
||||
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@ -1331,6 +1331,11 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/nprogress@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.3.tgz#b2150b054a13622fabcba12cf6f0b54c48b14287"
|
||||
integrity sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.12"
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz"
|
||||
@ -4695,6 +4700,14 @@ next@14.2.3:
|
||||
"@next/swc-win32-ia32-msvc" "14.2.3"
|
||||
"@next/swc-win32-x64-msvc" "14.2.3"
|
||||
|
||||
nextjs-toploader@^1.6.12:
|
||||
version "1.6.12"
|
||||
resolved "https://registry.yarnpkg.com/nextjs-toploader/-/nextjs-toploader-1.6.12.tgz#5b9f951e0de80450a23acd5101f4a311265c0d70"
|
||||
integrity sha512-nbun5lvVjlKnxLQlahzZ55nELVEduqoEXT03KCHnsEYJnFpI/3BaIzpMyq/v8C7UGU2NfxQmjq6ldZ310rsDqA==
|
||||
dependencies:
|
||||
nprogress "^0.2.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz"
|
||||
@ -4739,6 +4752,11 @@ npm-run-path@^5.1.0:
|
||||
dependencies:
|
||||
path-key "^4.0.0"
|
||||
|
||||
nprogress@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
|
||||
integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==
|
||||
|
||||
nypm@^0.3.8:
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.3.9.tgz#ab74c55075737466847611aa33c3c67741c01d8f"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user