mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-08 00: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:
|
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.)
|
- 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.
|
- 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-slot": "^1.0.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.0",
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/react-twemoji": "^0.4.3",
|
"@types/react-twemoji": "^0.4.3",
|
||||||
"@unocss/eslint-plugin": "^0.61.5",
|
"@unocss/eslint-plugin": "^0.61.5",
|
||||||
"@unocss/postcss": "^0.61.5",
|
"@unocss/postcss": "^0.61.5",
|
||||||
@ -47,6 +48,8 @@
|
|||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
"next-css-obfuscator": "^2.2.16",
|
"next-css-obfuscator": "^2.2.16",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
"nextjs-toploader": "^1.6.12",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"postcss-obfuscator": "^1.6.1",
|
"postcss-obfuscator": "^1.6.1",
|
||||||
"prettier": "^3.3.1",
|
"prettier": "^3.3.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { OnlineServer, ServerResponse } from "./components/ServerView";
|
import { OnlineServer, ServerResponse } from "./lib/types/server";
|
||||||
|
|
||||||
const serverCache: any = {};
|
const serverCache: any = {};
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ export var allCategories: Array<{
|
|||||||
async function requestServer(s: OnlineServer): Promise<ServerResponse> {
|
async function requestServer(s: OnlineServer): Promise<ServerResponse> {
|
||||||
if (serverCache[s.name] == undefined) {
|
if (serverCache[s.name] == undefined) {
|
||||||
const re = await fetch(
|
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();
|
const json = await re.json();
|
||||||
serverCache[s.name] = json.server;
|
serverCache[s.name] = json.server;
|
||||||
|
|||||||
@ -95,6 +95,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backdrop-blur {
|
||||||
|
-webkit-backdrop-filter: blur(8px)!important;
|
||||||
|
backdrop-filter: blur(8px)!important;
|
||||||
|
}
|
||||||
|
|
||||||
/* width */
|
/* width */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { GeistSans } from "geist/font/sans";
|
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 "./globals.css";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
@ -12,6 +12,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
|
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
|
||||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
|
import NextTopLoader from '@/lib/top-loader';
|
||||||
import { banner } from "@/banner";
|
import { banner } from "@/banner";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@ -24,6 +25,12 @@ import Link from "next/link";
|
|||||||
import TopBar from "@/components/clerk/Topbar";
|
import TopBar from "@/components/clerk/Topbar";
|
||||||
import TextFromPathname from "@/components/TextFromPathname";
|
import TextFromPathname from "@/components/TextFromPathname";
|
||||||
import { Inter as interFont } from "next/font/google";
|
import { Inter as interFont } from "next/font/google";
|
||||||
|
import {
|
||||||
|
CommandBar,
|
||||||
|
CommandBarer,
|
||||||
|
SearchCommandBar,
|
||||||
|
SubLinkCommandBar,
|
||||||
|
} from "@/components/CommandBar";
|
||||||
|
|
||||||
const inter = interFont({ variable: "--font-inter", subsets: ["latin"] });
|
const inter = interFont({ variable: "--font-inter", subsets: ["latin"] });
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
@ -40,6 +47,7 @@ export default async function RootLayout({
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|
||||||
{banner.isBanner && (
|
{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">
|
<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}
|
{banner.bannerText}
|
||||||
@ -65,8 +73,9 @@ export default async function RootLayout({
|
|||||||
</div>
|
</div>
|
||||||
<TopBar inter={inter.className} />
|
<TopBar inter={inter.className} />
|
||||||
</div>
|
</div>
|
||||||
<div>{children}</div>{" "}
|
<div><NextTopLoader/>{children}</div>{" "}
|
||||||
<Toaster position="bottom-center" reverseOrder={false} />
|
<Toaster position="bottom-center" reverseOrder={false} />
|
||||||
|
<CommandBarer />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</ClerkThemeProvider>
|
</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 { useEffect, useState } from "react";
|
||||||
import { Spinner } from "./ui/spinner";
|
import { Spinner } from "./ui/spinner";
|
||||||
import { Card, CardHeader, CardTitle } from "./ui/card";
|
import { Card, CardHeader, CardTitle } from "./ui/card";
|
||||||
import { ServerResponse } from "./ServerView";
|
import { ServerResponse } from "@/lib/types/server";
|
||||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
import { Button } from "./ui/button";
|
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 toast from "react-hot-toast";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
|
import { getAccountFavorites } from "@/lib/api";
|
||||||
|
import { useRouter } from '@/lib/useRouter'
|
||||||
|
|
||||||
export default function FavoritesView() {
|
export default function FavoritesView() {
|
||||||
const [apiFavorites, setApiFavorites] = useState<any>([]);
|
const [apiFavorites, setApiFavorites] = useState<any>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
fetch("/api/favorites/getAllFavorites").then((c) => {
|
getAccountFavorites().then((d) => {
|
||||||
c.json().then((d) => {
|
let num = 0;
|
||||||
let num = 0;
|
d.forEach((a: any, i: number) => {
|
||||||
d.result.forEach((a: any, i: number) => {
|
fetch("https://api.minehut.com/server/" + a + "?byName=true").then(
|
||||||
fetch("https://api.minehut.com/server/" + a + "?byName=true").then(
|
(b) =>
|
||||||
(b) =>
|
b.json().then((c) => {
|
||||||
b.json().then((c) => {
|
num++;
|
||||||
num++;
|
var apiClone = apiFavorites;
|
||||||
var apiClone = apiFavorites;
|
apiClone.push(c.server);
|
||||||
apiClone.push(c.server);
|
setApiFavorites(apiClone);
|
||||||
setApiFavorites(apiClone);
|
if (num == d.length) {
|
||||||
if (num == d.result.length) {
|
setLoading(false);
|
||||||
setLoading(false);
|
}
|
||||||
}
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
if (d.result.length == 0) setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
if (d.length == 0) setLoading(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ export default function FavoritesView() {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
className=" w-[32px] h-[32px] mb-2 ml-2 max-md:hidden"
|
className=" w-[32px] h-[32px] mb-2 ml-2 max-md:hidden"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = "/server/" + server.name;
|
router.push("/server/" + server.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Layers size={18} />
|
<Layers size={18} />
|
||||||
|
|||||||
@ -17,7 +17,8 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from "@/components/ui/chart";
|
} from "@/components/ui/chart";
|
||||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
import { ServerResponse } from "./ServerView";
|
import { ServerResponse } from "@/lib/types/server";
|
||||||
|
import { getCommunityServerFavorites, getShortTermData } from "@/lib/api";
|
||||||
|
|
||||||
const chartConfig = {
|
const chartConfig = {
|
||||||
player_count: {
|
player_count: {
|
||||||
@ -40,25 +41,10 @@ export function NewChart({ server }: { server: string }) {
|
|||||||
|
|
||||||
const allNums = { player_count: joins, favorites };
|
const allNums = { player_count: joins, favorites };
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
fetch("/api/history/getShortTermData", {
|
getShortTermData(server, ["player_count", "favorites", "time"]).then(
|
||||||
headers: { "Content-Type": "application/json" },
|
(c) => {
|
||||||
body: JSON.stringify({
|
setChartData(c);
|
||||||
scopes: ["player_count", "favorites", "time"],
|
getCommunityServerFavorites(server).then((b) => setFavorites(b));
|
||||||
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);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
|
fetch("https://api.minehut.com/server/" + server + "?byName=true").then(
|
||||||
(k) => {
|
(k) => {
|
||||||
k.json().then((p: { server: ServerResponse }) => {
|
k.json().then((p: { server: ServerResponse }) => {
|
||||||
@ -66,8 +52,8 @@ export function NewChart({ server }: { server: string }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -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, MoveRight } from "lucide-react";
|
import { Copy, EllipsisVertical, Layers, MoveRight, Router } from "lucide-react";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
@ -30,8 +30,11 @@ import {
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Tooltip } from "@radix-ui/react-tooltip";
|
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 Link from "next/link";
|
||||||
|
|
||||||
export default function ServerCard({ b, motd }: any) {
|
export default function ServerCard({ b, motd }: any) {
|
||||||
|
const router = useRouter()
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
@ -72,7 +75,7 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = "/server/" + b.name;
|
router.push("/server/" + b.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
@ -138,16 +141,15 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
|
<Link href={"/server/" + b.name}>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className=" w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
className=" w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||||
onClick={() => {
|
|
||||||
window.location.href = "/server/" + b.name;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Layers size={18} />
|
<Layers size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
Open up the server page to see more information about
|
Open up the server page to see more information about
|
||||||
@ -171,20 +173,19 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
</div>
|
</div>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
|
<Link href={"/server/" + b.name}>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
|
||||||
window.location.href = "/server/" + b.name;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
</ContextMenuItem>
|
</ContextMenuItem></Link>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{b.name != "Skylegendz" && (
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: motd }} />
|
<span dangerouslySetInnerHTML={{ __html: motd }} className="max-w-[12px] text-center"/>
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
@ -204,7 +205,7 @@ export default function ServerCard({ b, motd }: any) {
|
|||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = "/server/" + b.name;
|
router.push("/server/" + b.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
|
|||||||
@ -79,24 +79,25 @@ import {
|
|||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Spinner } from "./ui/spinner";
|
import { Spinner } from "./ui/spinner";
|
||||||
|
import { CommandIcon } from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { useEffectOnce } from "@/lib/useEffectOnce";
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
import ServerCard from "./ServerCard";
|
import ServerCard from "./ServerCard";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
import events from "@/lib/commandEvent";
|
||||||
|
import { BorderBeam } from "@/components/effects/border-beam";
|
||||||
|
|
||||||
export default function ServerList() {
|
export default function ServerList() {
|
||||||
const [loading, setLoading]: any = useState(true);
|
const [loading, setLoading]: any = useState(true);
|
||||||
const [command, setCommand] = useState(false);
|
|
||||||
const [randomText, setRandomText] = useState("");
|
const [randomText, setRandomText] = useState("");
|
||||||
const [motdList, setMotdList] = useState<any>({});
|
const [motdList, setMotdList] = useState<any>({});
|
||||||
const allText = [""];
|
const allText = [""];
|
||||||
const getRandomText = () => {
|
const getRandomText = () => {
|
||||||
return allText[Math.floor(Math.random() * allText.length)];
|
return allText[Math.floor(Math.random() * allText.length)];
|
||||||
};
|
};
|
||||||
const [searchRes, setSearchRes] = useState<any>(undefined);
|
|
||||||
const [templateFilter, setTemplateFilter] = useState(false);
|
const [templateFilter, setTemplateFilter] = useState(false);
|
||||||
const [random, setRandom] = useState(false);
|
const [random, setRandom] = useState(false);
|
||||||
const [serverList, setServerList] = useState(new ServersList([]));
|
const [serverList, setServerList] = useState(new ServersList([]));
|
||||||
@ -108,14 +109,13 @@ export default function ServerList() {
|
|||||||
server.playerData.playerCount < 15 &&
|
server.playerData.playerCount < 15 &&
|
||||||
server.playerData.playerCount > 7;
|
server.playerData.playerCount > 7;
|
||||||
const [nameFilters, setNameFilters] = useState<any>({});
|
const [nameFilters, setNameFilters] = useState<any>({});
|
||||||
useHotkeys("ctrl+k", () => setCommand(true), []);
|
|
||||||
const [inErrState, setErrState] = useState(false);
|
const [inErrState, setErrState] = useState(false);
|
||||||
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
||||||
const [filters, setFilters] = useState<
|
const [filters, setFilters] = useState<
|
||||||
Array<(server: OnlineServer) => Promise<boolean>>
|
Array<(server: OnlineServer) => Promise<boolean>>
|
||||||
>([]);
|
>([]);
|
||||||
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
||||||
undefined,
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
@ -175,9 +175,10 @@ export default function ServerList() {
|
|||||||
<div className=" max-lg:grid-cols-2 grid grid-cols-3 gap-4 ">
|
<div className=" max-lg:grid-cols-2 grid grid-cols-3 gap-4 ">
|
||||||
<Stat
|
<Stat
|
||||||
title="Players online"
|
title="Players online"
|
||||||
|
className="relative"
|
||||||
desc={serverList.getExtraData().total_players.toString()}
|
desc={serverList.getExtraData().total_players.toString()}
|
||||||
icon={CircleUser}
|
icon={CircleUser}
|
||||||
/>
|
><BorderBeam size={135} duration={12} delay={9}/></Stat>
|
||||||
<Stat
|
<Stat
|
||||||
title="Servers online"
|
title="Servers online"
|
||||||
desc={serverList.getExtraData().total_servers.toString()}
|
desc={serverList.getExtraData().total_servers.toString()}
|
||||||
@ -203,11 +204,15 @@ export default function ServerList() {
|
|||||||
<Separator />
|
<Separator />
|
||||||
<div className=" mt-3 ml-3">
|
<div className=" mt-3 ml-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setCommand(true)}
|
onClick={() => events.emit("search-request-event")}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className=" max-lg:mb-3"
|
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>
|
</Button>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
@ -338,7 +343,7 @@ export default function ServerList() {
|
|||||||
error: "Error while changing filters",
|
error: "Error while changing filters",
|
||||||
loading: "Changing filters...",
|
loading: "Changing filters...",
|
||||||
success: "Changed filters!",
|
success: "Changed filters!",
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
defaultValue={(() => {
|
defaultValue={(() => {
|
||||||
@ -571,7 +576,7 @@ export default function ServerList() {
|
|||||||
success: "Succesfully refreshed servers",
|
success: "Succesfully refreshed servers",
|
||||||
loading: "Refreshing...",
|
loading: "Refreshing...",
|
||||||
error: "Error while refreshing",
|
error: "Error while refreshing",
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -636,7 +641,7 @@ export default function ServerList() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTextCopied(true);
|
setTextCopied(true);
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
randomData.name + ".mshf.minehut.gg",
|
randomData.name + ".mshf.minehut.gg"
|
||||||
);
|
);
|
||||||
toast.success("Copied!");
|
toast.success("Copied!");
|
||||||
setTimeout(() => setTextCopied(false), 1000);
|
setTimeout(() => setTextCopied(false), 1000);
|
||||||
@ -655,68 +660,6 @@ export default function ServerList() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</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 />
|
<br />
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={serverList.currentServers.length}
|
dataLength={serverList.currentServers.length}
|
||||||
|
|||||||
@ -38,11 +38,14 @@ import toast from "react-hot-toast";
|
|||||||
import { SignedIn, SignedOut, SignInButton } from "@clerk/nextjs";
|
import { SignedIn, SignedOut, SignInButton } from "@clerk/nextjs";
|
||||||
import SignInPopoverButton from "./clerk/SignInPopoverButton";
|
import SignInPopoverButton from "./clerk/SignInPopoverButton";
|
||||||
import { Sparkle, Star, X } from "lucide-react";
|
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 }) {
|
export default function ServerView(props: { server: string }) {
|
||||||
const [single, setSingle] = useState(new ServerSingle(props.server));
|
const [single, setSingle] = useState(new ServerSingle(props.server));
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [favorited, setFavorited] = useState(false);
|
const [favorited, setFavorited] = useState(false);
|
||||||
|
const [loadingFavorite, setLoadingFavorite] = useState(false);
|
||||||
const [randomText, setRandomText] = useState("");
|
const [randomText, setRandomText] = useState("");
|
||||||
const [lastOnline, setLastOnline] = useState(0);
|
const [lastOnline, setLastOnline] = useState(0);
|
||||||
const [format, setFormat] = useState("");
|
const [format, setFormat] = useState("");
|
||||||
@ -54,22 +57,14 @@ export default function ServerView(props: { server: string }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRandomText(getRandomText());
|
setRandomText(getRandomText());
|
||||||
single.init().then(() => {
|
single.init().then(() => {
|
||||||
fetch("/api/favorites/isFavorited", {
|
isFavorited(single.grabOffline()?.name as string)
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
server: single.grabOffline()?.name,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((b) => {
|
.then((b) => {
|
||||||
b.json().then((c) => {
|
setFavorited(b);
|
||||||
setFavorited(c.result);
|
setLoading(false);
|
||||||
setLoading(false);
|
var online = single.grabOffline()?.last_online;
|
||||||
var online = single.grabOffline()?.last_online;
|
if (online != undefined) {
|
||||||
if (online != undefined) {
|
setLastOnline(online);
|
||||||
setLastOnline(online);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -208,42 +203,46 @@ export default function ServerView(props: { server: string }) {
|
|||||||
<SignInPopoverButton />
|
<SignInPopoverButton />
|
||||||
</SignedOut>
|
</SignedOut>
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
<Button
|
{loadingFavorite && (
|
||||||
variant="outline"
|
<LoadingButton variant="outline">Favorite Server</LoadingButton>
|
||||||
onClick={() => {
|
)}
|
||||||
fetch("/api/favorites/favoriteServer", {
|
{!loadingFavorite && (
|
||||||
headers: { "Content-Type": "application/json" },
|
<Button
|
||||||
body: JSON.stringify({
|
variant="outline"
|
||||||
server: single.grabOffline()?.name,
|
onClick={() => {
|
||||||
}),
|
setLoadingFavorite(true);
|
||||||
method: "POST",
|
favoriteServer(single.grabOffline()?.name as string).then(
|
||||||
}).then(() => {});
|
() => {
|
||||||
setFavorited(!favorited);
|
setFavorited(!favorited);
|
||||||
}}
|
setLoadingFavorite(false);
|
||||||
>
|
}
|
||||||
{favorited && (
|
);
|
||||||
<motion.div
|
}}
|
||||||
animate={{ color: "yellow", fill: "yellow" }}
|
>
|
||||||
transition={{ duration: 2 }}
|
{favorited && (
|
||||||
>
|
<motion.div
|
||||||
<Star
|
animate={{ color: "yellow", fill: "yellow" }}
|
||||||
className="mr-2"
|
transition={{ duration: 2 }}
|
||||||
size="16"
|
>
|
||||||
color="yellow"
|
<Star
|
||||||
fill="yellow"
|
className="mr-2"
|
||||||
/>
|
size="16"
|
||||||
</motion.div>
|
color="yellow"
|
||||||
)}
|
fill="yellow"
|
||||||
{!favorited && (
|
/>
|
||||||
<motion.div
|
</motion.div>
|
||||||
transition={{ duration: 1 }}
|
)}
|
||||||
animate={{ color: "yellow", fill: "yellow" }}
|
{!favorited && (
|
||||||
>
|
<motion.div
|
||||||
<Star className="mr-2" size="16" />
|
transition={{ duration: 1 }}
|
||||||
</motion.div>
|
animate={{ color: "yellow", fill: "yellow" }}
|
||||||
)}
|
>
|
||||||
Favorite Server
|
<Star className="mr-2" size="16" />
|
||||||
</Button>
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{favorited && "Unf"}{!favorited && "F"}avorite Server
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SignedIn>
|
</SignedIn>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
@ -269,85 +268,3 @@ function timeConverter(UNIX_timestamp: any) {
|
|||||||
var time = month + "/" + date + "/" + year;
|
var time = month + "/" + date + "/" + year;
|
||||||
return time;
|
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 { DollarSign } from "lucide-react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Children } from "react";
|
||||||
|
|
||||||
export default function Component(props: {
|
export default function Component(props: {
|
||||||
title: string;
|
title: string;
|
||||||
desc: string | JSX.Element;
|
desc: string | JSX.Element;
|
||||||
icon: any;
|
icon: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
children?: any;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className={props.className}>
|
<Card className={props.className}>
|
||||||
|
{props.children}
|
||||||
<CardHeader className=" flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className=" flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className=" text-sm font-medium m-0">
|
<CardTitle className=" text-sm font-medium m-0">
|
||||||
{props.title}
|
{props.title}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { useClerk, useUser } from "@clerk/nextjs";
|
import { useClerk, useUser } from "@clerk/nextjs";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Star, UserCog, X } from "lucide-react";
|
import { Star, UserCog, X } from "lucide-react";
|
||||||
|
import { useRouter } from '@/lib/useRouter'
|
||||||
|
|
||||||
export default function LoggedInPopover() {
|
export default function LoggedInPopover() {
|
||||||
const clerk = useClerk();
|
const clerk = useClerk();
|
||||||
|
const router = useRouter()
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -19,7 +21,7 @@ export default function LoggedInPopover() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
onClick={() => window.location.replace("/account/favorites")}
|
onClick={() => router.push("/account/favorites")}
|
||||||
>
|
>
|
||||||
<Star size={18} className=" mr-2" /> Favorites
|
<Star size={18} className=" mr-2" /> Favorites
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -11,15 +11,17 @@ import { SignIn, useClerk } from "@clerk/nextjs";
|
|||||||
|
|
||||||
export default function SignInPopoverButton({
|
export default function SignInPopoverButton({
|
||||||
className,
|
className,
|
||||||
|
variant
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
variant?: "default" | "destructive" | "secondary" | "outline" | "ghost" | "link";
|
||||||
}) {
|
}) {
|
||||||
const clerk = useClerk();
|
const clerk = useClerk();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button className={className}>Sign In</Button>
|
<Button className={className} variant={variant}>Sign In</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-full">
|
<PopoverContent className="w-full">
|
||||||
<div className=" grid w-[200px]">
|
<div className=" grid w-[200px]">
|
||||||
|
|||||||
@ -14,28 +14,24 @@ import InfoPopover from "../misc/InfoPopover";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function TopBar({ inter }: { inter: string }) {
|
export default function TopBar({ inter }: { inter: string }) {
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [isAuthenticating, setAuthenticating] = useState(false);
|
|
||||||
const clerk = useClerk();
|
const clerk = useClerk();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/isAuthenticating").then((b) => {
|
|
||||||
b.json().then((m) => {
|
|
||||||
setAuthenticating(m.message);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SignedOut>
|
<SignedOut>
|
||||||
<div className=" mt-1 gap-1 grid grid-cols-5">
|
<div className=" mt-1 gap-1 grid grid-cols-5">
|
||||||
{isAuthenticating && <SignInPopoverButton className="col-span-2" />}
|
<SignInPopoverButton className="col-span-2" variant="outline"/>
|
||||||
<Button size="icon" variant="ghost">
|
<Popover>
|
||||||
<InfoIcon size={18} />
|
<PopoverTrigger>
|
||||||
</Button>
|
<Button size="icon" variant="ghost">
|
||||||
|
<InfoIcon size={18} />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<InfoPopover />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 438.549 438.549"
|
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">
|
<div className="mt-1 grid grid-cols-4 gap-1">
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
{isAuthenticating && !loading && (
|
<Button size="icon" variant="ghost" className="mb-1">
|
||||||
<Button size="icon" variant="ghost" className="mb-1">
|
<Image
|
||||||
<Image
|
alt="Clerk Image"
|
||||||
alt="Clerk Image"
|
src={
|
||||||
src={
|
user?.imageUrl == undefined
|
||||||
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"
|
||||||
? "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
|
||||||
: user?.imageUrl
|
}
|
||||||
}
|
width={26}
|
||||||
width={26}
|
height={26}
|
||||||
height={26}
|
className="rounded-full"
|
||||||
className="rounded-full"
|
/>
|
||||||
/>
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<LoggedInPopover />
|
<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 Link from "next/link";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Activity, Calendar, Star } from "lucide-react";
|
import { Activity, Calendar, Star, TerminalIcon } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../ui/dialog";
|
} from "../ui/dialog";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Changelog, version } from "@/version";
|
import { Changelog, version } from "@/version";
|
||||||
|
import events from "@/lib/commandEvent"
|
||||||
|
|
||||||
export default function InfoPopover() {
|
export default function InfoPopover() {
|
||||||
const [changeLog, setChangelog] = useState(false);
|
const [changeLog, setChangelog] = useState(false);
|
||||||
@ -48,13 +49,8 @@ export default function InfoPopover() {
|
|||||||
>
|
>
|
||||||
<Star size={18} className="mr-2" /> Star on GitHub
|
<Star size={18} className="mr-2" /> Star on GitHub
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
|
||||||
variant="ghost"
|
<TerminalIcon size={18} className="mr-2" /> Open commands
|
||||||
onClick={() =>
|
|
||||||
window.open("https://mhsf.betteruptime.com/", "_blank")?.focus()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Activity size={18} className="mr-2" /> View status
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
import { Spinner } from "../ui/spinner";
|
import { Spinner } from "../ui/spinner";
|
||||||
|
import { useRouter } from '@/lib/useRouter'
|
||||||
|
|
||||||
export default function TabServer({
|
export default function TabServer({
|
||||||
server,
|
server,
|
||||||
@ -13,6 +14,7 @@ export default function TabServer({
|
|||||||
}) {
|
}) {
|
||||||
const [tab, setTab] = useState(tabDef);
|
const [tab, setTab] = useState(tabDef);
|
||||||
const [tabLoading, setTabLoading] = useState(false);
|
const [tabLoading, setTabLoading] = useState(false);
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex justify-center">
|
<div className="w-full flex justify-center">
|
||||||
@ -22,8 +24,8 @@ export default function TabServer({
|
|||||||
setTab(tac);
|
setTab(tac);
|
||||||
setTabLoading(true);
|
setTabLoading(true);
|
||||||
if (tac == "historical")
|
if (tac == "historical")
|
||||||
window.location.replace(`/server/${server}/short-term`);
|
router.push(`/server/${server}/short-term`);
|
||||||
if (tac == "general") window.location.replace(`/server/${server}`);
|
if (tac == "general") router.push(`/server/${server}`);
|
||||||
}}
|
}}
|
||||||
className="w-[300px]"
|
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 toast from "react-hot-toast";
|
||||||
|
import { getMOTDFromServer } from "./api";
|
||||||
|
|
||||||
var numberOfItemsInView = 20;
|
var numberOfItemsInView = 20;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ export default class ServersList {
|
|||||||
console.log(
|
console.log(
|
||||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
||||||
"font-weight: bold",
|
"font-weight: bold",
|
||||||
b,
|
b
|
||||||
);
|
);
|
||||||
toast.error(`
|
toast.error(`
|
||||||
Error while grabbing servers from API.
|
Error while grabbing servers from API.
|
||||||
@ -82,7 +83,7 @@ export default class ServersList {
|
|||||||
console.log(
|
console.log(
|
||||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
||||||
"font-weight: bold",
|
"font-weight: bold",
|
||||||
b,
|
b
|
||||||
);
|
);
|
||||||
bc();
|
bc();
|
||||||
});
|
});
|
||||||
@ -92,14 +93,14 @@ export default class ServersList {
|
|||||||
moveListDown() {
|
moveListDown() {
|
||||||
const slicedArray = this.servers.slice(
|
const slicedArray = this.servers.slice(
|
||||||
this.it * numberOfItemsInView,
|
this.it * numberOfItemsInView,
|
||||||
this.it * numberOfItemsInView + numberOfItemsInView,
|
this.it * numberOfItemsInView + numberOfItemsInView
|
||||||
);
|
);
|
||||||
this.currentServers = this.currentServers.concat(slicedArray);
|
this.currentServers = this.currentServers.concat(slicedArray);
|
||||||
this.it++;
|
this.it++;
|
||||||
console.log(
|
console.log(
|
||||||
"%c[MHSF] Moved list down! Updated entries: ",
|
"%c[MHSF] Moved list down! Updated entries: ",
|
||||||
"font-weight: bold",
|
"font-weight: bold",
|
||||||
slicedArray,
|
slicedArray
|
||||||
);
|
);
|
||||||
if (slicedArray.length != numberOfItemsInView) {
|
if (slicedArray.length != numberOfItemsInView) {
|
||||||
this.hasMore = false;
|
this.hasMore = false;
|
||||||
@ -114,16 +115,10 @@ export default class ServersList {
|
|||||||
this.hasMore = true;
|
this.hasMore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMOTDs(list: Array<{ server: string; motd: string }>) {
|
async getMOTDs(
|
||||||
let response = await fetch("/api/getMOTD", {
|
list: Array<{ server: string; motd: string }>
|
||||||
body: JSON.stringify({ motd: list }),
|
): Promise<Array<{ server: string; motd: string }>> {
|
||||||
method: "POST",
|
return await getMOTDFromServer(list);
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let json = await response.json();
|
|
||||||
return json.result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { twi } from "tw-to-css";
|
// rendering engine for MOTDs (aka Minehut)
|
||||||
|
|
||||||
const divList: any = {
|
const divList: any = {
|
||||||
black: "000000",
|
black: "000000",
|
||||||
dark_blue: "002bff",
|
dark_blue: "002bff",
|
||||||
@ -147,7 +148,7 @@ function createHTML(
|
|||||||
tag: string,
|
tag: string,
|
||||||
className: string,
|
className: string,
|
||||||
contents: string,
|
contents: string,
|
||||||
tw?: boolean,
|
tw?: boolean
|
||||||
) {
|
) {
|
||||||
if (className == undefined) className = "";
|
if (className == undefined) className = "";
|
||||||
if (contents == undefined) contents = "";
|
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";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export default class ServerSingle {
|
export default class ServerSingle {
|
||||||
@ -27,7 +27,7 @@ export default class ServerSingle {
|
|||||||
g(true);
|
g(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
} else g(true);
|
} else g(true);
|
||||||
});
|
});
|
||||||
@ -35,7 +35,7 @@ export default class ServerSingle {
|
|||||||
console.log(
|
console.log(
|
||||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
|
||||||
"font-weight: bold",
|
"font-weight: bold",
|
||||||
d,
|
d
|
||||||
);
|
);
|
||||||
toast.error(`
|
toast.error(`
|
||||||
Error while grabbing servers from API.
|
Error while grabbing servers from API.
|
||||||
@ -52,7 +52,7 @@ export default class ServerSingle {
|
|||||||
console.log(
|
console.log(
|
||||||
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
"%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
|
||||||
"font-weight: bold",
|
"font-weight: bold",
|
||||||
b,
|
b
|
||||||
);
|
);
|
||||||
bc();
|
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
|
// its fully automatic
|
||||||
|
|
||||||
import Favorites from "@/app/account/favorites/page";
|
import Favorites from "@/app/account/favorites/page";
|
||||||
import { OnlineServer } from "@/components/ServerView";
|
import { OnlineServer } from "@/lib/types/server";
|
||||||
import { Inngest } from "inngest";
|
import { Inngest } from "inngest";
|
||||||
import { serve } from "inngest/next";
|
import { serve } from "inngest/next";
|
||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
@ -16,8 +16,8 @@ export default serve({
|
|||||||
client: inngest,
|
client: inngest,
|
||||||
functions: [
|
functions: [
|
||||||
inngest.createFunction(
|
inngest.createFunction(
|
||||||
{ id: "every-60-min" },
|
{ id: "every-30-min" },
|
||||||
[{ cron: "*/30 * * * *" }],
|
[{ cron: "*/30 * * * *" }, { event: "test/30-min" }],
|
||||||
async ({ event, step }) => {
|
async ({ event, step }) => {
|
||||||
const mongo = new MongoClient(process.env.MONGO_DB as string);
|
const mongo = new MongoClient(process.env.MONGO_DB as string);
|
||||||
try {
|
try {
|
||||||
@ -79,7 +79,7 @@ export default serve({
|
|||||||
mongo.close();
|
mongo.close();
|
||||||
return { event, body: "Cloudflare.. aborting " + e };
|
return { event, body: "Cloudflare.. aborting " + e };
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
),
|
),
|
||||||
inngest.createFunction(
|
inngest.createFunction(
|
||||||
{ id: "every-two-months" },
|
{ id: "every-two-months" },
|
||||||
@ -118,7 +118,7 @@ export default serve({
|
|||||||
event,
|
event,
|
||||||
body: "Dropped database. ",
|
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,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const server = checkForInfoOrLeave(res, req.body.server);
|
const { server } = req.query;
|
||||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
|
|
||||||
await client.connect();
|
await client.connect();
|
||||||
@ -25,12 +25,6 @@ export default async function handler(
|
|||||||
client.close();
|
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) {
|
export async function increaseNum(client: MongoClient, server: string) {
|
||||||
const db = client.db("mhsf");
|
const db = client.db("mhsf");
|
||||||
const collection = db.collection("meta");
|
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 type { NextApiResponse, NextApiRequest } from "next";
|
||||||
import { MongoClient, ObjectId } from "mongodb";
|
import { MongoClient, ObjectId } from "mongodb";
|
||||||
import { getAuth } from "@clerk/nextjs/server";
|
import { getAuth } from "@clerk/nextjs/server";
|
||||||
import { decreaseNum, increaseNum } from "./getCommunityNum";
|
import { decreaseNum, increaseNum } from "./community-favorites";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -12,7 +12,7 @@ export default async function handler(
|
|||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
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);
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
await client.connect();
|
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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const { userId } = getAuth(req);
|
const { userId } = getAuth(req);
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
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);
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|
||||||
@ -24,9 +24,3 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
client.close();
|
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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const { userId } = getAuth(req);
|
const { userId } = getAuth(req);
|
||||||
|
|
||||||
@ -25,9 +25,3 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
client.close();
|
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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
const db = client.db("mhsf").collection("historical");
|
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 scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||||
|
|
||||||
const allData = await db.find({ server }).toArray();
|
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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||||
const db = client.db("mhsf").collection("history");
|
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 scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||||
|
|
||||||
const allData = await db.find({ server }).toArray();
|
const allData = await db.find({ server }).toArray();
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import parseToHTML from "@/lib/miniMessage2HTML";
|
import parseToHTML from "@/lib/motdEngine";
|
||||||
|
|
||||||
let num = 0;
|
let num = 0;
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
num++;
|
num++;
|
||||||
var body: Array<{ server: string; motd: string }> = req.body.motd;
|
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 }) => (
|
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]">
|
||||||
@ -8,6 +8,18 @@ const User = ({ user }: { user: string }) => (
|
|||||||
|
|
||||||
export const Changelog = () => (
|
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>
|
<div>
|
||||||
<strong className="flex items-center">
|
<strong className="flex items-center">
|
||||||
Version b-0.4.5 (July 26th 2024):
|
Version b-0.4.5 (July 26th 2024):
|
||||||
|
|||||||
@ -59,6 +59,12 @@ const config = {
|
|||||||
sm: "calc(var(--radius) - 4px)",
|
sm: "calc(var(--radius) - 4px)",
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
|
"border-beam": {
|
||||||
|
"100%": {
|
||||||
|
"offset-distance": "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"accordion-down": {
|
"accordion-down": {
|
||||||
from: { height: "0" },
|
from: { height: "0" },
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
@ -71,6 +77,8 @@ const config = {
|
|||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 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:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
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@*":
|
"@types/prop-types@*":
|
||||||
version "15.7.12"
|
version "15.7.12"
|
||||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz"
|
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-ia32-msvc" "14.2.3"
|
||||||
"@next/swc-win32-x64-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:
|
no-case@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz"
|
resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz"
|
||||||
@ -4739,6 +4752,11 @@ npm-run-path@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-key "^4.0.0"
|
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:
|
nypm@^0.3.8:
|
||||||
version "0.3.9"
|
version "0.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.3.9.tgz#ab74c55075737466847611aa33c3c67741c01d8f"
|
resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.3.9.tgz#ab74c55075737466847611aa33c3c67741c01d8f"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user