From 5699da4dc48ca4b12ed0b3ff9c373c0ae3cad936 Mon Sep 17 00:00:00 2001 From: dvelo <52332868+DeveloLongScript@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:34:51 -0500 Subject: [PATCH] feat: achievements --- biome.json | 10 + cron/src/index.ts | 418 ++++++++++---------- docs/advanced/external.mdx | 6 +- docs/getting-started.mdx | 2 +- docs/guides/customization.mdx | 6 +- docs/guides/linking.mdx | 4 +- docs/guides/owning-a-server.mdx | 4 +- docs/guides/reporting-server.mdx | 2 +- docs/legal/external-content-agreement.mdx | 4 +- docs/reading.mdx | 30 ++ package.json | 3 +- src/app/account/settings/layout.tsx | 17 + src/app/account/settings/options/page.tsx | 356 +---------------- src/app/account/settings/page.tsx | 222 ++++------- src/app/docs/[[...slug]]/page.tsx | 18 +- src/app/layout.tsx | 9 +- src/app/server/[server]/page.tsx | 33 +- src/components/AfterServerView.tsx | 69 ++-- src/components/PreferencesSidebar.tsx | 105 +++++ src/components/SLCustomizePage.tsx | 189 +++++++++ src/components/ServerList.tsx | 7 +- src/components/feat/AchievementList.tsx | 131 +++++++ src/components/misc/DiscordPopover.tsx | 4 +- src/components/misc/Link.tsx | 65 ++++ src/components/misc/MDXElements.tsx | 30 ++ src/components/ui/switch.tsx | 29 ++ src/config/docs.ts | 84 ++-- src/config/tags.ts | 442 +++++++++++----------- src/lib/api.ts | 25 ++ src/lib/types/achievement.ts | 18 + src/pages/api/v0/achievements/[server].ts | 18 + src/pages/api/v0/achievements/multiple.ts | 1 + yarn.lock | 151 ++++++-- 33 files changed, 1453 insertions(+), 1059 deletions(-) create mode 100644 biome.json create mode 100644 docs/reading.mdx create mode 100644 src/app/account/settings/layout.tsx create mode 100644 src/components/PreferencesSidebar.tsx create mode 100644 src/components/SLCustomizePage.tsx create mode 100644 src/components/feat/AchievementList.tsx create mode 100644 src/components/misc/Link.tsx create mode 100644 src/components/misc/MDXElements.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/lib/types/achievement.ts create mode 100644 src/pages/api/v0/achievements/[server].ts create mode 100644 src/pages/api/v0/achievements/multiple.ts diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f51a068 --- /dev/null +++ b/biome.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "linter": { + "rules": { + "style": { + "useTemplate": "off" + } + } + } +} diff --git a/cron/src/index.ts b/cron/src/index.ts index 6deddae..c8e03d5 100644 --- a/cron/src/index.ts +++ b/cron/src/index.ts @@ -10,16 +10,16 @@ console.log(); import { MongoClient, WithId } from "mongodb"; import { config } from "dotenv"; import { - OnlineServer, - OnlineServerExtended, - ServerResponse, + OnlineServer, + OnlineServerExtended, + ServerResponse, } from "./types/mh-server.js"; import { CronJob } from "cron"; import { Achievement } from "./types/achievement.js"; // set-up config config({ - path: process.env.MHC_DOCKER != "true" ? "../.env.local" : "./.env.local", + path: process.env.MHC_DOCKER != "true" ? "../.env.local" : "./.env.local", }); let mongo = new MongoClient(process.env.MONGO_DB as string); @@ -32,28 +32,28 @@ const INFO = chalk.blueBright("INFO"); console.log(INFO, "Starting cron job #1"); CronJob.from({ - cronTime: "*/30 * * * *", - onTick: function () { - periodicCronJob().catch((e) => { - console.log(chalk.red("[CRON] " + ERROR + " Error while running: ")); - console.error(e); - }); - }, - start: true, - timeZone: "America/Los_Angeles", + cronTime: "*/30 * * * *", + onTick: function () { + periodicCronJob().catch((e) => { + console.log(chalk.red("[CRON] " + ERROR + " Error while running: ")); + console.error(e); + }); + }, + start: true, + timeZone: "America/Los_Angeles", }); console.log(INFO, "Starting cron job #2"); CronJob.from({ - cronTime: "0 */12 * * *", - onTick: function () { - achievementTask().catch((e) => { - console.log(chalk.red("[CRON] " + ERROR + " Error while running: ")); - console.error(e); - }); - }, - start: true, - timeZone: "America/Los_Angeles", + cronTime: "0 */12 * * *", + onTick: function () { + achievementTask().catch((e) => { + console.log(chalk.red("[CRON] " + ERROR + " Error while running: ")); + console.error(e); + }); + }, + start: true, + timeZone: "America/Los_Angeles", }); /** @@ -65,223 +65,213 @@ CronJob.from({ * If an error occurs, it logs the error and closes the MongoDB connection. */ async function periodicCronJob() { - 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"); + 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"); + 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(), - }); + await mha.insertOne({ + total_players: mh.total_players, + total_servers: mh.total_servers, + date: new Date(), + }); - let y = 0; + let y = 0; - mh.servers.forEach(async (server: OnlineServer, i: number) => { - const serverFavoritesObject = await meta.findOne({ - server: server.name, - }); - let favorites = 0; - if (serverFavoritesObject != undefined) - favorites = serverFavoritesObject.favorites; + mh.servers.forEach(async (server: OnlineServer, 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(), - }); + 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." - ); + 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) { + console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); - return; - } - }); - } catch (e) { - console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); - - return; - } + return; + } } async function achievementTask() { - try { - 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(); - const meta = mongo.db("mhsf").collection("meta"); - const achievements = mongo.db("mhsf").collection("achievements"); - console.log("adding achievements"); + try { + 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(); + const meta = mongo.db("mhsf").collection("meta"); + const achievements = mongo.db("mhsf").collection("achievements"); + console.log("adding achievements"); - mh.servers.forEach(async (server: OnlineServer, i: number) => { - const serverFavoritesObject = await meta.findOne({ - server: server.name, - }); - let favorites = 0; - if (serverFavoritesObject != undefined) - favorites = serverFavoritesObject.favorites; + mh.servers.forEach(async (server: OnlineServer, i: number) => { + const serverFavoritesObject = await meta.findOne({ + server: server.name, + }); + let favorites = 0; + if (serverFavoritesObject != undefined) + favorites = serverFavoritesObject.favorites; - const srvExt: OnlineServerExtended = { - ...server, - favorites, - position: { joins: i + 1 }, - }; - const prevAchievements = ((await achievements.findOne({ - name: server.name, - })) || { _id: "", name: server.name, achievements: [] }) as WithId<{ - name: string; - achievements: Achievement[]; - }>; + const srvExt: OnlineServerExtended = { + ...server, + favorites, + position: { joins: i + 1 }, + }; + const prevAchievements = ((await achievements.findOne({ + name: server.name, + })) || { _id: "", name: server.name, achievements: [] }) as WithId<{ + name: string; + achievements: Achievement[]; + }>; - const achievementsTsk = await achievementEngine( - srvExt, - prevAchievements.achievements - ); + const achievementsTsk = await achievementEngine( + srvExt, + prevAchievements.achievements, + ); - await achievements.insertOne({ - name: server.name, - achievements: achievementsTsk, - }); - }); - } catch (e) { - console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); + await achievements.insertOne({ + name: server.name, + achievements: achievementsTsk, + }); + }); + } catch (e) { + console.log("[CRON] " + ERROR + " Error while parsing JSON:", e); - return; - } + return; + } } async function achievementEngine( - server: OnlineServerExtended, - currentAchievements: Achievement[] + server: OnlineServerExtended, + currentAchievements: Achievement[], ): Promise { - const achievements: Array = []; + const achievements: Array = []; - if ( - server.favorites >= 1000 && - currentAchievements.find((c) => c.type == "has1kFavorites") === undefined - ) { - achievements.push({ - type: "has1kFavorites", - date: new Date().toISOString(), - }); - } + if ( + server.favorites >= 1000 && + currentAchievements.find((c) => c.type == "has1kFavorites") === undefined + ) { + achievements.push({ + type: "has1kFavorites", + date: new Date().toISOString(), + }); + } - if ( - server.favorites >= 100000 && - currentAchievements.find((c) => c.type == "has100kFavorites") === undefined - ) { - achievements.push({ - type: "has100kFavorites", - date: new Date().toISOString(), - }); - } + if ( + server.favorites >= 100000 && + currentAchievements.find((c) => c.type == "has100kFavorites") === undefined + ) { + achievements.push({ + type: "has100kFavorites", + date: new Date().toISOString(), + }); + } - if ( - server.playerData.playerCount >= 2 && - currentAchievements.find((c) => c.type == "has1kTotalJoins") === undefined - ) { - const v: { server: ServerResponse } = await ( - await fetch( - "https://api.minehut.com/server/" + server.name + "?byName=true" - ) - ).json(); + if ( + server.playerData.playerCount >= 2 && + currentAchievements.find((c) => c.type == "has1kTotalJoins") === undefined + ) { + const v: { server: ServerResponse } = await ( + await fetch( + "https://api.minehut.com/server/" + server.name + "?byName=true", + ) + ).json(); - if (v.server.joins >= 1000) { - achievements.push({ - type: "has1kTotalJoins", - date: new Date().toISOString(), - }); - } - } - if ( - server.playerData.playerCount >= 10 && - currentAchievements.find((c) => c.type == "has100kTotalJoins") === undefined - ) { - const v: { server: ServerResponse } = await ( - await fetch( - "https://api.minehut.com/server/" + server.name + "?byName=true" - ) - ).json(); + if (v.server.joins >= 1000) { + achievements.push({ + type: "has1kTotalJoins", + date: new Date().toISOString(), + }); + } + } + if ( + server.playerData.playerCount >= 10 && + currentAchievements.find((c) => c.type == "has100kTotalJoins") === undefined + ) { + const v: { server: ServerResponse } = await ( + await fetch( + "https://api.minehut.com/server/" + server.name + "?byName=true", + ) + ).json(); - if (v.server.joins >= 100000) { - achievements.push({ - type: "has100kTotalJoins", - date: new Date().toISOString(), - }); - } - } + if (v.server.joins >= 100000) { + achievements.push({ + type: "has100kTotalJoins", + date: new Date().toISOString(), + }); + } + } - if (server.position.joins === 1) { - achievements.push({ - type: "mostJoined", - date: new Date().toISOString(), - }); - } + if (server.position.joins === 1) { + achievements.push({ + type: "mostJoined", + date: new Date().toISOString(), + }); + } - return achievements; + return achievements; } diff --git a/docs/advanced/external.mdx b/docs/advanced/external.mdx index eed56f0..2c8ecd1 100644 --- a/docs/advanced/external.mdx +++ b/docs/advanced/external.mdx @@ -4,7 +4,7 @@ title: "Troubleshooting: Making external servers on Minehut" # External Servers on Minehut I think creating external servers on Minehut is an advanced subject, and it is not documented well enough for the circumstances that -might occur with server owners. This is a [extension/rephrasing of the offical wiki guide](https://minehut.wiki.gg/wiki/External). All points in **bold** are things you shouldn't miss, and are commonly misread. +might occur with server owners. This is a [extension/rephrasing of the offical wiki guide](Wiki:External). All points in **bold** are things you shouldn't miss, and are commonly misread.
@@ -87,7 +87,7 @@ proxy-protocol: true ``` ## Thats it! -After this, there are mostly no more common issues. Continue on [the wiki](https://minehut.wiki.gg/wiki/External#Connect_Your_External_Server_Plan_on_Minehut)! +After this, there are mostly no more common issues. Continue on [the wiki](Wiki:External#Connect_Your_External_Server_Plan_on_Minehut)! ## Have any issues? -Go to the offical Minehut Discord server and go into the [#ask-for-help](https://discord.com/channels/239599059415859200/1014801630295760897) channel and create a thread. \ No newline at end of file +Go to the offical Minehut Discord server and go into the [#ask-for-help](https://discord.com/channels/239599059415859200/1014801630295760897) channel and create a thread. \ No newline at end of file diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 7d46c2d..5d569f5 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -28,4 +28,4 @@ No. Your Minehut account is not associated with your MHSF one, and consequently, ## Conclusion -If you'd like to use MHSF, go to the server list [here](/) to try it out! You may also give MHSF a star on GitHub if you feel like this project deserves it. +If you'd like to use MHSF, go to the server list [here](Special:Root) to try it out! You may also give MHSF a star on GitHub if you feel like this project deserves it. diff --git a/docs/guides/customization.mdx b/docs/guides/customization.mdx index 634d8a6..63cf6d6 100644 --- a/docs/guides/customization.mdx +++ b/docs/guides/customization.mdx @@ -3,10 +3,10 @@ title: "Customization" --- # Customize your server -Customizing your server is very easy after you have [linked your account](/docs/guides/linking). Make sure you have done that before then. +Customizing your server is very easy after you have [linked your account](Docs:guides/linking). Make sure you have done that before then. ## Getting started -Make sure you've also [owned your server](/docs/guides/owning-a-server). +Make sure you've also [owned your server](Docs:guides/owning-a-server). ## Customization Types ### Discord Server @@ -22,4 +22,4 @@ You can pick any color in the box and choosing a color scheme to show on your se You can use Markdown formatting to add a description to describe what your server is. ## Thats it! -If you have additional customization types or other things you'd like us to add, [feel free to hit us with an issue on GitHub!](https://github.com/DeveloLongScript/MHSF) \ No newline at end of file +If you have additional customization types or other things you'd like us to add, [feel free to hit us with an issue on GitHub!](Special:GitHub) \ No newline at end of file diff --git a/docs/guides/linking.mdx b/docs/guides/linking.mdx index 917a8da..6d11a14 100644 --- a/docs/guides/linking.mdx +++ b/docs/guides/linking.mdx @@ -22,7 +22,7 @@ Login to the server `MHSFPV.minehut.gg`. (its on a free plan, you may have to st You can do many things with a linked account: -- [Own a server](/docs/guides/owning-a-server) -- [Customize a server](/docs/guides/customization) +- [Own a server](Docs:guides/owning-a-server) +- [Customize a server](Docs:guides/customization) More will be coming in future updates, however the only thing you can do with a linked account is server-based functions. diff --git a/docs/guides/owning-a-server.mdx b/docs/guides/owning-a-server.mdx index b2b053f..b6054de 100644 --- a/docs/guides/owning-a-server.mdx +++ b/docs/guides/owning-a-server.mdx @@ -4,11 +4,11 @@ title: "Own a server" # Owning a server -Owning a server is quite simple and allows you to [customize your server](/docs/guides/customization) your server and make it stand out from other servers. Before owning your server, make sure you agree to the [ECA](/docs/legal/external-content-agreement). +Owning a server is quite simple and allows you to [customize your server](/docs/guides/customization) your server and make it stand out from other servers. Before owning your server, make sure you agree to the [ECA](Docs:legal/external-content-agreement). ## Linking -Find the server you would like to own (either by looking for it, or using the keyboard shortcut `Ctrl`+`Shift`+`K` and searching for it), and make sure your account has [already been linked with your Minecraft account](/docs/guides/linking). Go to the server, and hit the Customization tab. If the owner of the server, and the user your linked to match, you will gain access to the server. +Find the server you would like to own (either by looking for it, or using the keyboard shortcut `Ctrl`+`Shift`+`K` and searching for it), and make sure your account has [already been linked with your Minecraft account](Docs:guides/linking). Go to the server, and hit the Customization tab. If the owner of the server, and the user your linked to match, you will gain access to the server. If they match, you should see a button named Click to own. Press that button, and you should automagically own the server. Congratulations! ## I can't link my server, because my server doesn't have a author diff --git a/docs/guides/reporting-server.mdx b/docs/guides/reporting-server.mdx index d966d56..551bd83 100644 --- a/docs/guides/reporting-server.mdx +++ b/docs/guides/reporting-server.mdx @@ -4,7 +4,7 @@ title: "Reporting a server" # Reporting a server -If you believe a server that you've seen is under breach of the [ECA Agreement](/docs/legal/external-content-agreement), you may request the server in question to be taken down. +If you believe a server that you've seen is under breach of the [ECA Agreement](Docs:legal/external-content-agreement), you may request the server in question to be taken down. Make sure you are logged into a account, and go to the server page and hit the customization tab. Hit the Report button, and add a reason to your report. Your report will be processed and the appropriate action will be taken. diff --git a/docs/legal/external-content-agreement.mdx b/docs/legal/external-content-agreement.mdx index 5cf6b43..b1ae076 100644 --- a/docs/legal/external-content-agreement.mdx +++ b/docs/legal/external-content-agreement.mdx @@ -12,14 +12,14 @@ uploaded onto the platform are. ## Source Code -The source code for MHSF is defined by the [MIT License](https://github.com/DeveloLongScript/MHSF/blob/main/LICENSE). You are free to use MHSF for commercial use, and you may modify the software however you'd like. Taking copies of the software (aka _"forking"_) is also freely allowed. +The source code for MHSF is defined by the [MIT License](Special:GitHub/blob/main/LICENSE). You are free to use MHSF for commercial use, and you may modify the software however you'd like. Taking copies of the software (aka _"forking"_) is also freely allowed. ## What your limits are When creating content, if its a matter of making a profile picture, or editing the description for a server, (and more), you must follow the underlying agreements below.
-For making banners & descriptions, you must follow [Minehuts Terms of Service](https://minehut.wiki.gg/wiki/Rules) _as all content made is associated to Minehut (as the server is mostly on a community for Minehut)._
+For making banners & descriptions, you must follow [Minehuts Terms of Service](Wiki:Rules) _as all content made is associated to Minehut (as the server is mostly on a community for Minehut)._
For making Discord server embeds, you must follow [Discords Terms of Service](https://discord.com/terms/) _as all content made is associated to Discord._ ### All other content diff --git a/docs/reading.mdx b/docs/reading.mdx new file mode 100644 index 0000000..4505313 --- /dev/null +++ b/docs/reading.mdx @@ -0,0 +1,30 @@ +--- +title: "Reading the docs" +--- + +# Reading the docs +The documentation in MHSF has some special symbols used in the docs that might be useful to know. + + + +## Icons +When looking at a link, there will be symbols used that indicate where the link is going: *(these apply to the whole site)* + - indicates the link will link to the [official Minehut wiki](Wiki:/) + - indicates the link will link to another page on the docs + - indicates the link will go to an external site + +When contributing, these links are as follows: +``` +Wiki: +Docs: +Special:Root (links back to /) +https://example.com + +**Example:** +[Ranks](Wiki:Ranks) +[Getting Started](Docs:Getting-started) +https://google.com +``` + +The icons above will be automatically added when using the syntax above.
+The source code for above is stored [here](Special:GitHub/edit/main/src/components/misc/Link.tsx) \ No newline at end of file diff --git a/package.json b/package.json index d44f94a..2c28c9c 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@babel/parser": "^7.24.7", + "@biomejs/biome": "^1.8.3", "@clerk/nextjs": "^5.1.3", "@emotion/is-prop-valid": "^1.3.0", "@monaco-editor/react": "^4.6.0", @@ -48,8 +49,6 @@ "react-dom": "^18", "react-fade-in": "^2.0.1", "react-fast-marquee": "^1.6.5", - "react-marquee-text": "^1.0.4", - "react-smart-ticker": "^1.2.0", "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.0", "tailwind-merge": "^2.3.0", diff --git a/src/app/account/settings/layout.tsx b/src/app/account/settings/layout.tsx new file mode 100644 index 0000000..977b494 --- /dev/null +++ b/src/app/account/settings/layout.tsx @@ -0,0 +1,17 @@ +"use client"; +import { Sidebar } from "@/components/PreferencesSidebar"; +import { usePathname } from "next/navigation"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const pathname = usePathname(); + + return ( + + {children} + + ); +} diff --git a/src/app/account/settings/options/page.tsx b/src/app/account/settings/options/page.tsx index dd56984..060c3d7 100644 --- a/src/app/account/settings/options/page.tsx +++ b/src/app/account/settings/options/page.tsx @@ -1,14 +1,7 @@ "use client"; -import { Button } from "@/components/ui/button"; import { useClerk, useUser } from "@clerk/nextjs"; -import { ExternalLink, KeyRound, UserPen, Link, Cog } from "lucide-react"; -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, -} from "@/components/ui/resizable"; -import { default as NextLink } from "next/link"; import { useEffect, useState } from "react"; +import { SLCustomize } from "@/components/SLCustomizePage"; export default function Settings() { const clerk = useClerk(); @@ -20,348 +13,11 @@ export default function Settings() { }, [user, isSignedIn]); return ( -
- - -
- - - - - - -
-
- - -
-
- - - - - - - -
- Profile Preferences -
-
-
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
-
-
-
-
-
+
+ Profile Preferences +
+
+
); } - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { Card, CardContent } from "@/components/ui/card"; -import { setAccountSL } from "@/lib/api"; -import toast from "react-hot-toast"; - -const FormSchema = z.object({ - type: z.enum(["15", "30", "40", "60", "100", "200"], { - required_error: "You need to select a valid padding.", - }), -}); - -const FormSchemaGrid = z.object({ - grid: z.enum(["4", "5", "6"], { - required_error: "You need to select a valid grid type.", - }), -}); - -function RowRadioForm() { - const { user } = useUser(); - const form = useForm>({ - resolver: zodResolver(FormSchemaGrid), - defaultValues: { - grid: user?.publicMetadata.ipr as "4" | "5" | "6" | undefined, - }, - }); - - function onSubmit(data: z.infer) { - toast.promise(setAccountSL(Number.parseInt(data.grid), "ipr"), { - loading: "Saving...", - success: "Saved!", - error: "Failed to save", - }); - } - - return ( -
- - ( - - Items per row - - - - - - - - 4 items per row - - - - - - - - 5 items per row - - - - - - - - 6 items per row - - - - - - - )} - /> - - - - ); -} - -function PaddingRadioForm() { - const { user } = useUser(); - const form = useForm>({ - resolver: zodResolver(FormSchema), - - defaultValues: { - type: user?.publicMetadata.pad as - | "15" - | "30" - | "40" - | "60" - | "100" - | "200" - | undefined, - }, - }); - - function onSubmit(data: z.infer) { - toast.promise(setAccountSL(Number.parseInt(data.type), "pad"), { - loading: "Saving...", - success: "Saved!", - error: "Failed to save", - }); - } - - return ( -
- - ( - - Padding of servers - - - - - - - 15px - - - - - - 30px - - - - - - 40px - - - - - - 60px - - - - - - 100px - - - - - - 200px - - - - - - )} - /> - - - - - ); -} -const FormSchemaCB = z.object({ - padding: z.boolean().default(false).optional(), -}); - -function ServerPaddingForm() { - const { user } = useUser(); - const srv = user?.publicMetadata.srv as boolean | undefined; - const form = useForm>({ - resolver: zodResolver(FormSchemaCB), - defaultValues: { - padding: srv === undefined ? false : srv, - }, - }); - - function onSubmit(data: z.infer) { - toast.promise( - setAccountSL(data.padding === undefined ? false : data.padding, "srv"), - { - loading: "Saving...", - success: "Saved!", - error: "Failed to save", - } - ); - } - - return ( -
- - ( - - - - -
- - Use padding on the sides of only the servers, not the whole - server list - - - Only show the padding settings on the servers themselves, not - the whole entire page of the server list. - -
-
- )} - /> - - - - ); -} diff --git a/src/app/account/settings/page.tsx b/src/app/account/settings/page.tsx index ffec0b7..4a815f7 100644 --- a/src/app/account/settings/page.tsx +++ b/src/app/account/settings/page.tsx @@ -1,13 +1,6 @@ "use client"; import { Button } from "@/components/ui/button"; import { useClerk, useUser } from "@clerk/nextjs"; -import { ExternalLink, KeyRound, UserPen, Link, Cog } from "lucide-react"; -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, -} from "@/components/ui/resizable"; -import { default as NextLink } from "next/link"; import toast from "react-hot-toast"; import { unlinkMCAccount } from "@/lib/api"; import { useEffect, useState } from "react"; @@ -16,154 +9,81 @@ import { DialogContent, DialogTrigger } from "@/components/ui/dialog"; import CodeDialog from "@/components/misc/LinkDialog"; export default function Settings() { - const clerk = useClerk(); + const clerk = useClerk(); - const { user, isSignedIn } = useUser(); - const [linked, setLinked] = useState(false); - useEffect(() => { - setLinked(user?.publicMetadata.player != undefined); - }, [user, isSignedIn]); + const { user, isSignedIn } = useUser(); + const [linked, setLinked] = useState(false); + useEffect(() => { + setLinked(user?.publicMetadata.player != undefined); + }, [user, isSignedIn]); - return ( -
- - -
- - - - - - -
-
- - -
-
- - - - - - -
- Linking -
-
- Link Account -
-

- Link a Minecraft account to customize a server you own. -
{" "} - {user?.publicMetadata.player != undefined && linked && ( - <> - Currently linked to {user?.publicMetadata.player as string} - - )} -

+ return ( +
+ Linking +
+
+ Link Account +
+

+ Link a Minecraft account to customize a server you own. +
{" "} + {user?.publicMetadata.player != undefined && linked && ( + <>Currently linked to {user?.publicMetadata.player as string} + )} +

- - - {!linked && ( - - )} - - - { - setLinked(c); - }} - /> - - + + + {!linked && } + + + { + setLinked(c); + }} + /> + + - {linked && ( - - )} -
-
- Unlink Account -
-

- Unlink your Minecraft acconut if you have already linked one. -

+ {linked && ( + + )} +
+
+ Unlink Account +
+

Unlink your Minecraft acconut if you have already linked one.

- {!linked && ( - - )} + {!linked && ( + + )} - {linked && ( - - )} -
- - All of your customizations stay the same, and can be changed if{" "} - another account links your Minecraft account. - -
- - -
- ); + {linked && ( + + )} + + + All of your customizations stay the same, and can be changed if another + account links your Minecraft account. + +
+ ); } diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx index 65e6037..3c024e9 100644 --- a/src/app/docs/[[...slug]]/page.tsx +++ b/src/app/docs/[[...slug]]/page.tsx @@ -2,9 +2,11 @@ import TableOfContent from "@/components/docs/TOC"; import { ScrollArea } from "@/components/ui/scroll-area"; import { allDocs } from "contentlayer/generated"; import { useMDXComponent } from "next-contentlayer/hooks"; -import Link from "next/link"; +import NextLink from "next/link"; import { notFound } from "next/navigation"; import { Separator } from "@/components/ui/separator"; +import { ALegacy } from "@/components/misc/Link"; +import { MDXElements } from "@/components/misc/MDXElements"; export const generateStaticParams = async () => allDocs.map((post) => ({ slug: [post._raw.flattenedPath] })); @@ -34,7 +36,13 @@ const PostLayout = ({ params }: { params: { slug: string[] } }) => {
- + , + ...MDXElements, + }} + />
{doc.toc && ( @@ -53,7 +61,7 @@ const PostLayout = ({ params }: { params: { slug: string[] } }) => {

Contribute

  • - { + /> Edit page on GitHub - +
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0dd30fc..dac3968 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -24,10 +24,17 @@ import { BrandingGenericIcon } from "@/components/Icon"; import type { Metadata } from "next"; export const metadata = { + twitter: { + images: [ + { + url: "/public/imgs/icon-cf.png", + }, + ], + }, openGraph: { images: [ { - url: "/branding/meta-banner.png", + url: "/public/imgs/icon-cf.png", }, ], }, diff --git a/src/app/server/[server]/page.tsx b/src/app/server/[server]/page.tsx index 601b48a..f52d777 100644 --- a/src/app/server/[server]/page.tsx +++ b/src/app/server/[server]/page.tsx @@ -48,7 +48,36 @@ export async function generateMetadata( : "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + (json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) + ".png", - twitter: {}, + twitter: { + title: + json.server == null + ? "Server doesn't exist | MHSF" + : json.server.name + + ", " + + (json.server.online + ? json.server.playerCount + + (json.server.maxPlayers != 10 + ? "/" + json.server.maxPlayers + : "") + + " online" + : "Offline") + + " | MHSF", + description: + json.server == null + ? `The server ${server} doesn't exist.` + : `View ${server} on Minehut Server Finder!`, + images: [ + { + url: + "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + + json.server.icon + + ".png", + }, + { + url: "/public/imgs/icon-cf.png", + }, + ], + }, openGraph: { type: "profile", siteName: "MHSF (Minehut Server Finder)", @@ -61,7 +90,7 @@ export async function generateMetadata( ".png", }, { - url: "/favicon.ico", + url: "/public/imgs/icon-cf.png", }, ], }, diff --git a/src/components/AfterServerView.tsx b/src/components/AfterServerView.tsx index 934d5f4..465f9f6 100644 --- a/src/components/AfterServerView.tsx +++ b/src/components/AfterServerView.tsx @@ -17,6 +17,7 @@ import { Copy, Info } from "lucide-react"; import toast, { CheckmarkIcon } from "react-hot-toast"; import { MHSF } from "@/lib/mhsf"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; +import AchievementList from "./feat/AchievementList"; export default function AfterServerView({ server }: { server: string }) { const [description, setDescription] = useState(""); @@ -24,7 +25,9 @@ export default function AfterServerView({ server }: { server: string }) { const [mhsf, setMHSF] = useState(new MHSF()); const { resolvedTheme } = useTheme(); const [loading, setLoading] = useState(true); - const [view, setView] = useState("desc"); + const [view, setView] = useState( + description !== "" || discord !== "" ? "desc" : "extra" + ); const [serverObject, setServerObject] = useState( undefined ); @@ -51,20 +54,16 @@ export default function AfterServerView({ server }: { server: string }) { return ( <> -
+
- + {(description != "" || discord != "") && ( + + )} +
- {(description != "" || discord != "") && ( -
-
+
+
+ {(description != "" || discord != "") && ( - -
+ )} + +
- )} +
{description != "" && view == "desc" && ( @@ -118,7 +130,12 @@ export default function AfterServerView({ server }: { server: string }) { )}{" "} - {((description == "" && discord == "") || view == "extra") && ( + {view == "achievements" && ( +
+ +
+ )} + {view == "extra" && (
diff --git a/src/components/PreferencesSidebar.tsx b/src/components/PreferencesSidebar.tsx new file mode 100644 index 0000000..61acd05 --- /dev/null +++ b/src/components/PreferencesSidebar.tsx @@ -0,0 +1,105 @@ +import { Cog, ExternalLink, KeyRound, Link, UserPen } from "lucide-react"; +import { Button } from "./ui/button"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "./ui/resizable"; +import NextLink from "next/link"; +import { useClerk } from "@clerk/nextjs"; + +export function Sidebar({ + children, + curPage, +}: { + children: React.ReactNode; + curPage: string; +}) { + const clerk = useClerk(); + + return ( + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + +
+ {children}{" "} +
+
+ ); +} diff --git a/src/components/SLCustomizePage.tsx b/src/components/SLCustomizePage.tsx new file mode 100644 index 0000000..92e5f27 --- /dev/null +++ b/src/components/SLCustomizePage.tsx @@ -0,0 +1,189 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Button } from "@/components/ui/button"; +import { Switch } from "./ui/switch"; +import { setAccountSL } from "@/lib/api"; +import toast from "react-hot-toast"; +import { useUser } from "@clerk/nextjs"; + +export function SLCustomize() { + const [padding, setPadding] = useState("0"); + const [itemsPerRow, setItemsPerRow] = useState("4"); + const [usePaddingOnSides, setUsePaddingOnSides] = useState(false); + const [advanced, setAdvanced] = useState(false); + const { user } = useUser(); + + useEffect(() => { + setItemsPerRow((user?.publicMetadata.ipr as string | undefined) || "4"); + setPadding((user?.publicMetadata.pad as string | undefined) || "0"); + setUsePaddingOnSides( + (user?.publicMetadata.srv as boolean | undefined) || false + ); + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (padding) setAccountSL(Number(padding), "pad"); + if (usePaddingOnSides) setAccountSL(usePaddingOnSides, "srv"); + if (itemsPerRow) setAccountSL(Number(itemsPerRow), "ipr"); + toast.success("Set account preferences"); + }; + + return ( +
+ + +
+
+ Server Display Settings + + Customize how servers are displayed in the list + +
+
+ + +
+
+
+ +
+ +
+

Padding of servers

+

+ Adjust the spacing between server items in the list. +

+ +
+ + +
+

+ Standard spacing, balanced appearance. +

+
+ + +
+

+ More spacious, easier to distinguish between servers. +

+
+ + +
+

+ Better for larger screens. +

+
+
+ +
+

Items per row

+

+ Set how many server items appear in each row of the list. +

+ +
+ + +
+

+ Balanced size and quantity, suitable for most screens. +

+
+ + +
+

+ More compact view, fit more servers on screen. +

+
+ + +
+

+ Great for monitors/screens with more space. +

+
+
+ +
+
+ + setUsePaddingOnSides(checked as boolean) + } + /> + +
+

+ When enabled, applies padding to individual server items instead + of the entire list container. This can create a more distinct + separation between servers. +

+
+
+ + + +
+
+
+ ); +} diff --git a/src/components/ServerList.tsx b/src/components/ServerList.tsx index 3fb9733..79d4cd7 100644 --- a/src/components/ServerList.tsx +++ b/src/components/ServerList.tsx @@ -70,6 +70,7 @@ import { cn } from "@/lib/utils"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { SignInPopover } from "./clerk/SignInPopoverButton"; import { BentoCard, BentoGrid } from "./effects/bento-grid"; +import { pageFind } from "./misc/Link"; const features = [ { @@ -227,7 +228,7 @@ export default function ServerList() {
<> {(!isSignedIn || hero) && ( -
+
router.push(`/server/${server.name}`)} + onClick={() => + router.push(pageFind(`Server:${server.name}`)) + } >
diff --git a/src/components/feat/AchievementList.tsx b/src/components/feat/AchievementList.tsx new file mode 100644 index 0000000..b4281e7 --- /dev/null +++ b/src/components/feat/AchievementList.tsx @@ -0,0 +1,131 @@ +import { getAchievements } from "@/lib/api"; +import type { Achievement } from "@/lib/types/achievement"; +import { useEffectOnce } from "@/lib/useEffectOnce"; +import { useState } from "react"; +import { Card, CardContent } from "../ui/card"; +import { Medal, Sparkle, Sparkles, Users } from "lucide-react"; +import { Skeleton } from "../ui/skeleton"; +import A from "../misc/Link"; + +export default function AchievementList({ server }: { server: string }) { + const [achievements, setAchievements] = useState< + Array> + >([]); + const [loading, setLoading] = useState(true); + + useEffectOnce(() => { + setAchievements(() => []); + getAchievements(server).then((v) => { + for (const a of v) + a.achievements.forEach((item, interval) => + setAchievements((prev) => [...prev, { interval, ...item }]) + ); + + setLoading(false); + }); + }); + + if (loading) + return ( +
+ + + +
+ ); + + return ( +
+ + Achievements are earned automatically when the server is online. See{" "} + Special:Root + + {achievements + .filter( + (value, index) => listify(achievements).indexOf(value.type) === index + ) + .map((a) => { + const Icon = formalNames[a.type].icon; + return ( +
+ + + + + + +

{formalNames[a.type].description}

+ + Achieved on {new Date(a.date).getMonth()}/ + {new Date(a.date).getDate()}/ + {new Date(a.date).getFullYear()}{" "} + + {new Date(a.date).toLocaleTimeString()} + + +
+
+
+ ); + })} +
+ ); +} + +const formalNames = { + mostJoined: { + title: + "At one time, this server had the most players on the platform!", + description: + "This is awarded to servers that had the number 1 permission at the time of the achievements getting resolved.", + color: "#9aedff", + icon: Medal, + }, + has1kFavorites: { + title: "This server has more than 1,000 favorites on MHSF!", + description: + "This is awarded to servers that had 1,000 favorites at the time of the achievements getting resolved.", + color: "#d064ff", + icon: Sparkle, + }, + has1kTotalJoins: { + title: "This server has more than 1,000 total joins on Minehut!", + description: + "This is awarded to servers that had 1,000 total joins at the time of the achievements getting resolved.", + color: "#aefa1f", + icon: Users, + }, + has100kFavorites: { + title: "This server has more than 100,000 favorites on MHSF!", + description: + "This is awarded to servers that had 100,000 favorites at the time of the achievements getting resolved.", + color: "#fa5b07", + icon: Sparkles, + }, + has100kTotalJoins: { + title: "This server has more than 100,000 total joins on Minehut!", + description: + "This is awarded to servers that had 100,000 total joins at the time of the achievements getting resolved.", + color: "#bdcffa", + icon: Users, + }, +}; + +type WithInterval = K & { + interval: number; +}; + +const listify = (list: WithInterval[]) => { + const newL: Array = []; + + list.forEach((c) => newL.push(c.type)); + + return newL; +}; diff --git a/src/components/misc/DiscordPopover.tsx b/src/components/misc/DiscordPopover.tsx index 8bca89b..6947961 100644 --- a/src/components/misc/DiscordPopover.tsx +++ b/src/components/misc/DiscordPopover.tsx @@ -43,8 +43,8 @@ export function DiscordPopover({ server, get }: { server: string; get: any }) { } useEffect(() => { - setValue(get.discord == null ? "" : get.discord); - form.reset({ id: get.discord == null ? "" : get.discord }); + setValue(get.discord === undefined ? "" : get.discord); + form.reset({ id: get.discord === undefined ? "" : get.discord }); }, [get]); return ( diff --git a/src/components/misc/Link.tsx b/src/components/misc/Link.tsx new file mode 100644 index 0000000..3d11137 --- /dev/null +++ b/src/components/misc/Link.tsx @@ -0,0 +1,65 @@ +import type { ReactNode } from "react"; +import { default as NextLink } from "next/link"; +import { Book, ExternalLink, NotebookText } from "lucide-react"; + +export default function A({ + children, + alt, +}: { + children: string; + alt: string | ReactNode; +}) { + return ( + + {children.startsWith("Docs:") && } + {alt} + + ); +} + +export function ALegacy({ + children, + href, +}: { + children?: string | ReactNode; + href?: string; +}) { + return ( + + {(href || "").startsWith("Docs:") && ( + + )} + {(href || "").startsWith("Wiki:") && ( + + )} + {children} + {(href || "").startsWith("https") && ( + + )} + + ); +} + +export const pageFind = (text: string) => { + if (text.startsWith("Docs:")) { + return "/docs/" + text.substring(5); + } + if (text === "Special:Root") return "/"; + if (text === "Special:Preferences") return "/account/settings"; + if (text === "Special:AccountOptions") return "/account/settings/options"; + if (text.startsWith("Server:") && text.endsWith("/Customization")) + return "/server/" + text.substring(7, text.length - 14) + "/customization"; + if (text.startsWith("Server:")) return "/server/" + text.substring(7); + if (text.startsWith("Wiki:")) + return "https://minehut.wiki.gg/wiki/" + text.substring(5); + if (text.startsWith("GitHub:")) + return "https://github.com/" + text.substring(7); + if (text === "Special:GitHub") + return "https://github.com/DeveloLongScript/MHSF"; + if (text.startsWith("Special:GitHub/")) + return "https://github.com/DeveloLongScript/MHSF/" + text.substring(15); + return text; +}; diff --git a/src/components/misc/MDXElements.tsx b/src/components/misc/MDXElements.tsx new file mode 100644 index 0000000..14db379 --- /dev/null +++ b/src/components/misc/MDXElements.tsx @@ -0,0 +1,30 @@ +import { Book, ExternalLink, NotebookText } from "lucide-react"; +import type { SVGProps } from "react"; + +type MDXElementType = { + [key: string]: (props: any) => JSX.Element; +}; + +const Discord = (props: SVGProps) => ( + + + +); + +export const MDXElements: MDXElementType = { + Discord, + Book: (props) => , + Notebook: (props) => , + ExternalLink: (props) => , +}; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..5f4117f --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/config/docs.ts b/src/config/docs.ts index 50a42fa..1edec78 100644 --- a/src/config/docs.ts +++ b/src/config/docs.ts @@ -1,48 +1,52 @@ export const allFolders: (DocsFolder | Docs)[] = [ - { - title: "Getting Started", - url: "/docs/getting-started", - }, - { - name: "Guides", - docs: [ - { - title: "Linking", - url: "/docs/guides/linking", - }, - { - title: "Owning a Server", - url: "/docs/guides/owning-a-server", - }, - { - title: "Server Customization", - url: "/docs/guides/customization", - }, - { title: "Reporting a server", url: "/docs/guides/reporting-server" }, - ], - }, - { - name: "Advanced", - docs: [ - { title: "Tech Stack", url: "/docs/advanced/tech-stack" }, - { title: "Using the Command-bar", url: "/docs/advanced/command-bar" }, - { title: "Tips with external servers", url: "/docs/advanced/external"} - ], - }, - { - name: "Legal", - docs: [ - { title: "ECA Agreement", url: "/docs/legal/external-content-agreement" }, - ], - }, + { + title: "Getting Started", + url: "/docs/getting-started", + }, + { + title: "Reading", + url: "/docs/reading", + }, + { + name: "Guides", + docs: [ + { + title: "Linking", + url: "/docs/guides/linking", + }, + { + title: "Owning a Server", + url: "/docs/guides/owning-a-server", + }, + { + title: "Server Customization", + url: "/docs/guides/customization", + }, + { title: "Reporting a server", url: "/docs/guides/reporting-server" }, + ], + }, + { + name: "Advanced", + docs: [ + { title: "Tech Stack", url: "/docs/advanced/tech-stack" }, + { title: "Using the Command-bar", url: "/docs/advanced/command-bar" }, + { title: "Tips with external servers", url: "/docs/advanced/external" }, + ], + }, + { + name: "Legal", + docs: [ + { title: "ECA Agreement", url: "/docs/legal/external-content-agreement" }, + ], + }, ]; export type Docs = { - title: string; - url: string; + title: string; + url: string; }; export type DocsFolder = { - name: string; - docs: Array; + name: string; + docs: Array; }; diff --git a/src/config/tags.ts b/src/config/tags.ts index 835fb15..64dc0f3 100644 --- a/src/config/tags.ts +++ b/src/config/tags.ts @@ -15,62 +15,62 @@ const serverCache: any = {}; // __filter: if your name isn't static, set this to true // // You may also use `requestServer()` to grab the offline version of the server from the API, which may get you more information about the server (ServerResponse) -export var allTags: Array<{ - name: (server: OnlineServer) => Promise; - condition: (server: OnlineServer) => Promise; - listCondition?: (server: ServerResponse) => Promise; - tooltipDesc: string; - htmlDocs: string; - docsName: string; - primary: boolean; - role?: - | "default" - | "destructive" - | "outline" - | "secondary" - | "red" - | "orange" - | "yellow" - | "green" - | "lime" - | "blue" - | "teal" - | "cyan" - | "violet" - | "indigo" - | "purple" - | "fuchsia" - | "pink"; - __disab?: boolean; - __filter?: boolean; +export const allTags: Array<{ + name: (server: OnlineServer) => Promise; + condition: (server: OnlineServer) => Promise; + listCondition?: (server: ServerResponse) => Promise; + tooltipDesc: string; + htmlDocs: string; + docsName: string; + primary: boolean; + role?: + | "default" + | "destructive" + | "outline" + | "secondary" + | "red" + | "orange" + | "yellow" + | "green" + | "lime" + | "blue" + | "teal" + | "cyan" + | "violet" + | "indigo" + | "purple" + | "fuchsia" + | "pink"; + __disab?: boolean; + __filter?: boolean; }> = [ - { - name: async () => "Always Online", - condition: async (b: any) => b.staticInfo.alwaysOnline, - tooltipDesc: - '"Always online" means that the server will not shut down until the plan associated with it expires.', - htmlDocs: ` + { + name: async () => "Always Online", + condition: async (b: any) => b.staticInfo.alwaysOnline, + tooltipDesc: + '"Always online" means that the server will not shut down until the plan associated with it expires.', + htmlDocs: ` This tag appears on servers where the plan they are under allows the server to be always online. However, if the plan associated with the tag expires, the server will no longer be Always Online. This is in servers with one of the more expensive plans, or just a server that is external. `, - primary: true, - docsName: "Always Online", - role: "secondary", - __disab: true, - }, - { - name: async (s) => s.staticInfo.planMaxPlayers + " max players", - condition: async (s) => s.staticInfo.planMaxPlayers != null, - tooltipDesc: - "This tag represents the maximum amount of players the server can have at one time.", - docsName: "Max Players", - htmlDocs: - "This tag represents the maximum amount of players the server can have at one time. This doesn't mean the amount of players before the server crashes, it means the amount Minehut said the server can handle or the plan the server is on. However, sometimes it might not appear because the server is external.", - primary: true, - role: "secondary", - __filter: true, - }, - // deprecated - /**{ + primary: true, + docsName: "Always Online", + role: "secondary", + __disab: true, + }, + { + name: async (s) => s.staticInfo.planMaxPlayers + " max players", + condition: async (s) => s.staticInfo.planMaxPlayers != null, + tooltipDesc: + "This tag represents the maximum amount of players the server can have at one time.", + docsName: "Max Players", + htmlDocs: + "This tag represents the maximum amount of players the server can have at one time. This doesn't mean the amount of players before the server crashes, it means the amount Minehut said the server can handle or the plan the server is on. However, sometimes it might not appear because the server is external.", + primary: true, + role: "secondary", + __filter: true, + }, + // deprecated + /**{ name: async () => "Velocity", condition: async (s) => { var type = await requestServer(s); @@ -86,176 +86,176 @@ export var allTags: Array<{ }, */ ]; -export var allCategories: Array<{ - name: string; - condition: (server: OnlineServer) => Promise; - primary: boolean; - role?: - | "default" - | "destructive" - | "outline" - | "secondary" - | "red" - | "orange" - | "yellow" - | "green" - | "lime" - | "blue" - | "teal" - | "cyan" - | "violet" - | "indigo" - | "purple" - | "fuchsia" - | "pink"; +export const allCategories: Array<{ + name: string; + condition: (server: OnlineServer) => Promise; + primary: boolean; + role?: + | "default" + | "destructive" + | "outline" + | "secondary" + | "red" + | "orange" + | "yellow" + | "green" + | "lime" + | "blue" + | "teal" + | "cyan" + | "violet" + | "indigo" + | "purple" + | "fuchsia" + | "pink"; }> = [ - { - name: "Farming", - condition: async (b: any) => { - return b.allCategories.includes("farming"); - }, - primary: true, - role: "secondary", - }, - { - name: "SMP", - condition: async (b: any) => { - return b.allCategories.includes("smp"); - }, - primary: true, - role: "secondary", - }, - { - name: "Factions", - condition: async (b: any) => { - return b.allCategories.includes("factions"); - }, - primary: true, - role: "secondary", - }, - { - name: "Meme", - condition: async (b: any) => { - return b.allCategories.includes("meme"); - }, - primary: true, - role: "secondary", - }, - { - name: "Puzzle", - condition: async (b: any) => { - return b.allCategories.includes("puzzle"); - }, - primary: true, - role: "secondary", - }, - { - name: "Box", - condition: async (b: any) => { - return b.allCategories.includes("box"); - }, - primary: true, - role: "secondary", - }, - { - name: "Minigames", - condition: async (b: any) => { - return b.allCategories.includes("minigames"); - }, - primary: true, - role: "secondary", - }, - { - name: "RPG", - condition: async (b: any) => { - return b.allCategories.includes("rpg"); - }, - primary: true, - role: "secondary", - }, - { - name: "Parkour", - condition: async (b: any) => { - return b.allCategories.includes("parkour"); - }, - primary: true, - role: "secondary", - }, - { - name: "Lifesteal", - condition: async (b: any) => { - return b.allCategories.includes("lifesteal"); - }, - primary: true, - role: "secondary", - }, - { - name: "Prison", - condition: async (b: any) => { - return b.allCategories.includes("prison"); - }, - primary: true, - role: "secondary", - }, - { - name: "Gens", - condition: async (b: any) => { - return b.allCategories.includes("gens"); - }, - primary: true, - role: "secondary", - }, - { - name: "Skyblock", - condition: async (b: any) => { - return b.allCategories.includes("skyblock"); - }, - primary: true, - role: "secondary", - }, - { - name: "Roleplay", - condition: async (b: any) => { - return b.allCategories.includes("roleplay"); - }, - primary: true, - role: "secondary", - }, - { - name: "PvP", - condition: async (b: any) => { - return b.allCategories.includes("pvp"); - }, - primary: true, - role: "secondary", - }, - { - name: "Modded", - condition: async (b: any) => { - return b.allCategories.includes("modded"); - }, - primary: true, - role: "secondary", - }, - { - name: "Creative", - condition: async (b: any) => { - return b.allCategories.includes("creative"); - }, - primary: true, - role: "secondary", - }, + { + name: "Farming", + condition: async (b: any) => { + return b.allCategories.includes("farming"); + }, + primary: true, + role: "secondary", + }, + { + name: "SMP", + condition: async (b: any) => { + return b.allCategories.includes("smp"); + }, + primary: true, + role: "secondary", + }, + { + name: "Factions", + condition: async (b: any) => { + return b.allCategories.includes("factions"); + }, + primary: true, + role: "secondary", + }, + { + name: "Meme", + condition: async (b: any) => { + return b.allCategories.includes("meme"); + }, + primary: true, + role: "secondary", + }, + { + name: "Puzzle", + condition: async (b: any) => { + return b.allCategories.includes("puzzle"); + }, + primary: true, + role: "secondary", + }, + { + name: "Box", + condition: async (b: any) => { + return b.allCategories.includes("box"); + }, + primary: true, + role: "secondary", + }, + { + name: "Minigames", + condition: async (b: any) => { + return b.allCategories.includes("minigames"); + }, + primary: true, + role: "secondary", + }, + { + name: "RPG", + condition: async (b: any) => { + return b.allCategories.includes("rpg"); + }, + primary: true, + role: "secondary", + }, + { + name: "Parkour", + condition: async (b: any) => { + return b.allCategories.includes("parkour"); + }, + primary: true, + role: "secondary", + }, + { + name: "Lifesteal", + condition: async (b: any) => { + return b.allCategories.includes("lifesteal"); + }, + primary: true, + role: "secondary", + }, + { + name: "Prison", + condition: async (b: any) => { + return b.allCategories.includes("prison"); + }, + primary: true, + role: "secondary", + }, + { + name: "Gens", + condition: async (b: any) => { + return b.allCategories.includes("gens"); + }, + primary: true, + role: "secondary", + }, + { + name: "Skyblock", + condition: async (b: any) => { + return b.allCategories.includes("skyblock"); + }, + primary: true, + role: "secondary", + }, + { + name: "Roleplay", + condition: async (b: any) => { + return b.allCategories.includes("roleplay"); + }, + primary: true, + role: "secondary", + }, + { + name: "PvP", + condition: async (b: any) => { + return b.allCategories.includes("pvp"); + }, + primary: true, + role: "secondary", + }, + { + name: "Modded", + condition: async (b: any) => { + return b.allCategories.includes("modded"); + }, + primary: true, + role: "secondary", + }, + { + name: "Creative", + condition: async (b: any) => { + return b.allCategories.includes("creative"); + }, + primary: true, + role: "secondary", + }, ]; async function requestServer(s: OnlineServer): Promise { - if (serverCache[s.name] == undefined) { - const re = await fetch( - "https://api.minehut.com/server/" + s.name + "?byName=true" - ); - const json = await re.json(); - serverCache[s.name] = json.server; - return json.server; - } else { - return serverCache[s.name]; - } + if (serverCache[s.name] == undefined) { + const re = await fetch( + "https://api.minehut.com/server/" + s.name + "?byName=true", + ); + const json = await re.json(); + serverCache[s.name] = json.server; + return json.server; + } else { + return serverCache[s.name]; + } } diff --git a/src/lib/api.ts b/src/lib/api.ts index dad01da..37749a5 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -5,12 +5,30 @@ */ // +import { Achievement } from "./types/achievement"; + const connector = ( endpoint: string, options: { version: number; starting?: string }, ) => `${options.starting == undefined ? "/" : `${options.starting}/`}api/v${options.version}${endpoint}`; +async function apiConstructor( + connector: string, + requestInit: RequestInit, + modifier: (data: any) => K, +): Promise { + try { + const response = await fetch(connector, requestInit); + + if (response.status >= 400) { + throw Error("Error while running API"); + } + return modifier(await response.json()); + } catch { + throw Error("Error while running API"); + } +} export async function getMOTDFromServer( list: Array<{ server: string; motd: string }>, ): Promise> { @@ -428,3 +446,10 @@ export async function setAccountSL( throw Error("Error while running API"); } } + +export const getAchievements = async (server: string) => + apiConstructor<{ _id: string; name: string; achievements: Achievement[] }[]>( + connector(`/achievements/${server}`, { version: 0 }), + {}, + (data) => data.result, + ); diff --git a/src/lib/types/achievement.ts b/src/lib/types/achievement.ts new file mode 100644 index 0000000..985e31f --- /dev/null +++ b/src/lib/types/achievement.ts @@ -0,0 +1,18 @@ +export type Achievement = { + type: + | "mostJoined" + | "has1kFavorites" + | "has1kTotalJoins" + | "has100kFavorites" + | "has100kTotalJoins"; + /** The ISO time of the gaining of the achievement. */ + date: string; +}; + +export const orderOfAchievements = [ + "mostJoined", + "has100kFavorites", + "has100kTotalJoins", + "has1kFavorites", + "has1kTotalJoins", +]; diff --git a/src/pages/api/v0/achievements/[server].ts b/src/pages/api/v0/achievements/[server].ts new file mode 100644 index 0000000..ef599e9 --- /dev/null +++ b/src/pages/api/v0/achievements/[server].ts @@ -0,0 +1,18 @@ +import { MongoClient } from "mongodb"; +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { server } = req.query; + if (!server) return res.status(400).send({ error: "No server was provided" }); + + const client = new MongoClient(process.env.MONGO_DB as string); + await client.connect(); + + const db = client.db("mhsf"); + const collection = db.collection("achievements"); + + res.send({ result: await collection.find({ name: server }).toArray() }); +} diff --git a/src/pages/api/v0/achievements/multiple.ts b/src/pages/api/v0/achievements/multiple.ts new file mode 100644 index 0000000..eda5e8a --- /dev/null +++ b/src/pages/api/v0/achievements/multiple.ts @@ -0,0 +1 @@ +// TODO: make multiple endpoint to allow achievements to be shown on the server-list diff --git a/yarn.lock b/yarn.lock index ac000cc..b52651b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -150,6 +150,60 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@biomejs/biome@^1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.8.3.tgz#3b5eecea90d973f71618aae3e6e8be4d2ca23e42" + integrity sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "1.8.3" + "@biomejs/cli-darwin-x64" "1.8.3" + "@biomejs/cli-linux-arm64" "1.8.3" + "@biomejs/cli-linux-arm64-musl" "1.8.3" + "@biomejs/cli-linux-x64" "1.8.3" + "@biomejs/cli-linux-x64-musl" "1.8.3" + "@biomejs/cli-win32-arm64" "1.8.3" + "@biomejs/cli-win32-x64" "1.8.3" + +"@biomejs/cli-darwin-arm64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.8.3.tgz#be2bfdd445cd2d3cb0ff41a96a72ec761753997c" + integrity sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A== + +"@biomejs/cli-darwin-x64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.8.3.tgz#47d408edd9f5c04069fbcf8610bacf1db8c6c0d9" + integrity sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw== + +"@biomejs/cli-linux-arm64-musl@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.8.3.tgz#44df284383d57cf4f28daeedd080dad7be05df78" + integrity sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ== + +"@biomejs/cli-linux-arm64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.8.3.tgz#6a6b1da1dfce0294a028cbb5d6c40d73691dd713" + integrity sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw== + +"@biomejs/cli-linux-x64-musl@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.8.3.tgz#ceef30a8ee1a00d4ad31e32dd31ba2a661f2719d" + integrity sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA== + +"@biomejs/cli-linux-x64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-1.8.3.tgz#665df74d19fb8f83001a9d80824d3a1723e2123f" + integrity sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw== + +"@biomejs/cli-win32-arm64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.8.3.tgz#0fb6f58990f4de0331a6ed22c47c66f5a89133cc" + integrity sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ== + +"@biomejs/cli-win32-x64@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.8.3.tgz#6a9dc5a4e13357277da43c015cd5cdc374035448" + integrity sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg== + "@clerk/backend@1.2.2": version "1.2.2" resolved "https://registry.npmjs.org/@clerk/backend/-/backend-1.2.2.tgz" @@ -1089,20 +1143,19 @@ dependencies: "@radix-ui/react-primitive" "2.0.0" -"@radix-ui/react-checkbox@^1.0.4": - version "1.0.4" - resolved "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz" - integrity sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg== +"@radix-ui/react-checkbox@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6" + integrity sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-controllable-state" "1.0.1" - "@radix-ui/react-use-previous" "1.0.1" - "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@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-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" "@radix-ui/react-collection@1.0.3": version "1.0.3" @@ -1506,29 +1559,28 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" -"@radix-ui/react-primitive@2.0.0": +"@radix-ui/react-primitive@2.0.0", "@radix-ui/react-primitive@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== dependencies: "@radix-ui/react-slot" "1.1.0" -"@radix-ui/react-radio-group@^1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz" - integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag== +"@radix-ui/react-radio-group@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.2.0.tgz#f937dd6b9436ded80c4bebdf3901c20cb8bcbb5a" + integrity sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-roving-focus" "1.0.4" - "@radix-ui/react-use-controllable-state" "1.0.1" - "@radix-ui/react-use-previous" "1.0.1" - "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-roving-focus" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" @@ -1599,6 +1651,19 @@ dependencies: "@radix-ui/react-compose-refs" "1.1.0" +"@radix-ui/react-switch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.0.tgz#fcf8e778500f1d60d4b2bec2fc3fad77a7c118e3" + integrity sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw== + 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-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/react-tabs@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0" @@ -1693,6 +1758,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-previous@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c" + integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og== + "@radix-ui/react-use-rect@1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz" @@ -1930,6 +2000,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/luxon@~3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7" + integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA== + "@types/mdast@^3.0.0": version "3.0.15" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5" @@ -2846,6 +2921,14 @@ core-util-is@^1.0.3: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cron@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.7.tgz#3423d618ba625e78458fff8cb67001672d49ba0d" + integrity sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw== + dependencies: + "@types/luxon" "~3.4.0" + luxon "~3.4.0" + cross-fetch@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" @@ -4905,6 +4988,11 @@ lucide-react@^0.416.0: resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.416.0.tgz#657da248f9b862703d7d80aafb912e79ad886313" integrity sha512-wPWxTzdss1CTz2aqcNWNlbh4YSnH9neJWP3RaeXepxpLCTW+pmu7WcT/wxJe+Q7Y7DqGOxAqakJv0pIK3431Ag== +luxon@~3.4.0: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + magic-bytes.js@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92" @@ -6552,6 +6640,11 @@ react-fade-in@^2.0.1: resolved "https://registry.yarnpkg.com/react-fade-in/-/react-fade-in-2.0.1.tgz#b4bcd7dac63d6857ebcd68facbff2f5f9616278f" integrity sha512-oqS/WT4znaXEHmL+yo0IDUDY7uC9K4RP35j1SdRUEBspR09B2iIC0i8oJ28tPOr6Ez/L2aktF9p89j+DbsTVNw== +react-fast-marquee@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/react-fast-marquee/-/react-fast-marquee-1.6.5.tgz#98929ae93eef087a607a71e9d45ab76bba97dc16" + integrity sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg== + react-hook-form@^7.52.2: version "7.52.2" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.2.tgz#ff40f4776250b86ddfcde6be68d34aa82b1c60fe"