2024-07-23 18:49:21 -05:00
|
|
|
"use client";
|
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
import { Spinner } from "./ui/spinner";
|
|
|
|
|
import IconDisplay from "./IconDisplay";
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardFooter,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
BetterHeader,
|
|
|
|
|
} from "@/components/ui/card";
|
|
|
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "@/components/ui/dialog";
|
|
|
|
|
import beautify from "json-beautify";
|
|
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from "@/components/ui/tooltip";
|
|
|
|
|
import { Badge } from "./ui/badge";
|
|
|
|
|
import Editor from "@monaco-editor/react";
|
|
|
|
|
import { allTags } from "@/allTags";
|
|
|
|
|
import ServerSingle from "@/lib/single";
|
|
|
|
|
import { parse, parseExpression } from "@babel/parser";
|
|
|
|
|
import { NEXT_ROUTER_STATE_TREE } from "next/dist/client/components/app-router-headers";
|
|
|
|
|
import { Button } from "./ui/button";
|
|
|
|
|
import * as prettierPluginBabel from "prettier/plugins/babel";
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
|
|
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 [randomText, setRandomText] = useState("");
|
|
|
|
|
const [lastOnline, setLastOnline] = useState(0);
|
|
|
|
|
const [format, setFormat] = useState("");
|
|
|
|
|
const allText = [""];
|
|
|
|
|
const getRandomText = () => {
|
|
|
|
|
return allText[Math.floor(Math.random() * allText.length)];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setRandomText(getRandomText());
|
|
|
|
|
single.init().then(() => {
|
2024-07-26 15:40:13 -05:00
|
|
|
fetch("/api/favorites/isFavorited", {
|
2024-07-23 18:49:21 -05:00
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
server: single.grabOffline()?.name,
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
.then((b) => {
|
|
|
|
|
b.json().then((c) => {
|
|
|
|
|
setFavorited(c.result);
|
|
|
|
|
setLoading(false);
|
|
|
|
|
var online = single.grabOffline()?.last_online;
|
|
|
|
|
if (online != undefined) {
|
|
|
|
|
setLastOnline(online);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
var online = single.grabOffline()?.last_online;
|
|
|
|
|
if (online != undefined) {
|
|
|
|
|
setLastOnline(online);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Spinner className="flex items-center" />
|
|
|
|
|
<br />
|
|
|
|
|
<div
|
|
|
|
|
className=" flex justify-center"
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: randomText }}
|
|
|
|
|
></div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{single.grabOnline() == undefined && (
|
|
|
|
|
<div className=" grid pl-4 pr-4">
|
|
|
|
|
<div
|
|
|
|
|
className=" rounded p-2"
|
|
|
|
|
style={{ backgroundColor: "rgba(244, 63, 94, .16)" }}
|
|
|
|
|
>
|
|
|
|
|
<strong className="flex items-center">
|
|
|
|
|
This server is offline <X />
|
|
|
|
|
</strong>
|
|
|
|
|
<p>
|
|
|
|
|
This means that the server can{"'"}t loading some resources, like
|
|
|
|
|
tags, icons and authors. However, all historical data still is
|
|
|
|
|
stored and works.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className=" grid p-4 sm:grid-cols-3 gap-4">
|
|
|
|
|
<Card className=" sm:col-span-2">
|
|
|
|
|
<BetterHeader>
|
|
|
|
|
<CardTitle className=" flex items-center">
|
|
|
|
|
{single.grabOnline() == undefined ? (
|
|
|
|
|
<div
|
|
|
|
|
className=" items-center mr-1"
|
|
|
|
|
style={{
|
|
|
|
|
width: ".75rem",
|
|
|
|
|
height: ".75rem",
|
|
|
|
|
borderRadius: "9999px",
|
|
|
|
|
backgroundColor: "#ff1744",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div
|
|
|
|
|
className=" items-center mr-1"
|
|
|
|
|
style={{
|
|
|
|
|
width: ".75rem",
|
|
|
|
|
height: ".75rem",
|
|
|
|
|
borderRadius: "9999px",
|
|
|
|
|
backgroundColor: "#0cce6b",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{props.server}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
{/* 1704088800000 is the Unix time (in milliseconds) of (GMT) Monday, January 1, 2024 6:00:00 AM */}
|
|
|
|
|
{lastOnline < 1704088800000 &&
|
|
|
|
|
single.grabOnline() == undefined && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger>
|
|
|
|
|
<Badge variant="secondary">
|
|
|
|
|
Server too old to grab data
|
|
|
|
|
</Badge>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
This server was last online before 1/1/24 or{" "}
|
|
|
|
|
<code>(GMT) Monday, January 1, 2024 6:00:00 AM</code>,
|
|
|
|
|
<br />
|
|
|
|
|
meaning data like tags, authors and other information
|
|
|
|
|
about the server cannot be seen.
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{single.getAuthor() != undefined && (
|
|
|
|
|
<p>by {single.getAuthor()}</p>
|
|
|
|
|
)}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</BetterHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<p>
|
|
|
|
|
<strong>Time:</strong>
|
|
|
|
|
<br />
|
|
|
|
|
<i>Last online</i>{" "}
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger>
|
|
|
|
|
<code>
|
|
|
|
|
{timeConverter(single.grabOffline()?.last_online)}
|
|
|
|
|
</code>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<code>{single.grabOffline()?.last_online}</code> in Unix time
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>{" "}
|
|
|
|
|
<br />
|
|
|
|
|
<i>Created on</i>{" "}
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger>
|
|
|
|
|
<code>{timeConverter(single.grabOffline()?.creation)}</code>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<code>{single.grabOffline()?.creation}</code> in Unix time
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Favorite the server?</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
By favoriting the server, you can see it later.{" "}
|
|
|
|
|
<SignedOut>
|
|
|
|
|
<strong>You need to sign in to favorite a server.</strong>
|
|
|
|
|
</SignedOut>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<SignedOut>
|
|
|
|
|
<SignInPopoverButton />
|
|
|
|
|
</SignedOut>
|
|
|
|
|
<SignedIn>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => {
|
2024-07-26 15:40:13 -05:00
|
|
|
fetch("/api/favorites/favoriteServer", {
|
2024-07-23 18:49:21 -05:00
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
server: single.grabOffline()?.name,
|
|
|
|
|
}),
|
|
|
|
|
method: "POST",
|
|
|
|
|
}).then(() => {});
|
|
|
|
|
setFavorited(!favorited);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{favorited && (
|
|
|
|
|
<motion.div
|
2024-07-26 15:40:13 -05:00
|
|
|
animate={{ color: "yellow", fill: "yellow" }}
|
2024-07-23 18:49:21 -05:00
|
|
|
transition={{ duration: 2 }}
|
|
|
|
|
>
|
|
|
|
|
<Star
|
|
|
|
|
className="mr-2"
|
|
|
|
|
size="16"
|
|
|
|
|
color="yellow"
|
|
|
|
|
fill="yellow"
|
|
|
|
|
/>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
{!favorited && (
|
|
|
|
|
<motion.div
|
|
|
|
|
transition={{ duration: 1 }}
|
2024-07-26 15:40:13 -05:00
|
|
|
animate={{ color: "yellow", fill: "yellow" }}
|
2024-07-23 18:49:21 -05:00
|
|
|
>
|
|
|
|
|
<Star className="mr-2" size="16" />
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
Favorite Server
|
|
|
|
|
</Button>
|
|
|
|
|
</SignedIn>
|
|
|
|
|
</CardContent>
|
|
|
|
|
<CardFooter>
|
|
|
|
|
<small>
|
|
|
|
|
This is unlike voting. The{" "}
|
|
|
|
|
<i>amount of people who favorited are public</i>, but the server
|
|
|
|
|
doesn{"'"}t know who favorited, as Favorites are completely
|
|
|
|
|
anonymous.
|
|
|
|
|
</small>
|
|
|
|
|
</CardFooter>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function timeConverter(UNIX_timestamp: any) {
|
|
|
|
|
var a = new Date(UNIX_timestamp);
|
|
|
|
|
var months = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
|
|
|
|
|
var year = a.getFullYear();
|
|
|
|
|
var month = months[a.getMonth()];
|
|
|
|
|
var date = a.getDate();
|
|
|
|
|
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;
|
|
|
|
|
}
|