mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 15:54:58 -05:00
1.0!
This commit is contained in:
parent
70687b3f35
commit
9be70112dc
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,6 +7,10 @@
|
||||
.yarn/install-state.gz
|
||||
.turbo
|
||||
|
||||
# cron
|
||||
/cron/dist
|
||||
/cron/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
25
cron/README.md
Normal file
25
cron/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# MHSF Cron Tasks
|
||||
|
||||
In version 1.0, MHSF moved from using Inngest to collect statistics to a self-hosted `crontab` Node.js script.
|
||||
|
||||
## Why the move?
|
||||
|
||||
When running Inngest, on Vercel's servers, when doing the `/servers` Minehut API endpoint to grab the currently online servers, a Cloudflare pop-up appeared. This made it so the JSON data expected, was blocked. This appeared to only run on Vercel's servers, and the only real solution (without spending a lot of money) was to run a minimal script every 30 minutes to grab the server data.
|
||||
|
||||
## How do you run this?
|
||||
|
||||
If you're on a Unix based machine, just type the following:
|
||||
|
||||
```
|
||||
# Make sure you already cloned the repo and are in the /cron directory.
|
||||
# This project uses NPM instead of Yarn for the website
|
||||
npm install
|
||||
|
||||
crontab -e
|
||||
```
|
||||
|
||||
and in `vi` go into insert mode (type `i`) and type the following:
|
||||
|
||||
```
|
||||
*/30 * * * * cd "<INSERT_REPO_DIR_HERE>/cron/" && npm start
|
||||
```
|
||||
175
cron/package-lock.json
generated
Normal file
175
cron/package-lock.json
generated
Normal file
@ -0,0 +1,175 @@
|
||||
{
|
||||
"name": "cron",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cron",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"mongodb": "^6.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz",
|
||||
"integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==",
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webidl-conversions": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
||||
},
|
||||
"node_modules/@types/whatwg-url": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||
"dependencies": {
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bson": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
|
||||
"integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/memory-pager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz",
|
||||
"integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==",
|
||||
"dependencies": {
|
||||
"@mongodb-js/saslprep": "^1.1.5",
|
||||
"bson": "^6.7.0",
|
||||
"mongodb-connection-string-url": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-providers": "^3.188.0",
|
||||
"@mongodb-js/zstd": "^1.1.0",
|
||||
"gcp-metadata": "^5.2.0",
|
||||
"kerberos": "^2.0.1",
|
||||
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||
"snappy": "^7.2.2",
|
||||
"socks": "^2.7.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-providers": {
|
||||
"optional": true
|
||||
},
|
||||
"@mongodb-js/zstd": {
|
||||
"optional": true
|
||||
},
|
||||
"gcp-metadata": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
},
|
||||
"socks": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||
"dependencies": {
|
||||
"@types/whatwg-url": "^11.0.2",
|
||||
"whatwg-url": "^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||
"dependencies": {
|
||||
"memory-pager": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||
"dependencies": {
|
||||
"tr46": "^4.1.1",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
cron/package.json
Normal file
17
cron/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "cron",
|
||||
"version": "1.0.0",
|
||||
"description": "In version 1.0, MHSF moved from using Inngest to collect statistics to a self-hosted `crontab` Node.js script.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "tsc --build && node ./dist/index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"mongodb": "^6.8.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
123
cron/src/index.ts
Normal file
123
cron/src/index.ts
Normal file
@ -0,0 +1,123 @@
|
||||
// MHSF crontab scripts
|
||||
// by dvelo - licensed under MIT license
|
||||
|
||||
import chalk from "chalk";
|
||||
|
||||
console.log(chalk.yellow(chalk.bold("MHSF crontab scripts")));
|
||||
console.log(chalk.yellow(chalk.bold("by dvelo - licensed under MIT license")));
|
||||
console.log();
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { config } from "dotenv";
|
||||
|
||||
// set-up config
|
||||
config({ path: "../.env.local" });
|
||||
|
||||
const mongo = new MongoClient(process.env.MONGO_DB as string);
|
||||
|
||||
main().catch((e) => {
|
||||
console.log(chalk.red("[CRON] " + ERROR + " Error while running: "));
|
||||
console.error(e);
|
||||
});
|
||||
const SUCCESS = chalk.green("SUCCESS");
|
||||
const ERROR = chalk.red("ERROR");
|
||||
const WARN = chalk.red("WARN");
|
||||
const INFO = chalk.blueBright("INFO");
|
||||
|
||||
/**
|
||||
* Main function that runs the script.
|
||||
*
|
||||
* @remarks
|
||||
* Connects to the MongoDB instance, fetches the server data from the Minehut API, and inserts the total player and server count into the "mh" collection.
|
||||
* Then, it iterates over each server and inserts the player count, server name, and date into the "history" collection.
|
||||
* If an error occurs, it logs the error and closes the MongoDB connection.
|
||||
*/
|
||||
async function main() {
|
||||
await mongo.connect();
|
||||
try {
|
||||
// No more mumbo jumbo
|
||||
const mh = await (
|
||||
await fetch("https://api.minehut.com/servers", {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"accept-language": Math.random().toString(),
|
||||
priority: "u=1, i",
|
||||
"sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "cross-site",
|
||||
"Content-Type": "application/json",
|
||||
Referer: "http://localhost:3000/",
|
||||
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||
},
|
||||
body: null,
|
||||
method: "GET",
|
||||
})
|
||||
).json();
|
||||
console.log("[CRON] " + SUCCESS + " Found", mh.servers.length, "servers");
|
||||
|
||||
const mha = mongo.db("mhsf").collection("mh");
|
||||
const meta = mongo.db("mhsf").collection("meta");
|
||||
const dbl = mongo.db("mhsf").collection("history");
|
||||
|
||||
await mha.insertOne({
|
||||
total_players: mh.total_players,
|
||||
total_servers: mh.total_servers,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
let y = 0;
|
||||
|
||||
mh.servers.forEach(async (server: any, i: number) => {
|
||||
const serverFavoritesObject = await meta.findOne({
|
||||
server: server.name,
|
||||
});
|
||||
let favorites = 0;
|
||||
if (serverFavoritesObject != undefined)
|
||||
favorites = serverFavoritesObject.favorites;
|
||||
|
||||
await dbl.insertOne({
|
||||
player_count: server.playerData.playerCount,
|
||||
favorites,
|
||||
server: server.name,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(
|
||||
"[CRON] " +
|
||||
INFO +
|
||||
" Remaining servers: " +
|
||||
(y + "/" + mh.servers.length)
|
||||
);
|
||||
y++;
|
||||
if (y == mh.servers.length) {
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(
|
||||
"[CRON] " + SUCCESS + " Finished! Closing MongoDB connection."
|
||||
);
|
||||
|
||||
// Close connection
|
||||
await mongo
|
||||
.close()
|
||||
.catch((e) =>
|
||||
console.log(
|
||||
"[CRON] " + WARN + " Error while closing MongoDB connection:",
|
||||
e
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
await mongo.close();
|
||||
console.log("[CRON] " + ERROR + " Error while parsing JSON:", e);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
12
cron/tsconfig.json
Normal file
12
cron/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": true,
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
"target": "ES6",
|
||||
"module": "NodeNext",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@clerk/nextjs": "^5.1.3",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-menubar": "^1.1.1",
|
||||
"@unocss/eslint-plugin": "^0.61.5",
|
||||
|
||||
@ -60,7 +60,15 @@ export default function Settings() {
|
||||
<br />
|
||||
<strong className="font-bold">Link Account</strong>
|
||||
<div className="flex items-center">
|
||||
<p>Link a Minecraft account to customize a server you own.</p>
|
||||
<p>
|
||||
Link a Minecraft account to customize a server you own.
|
||||
<br />{" "}
|
||||
{user?.publicMetadata.player != undefined && linked && (
|
||||
<>
|
||||
Currently linked to {user?.publicMetadata.player as string}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
|
||||
@ -100,6 +100,12 @@
|
||||
backdrop-filter: blur(8px) !important;
|
||||
}
|
||||
|
||||
body {
|
||||
scrollbar-width: 10px;
|
||||
scrollbar-color: #888;
|
||||
scrollbar-gutter: transparent;
|
||||
}
|
||||
|
||||
/** Cool scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
|
||||
@ -21,6 +21,7 @@ import { CommandBarer } from "@/components/CommandBar";
|
||||
import ThemedToaster from "@/components/misc/ThemedToaster";
|
||||
import UnofficalDialog from "@/components/misc/UnofficalDialog";
|
||||
import ClientFadeIn from "@/components/ClientFadeIn";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const inter = interFont({ variable: "--font-inter", subsets: ["latin"] });
|
||||
export default async function RootLayout({
|
||||
|
||||
@ -15,7 +15,14 @@ import {
|
||||
} from "./ui/card";
|
||||
import IconDisplay from "./IconDisplay";
|
||||
import { TagShower } from "./ServerList";
|
||||
import { Copy, EllipsisVertical, Layers, Star } from "lucide-react";
|
||||
import {
|
||||
ArrowRight,
|
||||
ChartArea,
|
||||
Copy,
|
||||
EllipsisVertical,
|
||||
Layers,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
Drawer,
|
||||
@ -33,9 +40,16 @@ import { useState } from "react";
|
||||
import { favoriteServer, isFavorited } from "@/lib/api";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import { useTheme } from "next-themes";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
import useClipboard from "@/lib/useClipboard";
|
||||
|
||||
export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
const router = useRouter();
|
||||
const clipboard = useClipboard();
|
||||
const [favoriteStar, setFavoriteStar] = useState(false);
|
||||
const [favoriteLoading, setFavoriteLoading] = useState(true);
|
||||
const { isSignedIn } = useUser();
|
||||
@ -61,7 +75,51 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="m-0">
|
||||
<IconDisplay server={b} /> {b.name}{" "}
|
||||
<span>
|
||||
<IconDisplay server={b} />
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Link href={"/server/" + b.name}>
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="text-2xl px-0 pl-1 font-semibold"
|
||||
>
|
||||
{b.name}
|
||||
</Button>
|
||||
</Link>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80 font-normal tracking-normal">
|
||||
<div className="flex justify-between space-x-4">
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-semibold">{b.name}</h4>
|
||||
<p className="text-sm">
|
||||
{motd && (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: motd }}
|
||||
className="w-[30px] text-center break-all overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center pt-2">
|
||||
<span className="text-xs text-muted-foreground flex items-center">
|
||||
<ArrowRight size={16} className="mr-2" />
|
||||
Open Server Page
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center pt-2">
|
||||
<span className="text-xs text-muted-foreground flex items-center">
|
||||
<ChartArea size={16} className="mr-2" />
|
||||
Running on{" "}
|
||||
{b.staticInfo.serverPlan == undefined
|
||||
? "Free Plan"
|
||||
: b.staticInfo.serverPlan}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</span>
|
||||
<Drawer>
|
||||
<DrawerTrigger>
|
||||
<Button
|
||||
@ -80,9 +138,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
b.name + ".mshf.minehut.gg"
|
||||
);
|
||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||
toast.success("Copied IP to clipboard");
|
||||
}}
|
||||
>
|
||||
@ -101,7 +157,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{b.author != undefined ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="text-sm text-muted-foreground font-normal tracking-normal">
|
||||
by {b.author}
|
||||
</div>
|
||||
) : (
|
||||
@ -109,7 +165,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
)}
|
||||
<TagShower server={b} />
|
||||
</CardTitle>
|
||||
<CardDescription className="float-left inline">
|
||||
<CardDescription className="float-left inline ">
|
||||
<span className="flex items-center">
|
||||
{b.playerData.playerCount == 0 ? (
|
||||
<div
|
||||
@ -147,9 +203,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
variant="secondary"
|
||||
className="min-w-[128px] max-w-[328px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
b.name + ".mshf.minehut.gg"
|
||||
);
|
||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||
toast.success("Copied IP to clipboard");
|
||||
}}
|
||||
>
|
||||
@ -178,9 +232,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
b.name + ".mshf.minehut.gg"
|
||||
);
|
||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||
toast.success("Copied IP to clipboard");
|
||||
}}
|
||||
>
|
||||
@ -210,7 +262,7 @@ export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||
toast.success("Copied IP to clipboard");
|
||||
}}
|
||||
>
|
||||
|
||||
@ -48,6 +48,7 @@ import {
|
||||
} from "@/components/ui/menubar";
|
||||
import ClientFadeIn from "./ClientFadeIn";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
import useClipboard from "@/lib/useClipboard";
|
||||
|
||||
export default function ServerList() {
|
||||
const [loading, setLoading]: any = useState(true);
|
||||
@ -70,6 +71,7 @@ export default function ServerList() {
|
||||
const [nameFilters, setNameFilters] = useState<any>({});
|
||||
const [inErrState, setErrState] = useState(false);
|
||||
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
||||
const clipboard = useClipboard();
|
||||
const router = useRouter();
|
||||
const [ipr, setIPR] = useState("4");
|
||||
const [filters, setFilters] = useState<
|
||||
@ -121,7 +123,7 @@ export default function ServerList() {
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-4 max-lg:grid-cols-2">
|
||||
<div className="md:grid md:grid-cols-3 gap-4 max-lg:grid-cols-2">
|
||||
<Skeleton className="h-[112px] rounded-xl" />
|
||||
<Skeleton className="h-[112px] rounded-xl" />
|
||||
<Skeleton className="h-[112px] rounded-xl" />
|
||||
@ -129,7 +131,7 @@ export default function ServerList() {
|
||||
<br />
|
||||
<Separator />
|
||||
<br />
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="md:grid md:grid-cols-4 gap-4">
|
||||
<Skeleton className="h-[450px] rounded-xl" />
|
||||
<Skeleton className="h-[450px] rounded-xl" />
|
||||
<Skeleton className="h-[450px] rounded-xl" />
|
||||
@ -663,9 +665,7 @@ export default function ServerList() {
|
||||
className="ml-1 h-[20px]"
|
||||
onClick={() => {
|
||||
setTextCopied(true);
|
||||
navigator.clipboard.writeText(
|
||||
randomData.name + ".mshf.minehut.gg"
|
||||
);
|
||||
clipboard.writeText(randomData.name + ".mshf.minehut.gg");
|
||||
toast.success("Copied!");
|
||||
setTimeout(() => setTextCopied(false), 1000);
|
||||
}}
|
||||
@ -716,7 +716,11 @@ export default function ServerList() {
|
||||
style={{ overflow: "hidden !important", paddingLeft: 6 }}
|
||||
>
|
||||
<ClientFadeIn delay={200}>
|
||||
<div className={" sm:grid " + "sm:grid-cols-" + ipr + " gap-4"}>
|
||||
<div
|
||||
className={
|
||||
" sm:grid " + "lg:grid-cols-" + ipr + " gap-4 sm:grid-cols-2"
|
||||
}
|
||||
>
|
||||
{servers.map((b: any) => (
|
||||
<>
|
||||
<ServerCard b={b} motd={motdList[b.name]} />
|
||||
@ -788,7 +792,7 @@ export function TagShower(props: {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="font-normal tracking-normal">
|
||||
{compatiableTags.map((t) => (
|
||||
<>
|
||||
{props.unclickable && (
|
||||
@ -832,6 +836,6 @@ export function TagShower(props: {
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ export default function LoggedInPopover() {
|
||||
return (
|
||||
<div className="grid w-full">
|
||||
<strong className="text-center">Logged in as {user?.username}</strong>
|
||||
<small className="text-center">
|
||||
<small className="text-center pb-6">
|
||||
Make comments about servers and favorite servers. Secured by Clerk
|
||||
</small>
|
||||
<br />
|
||||
|
||||
@ -33,7 +33,7 @@ export default function SignInPopoverButton({
|
||||
<PopoverContent className="w-full">
|
||||
<div className=" grid w-[200px]">
|
||||
<strong className="text-center">Login</strong>
|
||||
<small className="text-center">
|
||||
<small className="text-center pb-6">
|
||||
Make comments about servers and favorite servers. Secured by Clerk
|
||||
</small>
|
||||
<br />
|
||||
|
||||
@ -19,7 +19,7 @@ export function ShowInfo() {
|
||||
{open == true && (
|
||||
<>
|
||||
<p>
|
||||
By claiming your account, you can add markdown descriptions and{" "}
|
||||
By claiming your account, you can add Markdown descriptions and{" "}
|
||||
custom color schemes to your server (and more), making it stand out.
|
||||
To get started, join the server below on your Minecraft account.
|
||||
Enter the code in chat in the website, and you will link your
|
||||
|
||||
@ -3,9 +3,11 @@ import { useState } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import toast from "react-hot-toast";
|
||||
import { Check } from "lucide-react";
|
||||
import useClipboard from "@/lib/useClipboard";
|
||||
|
||||
export function TextCopyComp() {
|
||||
"use client";
|
||||
const clipboard = useClipboard();
|
||||
const [textCopied, setTextCopied] = useState(false);
|
||||
|
||||
return (
|
||||
@ -16,7 +18,7 @@ export function TextCopyComp() {
|
||||
className="ml-1 h-[20px]"
|
||||
onClick={() => {
|
||||
setTextCopied(true);
|
||||
navigator.clipboard.writeText("MHSFPV.minehut.gg");
|
||||
clipboard.writeText("MHSFPV.minehut.gg");
|
||||
toast.success("Copied!");
|
||||
setTimeout(() => setTextCopied(false), 1000);
|
||||
}}
|
||||
|
||||
29
src/components/ui/hover-card.tsx
Normal file
29
src/components/ui/hover-card.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const HoverCard = HoverCardPrimitive.Root
|
||||
|
||||
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
||||
|
||||
const HoverCardContent = React.forwardRef<
|
||||
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<HoverCardPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
||||
|
||||
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||
21
src/lib/useClipboard.ts
Normal file
21
src/lib/useClipboard.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
/** A hook to properly write text to the clipboard without triggering a client-side error
|
||||
* @version 1.0
|
||||
*/
|
||||
export default function useClipboard() {
|
||||
|
||||
const writeText = (text: string) => {
|
||||
if (navigator.clipboard == undefined)
|
||||
return toast.error("Clipboard doesn't exist");
|
||||
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
const write = (text: ClipboardItems) => {
|
||||
if (navigator.clipboard == undefined)
|
||||
return toast.error("Clipboard doesn't exist")
|
||||
|
||||
navigator.clipboard.write(text);
|
||||
}
|
||||
return { writeText, write };
|
||||
}
|
||||
@ -1,13 +1,47 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Separator } from "./components/ui/separator";
|
||||
import { Button } from "./components/ui/button";
|
||||
import Confetti, { ConfettiButton } from "./components/effects/confetti";
|
||||
|
||||
export const version = "b-0.10.7";
|
||||
export const version = "1.0";
|
||||
|
||||
const User = ({ user }: { user: string }) => (
|
||||
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
||||
{user}
|
||||
</span>
|
||||
);
|
||||
import confetti from "canvas-confetti";
|
||||
const handleClick = () => {
|
||||
const duration = 5 * 1000;
|
||||
const animationEnd = Date.now() + duration;
|
||||
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
|
||||
|
||||
const randomInRange = (min: number, max: number) =>
|
||||
Math.random() * (max - min) + min;
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now();
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
return clearInterval(interval);
|
||||
}
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration);
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
zIndex: 60,
|
||||
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
|
||||
});
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
zIndex: 60,
|
||||
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
|
||||
});
|
||||
}, 250);
|
||||
};
|
||||
|
||||
export const Changelog = () => (
|
||||
<>
|
||||
@ -43,6 +77,26 @@ export const Changelog = () => (
|
||||
`| ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE.substring(0, 24)}`}
|
||||
</code>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<strong className="flex items-center">
|
||||
Version 1.0.0 (August 22nd 2024)
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
• 1.0!{" "}
|
||||
<Button className="h-[25px] w-[50px] ml-2" onClick={handleClick}>
|
||||
woah!
|
||||
</Button>
|
||||
</li>
|
||||
<li>• New hover card on server title hover</li>
|
||||
<li>• Moving to self-hosted cron jobs</li>
|
||||
<li>• Fixing some mobile issues</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
<Separator />
|
||||
|
||||
<br />
|
||||
<div>
|
||||
<strong className="flex items-center">
|
||||
|
||||
21
yarn.lock
21
yarn.lock
@ -740,6 +740,21 @@
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
|
||||
"@radix-ui/react-hover-card@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.1.tgz#2982a5a91c7ae5a98e0cacd845fbdfbfdcdab355"
|
||||
integrity sha512-IwzAOP97hQpDADYVKrEEHUH/b2LA+9MgB0LgdmnbFO2u/3M5hmEofjjr2M6CyzUblaAqJdFm6B7oFtU72DPXrA==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.0"
|
||||
"@radix-ui/react-popper" "1.2.0"
|
||||
"@radix-ui/react-portal" "1.1.1"
|
||||
"@radix-ui/react-presence" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-icons@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69"
|
||||
@ -3028,9 +3043,9 @@ foreground-child@^3.1.0:
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
framer-motion@^11.3.8:
|
||||
version "11.3.8"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.3.8.tgz#682df8cbac6a9667b48af427e5a8bdaea7203713"
|
||||
integrity sha512-1D+RDTsIp4Rz2dq/oToqSEc9idEQwgBRQyBq4rGpFba+0Z+GCbj9z1s0+ikFbanWe3YJ0SqkNlDe08GcpFGj5A==
|
||||
version "11.3.29"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.3.29.tgz#5ec10a350b89789d43ea7d9c6bde45b28470f196"
|
||||
integrity sha512-uyDuUOeOElJEA3kbkbyoTNEf75Jih1EUg0ouLKYMlGDdt/LaJPmO+FyOGAGxM2HwKhHcAoKFNveR5A8peb7yhw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user