feat: achievements

This commit is contained in:
dvelo 2024-09-08 22:34:51 -05:00
parent 190024aef5
commit 5699da4dc4
33 changed files with 1453 additions and 1059 deletions

10
biome.json Normal file

@ -0,0 +1,10 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"style": {
"useTemplate": "off"
}
}
}
}

@ -122,24 +122,14 @@ async function periodicCronJob() {
"[CRON] " + "[CRON] " +
INFO + INFO +
" Remaining servers: " + " Remaining servers: " +
(y + "/" + mh.servers.length) (y + "/" + mh.servers.length),
); );
y++; y++;
if (y == mh.servers.length) { if (y == mh.servers.length) {
process.stdout.clearLine(0); process.stdout.clearLine(0);
process.stdout.cursorTo(0); process.stdout.cursorTo(0);
process.stdout.write( process.stdout.write(
"[CRON] " + SUCCESS + " Finished! Closing MongoDB connection." "[CRON] " + SUCCESS + " Finished! Closing MongoDB connection.",
);
// Close connection
await mongo
.close()
.catch((e) =>
console.log(
"[CRON] " + WARN + " Error while closing MongoDB connection:",
e
)
); );
return; return;
@ -200,7 +190,7 @@ async function achievementTask() {
const achievementsTsk = await achievementEngine( const achievementsTsk = await achievementEngine(
srvExt, srvExt,
prevAchievements.achievements prevAchievements.achievements,
); );
await achievements.insertOne({ await achievements.insertOne({
@ -217,7 +207,7 @@ async function achievementTask() {
async function achievementEngine( async function achievementEngine(
server: OnlineServerExtended, server: OnlineServerExtended,
currentAchievements: Achievement[] currentAchievements: Achievement[],
): Promise<Achievement[]> { ): Promise<Achievement[]> {
const achievements: Array<Achievement> = []; const achievements: Array<Achievement> = [];
@ -247,7 +237,7 @@ async function achievementEngine(
) { ) {
const v: { server: ServerResponse } = await ( const v: { server: ServerResponse } = await (
await fetch( await fetch(
"https://api.minehut.com/server/" + server.name + "?byName=true" "https://api.minehut.com/server/" + server.name + "?byName=true",
) )
).json(); ).json();
@ -264,7 +254,7 @@ async function achievementEngine(
) { ) {
const v: { server: ServerResponse } = await ( const v: { server: ServerResponse } = await (
await fetch( await fetch(
"https://api.minehut.com/server/" + server.name + "?byName=true" "https://api.minehut.com/server/" + server.name + "?byName=true",
) )
).json(); ).json();

@ -4,7 +4,7 @@ title: "Troubleshooting: Making external servers on Minehut"
# 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 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.
<br/> <br/>
<Separator/> <Separator/>
@ -87,7 +87,7 @@ proxy-protocol: true
``` ```
## Thats it! ## 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? ## 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. Go to the offical <Discord/> Minehut Discord server and go into the [#ask-for-help](https://discord.com/channels/239599059415859200/1014801630295760897) channel and create a thread.

@ -28,4 +28,4 @@ No. Your Minehut account is not associated with your MHSF one, and consequently,
## Conclusion ## 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.

@ -3,10 +3,10 @@ title: "Customization"
--- ---
# Customize your server # 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 ## 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 ## Customization Types
### Discord Server ### 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. You can use Markdown formatting to add a description to describe what your server is.
## Thats it! ## 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) 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)

@ -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: You can do many things with a linked account:
- [Own a server](/docs/guides/owning-a-server) - [Own a server](Docs:guides/owning-a-server)
- [Customize a server](/docs/guides/customization) - [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. More will be coming in future updates, however the only thing you can do with a linked account is server-based functions.

@ -4,11 +4,11 @@ title: "Own a server"
# Owning 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 ## 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! 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 ## I can't link my server, because my server doesn't have a author

@ -4,7 +4,7 @@ title: "Reporting a server"
# 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. 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. Your report will be processed and the appropriate action will be taken.

@ -12,14 +12,14 @@ uploaded onto the platform are.
## Source Code ## 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 ## What your limits are
When creating content, if its a matter of making a profile picture, When creating content, if its a matter of making a profile picture,
or editing the description for a server, (and more), you must follow or editing the description for a server, (and more), you must follow
the underlying agreements below.<br/> the underlying agreements below.<br/>
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)._<br/> 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)._<br/>
For making Discord server embeds, you must follow [Discords Terms of Service](https://discord.com/terms/) _as all content made is associated to Discord._ 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 ### All other content

30
docs/reading.mdx Normal file

@ -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.
<Separator/>
## Icons
When looking at a link, there will be symbols used that indicate where the link is going: *(these apply to the whole site)*
- <Notebook className="inline-block mb-1" size={20}/> indicates the link will link to the [official Minehut wiki](Wiki:/)
- <Book className="inline-block mb-1" size={20} /> indicates the link will link to another page on the docs
- <ExternalLink className="inline-block mb-1" size={20}/> indicates the link will go to an external site
When contributing, these links are as follows:
```
Wiki:<wiki url after https://minehut.wiki.gg/wiki/>
Docs:<docs url after /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. <br/>
<small>The source code for above is stored [here](Special:GitHub/edit/main/src/components/misc/Link.tsx)</small>

@ -14,6 +14,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/parser": "^7.24.7", "@babel/parser": "^7.24.7",
"@biomejs/biome": "^1.8.3",
"@clerk/nextjs": "^5.1.3", "@clerk/nextjs": "^5.1.3",
"@emotion/is-prop-valid": "^1.3.0", "@emotion/is-prop-valid": "^1.3.0",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
@ -48,8 +49,6 @@
"react-dom": "^18", "react-dom": "^18",
"react-fade-in": "^2.0.1", "react-fade-in": "^2.0.1",
"react-fast-marquee": "^1.6.5", "react-fast-marquee": "^1.6.5",
"react-marquee-text": "^1.0.4",
"react-smart-ticker": "^1.2.0",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",

@ -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 (
<span className="pt-[48px]">
<Sidebar curPage={pathname as string}>{children}</Sidebar>
</span>
);
}

@ -1,14 +1,7 @@
"use client"; "use client";
import { Button } from "@/components/ui/button";
import { useClerk, useUser } from "@clerk/nextjs"; 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 { useEffect, useState } from "react";
import { SLCustomize } from "@/components/SLCustomizePage";
export default function Settings() { export default function Settings() {
const clerk = useClerk(); const clerk = useClerk();
@ -20,348 +13,11 @@ export default function Settings() {
}, [user, isSignedIn]); }, [user, isSignedIn]);
return ( return (
<main className="pt-[48px] p-4"> <main className="p-4">
<ResizablePanelGroup
direction="horizontal"
className="min-h-[calc(100vh-70px)] "
>
<ResizablePanel className="min-w-[285px] max-w-[285px] w-[285px] max-md:hidden">
<div className="w-[300px] mt-[20px] ml-[10px]">
<NextLink href="/account/settings" className="text-inherit">
<Button className="mb-[2px] w-[250px]" variant="ghost">
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<Button className="mb-[2px] w-[250px]">
<Cog size={16} className="mr-2" /> Options
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
</ResizablePanel>
<ResizableHandle className="max-md:hidden" />
<ResizablePanel>
<div className="p-4">
<div className="md:hidden">
<NextLink href="/account/settings" className="text-inherit">
<Button className="mr-[2px]" variant="ghost">
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<Button className="mr-[2px] ">
<Cog size={16} className="mr-2" /> Options
</Button>
<Button
className="mr-[2px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mr-[2px] mb-[30px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
<strong className="text-3xl">Profile Preferences</strong> <strong className="text-3xl">Profile Preferences</strong>
<br /> <br />
<br /> <br />
<div className="md:grid md:grid-cols-3 gap-1.5"> <SLCustomize />
<Card>
<CardContent>
<br />
<PaddingRadioForm />
</CardContent>
</Card>
<br className="md:hidden" />
<Card>
<CardContent>
<br />
<RowRadioForm />
</CardContent>
</Card>
<br className="md:hidden" />
<Card>
<CardContent>
<br />
<ServerPaddingForm />
</CardContent>
</Card>
</div>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</main> </main>
); );
} }
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<z.infer<typeof FormSchemaGrid>>({
resolver: zodResolver(FormSchemaGrid),
defaultValues: {
grid: user?.publicMetadata.ipr as "4" | "5" | "6" | undefined,
},
});
function onSubmit(data: z.infer<typeof FormSchemaGrid>) {
toast.promise(setAccountSL(Number.parseInt(data.grid), "ipr"), {
loading: "Saving...",
success: "Saved!",
error: "Failed to save",
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="grid"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Items per row</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="4" />
</FormControl>
<FormLabel className="font-normal">
4 items per row
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="5" />
</FormControl>
<FormLabel className="font-normal">
5 items per row
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="6" />
</FormControl>
<FormLabel className="font-normal">
6 items per row
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}
function PaddingRadioForm() {
const { user } = useUser();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
type: user?.publicMetadata.pad as
| "15"
| "30"
| "40"
| "60"
| "100"
| "200"
| undefined,
},
});
function onSubmit(data: z.infer<typeof FormSchema>) {
toast.promise(setAccountSL(Number.parseInt(data.type), "pad"), {
loading: "Saving...",
success: "Saved!",
error: "Failed to save",
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Padding of servers</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="15" />
</FormControl>
<FormLabel className="font-normal">15px</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="30" />
</FormControl>
<FormLabel className="font-normal">30px</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="40" />
</FormControl>
<FormLabel className="font-normal">40px</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="60" />
</FormControl>
<FormLabel className="font-normal">60px</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="100" />
</FormControl>
<FormLabel className="font-normal">100px</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="200" />
</FormControl>
<FormLabel className="font-normal">200px</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
<Button
onClick={() => setAccountSL(null, "pad")}
variant="outline"
type="button"
className="ml-2"
>
Clear
</Button>
</form>
</Form>
);
}
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<z.infer<typeof FormSchemaCB>>({
resolver: zodResolver(FormSchemaCB),
defaultValues: {
padding: srv === undefined ? false : srv,
},
});
function onSubmit(data: z.infer<typeof FormSchemaCB>) {
toast.promise(
setAccountSL(data.padding === undefined ? false : data.padding, "srv"),
{
loading: "Saving...",
success: "Saved!",
error: "Failed to save",
}
);
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="padding"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
Use padding on the sides of only the servers, not the whole
server list
</FormLabel>
<FormDescription>
Only show the padding settings on the servers themselves, not
the whole entire page of the server list.
</FormDescription>
</div>
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}

@ -1,13 +1,6 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useClerk, useUser } from "@clerk/nextjs"; 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 toast from "react-hot-toast";
import { unlinkMCAccount } from "@/lib/api"; import { unlinkMCAccount } from "@/lib/api";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -25,71 +18,7 @@ export default function Settings() {
}, [user, isSignedIn]); }, [user, isSignedIn]);
return ( return (
<main className="pt-[48px] p-4"> <main className="p-4">
<ResizablePanelGroup
direction="horizontal"
className="min-h-[calc(100vh-70px)]"
>
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
<div className="w-[300px] mt-[20px] ml-[10px]">
<Button className="mb-[2px] w-[250px]">
<Link size={16} className="mr-2" /> Linking
</Button>
<NextLink href="/account/settings/options" className="text-inherit">
<Button className="mb-[2px] w-[250px] " variant="ghost">
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
</ResizablePanel>
<ResizableHandle className="max-md:hidden" />
<ResizablePanel>
<div className="p-4">
<div className="md:hidden">
<Button className="mr-[2px]">
<Link size={16} className="mr-2" /> Linking
</Button>
<NextLink
href="/account/settings/options"
className="text-inherit"
>
<Button className="mr-[2px] " variant="ghost">
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mr-[2px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mr-[2px] mb-[30px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
<strong className="text-3xl">Linking</strong> <strong className="text-3xl">Linking</strong>
<br /> <br />
<br /> <br />
@ -99,17 +28,13 @@ export default function Settings() {
Link a Minecraft account to customize a server you own. Link a Minecraft account to customize a server you own.
<br />{" "} <br />{" "}
{user?.publicMetadata.player != undefined && linked && ( {user?.publicMetadata.player != undefined && linked && (
<> <>Currently linked to {user?.publicMetadata.player as string}</>
Currently linked to {user?.publicMetadata.player as string}
</>
)} )}
</p> </p>
<Dialog> <Dialog>
<DialogTrigger> <DialogTrigger>
{!linked && ( {!linked && <Button className="h-[30px] ml-2">Link Account</Button>}
<Button className="h-[30px] ml-2">Link Account</Button>
)}
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<CodeDialog <CodeDialog
@ -130,9 +55,7 @@ export default function Settings() {
<br /> <br />
<strong className="font-bold">Unlink Account</strong> <strong className="font-bold">Unlink Account</strong>
<div className="flex items-center"> <div className="flex items-center">
<p> <p>Unlink your Minecraft acconut if you have already linked one.</p>
Unlink your Minecraft acconut if you have already linked one.
</p>
{!linked && ( {!linked && (
<Button className="h-[30px] ml-2" disabled> <Button className="h-[30px] ml-2" disabled>
@ -158,12 +81,9 @@ export default function Settings() {
)} )}
</div> </div>
<small className="mt-0"> <small className="mt-0">
All of your customizations stay the same, and can be changed if{" "} All of your customizations stay the same, and can be changed if another
another account links your Minecraft account. account links your Minecraft account.
</small> </small>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</main> </main>
); );
} }

@ -2,9 +2,11 @@ import TableOfContent from "@/components/docs/TOC";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { allDocs } from "contentlayer/generated"; import { allDocs } from "contentlayer/generated";
import { useMDXComponent } from "next-contentlayer/hooks"; import { useMDXComponent } from "next-contentlayer/hooks";
import Link from "next/link"; import NextLink from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { ALegacy } from "@/components/misc/Link";
import { MDXElements } from "@/components/misc/MDXElements";
export const generateStaticParams = async () => export const generateStaticParams = async () =>
allDocs.map((post) => ({ slug: [post._raw.flattenedPath] })); allDocs.map((post) => ({ slug: [post._raw.flattenedPath] }));
@ -34,7 +36,13 @@ const PostLayout = ({ params }: { params: { slug: string[] } }) => {
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]"> <main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
<div className="mx-auto w-full min-w-0"> <div className="mx-auto w-full min-w-0">
<div className="pb-12 pt-8 prose dark:prose-invert"> <div className="pb-12 pt-8 prose dark:prose-invert">
<MDXContent components={{ Separator }} /> <MDXContent
components={{
Separator,
a: (props) => <ALegacy {...props} />,
...MDXElements,
}}
/>
</div> </div>
</div> </div>
{doc.toc && ( {doc.toc && (
@ -53,7 +61,7 @@ const PostLayout = ({ params }: { params: { slug: string[] } }) => {
<p className="font-medium">Contribute</p> <p className="font-medium">Contribute</p>
<ul className="m-0 list-none"> <ul className="m-0 list-none">
<li className="mt-0 pt-2"> <li className="mt-0 pt-2">
<Link <NextLink
href={ href={
"https://github.com/DeveloLongScript/MHSF/edit/main/docs/" + "https://github.com/DeveloLongScript/MHSF/edit/main/docs/" +
doc._raw.flattenedPath + doc._raw.flattenedPath +
@ -71,10 +79,10 @@ const PostLayout = ({ params }: { params: { slug: string[] } }) => {
<path <path
fill="currentColor" fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z" d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path> />
</svg> </svg>
Edit page on GitHub Edit page on GitHub
</Link> </NextLink>
</li> </li>
</ul> </ul>
</div> </div>

@ -24,10 +24,17 @@ import { BrandingGenericIcon } from "@/components/Icon";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata = { export const metadata = {
twitter: {
images: [
{
url: "/public/imgs/icon-cf.png",
},
],
},
openGraph: { openGraph: {
images: [ images: [
{ {
url: "/branding/meta-banner.png", url: "/public/imgs/icon-cf.png",
}, },
], ],
}, },

@ -48,7 +48,36 @@ export async function generateMetadata(
: "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" + : "https://minehut-server-icons-live.s3.us-west-2.amazonaws.com/" +
(json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) + (json.server.icon == undefined ? "OAK_SIGN" : json.server.icon) +
".png", ".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: { openGraph: {
type: "profile", type: "profile",
siteName: "MHSF (Minehut Server Finder)", siteName: "MHSF (Minehut Server Finder)",
@ -61,7 +90,7 @@ export async function generateMetadata(
".png", ".png",
}, },
{ {
url: "/favicon.ico", url: "/public/imgs/icon-cf.png",
}, },
], ],
}, },

@ -17,6 +17,7 @@ import { Copy, Info } from "lucide-react";
import toast, { CheckmarkIcon } from "react-hot-toast"; import toast, { CheckmarkIcon } from "react-hot-toast";
import { MHSF } from "@/lib/mhsf"; import { MHSF } from "@/lib/mhsf";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
import AchievementList from "./feat/AchievementList";
export default function AfterServerView({ server }: { server: string }) { export default function AfterServerView({ server }: { server: string }) {
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
@ -24,7 +25,9 @@ export default function AfterServerView({ server }: { server: string }) {
const [mhsf, setMHSF] = useState(new MHSF()); const [mhsf, setMHSF] = useState(new MHSF());
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [view, setView] = useState("desc"); const [view, setView] = useState(
description !== "" || discord !== "" ? "desc" : "extra"
);
const [serverObject, setServerObject] = useState<ServerResponse | undefined>( const [serverObject, setServerObject] = useState<ServerResponse | undefined>(
undefined undefined
); );
@ -51,20 +54,16 @@ export default function AfterServerView({ server }: { server: string }) {
return ( return (
<> <>
<FadeIn> <FadeIn>
<div <div className="grid sm:grid-cols-6 h-full pl-4 pr-4">
className={
description != "" || discord != ""
? "grid sm:grid-cols-6 h-full pl-4 pr-4"
: ""
}
>
<div className="ml-5 mb-2 flex items-center sm:hidden"> <div className="ml-5 mb-2 flex items-center sm:hidden">
{(description != "" || discord != "") && (
<Button <Button
variant={view == "desc" ? undefined : "ghost"} variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")} onClick={() => setView("desc")}
> >
Description Description
</Button> </Button>
)}
<Button <Button
variant={view == "extra" ? undefined : "ghost"} variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")} onClick={() => setView("extra")}
@ -72,25 +71,38 @@ export default function AfterServerView({ server }: { server: string }) {
> >
Server Information Server Information
</Button> </Button>
<Button
variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
className="ml-2"
>
Achievements
</Button>
</div> </div>
{(description != "" || discord != "") && (
<div className="max-sm:hidden"> <div className="max-sm:hidden">
<div className="grid"> <div className="grid">
{(description != "" || discord != "") && (
<Button <Button
variant={view == "desc" ? undefined : "ghost"} variant={view == "desc" ? undefined : "ghost"}
onClick={() => setView("desc")} onClick={() => setView("desc")}
> >
Description Description
</Button> </Button>
)}
<Button <Button
variant={view == "extra" ? undefined : "ghost"} variant={view == "extra" ? undefined : "ghost"}
onClick={() => setView("extra")} onClick={() => setView("extra")}
> >
Server Information Server Information
</Button> </Button>
<Button
variant={view == "achievements" ? undefined : "ghost"}
onClick={() => setView("achievements")}
>
Achievements
</Button>
</div> </div>
</div> </div>
)}
<div className="grid lg:grid-cols-4 pl-4 pr-4 gap-3.5 col-span-5"> <div className="grid lg:grid-cols-4 pl-4 pr-4 gap-3.5 col-span-5">
{description != "" && view == "desc" && ( {description != "" && view == "desc" && (
@ -118,7 +130,12 @@ export default function AfterServerView({ server }: { server: string }) {
</CardHeader> </CardHeader>
</Card> </Card>
)}{" "} )}{" "}
{((description == "" && discord == "") || view == "extra") && ( {view == "achievements" && (
<div className="col-span-4">
<AchievementList server={server} />
</div>
)}
{view == "extra" && (
<div className="sm:grid sm:grid-cols-3 col-span-4 gap-4"> <div className="sm:grid sm:grid-cols-3 col-span-4 gap-4">
<Card> <Card>
<CardHeader> <CardHeader>

@ -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 (
<ResizablePanelGroup
direction="horizontal"
className="min-h-[calc(100vh-70px)] pt-[70px]"
>
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
<div className="w-[300px] ml-[10px]">
<NextLink href="/account/settings" className="text-inherit">
<Button
className="mb-[2px] w-[250px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink href="/account/settings/options" className="text-inherit">
<Button
className="mb-[2px] w-[250px] "
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mb-[2px] w-[250px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
</ResizablePanel>
<ResizableHandle className="max-md:hidden" />
<ResizablePanel>
<div className="md:hidden ml-2">
<NextLink href="/account/settings" className="text-inherit">
<Button
className="mr-[2px]"
variant={curPage !== "/account/settings" ? "ghost" : "default"}
>
<Link size={16} className="mr-2" /> Linking
</Button>
</NextLink>
<NextLink href="/account/settings/options" className="text-inherit">
<Button
className="mr-[2px]"
variant={
curPage !== "/account/settings/options" ? "ghost" : "default"
}
>
<Cog size={16} className="mr-2" /> Options
</Button>
</NextLink>
<Button
className="mr-[2px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<UserPen size={16} className="mr-2" /> Profile{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
<Button
className="mr-[2px] mb-[30px]"
variant="ghost"
onClick={() => clerk.openUserProfile({})}
>
<KeyRound size={16} className="mr-2" /> Security{" "}
<ExternalLink size={16} className="ml-2" />
</Button>
</div>
{children}{" "}
</ResizablePanel>
</ResizablePanelGroup>
);
}

@ -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 (
<div className="container mx-auto p-4">
<Card className="w-full max-w-2xl mx-auto">
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>Server Display Settings</CardTitle>
<CardDescription className="pt-2">
Customize how servers are displayed in the list
</CardDescription>
</div>
<div className="flex items-center space-x-2">
<Switch
id="advanced-mode"
checked={advanced}
onCheckedChange={setAdvanced}
/>
<Label htmlFor="advanced-mode">Advanced Mode</Label>
</div>
</div>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-8">
<div className="space-y-4">
<h3 className="text-lg font-medium">Padding of servers</h3>
<p className="text-sm text-muted-foreground">
Adjust the spacing between server items in the list.
</p>
<RadioGroup value={padding} onValueChange={setPadding}>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="0" id="padding-normal" />
<Label htmlFor="padding-normal">
Normal{" "}
{advanced && (
<span className="text-muted-foreground">(0px)</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
Standard spacing, balanced appearance.
</p>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="15" id="padding-relaxed" />
<Label htmlFor="padding-normal">
Relaxed{" "}
{advanced && (
<span className="text-muted-foreground">(15px)</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
More spacious, easier to distinguish between servers.
</p>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="40" id="padding-comfortable" />
<Label htmlFor="padding-comfortable">
Comfortable{" "}
{advanced && (
<span className="text-muted-foreground">(40px)</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
Better for larger screens.
</p>
</RadioGroup>
</div>
<div className="space-y-4">
<h3 className="text-lg font-medium">Items per row</h3>
<p className="text-sm text-muted-foreground">
Set how many server items appear in each row of the list.
</p>
<RadioGroup value={itemsPerRow} onValueChange={setItemsPerRow}>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="4" id="items-4" />
<Label htmlFor="items-4">
4 items{" "}
{advanced && (
<span className="text-muted-foreground">per row</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
Balanced size and quantity, suitable for most screens.
</p>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="5" id="items-5" />
<Label htmlFor="items-5">
5 items{" "}
{advanced && (
<span className="text-muted-foreground">per row</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
More compact view, fit more servers on screen.
</p>
<div className="flex items-center space-x-2 mt-2">
<RadioGroupItem value="6" id="items-6" />
<Label htmlFor="items-6">
6 items{" "}
{advanced && (
<span className="text-muted-foreground">per row</span>
)}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
Great for monitors/screens with more space.
</p>
</RadioGroup>
</div>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id="use-padding-sides"
checked={usePaddingOnSides}
onCheckedChange={(checked) =>
setUsePaddingOnSides(checked as boolean)
}
/>
<Label
htmlFor="use-padding-sides"
className="text-sm font-medium"
>
Use padding on the sides of only the servers, not the whole
server list
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
When enabled, applies padding to individual server items instead
of the entire list container. This can create a more distinct
separation between servers.
</p>
</div>
</CardContent>
<CardFooter>
<Button type="submit">Save Settings</Button>
</CardFooter>
</form>
</Card>
</div>
);
}

@ -70,6 +70,7 @@ import { cn } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { SignInPopover } from "./clerk/SignInPopoverButton"; import { SignInPopover } from "./clerk/SignInPopoverButton";
import { BentoCard, BentoGrid } from "./effects/bento-grid"; import { BentoCard, BentoGrid } from "./effects/bento-grid";
import { pageFind } from "./misc/Link";
const features = [ const features = [
{ {
@ -227,7 +228,7 @@ export default function ServerList() {
<div className="p-0 branding-hero"> <div className="p-0 branding-hero">
<> <>
{(!isSignedIn || hero) && ( {(!isSignedIn || hero) && (
<div className=" py-[300px] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 "> <div className=" pb-[300px] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 ">
<Particles <Particles
className="absolute inset-0 -z-10 block" className="absolute inset-0 -z-10 block"
quantity={100} quantity={100}
@ -320,7 +321,9 @@ export default function ServerList() {
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " + "border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]" "dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
)} )}
onClick={() => router.push(`/server/${server.name}`)} onClick={() =>
router.push(pageFind(`Server:${server.name}`))
}
> >
<div className="items-center gap-2 p-4"> <div className="items-center gap-2 p-4">
<div> <div>

@ -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<WithInterval<Achievement>>
>([]);
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 (
<div>
<Skeleton className="w-full h-[112px] my-4" />
<Skeleton className="w-full h-[112px] my-4" />
<Skeleton className="w-full h-[112px] my-4" />
</div>
);
return (
<div>
<span>
Achievements are earned automatically when the server is online. See{" "}
<A alt="Achievement collection">Special:Root</A>
</span>
{achievements
.filter(
(value, index) => listify(achievements).indexOf(value.type) === index
)
.map((a) => {
const Icon = formalNames[a.type].icon;
return (
<div key={`${a.date}--${a.interval}`}>
<Card className="my-4">
<CardContent className="pt-4">
<span
className="flex items-center"
style={{ color: formalNames[a.type].color }}
>
<Icon size={16} className="mr-2" />
<span
dangerouslySetInnerHTML={{
__html: formalNames[a.type].title,
}}
/>
</span>
<p>{formalNames[a.type].description}</p>
<span className="text-sm text-muted-foreground">
Achieved on {new Date(a.date).getMonth()}/
{new Date(a.date).getDate()}/
{new Date(a.date).getFullYear()}{" "}
<span className="text-muted-foreground/70">
{new Date(a.date).toLocaleTimeString()}
</span>
</span>
</CardContent>
</Card>
</div>
);
})}
</div>
);
}
const formalNames = {
mostJoined: {
title:
"At one time, <b>this server had the most players on the platform!</b>",
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 <b>1,000 favorites on MHSF!</b>",
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 <b>1,000 total joins on Minehut!</b>",
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 <b>100,000 favorites on MHSF!</b>",
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 <b>100,000 total joins on Minehut!</b>",
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> = K & {
interval: number;
};
const listify = (list: WithInterval<Achievement>[]) => {
const newL: Array<string> = [];
list.forEach((c) => newL.push(c.type));
return newL;
};

@ -43,8 +43,8 @@ export function DiscordPopover({ server, get }: { server: string; get: any }) {
} }
useEffect(() => { useEffect(() => {
setValue(get.discord == null ? "" : get.discord); setValue(get.discord === undefined ? "" : get.discord);
form.reset({ id: get.discord == null ? "" : get.discord }); form.reset({ id: get.discord === undefined ? "" : get.discord });
}, [get]); }, [get]);
return ( return (

@ -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 (
<NextLink href={pageFind(children)}>
{children.startsWith("Docs:") && <Book />}
{alt}
</NextLink>
);
}
export function ALegacy({
children,
href,
}: {
children?: string | ReactNode;
href?: string;
}) {
return (
<NextLink
href={pageFind(href || "")}
className="no-underline transition duration-300 hover:underline "
>
{(href || "").startsWith("Docs:") && (
<Book size={16} className="mr-[2px] inline-flex" />
)}
{(href || "").startsWith("Wiki:") && (
<NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" />
)}
{children}
{(href || "").startsWith("https") && (
<ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" />
)}
</NextLink>
);
}
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;
};

@ -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<SVGSVGElement>) => (
<svg
viewBox="0 0 256 199"
width="1em"
height="1em"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
className="inline-block mb-1"
{...props}
>
<path
d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z"
fill="#5865F2"
/>
</svg>
);
export const MDXElements: MDXElementType = {
Discord,
Book: (props) => <Book {...props} />,
Notebook: (props) => <NotebookText {...props} />,
ExternalLink: (props) => <ExternalLink {...props} />,
};

@ -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<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

@ -3,6 +3,10 @@ export const allFolders: (DocsFolder | Docs)[] = [
title: "Getting Started", title: "Getting Started",
url: "/docs/getting-started", url: "/docs/getting-started",
}, },
{
title: "Reading",
url: "/docs/reading",
},
{ {
name: "Guides", name: "Guides",
docs: [ docs: [
@ -26,7 +30,7 @@ export const allFolders: (DocsFolder | Docs)[] = [
docs: [ docs: [
{ title: "Tech Stack", url: "/docs/advanced/tech-stack" }, { title: "Tech Stack", url: "/docs/advanced/tech-stack" },
{ title: "Using the Command-bar", url: "/docs/advanced/command-bar" }, { title: "Using the Command-bar", url: "/docs/advanced/command-bar" },
{ title: "Tips with external servers", url: "/docs/advanced/external"} { title: "Tips with external servers", url: "/docs/advanced/external" },
], ],
}, },
{ {

@ -15,7 +15,7 @@ const serverCache: any = {};
// __filter: if your name isn't static, set this to true // __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) // 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<{ export const allTags: Array<{
name: (server: OnlineServer) => Promise<string>; name: (server: OnlineServer) => Promise<string>;
condition: (server: OnlineServer) => Promise<boolean>; condition: (server: OnlineServer) => Promise<boolean>;
listCondition?: (server: ServerResponse) => Promise<boolean>; listCondition?: (server: ServerResponse) => Promise<boolean>;
@ -86,7 +86,7 @@ export var allTags: Array<{
}, */ }, */
]; ];
export var allCategories: Array<{ export const allCategories: Array<{
name: string; name: string;
condition: (server: OnlineServer) => Promise<boolean>; condition: (server: OnlineServer) => Promise<boolean>;
primary: boolean; primary: boolean;
@ -250,7 +250,7 @@ export var allCategories: Array<{
async function requestServer(s: OnlineServer): Promise<ServerResponse> { async function requestServer(s: OnlineServer): Promise<ServerResponse> {
if (serverCache[s.name] == undefined) { if (serverCache[s.name] == undefined) {
const re = await fetch( const re = await fetch(
"https://api.minehut.com/server/" + s.name + "?byName=true" "https://api.minehut.com/server/" + s.name + "?byName=true",
); );
const json = await re.json(); const json = await re.json();
serverCache[s.name] = json.server; serverCache[s.name] = json.server;

@ -5,12 +5,30 @@
*/ */
// //
import { Achievement } from "./types/achievement";
const connector = ( const connector = (
endpoint: string, endpoint: string,
options: { version: number; starting?: string }, options: { version: number; starting?: string },
) => ) =>
`${options.starting == undefined ? "/" : `${options.starting}/`}api/v${options.version}${endpoint}`; `${options.starting == undefined ? "/" : `${options.starting}/`}api/v${options.version}${endpoint}`;
async function apiConstructor<K>(
connector: string,
requestInit: RequestInit,
modifier: (data: any) => K,
): Promise<K> {
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( export async function getMOTDFromServer(
list: Array<{ server: string; motd: string }>, list: Array<{ server: string; motd: string }>,
): Promise<Array<{ server: string; motd: string }>> { ): Promise<Array<{ server: string; motd: string }>> {
@ -428,3 +446,10 @@ export async function setAccountSL(
throw Error("Error while running API"); 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,
);

@ -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",
];

@ -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() });
}

@ -0,0 +1 @@
// TODO: make multiple endpoint to allow achievements to be shown on the server-list

151
yarn.lock

@ -150,6 +150,60 @@
"@babel/helper-validator-identifier" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0" 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": "@clerk/backend@1.2.2":
version "1.2.2" version "1.2.2"
resolved "https://registry.npmjs.org/@clerk/backend/-/backend-1.2.2.tgz" resolved "https://registry.npmjs.org/@clerk/backend/-/backend-1.2.2.tgz"
@ -1089,20 +1143,19 @@
dependencies: dependencies:
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-checkbox@^1.0.4": "@radix-ui/react-checkbox@^1.1.1":
version "1.0.4" version "1.1.1"
resolved "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz" resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz#a559c4303957d797acee99914480b755aa1f27d6"
integrity sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg== integrity sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@radix-ui/primitive" "1.1.0"
"@radix-ui/primitive" "1.0.1" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-context" "1.1.0"
"@radix-ui/react-context" "1.0.1" "@radix-ui/react-presence" "1.1.0"
"@radix-ui/react-presence" "1.0.1" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.0.1" "@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-use-previous" "1.0.1" "@radix-ui/react-use-size" "1.1.0"
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/react-collection@1.0.3": "@radix-ui/react-collection@1.0.3":
version "1.0.3" version "1.0.3"
@ -1506,29 +1559,28 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.2" "@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" version "2.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==
dependencies: dependencies:
"@radix-ui/react-slot" "1.1.0" "@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-radio-group@^1.1.3": "@radix-ui/react-radio-group@^1.2.0":
version "1.1.3" version "1.2.0"
resolved "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz" resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.2.0.tgz#f937dd6b9436ded80c4bebdf3901c20cb8bcbb5a"
integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag== integrity sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw==
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@radix-ui/primitive" "1.1.0"
"@radix-ui/primitive" "1.0.1" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-context" "1.1.0"
"@radix-ui/react-context" "1.0.1" "@radix-ui/react-direction" "1.1.0"
"@radix-ui/react-direction" "1.0.1" "@radix-ui/react-presence" "1.1.0"
"@radix-ui/react-presence" "1.0.1" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-roving-focus" "1.1.0"
"@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.0.1" "@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-use-previous" "1.0.1" "@radix-ui/react-use-size" "1.1.0"
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/react-roving-focus@1.0.4": "@radix-ui/react-roving-focus@1.0.4":
version "1.0.4" version "1.0.4"
@ -1599,6 +1651,19 @@
dependencies: dependencies:
"@radix-ui/react-compose-refs" "1.1.0" "@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": "@radix-ui/react-tabs@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz#0a6db1caed56776a1176aae68532060e301cc1c0"
@ -1693,6 +1758,11 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@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": "@radix-ui/react-use-rect@1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz" 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" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== 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": "@types/mdast@^3.0.0":
version "3.0.15" version "3.0.15"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5" 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" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== 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: cross-fetch@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" 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" resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.416.0.tgz#657da248f9b862703d7d80aafb912e79ad886313"
integrity sha512-wPWxTzdss1CTz2aqcNWNlbh4YSnH9neJWP3RaeXepxpLCTW+pmu7WcT/wxJe+Q7Y7DqGOxAqakJv0pIK3431Ag== 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: magic-bytes.js@^1.10.0:
version "1.10.0" version "1.10.0"
resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92" 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" resolved "https://registry.yarnpkg.com/react-fade-in/-/react-fade-in-2.0.1.tgz#b4bcd7dac63d6857ebcd68facbff2f5f9616278f"
integrity sha512-oqS/WT4znaXEHmL+yo0IDUDY7uC9K4RP35j1SdRUEBspR09B2iIC0i8oJ28tPOr6Ez/L2aktF9p89j+DbsTVNw== 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: react-hook-form@^7.52.2:
version "7.52.2" version "7.52.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.2.tgz#ff40f4776250b86ddfcde6be68d34aa82b1c60fe" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.2.tgz#ff40f4776250b86ddfcde6be68d34aa82b1c60fe"